Sample CI/CD pipeline
A key principle of policy DevOps is that authorization policy changes go through the same automated quality gates as application code. When a developer modifies a policy, attribute, or attribute connector, the CI/CD pipeline verifies the change with tests and then delivers the authorization domain to ADS.
How the domain reaches ADS depends on your delivery model. Every pipeline begins with testing, but delivery steps differ:
| Delivery model | After ./gradlew test | A policy change reaches ADS by |
|---|---|---|
| Authorization Hub | Push the domain and its building blocks to Authorization Hub | ADS loading the domain from Authorization Hub |
| Container image | Stage the build context, build and push the ADS image | Deploying the new image |
| Custom delivery | Deliver domain.yaml to storage that ADS reads | ADS reloading the file |
See Integration and delivery to choose a delivery model. The models can be combined, for example Authorization Hub for development and test, and container images for production.
Before you start
Gradle downloads APD and ADS from the Axiomatics repository. Store the access keys as secrets of your CI/CD system and expose them to the build as the environment variables AXIOMATICS_ACCESS_KEY_ID and AXIOMATICS_SECRET_ACCESS_KEY. Do the same for every other secret the delivery needs, such as an Authorization Hub client secret. Never write secrets in build.gradle or the pipeline file.
The examples below show the steps only. Add the checkout, Java setup, and secrets as usual in your CI/CD system.
Authorization Hub
APD pushes the domain and its building blocks to Authorization Hub, and ADS loads the domain from there. The image does not change when a policy changes. See Authorization Hub integration for the configuration and all tasks.
The examples use the two connections dev and prod from Several Authorization Hub connections, which read their client secrets from HUB_DEV_CLIENT_SECRET and HUB_PROD_CLIENT_SECRET.
# 1. Run unit and integration tests against the authorization domain
./gradlew test
# 2. Push the dictionary, attribute connectors and policies, and the domain ADS loads, to dev
./gradlew AllPushTodev DomainPushTodev
# 3. Later, for example after an approval: copy the domain tested in dev to prod
./gradlew DomainPromoteFromdevToprod
AllPushTodevpushes the dictionary, attribute connectors and policies, so you can view and edit them in Authorization Hub.DomainPushTodevpushes the domain that APD built and tested, which ADS loads.- A successful push is live immediately. A push fails when Authorization Hub rejects the content.
- Run the pipeline on a committed Git tree: the change message stored in Authorization Hub then contains the commit, without
-dirty. DomainPromoteFromdevToprodcopies the domain unchanged, so production gets exactly the domain that was tested in dev. It needs the secrets of both connections.
- Jenkins
- Azure DevOps
- GitHub Actions
pipeline {
agent any
stages {
stage('Test') {
steps {
sh './gradlew test'
}
}
stage('Push to dev') {
steps {
withCredentials([string(credentialsId: 'hub-dev-client-secret', variable: 'HUB_DEV_CLIENT_SECRET')]) {
sh './gradlew AllPushTodev DomainPushTodev'
}
}
}
stage('Promote to prod') {
input {
message 'Promote the domain from dev to prod?'
}
steps {
withCredentials([string(credentialsId: 'hub-dev-client-secret', variable: 'HUB_DEV_CLIENT_SECRET'),
string(credentialsId: 'hub-prod-client-secret', variable: 'HUB_PROD_CLIENT_SECRET')]) {
sh './gradlew DomainPromoteFromdevToprod'
}
}
}
}
}
stages:
- stage: Dev
jobs:
- job: TestAndPush
steps:
- task: Gradle@3
displayName: Test
inputs:
tasks: test
- task: Gradle@3
displayName: Push to dev
inputs:
tasks: AllPushTodev DomainPushTodev
env:
HUB_DEV_CLIENT_SECRET: $(hubDevClientSecret)
- stage: Prod
dependsOn: Dev
jobs:
# an environment with an approval check makes the promote wait for an approval
- deployment: Promote
environment: prod
strategy:
runOnce:
deploy:
steps:
- checkout: self
- task: Gradle@3
displayName: Promote to prod
inputs:
tasks: DomainPromoteFromdevToprod
env:
HUB_DEV_CLIENT_SECRET: $(hubDevClientSecret)
HUB_PROD_CLIENT_SECRET: $(hubProdClientSecret)
jobs:
dev:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Test
run: ./gradlew test
- name: Push to dev
run: ./gradlew AllPushTodev DomainPushTodev
env:
HUB_DEV_CLIENT_SECRET: ${{ secrets.HUB_DEV_CLIENT_SECRET }}
prod:
needs: dev
runs-on: ubuntu-latest
# an environment with required reviewers makes the promote wait for an approval
environment: prod
steps:
- uses: actions/checkout@v4
- name: Promote to prod
run: ./gradlew DomainPromoteFromdevToprod
env:
HUB_DEV_CLIENT_SECRET: ${{ secrets.HUB_DEV_CLIENT_SECRET }}
HUB_PROD_CLIENT_SECRET: ${{ secrets.HUB_PROD_CLIENT_SECRET }}
Container image
The authorization domain is baked into the ADS container image: every policy change gives a new, versioned image. See Container image. The examples tag the image with the Git commit, the same commit as in the identity of the domain, so you can trace an image back to the commit it was built from.
# 1. Run unit and integration tests against the authorization domain
./gradlew test
# 2. Stage the ADS deployment build context
./gradlew stageDeployment
# 3. Build the container image
docker build -t <registry>/<image>:<git commit> build/install/deployment/ads/
# 4. Push the image to your registry
docker push <registry>/<image>:<git commit>
- Jenkins
- Azure DevOps
- GitHub Actions
pipeline {
agent any
stages {
stage('Test') {
steps {
sh './gradlew test'
}
}
stage('Stage deployment') {
steps {
sh './gradlew stageDeployment'
}
}
stage('Build and push image') {
steps {
sh '''
docker build -t ${REGISTRY}/${IMAGE}:${GIT_COMMIT} build/install/deployment/ads/
docker push ${REGISTRY}/${IMAGE}:${GIT_COMMIT}
'''
}
}
}
}
steps:
- task: Gradle@3
displayName: Test
inputs:
tasks: test
- task: Gradle@3
displayName: Stage deployment
inputs:
tasks: stageDeployment
- task: Docker@2
displayName: Build and push image
inputs:
command: buildAndPush
repository: $(imageRepository)
dockerfile: build/install/deployment/ads/Dockerfile
buildContext: build/install/deployment/ads/
tags: $(Build.SourceVersion)
- name: Test
run: ./gradlew test
- name: Stage deployment
run: ./gradlew stageDeployment
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: build/install/deployment/ads/
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }}
Custom delivery
APD builds domain.yaml, and the pipeline delivers it to storage that ADS reads: a shared drive, a Kubernetes volume, a server, or a repository. ADS reloads the domain when the file changes, so the image does not change when a policy changes. See Custom delivery for the ADS configuration and the delivery alternatives.
# 1. Run unit and integration tests against the authorization domain
./gradlew test
# 2. Build the domain and deliver it with your own Gradle task, see Custom delivery
./gradlew deployDomain
deployDomain is a task you add to build.gradle. It depends on buildAuthzDomain and delivers build/alfa/domain/ads/domain.yaml, for example by copying it to a shared drive. You can also deliver the file with a pipeline step instead, after ./gradlew buildAuthzDomain.
- Jenkins
- Azure DevOps
- GitHub Actions
pipeline {
agent any
stages {
stage('Test') {
steps {
sh './gradlew test'
}
}
stage('Deliver domain') {
steps {
sh './gradlew deployDomain'
}
}
}
}
steps:
- task: Gradle@3
displayName: Test
inputs:
tasks: test
- task: Gradle@3
displayName: Deliver domain
inputs:
tasks: deployDomain
- name: Test
run: ./gradlew test
- name: Deliver domain
run: ./gradlew deployDomain