-
Notifications
You must be signed in to change notification settings - Fork 12
Create Workload Identity Federation that can create other workload identity federations #160
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||||||
|
|
||||||
| ## 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. | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| ## 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. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| ### az cli | ||||||
|
|
||||||
| ```sh | ||||||
veikkoeeva marked this conversation as resolved.
Show resolved
Hide resolved
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"? :)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)? |
||||||
|
|
||||||
| # 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 | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
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. | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
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. | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| 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 | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
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 | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||||||||
| // 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 | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah - we're close here on getting the fix out. |
||||||||
| // 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]' = { | ||||||||
veikkoeeva marked this conversation as resolved.
Show resolved
Hide resolved
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||||
| } | ||||||||
veikkoeeva marked this conversation as resolved.
Show resolved
Hide resolved
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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' |
There was a problem hiding this comment.
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
Thoughts?