Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Create Workload Identity Federation that can create other workload identity federations
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether to fully round out the scenario, that we should provide another Bicep template - i.e an example template that is created by and checked-in by project developers, picked up by the CI/CD pipeline and deployed via the privileged Creator workload identity.:

folder structure:
workload-identity-creates-workload-identities

  • README.md
  • Creator-workload-identity
    • bicepconfig.json
    • main.bicep
    • main.bicepparam
  • Repo-workload-identity-example
    • bicepconfig.json
    • main.bicep
    • main.bicepparam

Thoughts?


## The Idea

Create federated identity credentials (FIC) at appropriate scopes and controls to deploy company and project specific resource. The process is rooted in a Git pull request review process in order to introduce checkpoints, audit trail and repeatability to process. This should restrict the need for manual work requiring high privileges.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Create federated identity credentials (FIC) at appropriate scopes and controls to deploy company and project specific resource. The process is rooted in a Git pull request review process in order to introduce checkpoints, audit trail and repeatability to process. This should restrict the need for manual work requiring high privileges.
Create federated identity credentials (FIC) at appropriate scopes and controls to automate deployment of company and project specific resources. The process use Git pull request reviews to introduce checkpoints, audit trail and repeatability. This should remove the need for manual work requiring high privileges.


## Example scenario

A high-privilege user creates an initial, bootstrap workload identity federation token between Azure and a certain GitHub repository. The workload in this repository is then allowed to access Microsoft Entra to create further workload identity federation tokens and assign them to other repositories.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean a user that is a member of a privileged role or a user that has consented to a high privilege permission?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either works? Actually, now that I think of this and your previous question, it's probably worth being more accurate and use the correct terms etc. Can you help with that? How could I phrase this part?

F.ex. while I deployed to root structures I ran az role assignment list --scope "/" --query "[?roleDefinitionName=='User Access Administrator'].{Username:principalName, ObjectId:objectId}" --output table returned my user name. Consequently I started to think that OK, I probably need to set up some small things to set up more from a GitHub and then delegate further to smaller privileged GitHub actions further things.

Copy link
Collaborator

@dkershaw10 dkershaw10 Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there are multiple things going on here, that probably need more explanation in the usage sections and maybe in the template comments. At this point you probably just need something high level.

The multiple things I'm thinking are:

  1. Delegated perms to create the "creator" workload identity (part 1): Just to "start" the picture, although this is somewhat transparent for most users. For delegated permissions, the calling application must also have the necessary privileges. In this case, Azure PS or Azure CLI has been pre-authorized with those permissions. They're also pre-authorized for full delegated access to ARM - so that both these apps are delegated to access ARM APIs as the signed-in user (they have whatever privileges the signed in user has).
  2. Delegated perms to create the "creator" workload identity (part 2): This is the high-privileged user that you mention. They need to have the privileges:
    • to create apps/service principals (i.e. create the creator workload identity) AND to grant that workload identity the permissions it needs to create other "repo" workload identities. These privileges are normally assigned to users through Entra RBAC roles.
    • if the repo workload identities (that are created via the creator workload identity) need access to Azure resources, then the creator workload identity needs to be assigned the privileges that allows it to create managed identities (for FIC) and assign (scoped) Azure roles to repo workload identities. My assumption here is that this signed-in user's (privileged Azure RBAC permissions/roles) are scoped to an Azure resource group, which allow them to assign the necessary Azure permissions/roles to the creator workload identity.
  3. Permissions in GitHub and GitHub actions: I think you allude to this in your last sentence. This is about how the workload identities' access is scoped in GitHub. I have less familiarity with this and not sure if there is any repo hierarchy, but I could imagine that the creator workload identity is scoped to an organization, which would allow it to create workload identities scoped to all repos in the organization. Or maybe the creator is scoped to a single repo, and that's the only place if can create GitHub actions. However, like I say, you probably have a better idea on this than me.

Copy link
Collaborator

@dkershaw10 dkershaw10 Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove the work "token" in this sentence - so instead: "A high-privileged user creates an initial, bootstrap workload identity federation between Azure and a certain GitHub repository."


The goal is to create a GitHub repository that is privileged enough to create further, more specific federated identifier according to needs of specific projects. This way creating resources can be support by pull request reviews. Together with application roles and restricting the federated identity credential (FIC) access to a certain repository, one can reduce the "blast radius" for any given project and repository and human operators.

## Benefits

- **Enhanced Security:** Restricting high-privilege usage reduces the attack surface and potential for unauthorized access.
- **Scalability:** Facilitates the growth of projects by enabling the creation of new identity federations as needed.
- **Efficient Management:** Streamlines the process of managing resource access across multiple projects and teams.

## Usage could look something like this
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## Usage could look something like this
## To bootstrap the Creator workload identity, run the following scripts in CLI or PS


### az cli

```sh
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should start adding a table with least privileged permissions required to run the template for both delegated and app-only modes - showing permissions required to deploy both the ARM resources and the Graph/Entra resources declared in the template.
This could be part of a readme "template" we provide for all such samples...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where could I find this template? Also, how can one systematically ensure the minimum privileges without just blindly trying or "happening to know"? :)

Copy link
Collaborator

@dkershaw10 dkershaw10 Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template doesn't exist :(

To run the Bicep template that creates the "creator workload identity app", the user would need (I think, but please verify):

This Bicep template itself needs to assign permissions to the creator workload identity (to create other apps, to create managed identities and assign Azure roles) and you can describe those in the template's comments.

# Sign in to Azure.
az login

# Sign in to GitHub.
gh auth login

# Deploy the Bicep template and capture the output as JSON.
deploymentOutput=$(az deployment tenant create --name bootstrap --location WestEurope --template-file ./main.bicep --parameters ./main.bicepparam --output json)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be more scoped to create the creator app scoped to a resource group (this is more about scoping the Azure access - Entra/Graph don't know anything about Azure scoping like resource groups or subscriptions)?
Or do we think each developer that is creating their project workload identities will do so under their own resource group scope?


# Extract appId from the deployment output using jq.
appId=$(echo $deploymentOutput | jq -r '.properties.outputs.appId.value')

# The GitHub repository information.
gitHubOwner="myRoot"
gitHubRepo="myRepo"

# Set GitHub secrets using the GitHub CLI
gh secret set AZURE_CLIENT_ID --repo "${gitHubOwner}/${gitHubRepo}" --body "$appId"
gh secret set AZURE_TENANT_ID --repo "${gitHubOwner}/${gitHubRepo}" --body "$(az account show --query tenantId --output tsv)"
```

### PowerShell

```powershell

# Sign in to Azure.
Connect-AzAccount

# Sign in to GitHub.
gh auth login

# Deploy the Bicep template and capture the output as a JSON string.
$deploymentOutput = New-AzTenantDeployment -Name bootstrap -Location "WestEurope" TemplateFile ./main.bicep -TemplateParametersFile ./main.bicepparam -Verbose

# Extract appId from the deployment output.
$appId = $deploymentOutput.Properties.Outputs.appId.Value

# The GitHub repository information.
$gitHubOwner = "myRoot"
$gitHubRepo = "myRepo"

# Set GitHub secrets using the GitHub CLI.
gh secret set AZURE_CLIENT_ID --repo "$gitHubOwner/$gitHubRepo" --body $appId

$tenantId = (Get-AzTenant).Id
gh secret set AZURE_TENANT_ID --repo "$gitHubOwner/$gitHubRepo" --body $tenantId
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"experimentalFeaturesEnabled": {
"extensibility": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
targetScope = 'tenant'
extension microsoftGraph


@description('The display name for the application.')
param applicationIdentityRegistrationDisplayName string

@description('The name for the application.')
param applicationIdentityRegistrationName string

@description('The owner of the organization that it assigned to a workload identity that is used by GitHub Actions to deploy further resources.')
param gitHubOwner string

@description('The GitHub repository that is assigned to a workload identity that is used by GitHub Actions to deploy further resources.')
param gitHubRepo string

@description('Subject of the GitHub Actions workflow\'s federated identity credentials (FIC) that is checked before issuing an Entra ID access token to access Azure resources. GitHub Actions subject examples can be found in https://docs.github.com/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#example-subject-claims.')
var gitHubActionsFederatedIdentitySubject = 'repo:${gitHubOwner}/${gitHubRepo}:ref:refs/heads/main'


// The githubOIDCProvider and microsoftEntraAudience are well-known values that are used to
// create the federated identity credentials for the GitHub Actions application so the two systems
// can properly authenticate and authorize between each other. For other OIDC enabled systems you need to check
// their documentation to find out the correct values so the federated identity credentials can be created
// to connect to Azure.
var githubOIDCProvider = 'https://token.actions.githubusercontent.com'
var microsoftEntraAudience = 'api://AzureADTokenExchange'

// In order for the Azure and GitHub Actions to communicate with each other, an
// application for GitHub Actions is created to Azure Entra. This application is then
Copy link
Collaborator

@dkershaw10 dkershaw10 Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// application for GitHub Actions is created to Azure Entra. This application is then
// application for GitHub Actions is registered in Microsoft Entra. This application is then

Entra is not part of Azure (any more) - yes it used to be Azure AD, but part of the rebranding was to decouple from Azure, as we are pushing Entra to be a multi-cloud offering (and not tied to Azure in any way).

// assigned an application role that allows it to access the Microsoft Graph API.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// assigned an application role that allows it to access the Microsoft Graph API.
// configured as a workload identity via federated identity credentials creation.

Assigning a role comes later. Here we are configuring it as a workload identity

resource githubActionsApplication 'Microsoft.Graph/[email protected]' = {
uniqueName: applicationIdentityRegistrationName
displayName: applicationIdentityRegistrationDisplayName

// This creates the federated identity credentials for the GitHub Actions application.
resource githubFederatedIdentityCredential '[email protected]' = {
name: '${githubActionsApplication.uniqueName}/githubFederatedIdentityCredential'
audiences: [microsoftEntraAudience]
description: 'Identity for application to deploy the root identity infrastructure.'
issuer: githubOIDCProvider
subject: gitHubActionsFederatedIdentitySubject
}
}


// This is the Azure service principal the GitHub Actions application is assigned to.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// This is the Azure service principal the GitHub Actions application is assigned to.
// This is the service principal representing the GitHub Actions application.

resource gitHubIdentityActionsServicePrincipal 'Microsoft.Graph/[email protected]' = {
displayName: applicationIdentityRegistrationDisplayName
appId: githubActionsApplication.appId
}


// The identifier '00000003-0000-0000-c000-000000000000' is a well-known identifier
// for Microsoft first part application 'Microsoft Graph', see more at
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// for Microsoft first part application 'Microsoft Graph', see more at
// for Microsoft first party application 'Microsoft Graph', see more at

// https://learn.microsoft.com/en-us/troubleshoot/azure/entra/entra-id/governance/verify-first-party-apps-sign-in.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// https://learn.microsoft.com/en-us/troubleshoot/azure/entra/entra-id/governance/verify-first-party-apps-sign-in.
// https://learn.microsoft.com/troubleshoot/azure/entra/entra-id/governance/verify-first-party-apps-sign-in#application-ids-of-commonly-used-microsoft-applications.

Recommend using the deep link above.

resource microsoftGraphServicePrincipal 'Microsoft.Graph/[email protected]' existing = {
appId: '00000003-0000-0000-c000-000000000000'
}


// This searches the used application role permission by its friendly name. There should be only one,
// so the fist one is taken to be used in the symbolic name.
Comment on lines +62 to +63
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// This searches the used application role permission by its friendly name. There should be only one,
// so the fist one is taken to be used in the symbolic name.
// This searches for a Microsoft Graph application role (appRole) by its friendly name.

app role "value" has a uniqueness constraint, so it's guaranteed to be the only one. Bicep filter always returns an array, so we need to pick the first one. Not sure that the additional stuff adds much value here.

// See at https://learn.microsoft.com/en-us/graph/permissions-reference#applicationreadwriteownedby
// for the actual description. Look also at https://www.azadvertizer.net/azEntraIdAPIpermissionsAdvertizer.html?targetPermissionId=18a4783c-866b-4cc7-a460-3d5e5662c884
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your call - but I wouldn't mention the azadvertizer web app. Not that it isn't great or useful, but I don't think it adds much here, and too many comments detract from the template code.
If it's your web app and you're trying to advertise its usage, then that's different :)

// how one can search for permissions GUIDs. Using clear names is clearer.
//
// TODO: At the moment 'Application.ReadWrite.All' is used because there's a bug in
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - we're close here on getting the fix out.
The is going through ring deployments - we'll verify it in an early ring, but if that checks out, this should complete rollout fingers-crossed, before end of the week.

// Azure. See at https://github.com/microsoftgraph/msgraph-bicep-types/issues/142 and at
// https://learn.microsoft.com/en-us/graph/permissions-reference#approleassignmentreadwriteall.
param appRoleName string = 'Application.ReadWrite.All'
var graphAppRoles = microsoftGraphServicePrincipal.appRoles
var appRoleDetail = filter(graphAppRoles, graphAppRoles => graphAppRoles.value == appRoleName)[0]


// Assign the GitHub Actions service principal the Application.ReadWrite.OwnedBy permission,
// which allows the service principal to be able to create and manage the applications and service principals is creates/owns.
resource symbolicname 'Microsoft.Graph/[email protected]' = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You still have symbolicname here, which I think is odd.

appRoleId: appRoleDetail.id
principalId: gitHubIdentityActionsServicePrincipal.id
resourceId: microsoftGraphServicePrincipal.id
}
Copy link
Collaborator

@dkershaw10 dkershaw10 Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like I said earlier, I think this template is missing assigning Azure roles to this Creator workload identity, and more specifically the Role-based access control administrator. This role assignment should probably be scoped to a resource group or subscription.

If this isn't done, then when the Creator tries to "deploy" repo workload identity Bicep templates that specify what Azure roles the repo workload identity needs, it'll fail.


// This outputs the application ID of the GitHub Actions application. So, for instance, this can be
// used in a PowerShell or a Bash Script to set the AZURE_CLIENT_ID environment variable in GitHub.
output appId string = githubActionsApplication.appId
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using './main.bicep'

@description('The display name for the application.')
param applicationIdentityRegistrationDisplayName = 'GitHub Actions Example Application Deployer'

@description('The name for the application.')
param applicationIdentityRegistrationName = 'root-appident-deployer'

@description('The owner of the organization that it assigned to a workload identity that is used by GitHub Actions to deploy further resources.')
param gitHubOwner = 'github-owner'

@description('The GitHub repository that is assigned to a workload identity that is used by GitHub Actions to deploy further resources.')
param gitHubRepo = 'github-repo'