CI/CD Integration¶
Integrate cc-scan into your pipeline to gate deployments on Azure Key Vault security findings.
How It Works¶
The scanner exits with a meaningful code your pipeline can act on:
| Exit Code | Meaning |
|---|---|
0 | Scan completed — no critical or high findings |
1 | Scan completed — critical or high findings detected |
2 | Scan failed (auth error, no subscriptions, network issue) |
3 | Invalid or missing API key |
Set your pipeline to fail on exit code 1 to block deployments when high-severity findings exist. See the Scanner reference for the full list of security checks and severities.
Authentication in Pipelines¶
All three platforms below use a service principal for Azure authentication and a CC_SCAN_KEY secret for the scanner API key.
Create a Service Principal¶
az ad sp create-for-rbac \
--name "cc-scan-pipeline" \
--role "Reader" \
--scopes /subscriptions/<your-subscription-id>
Grant vault-level read access (repeat per vault or assign at resource group level):
az role assignment create \
--assignee <sp-client-id> \
--role "Key Vault Secrets User" \
--scope /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault>
Get Your Scanner API Key¶
Register at certifyclouds.com or run cc-scan --register locally. The key looks like cc-scan-abc1234567.
GitHub Actions¶
Secrets to Configure¶
In your repository, go to Settings > Secrets and variables > Actions and add:
| Secret | Value |
|---|---|
AZURE_CLIENT_ID | Service principal client ID |
AZURE_CLIENT_SECRET | Service principal client secret |
AZURE_TENANT_ID | Azure AD tenant ID |
CC_SCAN_KEY | CertifyClouds scanner API key |
Workflow: Fail on Critical/High Findings¶
name: Key Vault Security Audit
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1' # Every Monday at 06:00 UTC
jobs:
vault-audit:
name: Audit Key Vaults
runs-on: ubuntu-latest
steps:
- name: Install cc-scan
run: pip install certifyclouds
- name: Run Key Vault audit
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
CC_SCAN_KEY: ${{ secrets.CC_SCAN_KEY }}
run: |
cc-scan \
--auth default \
--format json \
--output results.json
- name: Upload scan results
if: always()
uses: actions/upload-artifact@v4
with:
name: vault-audit-results
path: results.json
retention-days: 30
The workflow fails automatically when cc-scan exits with code 1 (critical or high findings). Exit code 2 (scan failure) also fails the build.
Workflow: Security Check Only¶
Use --security to check for misconfigurations only, without a full inventory:
- name: Check vault security posture
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
CC_SCAN_KEY: ${{ secrets.CC_SCAN_KEY }}
run: |
cc-scan --auth default --security
Workflow: Scan Specific Subscriptions¶
- name: Audit production subscription only
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
CC_SCAN_KEY: ${{ secrets.CC_SCAN_KEY }}
run: |
cc-scan \
--auth default \
--subscription ${{ vars.PROD_SUBSCRIPTION_ID }} \
--format json \
--output results.json
Soft failure mode
To audit without failing the build, capture the exit code manually:
Azure DevOps Pipelines¶
Variables to Configure¶
In your pipeline, go to Pipelines > Library > Variable Groups and add a variable group (e.g., certifyclouds-scanner) with:
| Variable | Value | Secret |
|---|---|---|
AZURE_CLIENT_ID | Service principal client ID | No |
AZURE_CLIENT_SECRET | Service principal client secret | Yes |
AZURE_TENANT_ID | Azure AD tenant ID | No |
CC_SCAN_KEY | CertifyClouds scanner API key | Yes |
Pipeline: Fail on Critical/High Findings¶
trigger:
branches:
include:
- main
schedules:
- cron: '0 6 * * 1'
displayName: Weekly Monday audit
branches:
include:
- main
always: true
pool:
vmImage: ubuntu-latest
variables:
- group: certifyclouds-scanner
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.11'
displayName: Set up Python
- script: pip install certifyclouds
displayName: Install cc-scan
- script: |
cc-scan \
--auth default \
--format json \
--output $(Build.ArtifactStagingDirectory)/results.json
displayName: Run Key Vault audit
env:
AZURE_CLIENT_ID: $(AZURE_CLIENT_ID)
AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET)
AZURE_TENANT_ID: $(AZURE_TENANT_ID)
CC_SCAN_KEY: $(CC_SCAN_KEY)
- task: PublishBuildArtifacts@1
condition: always()
inputs:
pathToPublish: $(Build.ArtifactStagingDirectory)
artifactName: vault-audit-results
displayName: Publish scan results
Pipeline: Security Check Only¶
- script: |
cc-scan --auth default --security
displayName: Check vault security posture
env:
AZURE_CLIENT_ID: $(AZURE_CLIENT_ID)
AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET)
AZURE_TENANT_ID: $(AZURE_TENANT_ID)
CC_SCAN_KEY: $(CC_SCAN_KEY)
Pipeline: Soft Failure (Warn, Don't Block)¶
- script: |
cc-scan --auth default --format json --output results.json
echo "##vso[task.setvariable variable=SCAN_EXIT]$?"
displayName: Run Key Vault audit (warn only)
continueOnError: true
env:
AZURE_CLIENT_ID: $(AZURE_CLIENT_ID)
AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET)
AZURE_TENANT_ID: $(AZURE_TENANT_ID)
CC_SCAN_KEY: $(CC_SCAN_KEY)
- script: |
echo "Scanner exit code: $(SCAN_EXIT)"
if [ "$(SCAN_EXIT)" = "1" ]; then
echo "##vso[task.logissue type=warning]Critical or high Key Vault findings detected — review results.json"
fi
displayName: Evaluate scan results
condition: always()
GitLab CI¶
CI/CD Variables to Configure¶
In your project, go to Settings > CI/CD > Variables and add:
| Variable | Value | Masked |
|---|---|---|
AZURE_CLIENT_ID | Service principal client ID | No |
AZURE_CLIENT_SECRET | Service principal client secret | Yes |
AZURE_TENANT_ID | Azure AD tenant ID | No |
CC_SCAN_KEY | CertifyClouds scanner API key | Yes |
Pipeline: Fail on Critical/High Findings¶
stages:
- audit
vault-audit:
stage: audit
image: python:3.11-slim
rules:
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "schedule"
script:
- pip install certifyclouds
- cc-scan
--auth default
--format json
--output results.json
artifacts:
when: always
paths:
- results.json
expire_in: 30 days
The job fails automatically when cc-scan exits with code 1.
Pipeline: Security Findings Only¶
vault-security-check:
stage: audit
image: python:3.11-slim
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- pip install certifyclouds
- cc-scan --auth default --security
Pipeline: Scheduled Weekly Audit¶
Add a CI/CD schedule in CI/CD > Schedules (every Monday at 06:00 UTC: 0 6 * * 1), then configure a job that only runs on schedules:
weekly-vault-audit:
stage: audit
image: python:3.11-slim
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
script:
- pip install certifyclouds
- cc-scan
--auth default
--format html
--output vault-audit.html
artifacts:
when: always
paths:
- vault-audit.html
expire_in: 90 days
Pipeline: Soft Failure (Allow Failure)¶
vault-audit-warn:
stage: audit
image: python:3.11-slim
allow_failure:
exit_codes: 1
script:
- pip install certifyclouds
- cc-scan --auth default --format json --output results.json
artifacts:
when: always
paths:
- results.json
expire_in: 30 days
Using allow_failure: exit_codes: 1 lets the pipeline continue when findings are detected, while still failing on exit code 2 (scan error).
Tips¶
Pin the scanner version to avoid unexpected behaviour on upgrades:
Cache the install to speed up pipelines. For GitHub Actions:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-certifyclouds
- run: pip install certifyclouds
Scan only expiring items if you want a lighter check:
This reports only secrets, certificates, and keys expiring within 30 days and returns exit code 1 if any critical or high findings exist in that set.
Related¶
- Scanner CLI Reference — Full flag reference, output formats, and security check catalogue
- Environment Setup — Environment variables reference
- Azure Permissions — Required IAM roles for the service principal