In this chapter, we will learn how to use GitHub Action to build and deploy our application to Azure. It’s often overlooked by developers that CI/CD usually have a power to access any systems in the enterprise, and it becomes a big liability which requires close attention. Zero trust security for CI/CD pipelines and infrastructure becomes one of the most important topics in DevOps these days. Zero trust security for CI/CD pipelines encompasses a number of techniques. For example, authenticating with your cloud provider via federated identity mechanisms like OIDC instead of using credentials, using the least privilege by minimizing the access of individual user or runner accounts, using self-hosted runners in an ephemeral way instead of reusing them, etc. In this chapter, we will implement some of those techniques to secure our CI/CD pipeline.
1. Accessing Azure in GitHub Actions without credentials using OIDC
Federated identity credentials are a new type of credential that enables workload identity federation for software workloads. Workload identity federation allows you to access Azure Active Directory (Azure AD) protected resources without needing to manage secrets. Details are at https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0
1.1. Create a Service Principal to access Azure
Create a new service principal to set up with federated identity. We will give Contributor
access for the demo, but you should use least privilege principle to minimize the access in real world.
$ az ad sp create-for-rbac \
--name github-action-sp-fi \
--scope /subscriptions/[SUBSCRIPTION_ID]/resourceGroups/[RESOURCE_GROUP_NAME] \
--role Contributor
1.2. Set up Federated Identity Credentials for the Service Principal
Now, Go to Azure portal → Azure AD → App registrations. Look up your service principal created by the command above, and click on Certificates & secrets → Federated credentials. + Add credential.

Click on + Add credential
.
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.
Challenge 1 : Modify the GitHub Actions workflow to run command az group list successfully. You can refer to https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-azure |
2. Creating Container Image and Pushing to Azure Container Registry(ACR) using GitHub Actions
Creating a container image normally requires writing a Dockerfile which could be cumbersome job for the most of the developers. In order to simplify this process, we will be using Cloud Native Buildpack. Cloud Native Buildpack is a CNCF project, and it’s a part of buildpacks.io project. It’s a new way of building container images and it’s getting more and more popular these days. In this chapter, we will first create an image using Cloud Native Buildpack(CNB) and then push the container image to ACR.
2.1. Creating Container Image with CNB
Here is the basic steps to create a container image using CNB.
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: Install pack CLIs including pack and yq
uses: buildpacks/github-actions/setup-pack@v5.0.0
with:
pack-version: '0.29.0'
- name: Pack build
run: |
pack build ${ACR_URL}/${IMAGE_NAME} --builder paketobuildpacks/builder:base --buildpack paketo-buildpacks/java-azure --env BP_JVM_VERSION=17 (1)
1 | ACR_URL is URL of the Azure Container Registry.(ex: https://acrjay.azurecr.io) IMAGE_NAME is the name and tag of the image to be created. (ex: cicd-java:0.0.1) |
2.2. Pushing Container Image to ACR
In order to push the created container image, we need to login to the Azure Container Registry. One thing to remember is that authentication to container registry is always HTTP Basic auth which requires username and password. Azure provides more secure way by generating short-lived token for the authentication. Here is the steps to generate a token and authenticate against ACR.
on: push: branches: [ "main" ] pull_request: branches: [ "main" ] permissions: id-token: write contents: read jobs: ... container: name: Build container with CNB and push to ACR needs: scan 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: ACR Login with AZ CLI run: | ACR_JSON=$(az acr login --name [ACR_NAME] --expose-token) (1) TOKEN=$(echo $ACR_JSON | jq -r .accessToken) LOGINSERVER=$(echo $ACR_JSON | jq -r .loginServer) docker login ${LOGINSERVER} --username 00000000-0000-0000-0000-000000000000 --password-stdin <<< $TOKEN (2)
1 | az acr login --name [ACR_NAME] --expose-token generates short-lived token for specified ACR. |
2 | docker login command uses the token generated by az acr login command to authenticate against ACR. Note that username is pre-defined as 00000000-0000-0000-0000-000000000000 . |
One last remaining step at this moment is to push container image to ACR which can be easily done by docker push
command.
Challenge 2 : Create one more step to push docker image to ACR. CNB has an option, |
Congratulations!! You have completed the third challenge!! |
3. Complete Example
Here is the complete example of the GitHub Actions workflow implementing the above steps.
name: CI/CD Spring Boot to Azure Kubernetes Service
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
id-token: write
contents: read
jobs:
test:
name: Unit Test and SpotBugs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'microsoft'
cache: 'maven'
- name: Build with Maven
run: mvn -B clean package site
- name: Upload SBOM(Cyclonedx)
uses: actions/upload-artifact@v3
with:
name: bom.json
path: './target/bom.json'
scan:
name: Scan dependencies with Trivy
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install latest Trivy CLI
run: |
wget https://github.com/aquasecurity/trivy/releases/download/v0.41.0/trivy_0.41.0_Linux-64bit.deb
sudo dpkg -i trivy_0.41.0_Linux-64bit.deb
- uses: actions/download-artifact@v3
with:
name: bom.json
- name: Run Trivy with SBOM
run: trivy sbom ./bom.json
container:
name: Build container with CNB and push to ACR
needs: scan
runs-on: ubuntu-latest
outputs:
LOGINSERVER: ${{ steps.image.outputs.LOGINSERVER }}
IMAGE: ${{ steps.versioning.outputs.IMAGE }}
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: ACR Login with AZ CLI
id: image
run: |
ACR_JSON=$(az acr login --name acrjay --expose-token)
TOKEN=$(echo $ACR_JSON | jq -r .accessToken)
LOGINSERVER=$(echo $ACR_JSON | jq -r .loginServer)
echo "LOGINSERVER=$LOGINSERVER" >> $GITHUB_ENV
echo "LOGINSERVER=$LOGINSERVER" >> $GITHUB_OUTPUT
docker login ${LOGINSERVER} --username 00000000-0000-0000-0000-000000000000 --password-stdin <<< $TOKEN
- name: Install pack CLIs including pack and yq
uses: buildpacks/github-actions/setup-pack@v5.0.0
with:
pack-version: '0.29.0'
- name: Set the image name and version
id: versioning
run: |
VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
REPO_NAME=${{ github.event.repository.name }}
echo "IMAGE=$REPO_NAME:$VERSION" >> $GITHUB_ENV
echo "IMAGE=$REPO_NAME:$VERSION" >> $GITHUB_OUTPUT
- name: Pack build
run: |
pack build ${LOGINSERVER}/${IMAGE} --builder paketobuildpacks/builder:base --buildpack paketo-buildpacks/java-azure --env BP_JVM_VERSION=17 --publish