Deploy from GitHub to Azure without any secrets using managed identities
I’ve been building a microservices template as part of my master’s thesis. It’s using GitHub for code hosting and Microsoft Azure for hosting the resources. One key requirement of my template is to use Managed identities for Azure everywhere and not use any secrets when connecting to dependent resources.
Managed identities are a great feature and very easy to use for built-in workloads like VMs, Azure Container Apps, App Services. However, until recently, managed identities could not be used for non-native workloads like GitHub Actions. We had to use an Azure AD app registration instead and store its credentials (including a CLIENT_SECRET
) as GitHub secrets. With the introduction of workload identity federation for app registrations, it was then possible to configure a trust relationship between GitHub and Azure that allows the GitHub Actions to authenticate to Azure without the need for providing a CLIENT_SECRET
. This would have already solved my requirement for not needing any secrets, but the problem is, that creating an Azure AD app registration requires elevated permissions and therefore often can’t easily be done by regular developers.
The good news is that Microsoft now also supports federated credentials for user-assigned managed identities! Managed identities only require regular Azure RBAC rights and can be created via Bicep templates and are therefore much easier to integrate into Infrastructure as Code-processes and CI/CD systems.
Uday Hegde has written a good blog post explaining federated credentials for managed identities: https://blog.identitydigest.com/azuread-federate-mi/
The remainder of this blog post will focus on how federated credentials for managed identities are used in my microservices template. Have a look at the project’s README.md for more details.
Overview
For my microservices template, the entire process for creating the Azure resources and connecting GitHub with Azure is automated via the init-platform.ps1 script. The script must be executed manually once, since there’s the “chicken and egg”-problem of already needing the managed identity to deploy Azure resources via a GitHub workflow.
The script will execute the following steps (among other things that are out of scope for this post):
- It will create a resource group in Azure that will host the managed identity.
- It will create a user-assigned managed identity in this resource group.
- The managed identity will be given
Contributor
&UserAccessAdministrator
rights on the Azure subscription.- (So that the GitHub workflows of my services can create Azure resources and assign rights to newly created managed identities)
- The managed identity will be given additional AAD permissions.
- (My GitHub workflows need to be able to query AAD groups)
- A GitHub environment called
platform
will be created via the GitHub CLI.- This will be used to protect further deployments with required reviewers or other protection rules.
- Federated credentials will be added to the managed identity for the
main
-branch and for theplatform
-environment.- There must be a federated credential for each branch and GitHub environment that we want to deploy from.
- The necessary resource IDs (tenant id, subscription id, client id of our managed identity) will be created as GitHub secrets
Deploying the managed identity and its federated credentials to Azure
The template creates all Azure resources via Bicep-templates stored in the infrastructure directory. The managed identity for GitHub is part of the platform-resources that are shared by all environments of my microservice template (e.g., development, production).
To create the managed identity for GitHub, the following Bicep-template is used:
|
|
Creating the federated credentials via Bicep is more complex. Since I need federated credentials for the main
-branch, the shared platform
-environment, and each actual application environment (development
, production
), I’m creating a list variable that holds the name
and subject
for each credential based on my global config-file:
|
|
The credential resources are then created via a Bicep-loop. It’s important that batchSize(1)
is used because concurrent writes are not supported and will result in a deployment error.
|
|
The fields audiences
, issuer
and subject
are set according to the requirements by GitHub.
Assigning RBAC-roles to the managed identity
In order for my GitHub-worflows to be able to deploy resources to Azure, the managed identity must have the appropriate RBAC permissions. For my template, I’m assigning the Contributor
-role and UserAccessAdministrator
-role at the subscription-scope to the identity. The UserAccessAdministrator
-role is necessary to allow my GitHub workflows to create other managed identities and to assign RBAC-roles to them.
To assign a RBAC-role, we must know its internal ID. For built-in roles, this ID can be found here: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
Here’s the code to reference the Contributor
-role:
|
|
With the reference to the role definition, we can now create the actual role assignment for the managed identity:
|
|
NOTE: Azure does not automatically delete RBAC-role assignments when the managed identity is deleted. You must manually delete them or future re-deployments will fail with a conflict.
Assigning AAD permissions to the managed identity
Some of my GitHub workflows need to be able to query the Azure AD graph for details about an Azure AD group and to do so, the managed identity must have the proper AAD permissions.
Unfortunately, AAD resources and permissions can NOT be created via Bicep templates, so we need to either use the AzureAD-module (which is planned for deprecation), its successor-module Microsoft.Graph, or use the Graph REST API.
Since I’m already using the Az-module to deploy my Bicep-templates, I didn’t want to use another module and potentially deal with separate sign-in methods and tokens, so I decided to just call the Graph API directly:
|
|
NOTE: To execute this step, you must have elevated permissions in Azure AD.
Creating a GitHub environment for the platform
My template uses GitHub environments to protect deployments to Azure.
As with all previous steps, this could be done manually via the UI, but I prefer automation and therefore create the environment via the script by using the GitHub CLI.
The GitHub CLI does not yet have support for environments, so we need to use gh api
to call the GitHub REST API.
The script also uses a custom Exec-function (copied from psake) to fail the PowerShell script if the invocation of the native EXE fails (you’d have to always check $LastExitCode
otherwise).
To create a GitHub environment, I’m using the following code:
|
|
Creating the resource IDs as secrets in GitHub
While there are no passwords to authenticate GitHub with Azure, we still need to tell GitHub about the managed identity and its target tenant & subscription. We therefore need to store some IDs as GitHub secrets. These IDs will then be used by the GitHub workflows when running deployments to Azure.
|
|
Using the managed identity in a GitHub workflow
With the preceding steps, the managed identity has been created, federated credentials have been assigned and GitHub has references to the required IDs as GitHub secrets. We’re therefore finally ready to do any further deployments via GitHub workflows.
My microservice template includes multiple GitHub workflows. There’s a workflow for each service, for shared environment resources, and for the shared platform resources.
We’ll look at the platform.yml workflow as an example for the following steps.
In order for GitHub-workflows to work with federated credentials, we must add permissions for the token:
|
|
We can then use azure/login
to authenticate with Azure (using the previously created GitHub secrets):
|
|
That’s it. Further calls via the Az
-module should then be able to run successfully:
|
|
Feel free to start a discussion or create an issue in cwe1ss/msa-template if you have any feedback.