Getting rid of credentials in Azure — Part 1

Andreas Helland
Contosio Labs
Published in
8 min readMar 24, 2022

--

Passwordless is a popular term, and it’s been a long time since it was considered acceptable to store credentials directly in code. There are however many nuances of this and how you go about achieving it is not necessarily easy. First you need to sort out the terms and tools available to you and then there’s the minor detail of implementing this in various ways.

I will try to go through this topic in an Azure context providing code samples and instructions along the way. This post is called “part one” because there’s more than we can cover in one sitting.

To clarify — we are not talking about avoiding identities. I appreciate that google.com is available to me without having to log in first, but we are not talking about anonymous access to services here. We still want identities for what they provide — just not with username, passwords and the like attached to them if we can avoid it.

Users vs machines
The first big distinction to make is whether one is thinking about the interactive user dimension or communication between servers.

For users it’s about getting rid of the password. By all means — typing in a short password is easier than generating one-time passwords that you have to read on one device and type in on another. Passwords can indeed be user-friendly. But when you want a good password and you need them to be unique across of multiple of services less so. That’s when you want something that is easy on a different level. This could be by using biometrics like your face or fingerprints, or it could be using a FIDO-based USB key, “magic links”, notifications on a mobile phone and more. Usually you will still need a fallback as your fingers can “stop working” if you went rock climbing during the weekend, or you happen to be wearing a face mask that prevents your face from being recognized, etc. There are mechanisms for dealing with this in Azure — we will cover those in a later part of the series.

For servers the most important thing is not storing credentials where they are visible for those that should not have access to them. The preferred option is to get rid of this altogether by using “managed identities”, but if you cannot avoid having keys put them somewhere safe like Azure Key Vault. Workloads in Azure can leverage the fact that Azure itself is a controlled environment and we will look into both regular web apps and microservices.

Federated credentials
We will get back to the mechanisms used for server-side identities as well, but let’s set the stage with a new addition to the feature set in Azure Active Directory — “Federated Credentials”. User identities have more or less all migrated to federation in some manner whether they use classic passwords or advanced biometrics. You don’t (or shouldn’t in most cases) roll your own identity solution from scratch. You let something like Azure AD handle that part and just integrate your app into the federation mechanism. Easier and more secure for you, and easier for the user who doesn’t need to have separate credentials for each web site they visit. (In this context we consider Azure AD to be a federated solution even if you are not federating with other organizations, and regardless of the apps being first- or third-party.)

Federated identities have now been extended to service identities. Microsoft has more explanations on the topic here:
https://docs.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=grap...

The supported providers at the moment are GitHub and Kubernetes (not counting custom OIDC providers). Building things from the ground up we will start with GitHub. The reason for this is simple enough — before we implement identity in an application we need to provision the infrastructure for running said app. Since the Infrastructure as Code approach is the recommended way these days we will have Bicep code stored in GitHub hooking into Azure to do this for us. Bicep is of course not the only tool for this purpose; if you want to use Terraform or Pulumi instead that should be fully doable. But since Bicep doesn’t depend on things like state files and is native to Azure it felt more natural going down that route. (Not that this is a guide on IaC anyways.)

Microsoft Learn has some excellent resources if you’re not up to date on Bicep:

If you have played with GitHub Actions and deploying to Azure before you might think this is nothing new or spectacular since that has been possible for a long time. Yes, you can generate a full set of credentials and paste into GitHub and it will work. The approach described here removes the secret and you’re not required to regenerate credentials when they expire and manage the secret as such. (You should of course still have a procedure in place for invalidating access from GitHub when needed by deactivating or deleting the federated credential.)

First thing you need to do is to clone or fork my repo (GitHub Actions require things to be in a repo and you need access to a settings section of your own):

If landing zones means nothing to you that’s ok. I put more stuff into this repo than just a connection to Azure and it’s as much about playing around with Bicep and building setups that span across multiple Azure services. Landing zone as a term is from the Azure Cloud Adoption Framework which is a bigger topic out of scope for this guide.

When I ran through the setup the official docs on the GitHub part was not correct and I had to adapt that to make it work. Whether it has been fixed or not you can go ahead and perform the app registration part here:
https://docs.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation-create-...

With a config similar to this:

GitHub Action Azure AD App Registration

I went with using Environment as the Entity type, and that’s what the IaC code is geared towards, but feel free to experiment with other types if you have a different use case. (Like branches for instance.)

You will need the clientId, tenantId and subscriptionId. Note that while these are values unique to your environment and part of the credential configuration these are not secrets as such. While one might have reasons not to publish these all over the internet they are not considered sensitive and these alone are not enough to “break into” an Azure environment.

You will also need to give permissions to this app registration to perform actions in Azure. I chose to give “contributor” access on the subscription in the IAM section of the properties for the subscription:

Azure Subscription Role Assignment

This can be restricted in other ways and should probably not be as permissive as this in prod, but it makes testing so much easier.

The way I have set things up is that the code is completely generic and the config specific to different Azure subscriptions are stored in GitHub Environments. While this is not a “keep credentials out of code” thing it is a clean thing to do in general. If you want it to work out of the box I went with the name “Dev” for the environment and AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID and AZURE_CLIENT_ID for the actual secrets:

GitHub Environment Secrets

And in case you end up running into strange errors and scratching your head trying to figure it out “Dev” is case sensitive, so mind that you case your environment names properly in GitHub. (Azure doesn’t care.)

Since things were a bit buggy and things are still in preview I went the real simple route to verify the federated identity works and created an Action that doesn’t actually touch any of the Bicep code:

name: Run Azure Login with OpenID Connect 
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to verify'
type: environment
required: true
permissions:
id-token: write
contents: read
jobs:
build-and-deploy:
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment }}
steps:
- name: 'Az CLI login'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: 'Run Azure CLI commands'
run: |
az account show az group list pwd

Link:
https://github.com/ahelland/Bicep-Landing-Zones/blob/main/.github/workflows/GHFedCredVerifier.yml

Using the aforementioned secrets we acquire a token from Azure, and while still in context we run printouts of details from the subscription, resource groups and which directory we’re in on the build agent. If this works the connection from GitHub to Azure is good.

Of course, this doesn’t properly prove we can actually do things in Azure. So I created a GitHub action that will create a resource group based on a Bicep definition along with a parameter file to show how that works at the same time:

name: Deploy empty resource group for verification 
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to verify'
type: environment
required: true
permissions:
id-token: write
contents: read
jobs:
(...)
Deploy:
runs-on: ubuntu-latest
needs: validate
environment: ${{ github.event.inputs.environment }}
steps:
- uses: actions/checkout@v2
- name: 'Az CLI login'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: 'Deploy Resource Group'
uses: azure/cli@v1
with:
inlineScript: |
az deployment sub create --location norwayeast \
--name ${{ github.run_number }} \
--template-file ./rg-as-verification/main.bicep \
--parameters ./rg-as-verification/azuredeploy.${{ github.event.inputs.environment }}.parameters.json env=${{ github.event.inputs.environment }}

Link:
https://github.com/ahelland/Bicep-Landing-Zones/blob/main/.github/workflows/DeployEmptyRG.ym l

If you check the complete piece of code it will look like a wall of text (I have trimmed that here for readability purposes) and you might be thinking that looks like overkill for a simple task like creating a resource group. In a sense you are right. It’s actually not related to credentials at all and not a requirement of GitHub Actions or Bicep. As I said I’m using this repo for doing more IaC testing and I went all in with linting, validation and what-if of the code before deploy. Remove it if you want to when testing, but it’s useful to know if your IaC code was ok before troubleshooting the identity stuff when we get to more complicated samples.

Empty Resource Group

Basically we are without credentials so far, and with the connection between GitHub and Azure in place we should be good to go for creating more interesting stuff in the next part :)

--

--