Files
git.stella-ops.org/docs/examples/binary-diff/ci-cd-integration.md

8.6 KiB

CI/CD Integration

This example demonstrates how to integrate binary diff attestation into your CI/CD pipelines.

GitHub Actions

Basic Workflow

# .github/workflows/binary-diff.yml
name: Binary Diff Attestation

on:
  push:
    tags:
      - 'v*'

jobs:
  binary-diff:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write  # For keyless signing

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Stella CLI
        uses: stellaops/setup-stella@v1
        with:
          version: 'latest'

      - name: Login to Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Get Previous Tag
        id: prev-tag
        run: |
          PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
          echo "tag=$PREV_TAG" >> $GITHUB_OUTPUT

      - name: Binary Diff
        if: steps.prev-tag.outputs.tag != ''
        run: |
          stella scan diff \
            --base ghcr.io/${{ github.repository }}:${{ steps.prev-tag.outputs.tag }} \
            --target ghcr.io/${{ github.repository }}:${{ github.ref_name }} \
            --mode=elf \
            --emit-dsse=./attestations/ \
            --format=json > diff.json

      - name: Upload Attestations
        uses: actions/upload-artifact@v4
        with:
          name: binary-diff-attestations
          path: |
            attestations/
            diff.json

      - name: Attach Attestation to Image
        run: |
          # Using cosign to attach attestation
          cosign attach attestation \
            --attestation ./attestations/linux-amd64-binarydiff.dsse.json \
            ghcr.io/${{ github.repository }}:${{ github.ref_name }}

With Release Gate

# .github/workflows/release-gate.yml
name: Release Gate with Binary Diff

on:
  workflow_dispatch:
    inputs:
      base_version:
        description: 'Base version to compare'
        required: true
      target_version:
        description: 'Target version to release'
        required: true

jobs:
  binary-diff-gate:
    runs-on: ubuntu-latest
    outputs:
      verdict: ${{ steps.analyze.outputs.verdict }}

    steps:
      - name: Setup Stella CLI
        uses: stellaops/setup-stella@v1

      - name: Binary Diff Analysis
        id: diff
        run: |
          stella scan diff \
            --base myapp:${{ inputs.base_version }} \
            --target myapp:${{ inputs.target_version }} \
            --format=json > diff.json

      - name: Analyze Results
        id: analyze
        run: |
          # Check for unknown verdicts
          UNKNOWN_COUNT=$(jq '.summary.verdicts.unknown // 0' diff.json)
          if [ "$UNKNOWN_COUNT" -gt "0" ]; then
            echo "verdict=review-required" >> $GITHUB_OUTPUT
            echo "::warning::Found $UNKNOWN_COUNT binaries with unknown verdicts"
          else
            echo "verdict=approved" >> $GITHUB_OUTPUT
          fi

      - name: Gate Decision
        if: steps.analyze.outputs.verdict == 'review-required'
        run: |
          echo "Manual review required for unknown binary changes"
          exit 1

GitLab CI

Basic Pipeline

# .gitlab-ci.yml
stages:
  - build
  - analyze
  - release

variables:
  STELLA_VERSION: "latest"

binary-diff:
  stage: analyze
  image: stellaops/cli:${STELLA_VERSION}
  script:
    - |
      # Get previous tag
      PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")

      if [ -n "$PREV_TAG" ]; then
        stella scan diff \
          --base ${CI_REGISTRY_IMAGE}:${PREV_TAG} \
          --target ${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG} \
          --mode=elf \
          --emit-dsse=attestations/ \
          --format=json > diff.json

        # Upload to GitLab artifacts
        echo "Binary diff completed"
      else
        echo "No previous tag found, skipping diff"
      fi
  artifacts:
    paths:
      - attestations/
      - diff.json
    expire_in: 30 days
  only:
    - tags

With Security Gate

# .gitlab-ci.yml
security-gate:
  stage: analyze
  image: stellaops/cli:latest
  script:
    - |
      stella scan diff \
        --base ${CI_REGISTRY_IMAGE}:${BASE_VERSION} \
        --target ${CI_REGISTRY_IMAGE}:${TARGET_VERSION} \
        --format=json > diff.json

      # Fail if any unknown verdicts
      UNKNOWN=$(jq '.summary.verdicts.unknown // 0' diff.json)
      if [ "$UNKNOWN" -gt "0" ]; then
        echo "Security gate failed: $UNKNOWN unknown binary changes"
        jq '.findings[] | select(.verdict == "unknown")' diff.json
        exit 1
      fi

      echo "Security gate passed"
  allow_failure: false

Jenkins Pipeline

// Jenkinsfile
pipeline {
    agent any

    environment {
        STELLA_VERSION = 'latest'
    }

    stages {
        stage('Binary Diff') {
            steps {
                script {
                    def prevTag = sh(
                        script: 'git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo ""',
                        returnStdout: true
                    ).trim()

                    if (prevTag) {
                        sh """
                            stella scan diff \\
                                --base ${REGISTRY}/${IMAGE}:${prevTag} \\
                                --target ${REGISTRY}/${IMAGE}:${TAG} \\
                                --mode=elf \\
                                --emit-dsse=attestations/ \\
                                --format=json > diff.json
                        """

                        archiveArtifacts artifacts: 'attestations/*, diff.json'

                        // Parse and check results
                        def diff = readJSON file: 'diff.json'
                        if (diff.summary.verdicts.unknown > 0) {
                            unstable("Found ${diff.summary.verdicts.unknown} unknown binary changes")
                        }
                    }
                }
            }
        }
    }
}

Azure DevOps

# azure-pipelines.yml
trigger:
  tags:
    include:
      - v*

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: Bash@3
    displayName: 'Install Stella CLI'
    inputs:
      targetType: 'inline'
      script: |
        curl -sSL https://get.stellaops.io | sh
        stella --version

  - task: Docker@2
    displayName: 'Login to Registry'
    inputs:
      containerRegistry: 'myRegistry'
      command: 'login'

  - task: Bash@3
    displayName: 'Binary Diff'
    inputs:
      targetType: 'inline'
      script: |
        PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
        if [ -n "$PREV_TAG" ]; then
          stella scan diff \
            --base $(REGISTRY)/$(IMAGE):${PREV_TAG} \
            --target $(REGISTRY)/$(IMAGE):$(Build.SourceBranchName) \
            --mode=elf \
            --emit-dsse=$(Build.ArtifactStagingDirectory)/attestations/ \
            --format=json > $(Build.ArtifactStagingDirectory)/diff.json
        fi

  - task: PublishBuildArtifacts@1
    inputs:
      pathToPublish: '$(Build.ArtifactStagingDirectory)'
      artifactName: 'binary-diff'

Best Practices

1. Always Use Digest References in Production

# Instead of tags
stella scan diff --base myapp:v1.0.0 --target myapp:v1.0.1

# Use digests for immutability
stella scan diff \
  --base myapp@sha256:abc123... \
  --target myapp@sha256:def456...

2. Store Attestations with Releases

Attach DSSE attestations to your container images or store them alongside release artifacts.

3. Set Appropriate Timeouts

# For large images, increase timeout
stella scan diff \
  --base myapp:v1 \
  --target myapp:v2 \
  --timeout=600

4. Use Caching

# GitHub Actions with caching
- uses: actions/cache@v4
  with:
    path: ~/.stella/cache
    key: stella-${{ runner.os }}-${{ hashFiles('**/Dockerfile') }}

5. Fail Fast on Critical Issues

# Exit code indicates issues
stella scan diff --base old --target new --format=json > diff.json
if [ $? -ne 0 ]; then
  echo "Diff failed"
  exit 1
fi

# Check for critical verdicts
jq -e '.summary.verdicts.unknown == 0' diff.json || exit 1

Troubleshooting

Registry Authentication

# Use Docker config
stella scan diff \
  --base myapp:v1 \
  --target myapp:v2 \
  --registry-auth=~/.docker/config.json

Platform Issues

# Explicitly specify platform for multi-arch
stella scan diff \
  --base myapp:v1 \
  --target myapp:v2 \
  --platform=linux/amd64

Timeout Issues

# Increase timeout for slow registries
stella scan diff \
  --base myapp:v1 \
  --target myapp:v2 \
  --timeout=900