microsoftgraph / msgraph-bicep-types

Repo contains Microsoft Graph resource types to integrate with bicep templates.
MIT License
37 stars 6 forks source link

Looks like not possible to deploy workload identity with role microsoft.directory/applications/create in Bicep #154

Open veikkoeeva opened 1 month ago

veikkoeeva commented 1 month ago

Bicep version

az bicep version Bicep CLI version 0.28.1 (ba1e9f8c1e)

Resource and API version Which Microsoft.Graph resource and API version has the issue?

Microsoft.Graph/applications@v1.0

Auth flow User signed to deploy a Bicep script from a command line using az deployment create ....

Deployment details I can provide if it makes sense.

Describe the bug A clear and concise description of what the bug is vs what you expected to happen

I am using the new Bicep Graph module to create a workload identity that can then be assigned to e.g. GitHub to a certain repository. The purpose is to assign further workload identities from this repository that are defined in Bicep files there.

When I try to deploy a service principal with a role that I think has some of the right assignableScopes, I get an error

see https://aka.ms/arm-deployment-operations for usage details.","details":[{"code":"InvalidActionOrNotAction","message":"The resource provider referenced in the action 'microsoft.directory/applications/create' is not returned in the list of providers from Azure Resource Manager."}]}]}]}}

It feels to me I should be able do to this in Bicep and just put wrong assignableScopes. But on the other hand there's probably more into this. This may be related to https://github.com/microsoftgraph/msgraph-bicep-types/issues/134. I am not sure if this is a bug or would be a feature. It may also very well be I don't know just how to make this happen in Bicep. :)

To Reproduce Additional context

The Bicep I use are like follows. I have permuted all kinds of things to assignableScopes, but as expected, the error message stays the same.


main.bicep:

resource azureRootManagementGroup 'Microsoft.Management/managementGroups@2023-04-01' existing = {
    scope: tenant()
    name: '<guid>'
}

var applicationIdentityRegistrationDisplayName = 'GitHub Actions Identity Application Deployer'
var applicationIdentityRegistrationName = 'root-appident-deployer'
var githubOIDCProvider = 'https://token.actions.githubusercontent.com'
var microsoftEntraAudience = 'api://AzureADTokenExchange'
var gitHubActionsFederatedIdentitySubject = 'repo:${gitHubOwner}/${gitHubRepo}:ref:refs/heads/main'
resource identityGithubActionsApplication 'Microsoft.Graph/applications@v1.0' = {
  uniqueName: applicationIdentityRegistrationName
  displayName: applicationIdentityRegistrationDisplayNamef

  resource githubFederatedIdentityCredential 'federatedIdentityCredentials@v1.0' = {
    name: '${identityGithubActionsApplication.uniqueName}/githubFederatedIdentityCredential'
    audiences: [microsoftEntraAudience]
    description: 'Identity for application to deploy the root identity infrastructure.'
    issuer: githubOIDCProvider
    subject: gitHubActionsFederatedIdentitySubject
    }
}

resource gitHubIdentityActionsServicePrincipal 'Microsoft.Graph/servicePrincipals@v1.0' = {
  displayName: applicationIdentityRegistrationDisplayName
  appId: identityGithubActionsApplication.appId
}

module companyIdentityPipelineRoleDefinitionAndAssignment './companyIdentityPipelineRoleDefinitionAndAssignment.bicep' = {
  name: 'bootstrapIdentityRoleDeployment'
  scope: tenant()
  params: {
    gitHubIdentityActionsServicePrincipalId: gitHubIdentityActionsServicePrincipal.id
    azureRootManagementGroupId: azureRootManagementGroup.name
  }
}

companyIdentityPipelineRoleDefinitionAndAssignment.bicep:

param azureRootManagementGroupName string

resource companyIdentityPipelineRoleDefinitionAndAssignment  'Microsoft.Authorization/roleDefinitions@2022-04-01' = {
  name: guid(tenant().tenantId, 'CompanyIdentityPipelineRole')
  properties: {
    roleName: ' identity deployment role'
    description: 'Grants rights to manage  identity resources.'
    type: 'CustomRole'
    assignableScopes: [
      /* Here e.g. slash, '/' won't work. */
      '/providers/Microsoft.Management/managementGroups/${azureRootManagementGroupName}'
    ]
    permissions: [
      {
        actions: [
            'microsoft.directory/applications/create'
            'microsoft.directory/applications/read'
            'microsoft.directory/applications/credentials/update'            
            'microsoft.directory/servicePrincipals/create'
            'microsoft.directory/servicePrincipals/read'
            'microsoft.directory/servicePrincipals/credentials/update'
        ]
        notActions: []
        dataActions: []
        notDataActions: []
      }
    ]
  }
}
dkershaw10 commented 1 month ago

@veikkoeeva thanks for filing this issue. I don't think this is an issue with Graph Bicep. It looks like you are trying to create a new Azure role definition. However it looks like you are trying to add Entra permissions (as actions) to this role. Pretty sure that mixing and matching between Azure and Entra RBAC systems doesn't work. I don't think ARM knows anything about Entra (Microsoft.Directory) permissions.

Can you elaborate a bit more on what you are trying to do with this custom role definition? Are you trying to grant your service principal a set of application and service principal action permissions?

veikkoeeva commented 1 month ago

@dkershaw10 No problem for filing, thanks for asking more.

So, what I tried to do was/is:

  1. Create bootsrap.bicep that creates very basic structures such as management groups, subscriptions and a policy to restrict all workload identities that try to access Azure to a certain organization (OrganizationX, see further). This Bicep script is run by a high privledged user from a command line like az deployment tenant create --name bootstrap --template-file ./main.bicep... (could be stack deploy, but Bicep Graph does not yet support it).

  2. The bootstrap Bicep script main.bicep creates subscriptions and management groups. It also creates one Workload identity and creates a policy that restricts all workload access to one organization and its repositories. Let's say this is https://github.com/OrganizationX/. This workload identity grants access to one projects to deploy resources to Azure. Let's say this is https://github.com/OrganizationX/BaseProject. These are high level resources such as policies and other things, created to management groups.

I was thinking that maybe the workload service principal granted to https://github.com/OrganizationX/BaseProject could also create furher workload identities. Let's say there is project https://github.com/OrganizationX/RegularProject that could have its own workload identity to access Azure, but it's severly more restricted and scoped e.g. to a resource group and so can do much less things.

But it appears that I cannot grant rolePermissions to the to workload identity granted to https://github.com/OrganizationX/ so it could create application with 'Microsoft.Graph/applications, then do Microsoft.Graph/servicePrincipals and continue to give approriate, limited rolePermissions that it is then given to https://github.com/OrganizationX/RegularProject.

dkershaw10 commented 1 month ago

That can be done, but not through ARM APIs (or Azure role management). You can grant those permissions to a service principal using the appRolesAssignedTo resource. You'd need to find the appRoleId for the Application.ReadWrite.OwnedBy permission that you would grant using appRolesAssignedTo.

You can see an example here.

In terms of looking up the appRoleId, that's a bit trickier. You need to find the service principal that represents Microsoft Graph in your tenant (appId = 00000003-0000-0000-c000-000000000000), and look at that service principal's app roles to find the one with Application.ReadWrite.OwnedBy to get its appRoleId.

veikkoeeva commented 1 month ago

That can be done, but not through ARM APIs (or Azure role management). You can grant those permissions to a service principal using the appRolesAssignedTo resource. You'd need to find the appRoleId for the Application.ReadWrite.OwnedBy permission that you would grant using appRolesAssignedTo.

You can see an example here.

In terms of looking up the appRoleId, that's a bit trickier. You need to find the service principal that represents Microsoft Graph in your tenant (appId = 00000003-0000-0000-c000-000000000000), and look at that service principal's app roles to find the one with Application.ReadWrite.OwnedBy to get its appRoleId.

Thanks! OK, let I try to make further progress, maybe useful for others who read this too.

If I go to https://www.azadvertizer.net/azEntraIdAPIpermissionsAdvertizer.html?targetAppId=00000003-0000-0000-c000-000000000000 and put OwnedBy as a term to Permission, I see several identifiers. It may be what we're interested is https://www.azadvertizer.net/azEntraIdAPIpermissionsAdvertizer.html?targetPermissionId=18a4783c-866b-4cc7-a460-3d5e5662c884 (Application.ReadWrite.OwnedBy)

Allows the app to create other applications, and fully manage those applications (read, update, update application secrets and delete), without a signed-in user.  It cannot update any apps that it is not an owner of.

Taking this together with the example you shared and the code I shared in this thread, could it be like

resource symbolicname 'Microsoft.Graph/appRoleAssignedTo@v1.0' = {
    appRoleId: '18a4783c-866b-4cc7-a460-3d5e5662c884' /* Probably would be neater to have this like a 'Bicep existing ='. */
    principalId: /* If resourceId is the GH app SP, what could this be then be?  The application created by the GH workload? */
    resourceId: gitHubIdentityActionsServicePrincipal.id /* Is this right, the resource is the GitHub workload SP? */
  }

I'm afraid I'm still taking some time here. I put the questions in the comments. I think the appRoleId is found and its always the same for every tenant, so can be looked up like that.

But is resourceId the GH workload SP that I create and assign to https://github.com/OrganizationX/BaseProject and put that service principal to resourceId is that right? In which I don't know what should be assigned to principalId.

It appears I am confused as to why would I need two service principal identifiers instead of one and can't figure out how to cough up at least other one (which would allow also just to try out permutations).

dkershaw10 commented 1 month ago

No problem @veikkoeeva Yes you are on the right lines here.

The principalId represents the principal that you want to grant the appRoleId to - in this case it would be the GitHub actions SP's ID. The resourceId represents the service principal ID of the resource/API that exposes the appRoleId - in this case it's Microsoft Graph.

I think the Bicep would look something like this (note I've not tested this E2E):

resource msGraphSP 'Microsoft.Graph/servicePrincipals@v1.0' existing = {
    appId: '00000003-0000-0000-c000-000000000000'
}

// search for appRole by app role permission friendly name
param appRoleName string = 'Application.ReadWrite.OwnedBy'
var graphAppRoles = msGraphSP.appRoles
var appRoleDetail = filter(graphAppRoles, graphAppRoles => graphAppRoles.value == appRoleName)

resource symbolicname 'Microsoft.Graph/appRoleAssignedTo@v1.0' = {
    appRoleId: appRoleDetail.id
    principalId: gitHubIdentityActionsServicePrincipal.id // Client SP being granted permission to access the resource (API)
    resourceId: maGraphSP.id // Resource here is Microsoft Graph
}
dkershaw10 commented 1 month ago

@veikkoeeva once you have your template(s) working, you are very welcome to contribute them to this repo here as an additional quickstart sample.

veikkoeeva commented 1 month ago

@veikkoeeva once you have your template(s) working, you are very welcome to contribute them to this repo here as an additional quickstart sample.

Sounds like a deal I try to do! I'm off-grid next week, so probably I try to deploy a minimal sample after next week and write a sample like those existing ones (maybe with a bit more comments :)).

dkershaw10 commented 1 month ago

@veikkoeeva LMK if you need any further help - glad to assist in any way.

veikkoeeva commented 1 month ago

@veikkoeeva LMK if you need any further help - glad to assist in any way.

I'm on it. I apologize for being so sluggish. I'm just a bit late after a week off from computer.

dkershaw10 commented 1 month ago

No worries or rush - I'm eager for the community to contribute samples, and hoping that your contribution might kickstart others to contribute too.

veikkoeeva commented 1 month ago

No worries or rush - I'm eager for the community to contribute samples, and hoping that your contribution might kickstart others to contribute too.

I want to worry a bit. I have a new small project in mind, better tie knots. I started a PR, but it's not ready yet. Mostly to show I got it started and if there's something that I should think already.

What I am thinking to write some more to README.md and add also azadvertiser resource with some explanations. Naturally I should test the script a bit too and document it. At the moment this indeed just that I put the pieces there. It's about midnight here now, I think I further this during the new few days so we hopefully get this over the finish line (knock, knock wood).

And sure, I'm pleased I can write and give back a bit. I think the issue is that people use a lot of time banging their head to the wall while solving problems. It would be awfully nice if a bit more were shared in problem-solution way and maybe a bit of reasoning and techniques to solve these kinds of issues. :)

dkershaw10 commented 1 month ago

I totally agree - I'm looking to restructure our template examples into proper "how-tos" that solve for real problems. My issue is that I can guess at some of these things, but requires real input from customers who are facing these problems. I might put out a call to action at the next Bicep community call, to ask folks for a short write-up of some of their real world problems in this space, and see if we can pivot towards more useful template examples that solve for these problems (or identify gaps we have).

veikkoeeva commented 1 month ago

@dkershaw10 My general feeling of the examples here is:

Now, that list one may be a propagation issue too. I did try it many times over a span of time almost an hour. I also went to Azure portal to explore and saw it adviced to check some issues with it. I did that, but it didn't work. Then from the portal I added the rights to manage all resources (bad, I know) and set up a daily schedule to run this Bicep. Since I went to sleep and though that GitHub retries on my behalf. Now the latest issue in fact is:

The client 'guid' with object id 'same guid as previous' does not have permission to perform action 'Microsoft.Authorization/roleDefinitions/write' at scope '

Which looks like progress to me. I'm fairly sure everyone wants to set up a role definition with this, so further rights need to given for this FDIC (that was created by the bootstrap thas has the Graph rights) that creates other FDICs for projects and assigns roles for them. So, not only other per-repository Graph resources but also assigns appropriate roles to them.

I consider if the URL to portal looks stable and/or adding a screenshot would be useful to help people looking at a sample like this. Then also it likely will be useful to to have some scripts lile az ad app permission list --id someId or even more complex like az role assignment list --scope "/providers/Microsoft.Management/managementGroups/some-mg-group" --query "[].{PrincipalId:principalId, RoleDefinitionName:roleDefinitionName}" -o json to show how to check some things. I don't remember this, I'm not sure if ChatGPT can always write one to help.

This is a bit of a self-study (probably helps with cert exams :)) so after a day of work, I think, I remove the apps, SPs etc. from Azure and re-run the bootstrap PS script with my high level privileges to create the root FDIC that is assigned to this GitHub repository, with the appropriate permissions to create new FDICs and assign role sto them. I screenshot the Azure portal image and paste here (if not for other things but to give and idea for readers who come later) and try to figure what priviledges it really needs to create new FDICs and assign roles.

As an aside, is there a good way to check what are sufficient privileges when the error message is that they were insufficient? It's trial-and-error for me and I suspect for many others too. I do see MS docs are being cleaned at the moment to not run all the examples with high privileges but what appears to be more limited or maybe even minimum ones, which is a good thing.

I hope not too messy. I wrote this quickly on a morning coffee to also leave some notes what happens when your random developer gets an idea to do something (and why e.g. MS Aspire might be popular). :)

dkershaw10 commented 1 month ago

@veikkoeeva Firstly - thank you for all the effort you are putting in here to develop a high quality sample, and set a great example for everyone else - plus all your thinking here.

I will review your PR (and ask some engineers on our team to take a look too), to provide feedback. A couple of points and thoughts for you:

  1. Unfortunately Deployment Stacks are not supported at all for extensibility resources. This is something (together with the Bicep team) we want to solve, but as yet we don't have a concrete date for this.
  2. There is indeed a lot of effort to clean up docs with regards to permissions and ensure that least privileged permissions are properly documented.
  3. Totally agree on the challenges to identify the least privileged permissions your app needs when you get a 403 Forbidden. We've talked about returning the least privileged permission required as part of the error message, but we need to make sure we have the correct info to be able to do this (goes back to item 2).
  4. Maybe we should add the least privileged permissions required to run the template sample, for both delegated and app-only modes?
dkershaw10 commented 1 month ago

@veikkoeeva In terms of permissions required to run the template - assuming that you are doing this with a signed-in user and using Az CLI/Az PS:

  1. ARM - whatever the minimal permissions are that allows a user to deploy a template. There are no Azure resources declared, so no further Azure permissions are required for the signed-in user.
  2. Graph - the caller is:
    • creating apps/SPs/FICs
    • reading service principals
    • creating an app role assignment to Microsoft Graph app roles

This Entra built-in roles topic should help you. with item 2.

Both these roles are highly privileged roles, and I would recommend the use of Privileged Identity Management here to temporarily provide these roles, to run this template.

veikkoeeva commented 1 month ago

@dkershaw10

Firstly - thank you for all the effort you are putting in here to develop a high quality sample, and set a great example for everyone else - plus all your thinking here.

No problem. I think at mimimum I need to get this to work for myself reliably so I can document it for others. Takes a bit of time as I need to arrange time to spend time with Azure (removing tokens, re-creating, re-running GitHub to check etc.).

For creating app role assignments to Graph app roles requires the additional **Privileged Role Administrator" role.

So, in order for the FDIC application on GitHub to create other FDICs it needs something else than Application.ReadWrite.OwnedBy?

I ask since I'm not sure what you mean. I assume so.

Both these roles are highly privileged roles, and I would recommend the use of Privileged Identity Management here to temporarily provide these roles, to run this template.

The idea I have had with this is that I can run a bootstrap script to create a FDIC with sufficient privileges to create others and restrict it then to a given repository and audit via PRs and roles what it can do and where. It consumes a Bicep script and creates FDICs for other projects/repositories. So the idea is to make this process more auditable and constrained like this.

Thanks for the comments on the PR. I'll fix those while working with this.

dkershaw10 commented 1 month ago

@veikkoeeva Let me try this again. To avoid confusion I think there are 3 things here:

  1. A bootstrap script/template (run once) - let's call it bootstrap.bicep
  2. A "creator" app (that uses federated identity creds where the scope of the federation could be to a GitHub Org)
  3. Apps (that use FIC) scoped to one or more repos in the GitHub Org, that are assigned some roles in Azure (or even Graph?)

The bootstrap script creates the "creator" app.
The "creator app" creates the apps in 3, based on Bicep templates - this uses CI/CD practises.

I think the sample you have is for item 1 - to create a "creator" app that can create other apps To interactively run bootstrap.bicep, the signed-in user must be in a privileged role to deploy a declared "creator" app, SP and federated identity credential and assign that creator app the app-only permissions it needs to be able to create apps (number 3). So to run bootstrap.bicep interactively using Az CLI/PS, the signed-in user needs the Entra Application Administrator and Privileged Role Administrator roles.

Does this make sense?

Questions

  1. For 3, I assume this uses CI/CD processes, where someone creates a PR with a Bicep template that declares the workload identity they need (scoped to a repo in the Org) and assigns it some roles, and once the PR is merged, the build pipeline deploys the Bicep template running as the creator app?
  2. What roles are being assigned to the workload identities created by the creator app? Are they Azure roles or something different?
veikkoeeva commented 1 month ago

I think the sample you have is for item 1 - to create a "creator" app that can create other apps To interactively run bootstrap.bicep, the signed-in user must be in a privileged role to deploy a declared "creator" app, SP and federated identity credential and assign that creator app the app-only permissions it needs to be able to create apps (number 3). So to run bootstrap.bicep interactively using Az CLI/PS, the signed-in user needs the Entra Application Administrator and Privileged Role Administrator roles.

Does this make sense?

Yes. This is what I do. I have a PowerShell script that runs a Bicep script that creates the initial "creator FDIC" and an application and sets up the parameters application and FDIC parameters to creator GitHub repository and e.g. assigns a role to this FDIC that that restrict only from that company account. So, this phase is run by my own, high level privileges.

The problem I described happend when I run a GitHub pipeline in creator repository where I want it can create a FDIC to project SomeProject using another Bicep. So, here I use the "creator FDIC" in the GitHub pipeline to deploy a Bicep template to Azure . I got that error Insufficient privileges to complete the operation. Graph client request id... in the GitHub pipeline in creator repository.

I plan to spend some time this evening to continue where I left off last time. As noted I just pumped from Azure portal what I considered was more priviledges and let a timer to run the script. Next morning I saw the error had changed to that "Microsoft.Authorization/roleDefinitions/write' at scope". In the Bicep script creator tries to deploy when it creates the FDIC to "SomeProject" there are also roles it tries to assign to that FDIC. The error looked to me the creator FDIC wasn't authorized to do that. So, this looks like being something I need to allow it to do in the initial bootstrap script.

dkershaw10 commented 1 month ago

@veikkoeeva Gotcha - we have a known issue that we are trying to get to the bottom of, and fix. The expectation is that an SP with the Application.ReadWrite.OwnedBy permission is set as the owner of any application it creates. That is not happening. So any subsequent operations to either add FIC or create an SP from the creator are failing with the 403. You can see some discussion about this in #142.

The only workaround (that doesn't involve manually updating the ownership on the created application) would be to give the creator app the Application.ReadWrite.All permission. This is a much more privileged permission that will allow the creator app the ability to create apps/SPs and update any app or SP in the tenant. It's probably fine to use this and verify the flow end to end, but I wouldn't recommend deploying the creator app to production with this privileged permission.

I plan to spend some time this evening to continue where I left off last time. As noted I just pumped from Azure portal what I considered was more priviledges and let a timer to run the script. Next morning I saw the error had changed to that "Microsoft.Authorization/roleDefinitions/write' at scope". In the Bicep script creator tries to deploy when it creates the FDIC to "SomeProject" there are also roles it tries to assign to that FDIC. The error looked to me the creator FDIC wasn't authorized to do that. So, this looks like being something I need to allow it to do in the initial bootstrap script.

Right - this was my second question - because as I think you've figured out, the creator SP needs to be assigned to some Azure RBAC role/permission that allows it to assign Azure roles to the workload identities it creates.

cc: @eketo-msft