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

Use ARM VM assigned manage identity as SP to use Graph API [app role assignments] #459

Closed
melkikh opened this issue Jun 11, 2021 · 9 comments · Fixed by #584
Closed

Use ARM VM assigned manage identity as SP to use Graph API [app role assignments] #459

melkikh opened this issue Jun 11, 2021 · 9 comments · Fixed by #584

Comments

@melkikh
Copy link

melkikh commented Jun 11, 2021

Edit by @manicminer: This should be solved with support for app role assignments


Hello!

I want to use managed identity service principle to get data from Microsoft Graph API (with a specific role).

There is instruction, that says that this is possible:

Managed identities work in conjunction with Azure Resource Manager (ARM), Azure AD, and the Azure Instance Metadata Service (IMDS).

Azure AD creates an AD identity when you configure an Azure resource to use a system-assigned managed identity.

I am trying to do this:

provider "azurerm" {
  features {}
}

provider "azuread" {
}

resource "azurerm_resource_group" "rg" {
  name     = "melkikh"
  location = var.location
}

terraform {
  required_version = ">= 0.12"
  required_providers {
    azuread = {
      source  = "hashicorp/azuread"
      version = "~> 1.5.0"
    }
  }
}

## application service principle with specific role
data "azuread_client_config" "current" {}

resource "azuread_application" "melkikh_test_app" {
  display_name     = "melkikh-test-app"
  owners           = [data.azuread_client_config.current.object_id]
  sign_in_audience = "AzureADMyOrg"

  required_resource_access {
    # Microsoft Graph
    # az ad sp list --display-name "Microsoft Graph" --query '[].{appDisplayName:appDisplayName, appId:appId}'
    resource_app_id = "00000003-0000-0000-c000-000000000000"

    resource_access {
      # Group.Read.All"
      # az ad sp list --filter "appId eq '00000003-0000-0000-c000-000000000000'"
      id   = "5b567255-7703-4780-807c-7be8301ae99b"
      type = "Role"
    }
  }
}

resource "azuread_application_app_role" "role" {
  display_name          = "melkikh-test-app-role"
  description           = "Read Group information from Microsoft Graph"
  application_object_id = azuread_application.melkikh_test_app.object_id
  allowed_member_types  = ["Application"]
  enabled               = true
  value                 = "Melkikh.Group.Read.All"
}

resource "azuread_service_principal" "sp" {
  application_id               = azuread_application.melkikh_test_app.application_id
  app_role_assignment_required = false
}

## instance with User Assigned managed identity
resource "azurerm_linux_virtual_machine" "melkikh_vm" {
  name                            = var.hostname
  location                        = var.location
  resource_group_name             = azurerm_resource_group.rg.name
  network_interface_ids           = [azurerm_network_interface.nic.id]
  size                            = "Standard_B2s"
  admin_username                  = var.admin_username
  computer_name                   = var.hostname
  disable_password_authentication = true


  identity {
    type         = "UserAssigned"
    identity_ids = [azuread_service_principal.sp.object_id]
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"
  }

  os_disk {
    storage_account_type = "Standard_LRS"
    name                 = "${var.hostname}_os"
    caching              = "ReadWrite"
  }

  admin_ssh_key {
    username   = var.admin_username
    public_key = file(var.public_key_file)
  }
}

and get an error:

terraform apply
random_string.dns-name: Refreshing state... [id=xku9]
...
╷
│ Error: Cannot parse Azure ID: parse "3905c5c8-1d59-4c26-b1cc-9a52d9d9352f": invalid URI for request
│ 
│   with azurerm_linux_virtual_machine.melkikh_vm,
│   on main.tf line 25, in resource "azurerm_linux_virtual_machine" "melkikh_vm":
│   25: resource "azurerm_linux_virtual_machine" "melkikh_vm" {
│ 
╵

How can I use managed identity to work with Graph API?
If it works only with SystemAssigned managed identity, that's ok for me, but I don't understand, how I can use principalId (like azurerm_linux_virtual_machine.melkikh_vm.identity.0.principal_id) assigned to VM, as application principal.

Thanks.

@manicminer
Copy link
Contributor

manicminer commented Jun 11, 2021

Hi @melkikh, thanks for reaching out with your query. Assigning permissions to a service principal that's backing a managed identity is done a little differently. We don't actually support the entire workflow end-to-end at present - we are planning this for version 2.0 of the AzureAD provider (see microsoftgraph/microsoft-graph-docs#323 for context) - but you can get most of the way with Terraform and shim the last mile with some PowerShell or an Azure CLI command.

In order to assign the managed identity to a VM in the first instance, you'll want to use the azurerm_user_assigned_identity resource instead of azuread_application / azuread_service_principal. This will create a managed service principal in the background and export its object ID. For example

resource "azurerm_user_assigned_identity" "melkikh_vm" {
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  name                = "melkikh-vm"
}

resource "azurerm_linux_virtual_machine" "melkikh_vm" {
  # ...

  identity {
    type         = "UserAssigned"
    identity_ids = [azurerm_user_assigned_identity.melkikh_vm.id]
  }
}

You can obtain the underlying service principal's object ID via azurerm_user_assigned_identity.melkikh_vm.principal_id.

The last bit, assigning the permissions, is done via app role assignments on that service principal (which incidentally happens via Microsoft Graph). As above, we don't yet have support for this but plan to add it soon. In the meantime, this guide should give you all you need to do this in PowerShell or build an Azure CLI (or curl) command.

I'm going to leave this issue open as a feature request for app role assignments, since it looks like we don't have one (although we are still tracking the feature on our backlog). I hope the above info helps you construct a workaround in the meantime!

Related: microsoftgraph/microsoft-graph-docs#164

General note: If you're assigning API permissions to an app registration, you want the required_resource_access block of the azuread_application resource; the app_role and oauth2_permission_scope blocks are for creating assignable permissions on your own apps :)

@manicminer manicminer added this to the v2.0.0 milestone Jun 11, 2021
@manicminer manicminer changed the title Use ARM VM assigned manage identity as SP to use Graph API Use ARM VM assigned manage identity as SP to use Graph API [app role assignments] Jun 11, 2021
@manicminer
Copy link
Contributor

manicminer commented Jun 11, 2021

Additionally, I believe our documentation here on the specific interactions between managed identities, their principals and the resources that assume them, could be improved - although you can find lots of material in Azure docs we could probably put together a complete Terraform-specific example once we have full support in place.

@melkikh
Copy link
Author

melkikh commented Jun 11, 2021

Hi, @manicminer
Thanks for your reply.
But I didn't understand how I can assign a role to a identity.
I suppose I should use az role assignment create --role $roleId, but what is the roleId (or role name) should I specify?

ps.
I have tried to assign this role with rest api like in microsoftgraph/microsoft-graph-docs#164
Firstly, I returned app_role and added identity:

resource "azuread_application_app_role" "group_reader" {
  display_name          = "melkikh-test-app-role"
  description           = "Read Group information from Microsoft Graph"
  application_object_id = azuread_application.melkikh_test_app.object_id
  allowed_member_types  = ["Application"]
  enabled               = true
  value                 = "Melkikh.Group.Read.All"
}

resource "azurerm_user_assigned_identity" "melkikh_sp" {
  name                = "melkikh-sp"
  resource_group_name = azurerm_resource_group.rg.name
  location            = var.location
}

After, I tried to make rest api request with these parameters:
resource_id = azuread_application.melkikh_test_app.object_id
app_role_id = azuread_application_app_role.group_reader.role_id
principal_id = azurerm_user_assigned_identity.melkikh_sp.principal_id

az rest --method POST -uri https://graph.microsoft.com/v1.0/servicePrincipals/$resource_id/appRoleAssignedTo --body "{'appRoleId':'$app_role_id','principalId':'$principal_id','resourceId':'$resource_id'}" --headers 'Content-Type=application/json'

and I see:
unrecognized arguments: https://graph.microsoft.com/v1.0/servicePrincipals/<replaced resource_id>/appRoleAssignedTo

@manicminer
Copy link
Contributor

manicminer commented Jun 11, 2021

Hi @melkikh, you'll want to assign roles published by Microsoft Graph rather than your own application. You should have a Microsoft Graph service principal in your tenant, you can query it to list the published roles. For example with Azure CLI: az ad sp show --id 00000003-0000-0000-c000-000000000000 (where the UUID is the app ID for MS Graph), and check the appRoles property. For the usage example you gave, the Group.Read.All role has the ID 5b567255-7703-4780-807c-7be8301ae99b.

@melkikh
Copy link
Author

melkikh commented Jun 14, 2021

Hi, @manicminer !
Sorry, but it still doesn't work :(
I tried to do this again:

provider "azurerm" {
  features {}
}

provider "azuread" {
}

resource "azurerm_resource_group" "rg" {
  name     = "melkikh"
  location = var.location
}

terraform {
  required_version = ">= 0.12"
  required_providers {
    azuread = {
      source  = "hashicorp/azuread"
      version = "~> 1.5.0"
    }
  }
}

resource "azurerm_user_assigned_identity" "melkikh_vm" {
  name                = "melkikh-vm"
  resource_group_name = azurerm_resource_group.rg.name
  location            = var.location
}

resource "azurerm_linux_virtual_machine" "melkikh_vm" {
  name                            = var.hostname
  location                        = var.location
  resource_group_name             = azurerm_resource_group.rg.name
  network_interface_ids           = [azurerm_network_interface.nic.id]
  size                            = "Standard_B2s"
  admin_username                  = var.admin_username
  computer_name                   = var.hostname
  disable_password_authentication = true


  identity {
    type         = "UserAssigned"
    identity_ids = [azurerm_user_assigned_identity.melkikh_vm.id]
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"
  }

  os_disk {
    storage_account_type = "Standard_LRS"
    name                 = "${var.hostname}_os"
    caching              = "ReadWrite"
  }

  admin_ssh_key {
    username   = var.admin_username
    public_key = file(var.public_key_file)
  }
}

output "sp_principal_id" {
  value = azurerm_user_assigned_identity.melkikh_vm.principal_id
}

output "sp_identity_id" {
  value = azurerm_user_assigned_identity.melkikh_vm.id
}

After applying this I got UUID for my managed identity: sp_principal_id.

After I tried to use REST API and catched an error again:

$ export resource_id="00000003-0000-0000-c000-000000000000" && export app_role_id="98830695-27a2-44f7-8c18-0c3ebc9698f6" && export principal_id="2fbfd36c-2cbd-48b1-a23b-76570c3f88fd" && az rest  --method POST -uri https://graph.microsoft.com/v1.0/servicePrincipals/$resource_id/appRoleAssignedTo --body "{\"appRoleId\":\"$app_role_id\",\"principalId\":\"$principal_id\",\"resourceId\":\"$resource_id\"}" --headers 'Content-Type=application/json'
unrecognized arguments: https://graph.microsoft.com/v1.0/servicePrincipals/00000003-0000-0000-c000-000000000000/appRoleAssignedTo

where:
00000003-0000-0000-c000-000000000000 - GUID for MS Graph App,
98830695-27a2-44f7-8c18-0c3ebc9698f6 - AppRole "GroupMember.Read.All" from MS Graph App appRoles,
2fbfd36c-2cbd-48b1-a23b-76570c3f88fd - sp_principal_id from the previous step.
I am confused :(

@manicminer
Copy link
Contributor

$ export resource_id="00000003-0000-0000-c000-000000000000" && export app_role_id="98830695-27a2-44f7-8c18-0c3ebc9698f6" && export principal_id="2fbfd36c-2cbd-48b1-a23b-76570c3f88fd" && az rest  --method POST -uri https://graph.microsoft.com/v1.0/servicePrincipals/$resource_id/appRoleAssignedTo --body "{\"appRoleId\":\"$app_role_id\",\"principalId\":\"$principal_id\",\"resourceId\":\"$resource_id\"}" --headers 'Content-Type=application/json'
unrecognized arguments: https://graph.microsoft.com/v1.0/servicePrincipals/00000003-0000-0000-c000-000000000000/appRoleAssignedTo

where:
00000003-0000-0000-c000-000000000000 - GUID for MS Graph App,
98830695-27a2-44f7-8c18-0c3ebc9698f6 - AppRole "GroupMember.Read.All" from MS Graph App appRoles,
2fbfd36c-2cbd-48b1-a23b-76570c3f88fd - sp_principal_id from the previous step.
I am confused :(

These look ok at first glance. Based on the error message and your code snippet, did you perhaps miss a dash on the --uri argument to the az command?

@psignoret
Copy link

(Cross-posting from microsoftgraph/microsoft-graph-docs-contrib#3377)

@melkikh resource_id should be the object ID of the service principal for Microsoft Graph, in the tenant where you're creating the app role assignment. You're tying to use the appId instead (00000003-0000-0000-c000-000000000000 is the appId for Microsoft Graph).

Because the appId of a service principal is also included in the "servicePrincipalNames" property, and az ad sp show --id {id-or-spn} allows specifying either the object ID or any service principal name in the --id parameter, this is probably the simplest way to get the object ID:

$ export resource_id=$(az ad sp show --id "00000003-0000-0000-c000-000000000000" --query "objectId" -o tsv)

Alternatively, you could also do this:

$ export resource_id=$(az ad sp list --filter "appId eq '00000003-0000-0000-c000-000000000000'" --query [].objectId -o tsv)

Or even this:

$ export resource_id=$(az rest --method GET --uri "https://graph.microsoft.com/v1.0/servicePrincipals?\$filter=appId eq '00000003-0000-0000-c000-000000000000'&\$select=id" --query "value[].id" -o tsv)

@github-actions
Copy link

This functionality has been released in v2.4.0 of the Terraform Provider. Please see the Terraform documentation on provider versioning or reach out if you need any assistance upgrading.

For further feature requests or bug reports with this functionality, please create a new GitHub issue following the template. Thank you!

@github-actions
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 Oct 24, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants