Skip to content

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:

      run: |
        cc-scan --auth default --format json --output results.json || EXIT=$?
        echo "Scanner exit code: ${EXIT:-0}"
        exit 0


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:

pip install certifyclouds==1.2.0

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:

cc-scan --auth default --expiring 30

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.