Safely access Azure Kubernetes Service in GitHub Action with AAD Federated Identity

Jay Lee
Microsoft Azure
Published in
5 min readFeb 21, 2023

--

Recently, I have been asked by a few developers to show them how to access AKS safely in GitHub Action. As this is a topic of significant interest for developers using Kubernetes, I am writing this comprehensive step-by-step guide.

I will start with the traditional way of doing it. Before the introduction of the AAD Federated Identity, az login inside GitHub Action has always been using a service principal that is created with “ — sdk-auth” like an example here. https://learn.microsoft.com/en-us/azure/container-instances/container-instances-github-action?tabs=userlevel

$ az ad sp create-for-rbac \
--name github-action-sp \
--scope /subscriptions/***/resourceGroups/sandbox-rg \
--role Contributor \
--sdk-auth
Option '--sdk-auth' has been deprecated and will be removed in a future release.
Creating 'Contributor' role assignment under scope '/subscriptions/***/resourceGroups/sandbox-rg'
The output includes credentials that you must protect. Be sure that you do not include these credentials in your code or check the credentials into your source control. For more information, see https://aka.ms/azadsp-cli
{
"clientId": "***",
"clientSecret": "***",
"subscriptionId": "***",
"tenantId": "***",
"activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
"resourceManagerEndpointUrl": "https://management.azure.com/",
"activeDirectoryGraphResourceId": "https://graph.windows.net/",
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
"galleryEndpointUrl": "https://gallery.azure.com/",
"managementEndpointUrl": "https://management.core.windows.net/"
}

This JSON output above is a parameter that azure/login@v1 expects. Create AZURE_CREDENTIALS Actions secrets with JSON and supply it with creds.

name: az login in GitHub Actions
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
azlogin:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: 'Az CLI Login'
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

- name: 'Run basic AZ CLI command'
run: az group list

This traditional way still works fine today, even though the message is alarming, “Option ‘ — sdk-auth’ has been deprecated and will be removed in a future release.” Usually, developers should be deeply concerned when they see something deprecated. This is where AAD Federated Identity can help. Recently, it has become GA, so it’s safe to use in production. Even GitHub Action advises developers to consider it because it’s a more elegant and safer approach to leveraging OIDC flow for authentication.

Set up Federated Identity

I must create a new service principal to set up with federated identity. This time, I will make one without sdk-auth option.

$ az ad sp create-for-rbac \
--name github-action-sp-fi \
--scope /subscriptions/***/resourceGroups/sandbox-rg \
--role Contributor

Now, Go to Azure portal → Certificates & secrets → Federated credentials, then + Add credential.

AAD apps on the Azure portal

Federated credentials support various scenarios like CMK, Kubernetes, GitHub Actions, etc. If you have ever used AKS workload identity, you must have seen this.

Chose GitHub Actions deploying Azure resources

Setup requires a bit of information regarding the GitHub repository where you run your Actions. The entity type controls which situation to issue the OIDC token, for example, pull request, specific tag, specific branch, etc. In my setup below, the OIDC token will be issued inside the GitHub Action run by main branch then used by azure/login@v1.

Federated credetial for GitHub Action

Service Principal federated identity is ready. Now, it requires an additional setup for GitHub Action to generate an OIDC token, which azure/login@v1 will eventually pick it up to exchange it against AAD. A few minor changes are made below. Specifically, look at permissions and azure/login@v1 parts below.

name: az login in GitHub Actions
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
id-token: write
contents: read
jobs:
azlogin:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: 'Az CLI Login'
uses: azure/login@v1
with:
client-id: ${{ secrets.CLIENT_ID }}
tenant-id: ${{ secrets.TENANT_ID }}
subscription-id: ${{ secrets.SUBSCRIPTION_ID }}

- name: 'Run basic AZ CLI command'
run: az group list

It’s a straightforward setup, so that action should run green without issues. The remaining steps are the AKS part. It will be a two-step process if you can depict the normal authentication flow of AKS users. 1. az aks get-credential to populate KUBECONFIG 2. 2FA with device login flow. This would be different for GitHub Action as it requires a non-interactive authentication. kubelogin is born to handle this requirement, a plug-in to handle the authentication with AAD in both interactive and non-interactive ways. It’s a command line tool that should be installed separately from Azure CLI or kubectl. Look at the workflow below first.

name: az login in GitHub Actions
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
id-token: write
contents: read
jobs:
azlogin:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: 'Az CLI Login'
uses: azure/login@v1
with:
client-id: ${{ secrets.CLIENT_ID }}
tenant-id: ${{ secrets.TENANT_ID }}
subscription-id: ${{ secrets.SUBSCRIPTION_ID }}
# creds: ${{ secrets.AZURE_CREDENTIALS }}

- name: 'Run basic AZ CLI command'
run: az group list
- uses: azure/setup-kubectl@v3
name: Setup kubectl
- name: Setup kubelogin
uses: azure/use-kubelogin@v1
with:
kubelogin-version: 'v0.0.26'
- name: Set AKS context
id: set-context
uses: azure/aks-set-context@v3
with:
resource-group: 'sandbox-rg'
cluster-name: 'rbac-cluster'
admin: 'false'
use-kubelogin: 'true'
- name: Run kubectl
run: |
kubectl get pods

We start with installing two command line tools azure/setup-kubectl@v3 and azure/use-kubelogin@v1, then use azure/aks-set-context@v3 to authenticate using kubelogin under the hood in a non-interactive way. Feel free to run this action now.

Extra Step

You might have thought that would be it, so you must be surprised to see the workflow fails. But it actually makes total sense as we’re using the service principal to authenticate against AAD, and it doesn’t have any role assigned to get the list of the pods. This is an extra step to give an admin role to our service principal on AKS so that it will complete the very last step.

$ az ad sp show --id {app id of previously created service principal} --query "id"

Then create a role binding with the id obtained from the command above. You need an admin role to create the role binding on AKS.

$ cat kubelogin-cluterrole.yml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: sp-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: {PUT ID HERE}
$ kubectl apply -f kubelogin-cluterrole.yml

Wrapping Up

I hope you can feel the benefits of using federated identity with GitHub action. It requires more additional steps to configure, but eliminating credentials in CI/CD pipeline should more than justify the extra manual effort. One last point worth mentioning, federated identity works even for Terraform in GitHub Action. If you’re interested, you can search forARM_USE_OIDC

If you liked my article, please leave a few claps or start following me. You can get notified whenever I publish something new. Let’s stay connected on Linkedin, too! Thanks so much for reading!

--

--

Jay Lee
Microsoft Azure

Cloud Native Enthusiast. Java, Spring, Python, Golang, Kubernetes.