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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

importing azurerm_pim_eligible_role_assignment fails with resource not existant #23111

Closed
1 task done
tim-krehan opened this issue Aug 29, 2023 · 66 comments · Fixed by #24077, #24524 or #25956
Closed
1 task done

importing azurerm_pim_eligible_role_assignment fails with resource not existant #23111

tim-krehan opened this issue Aug 29, 2023 · 66 comments · Fixed by #24077, #24524 or #25956

Comments

@tim-krehan
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Community Note

Terraform fails while importing the azurerm_pim_eligible_role_assignment. The assignment is still present in Azure but not in the state. To manage it, I tried importing it but it cannot find the assignment in Azure.
The import fails with
Cannot import non-existent remote object
The apply fails with
A resource with the ID "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test-001|/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleDefinitions/00482a5a-887f-4fb3-b363-3b7fe8e74483|11111111-1111-1111-1111-111111111111" already exists - to be managed via Terraform this resource needs to be imported into the State. Please see the resource documentation for "azurerm_pim_eligible_role_assignment" for more information.

  • 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 and review the contribution guide to help.

Terraform Version

1.5.6

AzureRM Provider Version

3.71.0

Affected Resource(s)/Data Source(s)

azurerm_pim_eligible_role_assignment

Terraform Configuration Files

resource "azurerm_pim_active_role_assignment" "pim_active" {
  scope              = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test-001"
  role_definition_id = "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleDefinitions/00482a5a-887f-4fb3-b363-3b7fe8e74483"
  principal_id       = "11111111-1111-1111-1111-111111111111"

  justification = "Sample Justification"

  schedule {
    start_date_time = "2023-07-13T11:40:26.0000000+02:00"
    expiration {
      duration_days = 180
    }
  }
}

import {
  to = azurerm_pim_active_role_assignment.pim_active
  id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test-001|/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleDefinitions/00482a5a-887f-4fb3-b363-3b7fe8e74483|11111111-1111-1111-1111-111111111111"
}

Debug Output/Panic Output

╷
│ Error: Cannot import non-existent remote object
│
│ While attempting to import an existing object to "azurerm_pim_active_role_assignment.pim_active",
│ the provider detected that no object exists with the given id. Only pre-existing objects can be imported; check that the id is correct and that it is associated with the
│ provider's configured region or endpoint, or use "terraform apply" to create a new remote object for this resource.

Expected Behaviour

The allready present assignment will get imported

Actual Behaviour

The Import throws the error, that the object in Azure is not existant, while it definitly is.
Even the apply fails to create it, stating that the object is allready present

Steps to Reproduce

  1. terraform apply

Important Factoids

No response

References

No response

@xuzhang3
Copy link
Contributor

xuzhang3 commented Sep 5, 2023

@tim-krehan Looks you want to import the azurerm_pim_eligible_role_assignment but the configuration is azurerm_pim_active_role_assignment. Can retry with the same resource type?

@tim-krehan
Copy link
Author

hmmm i had that issue with both active and eligible.
The thing is: i can't reproduce it right now 😢

Seems that it was a one-time thing?

I think this issue can be closed - I will reference it, once I come upon that issue again.

@audunsolemdal
Copy link
Contributor

I am using the correct resource type and having the same issue. The azurerm_pim_eligible_role_assignment has been buggy from the start in my case, a lot of times seemingly out of nowhere stating that it needs to be created, even though it has already been created via terraform

I know imports have worked previously for me, not sure where the issue comes from.

@xuzhang3
Copy link
Contributor

xuzhang3 commented Sep 6, 2023

@audunsolemdal There appears to be a delay in the response of this resource.. The resource cannot deleted within the first 5 mins etc. Can you retry later?

@unique-dominik
Copy link
Contributor

I believe this issue should be reopened. I can reproduce it right now…

terraform apply -target='azurerm_pim_eligible_role_assignment.this["user1"]'
--> yes
Error: A resource with the ID "/subscriptions/<s>|/subscriptions/<s>/providers/Microsoft.Authorization/roleDefinitions/324395f4-284c-2fba-f19b-4ee363d92e04|<user1>" already exists - to be managed via Terraform this resource needs to be imported into the State. Please see the resource documentation for "azurerm_pim_eligible_role_assignment" for more information.

Then:

terraform import 'azurerm_pim_eligible_role_assignment.this["user1"]' "/subscriptions/<s>|/subscriptions/<s>/providers/Microsoft.Authorization/roleDefinitions/324395f4-284c-2fba-f19b-4ee363d92e04|<user>"

-->

Prepared azurerm_pim_eligible_role_assignment for import

--> 

Error: Cannot import non-existent remote object
│
│ While attempting to import an existing object to "azurerm_pim_eligible_role_assignment.this[\"user1"]", the provider detected that no object exists with the given id. Only pre-existing objects can be imported; check that the id is correct and that it is
│ associated with the provider's configured region or endpoint, or use "terraform apply" to create a new remote object for this resource.

I can play this chess game back and forward ♟️

In the UI, the assignment exists. To me it currently occurs that it is just orphaned completely.

Terraform v1.6.0
on darwin_arm64
+ provider registry.terraform.io/hashicorp/azuread v2.43.0
+ provider registry.terraform.io/hashicorp/azurerm v3.75.0
+ provider registry.terraform.io/hashicorp/time v0.9.1

@audunsolemdal
Copy link
Contributor

I agree that this is still a problem

@braun-daniel
Copy link

I could reproduce this issue as well.

Terraform v1.6.2
on darwin_arm64
+ provider registry.terraform.io/hashicorp/azurerm v3.77.0

@MohnJadden
Copy link

I'm having the same issue when using an index key, opened #23657 in case it's something specific with index key imports. One thing I also just noticed is that when destroying azurerm_pim_eligible_role_assignment resources, the assignment is never actually removed. If you're re-trying the creation after destruction, the "resource already exists" situation will recur and thus lead you to the import issue once more.

The only sure fix has been clicking through the portal and removing the assignment. I just opened issue #23672 in hopes of getting it fixed.

The azurerm_pim_eligible_role_assignment resource provider has been buggy since it launched, unfortunately this is just more of the same.

@unique-dominik
Copy link
Contributor

It does not really help to resolve anyones issues but I'd like to also support the maintainers in sharing two discoveries I made over the recent weeks.

Scheduling changes

The whole eligibility setup thing does not base on pure CRUD like objects it seems. It bases on "requests" (orders) you make and the system "processing" those. The resulting resources are read-only. It can be seen in the REST Docs that you always make a request of a kind and then you wait for a result that "pops up somewhere" else.

E.g. revoking an eligibility needs a "request" of type AdminRemove and then, after some time, the change will be persisted.
I had to do that like two weeks ago to clean our terraform state as back then (see next point) the Management Group scoped PIM permissions had no UI!

An example for history:

curl --location --request PUT 'https://management.azure.com/providers/Microsoft.Management/managementGroups/uqe/providers/Microsoft.Authorization/roleEligibilityScheduleRequests/<random-uuid-from-an-uuid-tool>?api-version=2020-10-01' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <token>' \
--data '{
  "Properties": {
    "RoleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/<role>",
    "PrincipalId": "<principal>",
    "RequestType": "AdminRemove"
    }
}'

Afterwards, you can plan and apply again as the resulting "eligibility assignment" will be revoked.

This brings me to point 2.

New Azure UI

image

Since some days, you can do what I did via maddening REST calls via the UI! 🔔

It does not help much for the terraform issues but at least it's easier to clean the state 🤣

@tim-krehan tim-krehan reopened this Oct 25, 2023
@Scarlettliuyc
Copy link

hi @tim-krehan good day. I'm not sure whether you are the one from hashcrop, we still have this kind of issue.
When use "terraform plan" it shows the PIM doesn't exist. And use the import existing resource, there the errors:
Error: Cannot import non-existent remote object

│ While attempting to import an existing object to
│ "azurerm_pim_eligible_role_assignment.pim-495f1d9d-ab33-2196-479b-8aff97b3df0b",
│ the provider detected that no object exists with the given id. Only
│ pre-existing objects can be imported; check that the id is correct and that
│ it is associated with the provider's configured region or endpoint, or use
│ "terraform apply" to create a new remote object for this resource.

however the object add definition is correct. We want to confirm any progress for this issue

@tim-krehan
Copy link
Author

@Scarlettliuyc Sorry to disappoint you, I am just an user that is affected by this.

@smokedlinq
Copy link
Contributor

@xuzhang3 FYI I just tried this on 3.85.0 release and it still fails. If I try to do the apply, it says it already exists. I add the import and it says it doesn't exist.

@spectrum048k
Copy link

@xuzhang3 I have also test version 3.85 and have observed same behaviour as @smokedlinq - apply still fails saying resources exists and import fails saying it doesn't. Can this issue please be re-opened?

@unique-dominik
Copy link
Contributor

I was so sick of this that I "developed" this in the meantime:

import { AuthorizationManagementClient } from "@azure/arm-authorization";
import { DefaultAzureCredential } from "@azure/identity";

import { v4 as uuidv4 } from 'uuid';

const tfOutputSanitized = `
/subscriptions/xxxxxxx-b530-xxxx-9b08-xxxxxxxxx|/subscriptions/xxxxxx-xxxx-4277-9b08-xxxxxxxxx/providers/Microsoft.Authorization/roleDefinitions/xxxxxx-89b4-xxxx-9698-xxxxxxxxx|xxxxxx-f49b-xxxx-bd59-xxxxxxxx
`;

/**
 * This script is the automation of docs/docs/enterprise/single-tenant/troubleshooting.md#terraform-state-forgot-resources
 * I now had to fix it the third time, so I decided to automate it.
 * It would really be nice if either Microsoft or Hashicorp would fix this issue and until then we do this rain dance every 4-6 weeks.
 * npm run repair:subscriptions
 * 
 * The terraform apply output that is erroneous must be a bit revised to be used here, but it can nearly be pasted 1:1 and then the script will fix it.
 */

async function putRoleEligibilityScheduleRequests() {

  const subscriptionsToFixToday: {subscription: string, roleDefinitionId: string, principalId: string}[] = [];

  const lines = tfOutputSanitized.split("\n");
  for (const line of lines) {
    const parts = line.split("|");
    if (parts.length === 3) {
      const subscription = parts[0];
      const roleDefinitionId = parts[1];
      const principalId = parts[2];
      subscriptionsToFixToday.push({subscription, roleDefinitionId, principalId});
    }
  }

  console.log(`Parsed ${subscriptionsToFixToday.length} incidents to fix.`);

  console.log(`Revoking eligible role assignments for ${subscriptionsToFixToday.length} subscriptions.`)
  // Setup env and get a token for ARM
  const subscriptionId = process.env["AUTHORIZATION_SUBSCRIPTION_ID"] || "00000000-0000-0000-0000-000000000000";
  const credential = new DefaultAzureCredential();
  const client = new AuthorizationManagementClient(credential, subscriptionId);

  for (const assignment of subscriptionsToFixToday) {
    const roleEligibilityScheduleRequestName = uuidv4();
    const scope =`providers/Microsoft.Subscription${assignment.subscription}`;
    const parameters = {
      principalId: assignment.principalId,
      requestType: "AdminRemove",
      roleDefinitionId: assignment.roleDefinitionId,
    };
    const result = await client.roleEligibilityScheduleRequests.create(
      scope,
      roleEligibilityScheduleRequestName,
      parameters
    );
    console.log(`Assignment [${result.status?.toUpperCase()}] for scope ${result.expandedProperties?.scope?.displayName} for role ${result.expandedProperties?.roleDefinition?.displayName} for principal ${result.expandedProperties?.principal?.displayName}.`);
  }
}

putRoleEligibilityScheduleRequests();
{
  "name": "repair-kit",
  ...
  "scripts": {
    "repair:subscriptions": "npx ts-node src/subscriptions.ts"
  },
  ...
  "devDependencies": {
    "@azure/arm-authorization": "^9.0.0",
    "@azure/identity": "^4.0.0",
    "@types/node": "^20.10.4",
    "@types/uuid": "^9.0.7",
    "uuid": "^9.0.1"
  }
}

Where tfOutputSanitized is basically the tf output complaining and using multiple-time multi-cursor-edit to strip it just to the resource id.

Sorry but this is the best I got until now to get out of the misery.

@xuzhang3
Copy link
Contributor

xuzhang3 commented Dec 19, 2023

@smokedlinq can you share your TF script and the TRACE logs? I do not have the same env as you do so I can only generate a potentials scenario in my mind. One way you can do is check the role assignment in the PIM role assignment in the PIM role assignment page. The details I want to know is which scope you want to assign the role and the sub/upper resources role assign details. This role should be at least assigned to the different resource in different scope.

@unique-dominik
Copy link
Contributor

@smokedlinq can you share your TF script and the TRACE logs? I do not have the same env as you do so I can only generate a potentials scenario in my mind. One way you can do is check the role assignment in the PIM role assignment in the PIM role assignment page. The details I want to know is which scope you want to assign the role and the sub/upper resources role assign details. This role should be at least assigned to the different resource in different scope.

He @xuzhang3, they are not. They are 100% the same as the last time we/I ran apply.
I can reproduce what @smokedlinq reports like every other month. That is why I wrote this script. Let me know what I shall provide to debug next time but the scopes and the role_id's are 100% the same, we rerun the same tf code that applied 4 weeks ago without change and the error appears.

The 🔑 is to wait until something @ARM/Entra changes, then the tf state breaks until fixes as above. Check also my message #23111 (comment) where I basically did what you wanted to see 💟

@xuzhang3
Copy link
Contributor

@unique-dominik @spectrum048k I cannot reproduce this error in v3.85
Can you try the script for testing? The script should work fine in v3.85 but fails in v3.84

#terraform {
#  required_providers {
#    azurerm = {
#      source  = "hashicorp/azurerm"
#      version = "=3.85.0"
#    }
#  }
#}
provider "azurerm" {
  features {
  }
}

data "azurerm_client_config" "example" {}

resource "time_static" "example" {}

resource "azurerm_pim_eligible_role_assignment" "pim1" {
  scope              = "/subscriptions/<Sub ID>/resourceGroups/<Resource Group Name>"
  role_definition_id = "<Role definition ID>"
  principal_id       = data.azurerm_client_config.example.object_id

  justification = "test"

  schedule {
    start_date_time = time_static.example.rfc3339
    expiration {
      duration_days = 180
    }
  }
}

resource "azurerm_pim_eligible_role_assignment" "pim2" {
  scope              = "/subscriptions/Sub ID"
  role_definition_id = "<Role definition ID>"
  principal_id       = data.azurerm_client_config.example.object_id

  justification = "test"

  schedule {
    start_date_time = time_static.example.rfc3339
    expiration {
      duration_days = 180
    }
  }
}

@unique-dominik
Copy link
Contributor

Will try again next time yes. I can also not repro it right now, it needs some days time to break from the other end 😢
I will post back once it happens again, what detail would you need then 👁️

@MohnJadden
Copy link

I too am still getting the OP error in 3.85. If the PIM eligibility exists in Azure, Terraform says it has to be imported. If I try to import it, I get the import error as well.

If the eligibility doesn't exist in Azure, it creates without incident. If the eligibility is created by Terraform using the test that @xuzhang3 provided, it updates it, but it still doesn't provide for indefinite/permanent eligibility as described in #23366 and #22766 . It looks like the PR for #23295 has not been merged yet, which might help.

@spectrum048k
Copy link

@xuzhang3 In my testing I have noticed it takes circa 45 days for the pim assignment to 'fail' in the Azure backend before the issue presents itself in terraform.

@unique-dominik
Copy link
Contributor

@spectrum048k Sounds about right, I guessed 4-6 weeks above 👍

@kristeey
Copy link

kristeey commented Mar 4, 2024

Still experiencing this error for a azurerm_pim_active_role_assignment resource when trying to import it with hashicorp/azurerm version 3.94.0.

[...] the provider detected that no object exists with the given id. [...]

@xuzhang3
Copy link
Contributor

xuzhang3 commented Mar 5, 2024

@kristeey What is the status of the role assignment you are trying to import? Active or not active and does this role assignment created by current SPN?

@kristeey
Copy link

kristeey commented Mar 5, 2024

@xuzhang3 The status is Assigned and the assignment is created by the current SPN.
Skjermbilde 2024-03-05 kl  10 10 44

@kristeey
Copy link

kristeey commented Mar 5, 2024

Since this was a blocker I deleted the role assignment and reapplied it again.

@spectrum048k
Copy link

@xuzhang3 can you provide guidance on how to use this fix, i.e. do we need to import all the assignments missing from state? or do we have to delete the assignment and re-apply as @kristeey did?

@xuzhang3
Copy link
Contributor

xuzhang3 commented Mar 6, 2024

@spectrum048k the fix PR switch to new a endpoint and the new endpoint can get more role assignments which is not exist in the current state file. Run apply can pull the role assignments from remote and set to the state file. If possible I would suggest recreate the role assignment as a lot has changed with the new version
As for @kristeey issue I need to investigate why the resource cannot found. Does delete and re-apply fix this issue?

@kristeey
Copy link

kristeey commented Mar 6, 2024

@xuzhang3 Manual deleting the role assignment and re-apply it with terraform worked for me.

@manicminer manicminer reopened this Mar 6, 2024
@jakubslonxlab
Copy link

@spectrum048k the fix PR switch to new a endpoint and the new endpoint can get more role assignments which is not exist in the current state file. Run apply can pull the role assignments from remote and set to the state file. If possible I would suggest recreate the role assignment as a lot has changed with the new version As for @kristeey issue I need to investigate why the resource cannot found. Does delete and re-apply fix this issue?

Hi,

We are also experiencing the same problem as @kristeey after using the latest version of the provider:

Cannot import non-existent remote object

Can you clarify this statement please: "Run apply can pull the role assignments from remote and set to the state file" - does this mean the root cause of the original issue of failing import of the resources was that terraform was not able to pull role assignments from the remote?

@unique-dominik
Copy link
Contributor

@jakubslonxlab Yes, because they originally basically disappeared after 45 days 😢
Read: #23111 (comment)

@lahiruperamune
Copy link

Does anyone have a workaround for this until we have a permanent fix?

@unique-dominik
Copy link
Contributor

The fixes have been merged. What do you want to work around?
If you have existing assignments you can't import invalidate them via the portal or via the API (see my scripts or commands above). Then recreate them with the latest provider.

@TeamDman
Copy link

I'm trying to automate generating terraform from existing PIM assignments.

image

$management_groups = az account management-group list --no-register
$pim_assignments = $management_groups `
    | ForEach-Object -ThrottleLimit 20 -Parallel {
        $mg = $_
        Write-Host "Fetching PIM assignments from $($mg.displayName)"
        $url = "`"https://management.azure.com$($mg.id)/providers/Microsoft.Authorization/roleEligibilityScheduleInstances?api-version=2020-10-01`""
        az rest --method get --url $url | ConvertFrom-Json | Select-Object -ExpandProperty value
    }
$pim_assignments_to_import = $pim_assignments `
    | ForEach-Object { $_.properties.expandedProperties } `
    | Sort-Object -Property { $_ | ConvertTo-Json } -Unique
    
$entry_template = @"
import {
    id = "%ID%"
    to = azurerm_pim_eligible_role_assignment.%NAME%
}

"@

$content = ""
function sanitize($name) {
    $name -replace "[^a-zA-Z0-9]", "_"
}
foreach ($v in $pim_assignments_to_import) {
    $scope_id = $v.scope.id
    $scope_name = sanitize($v.scope.displayName)

    $role_definition_id = $v.roleDefinition.id
    $role_definition_name = sanitize($v.roleDefinition.displayName)

    $principal_id = $v.principal.id
    $principal_name = sanitize($v.principal.displayName)

    $ID = "$scope_id|$role_definition_id|$principal_id"
    $NAME = "${scope_name}____${role_definition_name}____${principal_name}"

    Add-Member -InputObject $v -MemberType NoteProperty -Name ResourceName -Value $NAME -Force
    $content += $entry_template -replace "%ID%", $ID -replace "%NAME%", $NAME
}
Set-Content ".\ignore\pim_imports.tf" $content
Write-Host "Wrote $($content.Length) chars to pim_imports.tf"

which yields

import {
    id = "/providers/Microsoft.Management/managementGroups/redacted|/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c|redacted-principal-id"
    to = azurerm_pim_eligible_role_assignment.Tenant_Root_Group____Contributor____AAD_CloudOps_Tenant_Contributor
}
import {
    id = "/providers/Microsoft.Management/managementGroups/redacted|/providers/Microsoft.Authorization/roleDefinitions/00482a5a-887f-4fb3-b363-3b7fe8e74483|redacted-principal-id"
    to = azurerm_pim_eligible_role_assignment.Tenant_Root_Group____Key_Vault_Administrator____AAD_CloudOps_Tenant_KeyVaultAdmin
}
import {
    id = "/providers/Microsoft.Management/managementGroups/redacted|/providers/Microsoft.Authorization/roleDefinitions/18d7d88d-d35e-4fb5-a5c3-7773c20a72d9|redacted-principal-id"
    to = azurerm_pim_eligible_role_assignment.Tenant_Root_Group____User_Access_Administrator____AAD_CloudOps_Tenant_UserAccessAdmin
}

then doing

terraform plan -generate-config-out="generated.tf" 2>&1 > terraform_plan_generate_config_out.log

yields

│ Error: Cannot import non-existent remote object
│ 
│ While attempting to import an existing object to
│ "azurerm_pim_eligible_role_assignment.Tenant_Root_Group____User_Access_Administrator____AAD_CloudOps_Tenant_UserAccessAdmin",
│ the provider detected that no object exists with the given id. Only
│ pre-existing objects can be imported; check that the id is correct and that
│ it is associated with the provider's configured region or endpoint, or use
│ "terraform apply" to create a new remote object for this resource.
╵
╷
│ Error: Cannot import non-existent remote object
│ 
│ While attempting to import an existing object to
│ "azurerm_pim_eligible_role_assignment.Tenant_Root_Group____Contributor____AAD_CloudOps_Tenant_Contributor",
│ the provider detected that no object exists with the given id. Only
│ pre-existing objects can be imported; check that the id is correct and that
│ it is associated with the provider's configured region or endpoint, or use
│ "terraform apply" to create a new remote object for this resource.

So, only 1 out of 3 imports succeeded D:

The PIM assignment that did succeed is newer than the others, and was also created using Terraform in a different state file.

Running with $env:TF_LOG="debug", the log file gives more insight:

[DEBUG] provider.terraform-provider-azurerm_v3.97.1_x5.exe: AzureRM Response for https://management.azure.com/providers/Microsoft.Management/managementGroups/redacted/providers/Microsoft.Authorization/roleEligibilitySchedules?%24filter=%28principalId+eq+%27redacted%27%29&api-version=2020-10-01: 
HTTP/2.0 200 OK

[DEBUG] provider.terraform-provider-azurerm_v3.97.1_x5.exe: AzureRM Response for https://management.azure.com/providers/Microsoft.Management/managementGroups/redacted/providers/Microsoft.Authorization/roleEligibilitySchedules?%24filter=%28principalId+eq+%27redacted%27%29&api-version=2020-10-01: 
HTTP/2.0 200 OK

[DEBUG] provider.terraform-provider-azurerm_v3.97.1_x5.exe: AzureRM Response for https://management.azure.com/providers/Microsoft.Management/managementGroups/redacted/providers/Microsoft.Authorization/roleEligibilitySchedules?%24filter=%28principalId+eq+%27redacted%27%29&api-version=2020-10-01: 
HTTP/2.0 200 OK

[DEBUG] provider.terraform-provider-azurerm_v3.97.1_x5.exe: AzureRM Response for https://management.azure.com/providers/Microsoft.Management/managementGroups/redacted/providers/Microsoft.Authorization/roleEligibilityScheduleRequests/redacted1?api-version=2020-10-01: 
HTTP/2.0 404 Not Found

[DEBUG] provider.terraform-provider-azurerm_v3.97.1_x5.exe: AzureRM Response for https://management.azure.com/providers/Microsoft.Management/managementGroups/redacted/providers/Microsoft.Authorization/roleEligibilityScheduleRequests/redacted2?api-version=2020-10-01: 
HTTP/2.0 200 OK

[DEBUG] provider.terraform-provider-azurerm_v3.97.1_x5.exe: AzureRM Response for https://management.azure.com/providers/Microsoft.Management/managementGroups/redacted/providers/Microsoft.Authorization/roleEligibilityScheduleRequests/redacted3?api-version=2020-10-01: 
HTTP/2.0 404 Not Found

So, Terraform is hitting roleEligibilitySchedules and finding something, then using that result to do a further lookup on roleEligibilityScheduleRequests which fails.

using az rest --method get --url $whatever we can peek at what is returned for roleEligibilityScheduleRequests

$requests = az rest --method get --url 'https://management.azure.com/providers/Microsoft.Management/managementGroups/redacted/providers/Microsoft.Authorization/roleEligibilityScheduleRequests?api-version=2020-10-01' | ConvertFrom-Json$requests.value.properties | % { $_.status + "`t" + $_.expandedProperties.roleDefinition.displayName }
Revoked Contributor
Revoked Contributor
Revoked Contributor
Revoked Contributor
Provisioned     Key Vault Administrator

This aligns with Key Vault Admin being the only PIM assignment of mine to successfully import

Meanwhile, I used roleEligibilityScheduleInstances to build my import blocks in the first place

$instances = az rest --method get --url 'https://management.azure.com/providers/Microsoft.Management/managementGroups/redacted/providers/Microsoft.Authorization/roleEligibilityScheduleInstances?api-version=2020-10-01' | ConvertFrom-Json$instances.value.properties | % { $_.status + "`t" + $_.expandedProperties.roleDefinition.displayName }
Provisioned     Contributor
Provisioned     Key Vault Administrator
Provisioned     User Access Administrator

One issue I found:

#23366

and the merged PR:

#24524

which had this comment

manicminer left a comment
@ xuzhang3 Thanks for working on this! The PIM APIs are a challenge and switching to the /roleAssignmentScheduleRequests / /roleEligibilityScheduleRequests endpoints seems the right way to go. I've made a few linting and code style stweaks, but this otherwise LGTM 👍

I couldn't figure out if this was saying that the PR made this change, or if this was a good change to consider for the future.
If the former, I'm confused because my provider is not hitting the instances endpoint. If the latter, I agree with @manicminer.

To me, it seems that roleEligibilityScheduleInstances is the right way to get what exists. It is also the endpoint that the portal is hitting if you open devtools on the PIM assignments page.

@TeamDman
Copy link

Upon further review:

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/pim_eligible_role_assignment

supports

  justification = "Expiration Duration Set"

  ticket {
    number = "1"
    system = "example ticket system"
  }

which is why roleEligibilityScheduleRequests is checked after roleEligibilitySchedules -- roleEligibilityScheduleRequests is the only endpoint to include justification and ticketInfo

Scroll horizontally to compare.

roleEligibilitySchedules roleEligibilityScheduleInstances roleEligibilityScheduleRequests
{
    "id": "/providers/Microsoft.Management/managementGroups/redacted/providers/Microsoft.Authorization/roleEligibilitySchedules/guid-here",
    "name": "guid-here",
    "properties": {
        "createdOn": "2023-12-01T15:06:51.04Z",
        "expandedProperties": {
            "principal": {
                "displayName": "AAD-CloudOps-Tenant-UserAccessAdmin",
                "id": "guid-here",
                "type": "Group"
            },
            "roleDefinition": {
                "displayName": "User Access Administrator",
                "id": "/providers/Microsoft.Authorization/roleDefinitions/18d7d88d-d35e-4fb5-a5c3-7773c20a72d9",
                "type": "BuiltInRole"
            },
            "scope": {
                "displayName": "Tenant Root Group",
                "id": "/providers/Microsoft.Management/managementGroups/guid-here",
                "type": "managementgroup"
            }
        },
        "memberType": "Direct",
        "principalId": "guid-here",
        "principalType": "Group",
        "roleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/18d7d88d-d35e-4fb5-a5c3-7773c20a72d9",
        "roleEligibilityScheduleRequestId": "/providers/Microsoft.Management/managementGroups/guid-here/providers/Microsoft.Authorization/roleEligibilityScheduleRequests/guid-here",
        "scope": "/providers/Microsoft.Management/managementGroups/guid-here",
        "startDateTime": "2023-12-01T15:06:51.04Z",
        "status": "Provisioned",
        "updatedOn": "2023-12-01T15:06:51.04Z"
    },
    "type": "Microsoft.Authorization/roleEligibilitySchedules"
}
{
    "id": "/providers/Microsoft.Management/managementGroups/guid-here/providers/Microsoft.Authorization/roleEligibilityScheduleInstances/guid-here",
    "name": "guid-here",
    "properties": {
        "createdOn": "2023-11-27T14:38:21.097Z",
        "expandedProperties": {
            "principal": {
                "displayName": "AAD-CloudOps-Tenant-Contributor",
                "id": "guid-here",
                "type": "Group"
            },
            "roleDefinition": {
                "displayName": "Contributor",
                "id": "/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c",
                "type": "BuiltInRole"
            },
            "scope": {
                "displayName": "Tenant Root Group",
                "id": "/providers/Microsoft.Management/managementGroups/guid-here",
                "type": "managementgroup"
            }
        },
        "memberType": "Direct",
        "principalId": "guid-here",
        "principalType": "Group",
        "roleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c",
        "roleEligibilityScheduleId": "/providers/Microsoft.Management/managementGroups/guid-here/providers/Microsoft.Authorization/roleEligibilitySchedules/guid-here",
        "scope": "/providers/Microsoft.Management/managementGroups/guid-here",
        "startDateTime": "2023-11-27T14:38:21.097Z",
        "status": "Provisioned"
    },
    "type": "Microsoft.Authorization/roleEligibilityScheduleInstances"
},
{
    "id": "/providers/Microsoft.Management/managementGroups/guid-here/providers/Microsoft.Authorization/roleEligibilityScheduleRequests/guid-here",
    "name": "guid-here",
    "properties": {
        "createdOn": "2024-03-11T14:31:04.42Z",
        "expandedProperties": {
            "principal": {
                "displayName": "AAD-CloudOps-Tenant-KeyVaultAdmin",
                "id": "guid-here",
                "type": "Group"
            },
            "roleDefinition": {
                "displayName": "Key Vault Administrator",
                "id": "/providers/Microsoft.Authorization/roleDefinitions/00482a5a-887f-4fb3-b363-3b7fe8e74483",
                "type": "BuiltInRole"
            },
            "scope": {
                "displayName": "Tenant Root Group",
                "id": "/providers/Microsoft.Management/managementGroups/guid-here",
                "type": "managementgroup"
            }
        },
        "justification": "",
        "principalId": "guid-here",
        "principalType": "Group",
        "requestType": "AdminAssign",
        "requestorId": "guid-here",
        "roleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/00482a5a-887f-4fb3-b363-3b7fe8e74483",
        "scheduleInfo": {
            "expiration": {
                "type": "NoExpiration"
            },
            "startDateTime": "2024-03-11T14:31:04.4341161+00:00"
        },
        "scope": "/providers/Microsoft.Management/managementGroups/guid-here",
        "status": "Provisioned",
        "targetRoleEligibilityScheduleId": "/providers/Microsoft.Management/managementGroups/guid-here/providers/Microsoft.Authorization/roleEligibilitySchedules/guid-here",
        "ticketInfo": {}
    },
    "type": "Microsoft.Authorization/roleEligibilityScheduleRequests"
}

However, the azure portal interface doesn't use the justification or ticketInfo fields in any obvious capacity

image

image

image

image

I tried creating a new PIM eligibility from the portal and it did create a new entry in roleEligibilityScheduleRequests, but it didn't include the justification field at all and it set "ticketInfo": {}.

It's likely that recreating the assignments I want to import would also create the entries that would let the import succeed... but that's a pain. Perhaps there's a middle ground?

If the roleEligibilitySchedules entry exists but the roleEligibilityScheduleRequests entry it references does not exist, I would rather it default-value the justification and ticketInfo fields instead of fail the import entirely, considering that the first request has the necessary info to proceed except for those fields.

I believe this is the relevant source code

roleEligibilityScheduleRequestId, err := parse.RoleEligibilityScheduleRequestIdFromSchedule(schedule)
if err != nil {
return err
}
scheduleRequestId := roleeligibilityschedulerequests.NewScopedRoleEligibilityScheduleRequestID(id.Scope, *roleEligibilityScheduleRequestId)
resp, err := clientRequest.Get(ctx, scheduleRequestId)
if err != nil {
if response.WasNotFound(resp.HttpResponse) {
return metadata.MarkAsGone(*id)
}
return fmt.Errorf("retrieving %s: %+v", *id, err)
}
if model := resp.Model; model != nil {
schema.Scope = id.Scope
if err = r.mapRoleAssignmentScheduleRequestToPimEligibleRoleAssignmentResourceSchema(*model, &schema); err != nil {
return fmt.Errorf("flattening model: %+v", err)
}
}

https://github.com/hashicorp/go-azure-sdk/blob/main/resource-manager/authorization/2020-10-01/roleeligibilityschedulerequests/method_get.go


I found this regex useful to censor info in vscode

Find:

(?<!/roleDefinitions/)\b\w+-\w+-\w+-\w+-\w+

Replace:

guid-here

@J0F3
Copy link

J0F3 commented Apr 30, 2024

I tried creating a new PIM eligibility from the portal and it did create a new entry in roleEligibilityScheduleRequests, but it didn't include the justification field at all and it set "ticketInfo": {}.

I think @TeamDman is absolutely right. The justification and ticketInfo values doe not make any sense for a PIM eligibility role assignment wich is actually made by an admin. The justifcation and ticketinfo make only sense either for an active PIM assignment (which must created with an other resource: azurerm_pim_active_role_assignment) or when an actual user activates the eligibility role assignment (resulting in actually permission for a restricted time) wich is not possibible with Terraform anyway.
So I think these fiels should be removed form the azurerm_pim_eligible_role_assignment completely because they are part of the problem and are actually irrelevant for the resource.

The issue that the import is not possible as, in my option, the same root cause as the suddenly recreation of the resource after approx. 45 days for which I just create a new issue because it is stillt not fixed: #25811.

The root cause for the import problem and for #25811 seems to be in the section which @TeamDman also referenced above.

See also for more context and explanation of my findings: #25811 (comment)

@MohnJadden
Copy link

@J0F3 A lot of larger organizations can and will separate privileges so that the same person or team cannot create and approve resources, IAM/RBAC/identity changes, etc. The justification and ticket info fields fall into that usage.

For example, the person or process creating a PIM eligible role assignment might not be a full-on admin - they may be on a team that handles identity management or on a helpdesk that handles creating the requests, and approvals may be required at some point. Justification and ticket info would allow an auditor, infosec team member, SIEM, log review platform, automated process, etc. to note that a new PIM eligible role has been created and to check back against the ticketInfo field. It could then take the ticketInfo content and check against the ticketing system, and if there is a mismatch, fire an alert or send an automated API call to revoke the role.

The same holds true for activating roles - "I need to activate Cloud App Admin in order to make Change X against Enterprise App Y as part of Change Control Z. I've gotten approval from the App Y team under ticket # 12345. Please approve." The ticketinfo and justification fields cover that for when the eligibility is created, and they cover the activation as well.

It does show those properties in the API reference for PIM, so it seems they're part of the API itself - just not revealed in the Azure portal.

My uninformed and unresearched opinion is that if the assignment is present in Azure, it doesn't write back its presence to the TF state in a way that the state can read or store. It's almost like there's a disconnect - Terraform sends a properly formatted API call but it mis-reads or drops the response, interpreting it as failed. I wish I had more knowledge to dig into this myself.

@xuzhang3 @manicminer Is there any chance there's further PRs or work associated with fixing this?

@J0F3
Copy link

J0F3 commented Apr 30, 2024

@MohnJadden

It does show those properties in the API reference for PIM, so it seems they're part of the API itself - just not revealed in the Azure portal.

This documentation is for PIM for Entra Roles that is not the same API as it is for Azure Resources for wich azurerm_pim_eligible_role_assignment is made for. The documentation for PIM for Azure Resources is here: https://learn.microsoft.com/en-us/rest/api/authorization/privileged-role-eligibility-rest-sample

A lot of larger organizations can and will separate privileges so that the same person or team cannot create and approve resources, IAM/RBAC/identity changes, etc. The justification and ticket info fields fall into that usage.

I know that and I am using PIM for Azure Resource on a daily basis in a larger organisation. Basically that is why the whole PIM system exists. Anyway, if the justification properties is part of the API and just not shown in the Portal the principle keeps the same. Any existing eligible role assignment must be get trough the roleEligibilitySchedules API and not trough the roleEligibilityScheduleRequests API which the azurerm provider currently does. I have explained this in #25811 (comment) in more details.

@MohnJadden
Copy link

@J0F3 You are correct - that original API reference was indeed for Entra roles since it references the directory as the scope, my mistake.

However, the AzureRM PIM guide you linked mentions that we should use the Role Eligibility Schedule Request API, which in turn has properties.Justification and properties.ticketInfo as optional fields for the request body.

The page section for "Grant eligible assignment" doesn't list those fields; they're mentioned within the API reference but not that page you linked. To me, those look like what the Azure resource PIM API would use from the justification and ticket arguments in TF, but I defer to the skills and knowledge of folks who operate at the API level more than me.

@manicminer
Copy link
Contributor

manicminer commented May 14, 2024

Thanks @TeamDman, @J0F3 and @MohnJadden for the excellent analysis on the API interactions and discussion around required features.

I agree with your conclusions and believe I have already resolved a number of bugs including the hard dependency on the roleAssignmentScheduleRequests API at read/import time. I'm reworking this so that we can still continue when expired requests are no longer being returned. We will simply omit the fields available in that API, so you might get a diff but should be workable by tweaking your config or using ignore_changes for the justification block (which we are keeping).

These APIs are very counterintuitive and contradict each other in places, but progress is being made! If anyone has any insight into the relationship specifically between the roleAssignmentSchedules and roleAssignmentScheduleInstances APIs, your input would be appreciated.

@MohnJadden
Copy link

the relationship specifically between the roleAssignmentSchedules and roleAssignmentScheduleInstances

tl;dr: roleAssignmentSchedules is "show me any PIM role assignments regardless of whether they're active, expired, or just eligible". roleAssignmentScheduleInstances is "show me only active PIM role assignments.

If I haven't said it before, I'll say it now: I'm no API dev, but hopefully I can decipher the objects/properties of Azure such that actual devs can consume them. That said, here's my quick reading results:

roleAssignmentSchedules is the actual, existing assignment of any and all Azure resource PIM assignments, whether they are eligible (as in they can be activated), active (as in they have been activated, or are active based on a scheduled start/end date/time), or expired (no longer active or eligible). It is meant to be queried against either a Management Group, Subscription, Resource Group, or specific resource. Let's say I go into PIM via the Azure portal and assign the Contributors role to the AZ-Contributors security group for the Foobar subscription. They are eligible to activate the role for the next six months. If I call the roleAssignmentSchedules API, it shows the details of the assignment, something like:

        {
            "id": "unique identifier of the assignment itself",
            "principalId": "GUID of user or group that has been assigned to the rule",
            "roleDefinitionId": "GUID of the Azure resource IAM role that has been assigned - in this case, Contributor",
            "directoryScopeId": "I'm guessing this is the scope of the assignment - in this example, /subscriptions/guid-for-foobar",
            "appScopeId": "not sure - maybe it's if you're using conditions within the PIM assignment to only allow specific enterprise apps or app registrations to access the assigned role",
            "startDateTime": "the start date/time for the PIM eligibility or active role",
            "endDateTime": "the end date/time for the PIM eligibility or active role",
            "assignmentType": "I think this is whether the role is eligible (someone or something has to activate the role via the PIM process in the Azure portal or via automation/API calls/az CLI/Powershell)",
            "memberType": "Not sure - maybe it applies to dynamic group membership via AAD/Entra groups",
            "roleAssignmentOriginId": "identifier of the role assignment in Entra - I'm guessing this is equivalent to a GUID or UUID for the PIM assignment itself, identical to the ID field above, and identifies the role to AAD/Entra",
            "roleAssignmentScheduleId": "identifier of the unifiedRoleAssignmentSchedule object in AAD/Entra - the way I read this, this is the same ID but it is assigned to the schedule and is used to check against **whether a PIM assignment is still valid, rather than what the PIM assignment actually grants or to whom/where it is granted** (emphasis mine)"
        },

Meanwhile, roleAssignmentScheduleInstances does the same as above but only for active assignments (not eligible or inactive roles). If we assigned the Contributor role to the AZ-Contributors group for the Foobar subscription, but it's for a future date, querying the API would not return a result for Foobar since it is not active. If it is permanently active, or active due to falling within the start/end date range, then it would show up with the same fields as above.

I'm guessing that calling the API shows all Azure resource PIM assignments in an entire tenant unless otherwise specified but I'm honestly not sure. I suck at query language, so I'm guessing the Additional Query Parameters section would be where you scope or specify what role assignment schedules you want by individual properties.

@manicminer
Copy link
Contributor

Thanks @MohnJadden, that aligns with my understanding 👍

I don't believe there's any cause at this time to query the roleAssignmentScheduleInstances API, so as per my earlier comment I've refactored both resources (azurerm_active_role_assignment, azurerm_pim_eligible_role_assignment) so that a best effort is made to locate the latest Request object, but if it isn't found then it will proceed with some properties no longer able to be refreshed (durations, justification, ticket details). I've also noticed some more bugs related to the start_date_time / end_date_time properties which all 3 APIs seem to mutate during the processing of a request, so I've tried to further shield against that too.

@manicminer manicminer modified the milestones: v3.85.0, v3.104.0 May 15, 2024
Copy link

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 15, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.