# CI/CD Integration This example demonstrates how to integrate binary diff attestation into your CI/CD pipelines. ## GitHub Actions ### Basic Workflow ```yaml # .github/workflows/binary-diff.yml name: Binary Diff Attestation on: push: tags: - 'v*' jobs: binary-diff: runs-on: ubuntu-latest permissions: contents: read 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: Write Signing Key run: | mkdir -p keys printf '%s' "${{ secrets.BINARYDIFF_SIGNING_KEY_PEM }}" > keys/binarydiff.pem chmod 600 keys/binarydiff.pem - 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/ \ --signing-key=./keys/binarydiff.pem \ --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 ```yaml # .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 ```yaml # .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 mkdir -p keys printf '%s' "$BINARYDIFF_SIGNING_KEY_PEM" > keys/binarydiff.pem chmod 600 keys/binarydiff.pem stella scan diff \ --base ${CI_REGISTRY_IMAGE}:${PREV_TAG} \ --target ${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG} \ --mode=elf \ --emit-dsse=attestations/ \ --signing-key=keys/binarydiff.pem \ --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 ```yaml # .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 ```groovy // 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) { withCredentials([string(credentialsId: 'binarydiff-signing-key-pem', variable: 'BINARYDIFF_SIGNING_KEY_PEM')]) { sh 'mkdir -p keys && printf "%s" "$BINARYDIFF_SIGNING_KEY_PEM" > keys/binarydiff.pem && chmod 600 keys/binarydiff.pem' sh """ stella scan diff \\ --base ${REGISTRY}/${IMAGE}:${prevTag} \\ --target ${REGISTRY}/${IMAGE}:${TAG} \\ --mode=elf \\ --emit-dsse=attestations/ \\ --signing-key=keys/binarydiff.pem \\ --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 ```yaml # 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 mkdir -p $(Build.SourcesDirectory)/keys printf '%s' "$(BINARYDIFF_SIGNING_KEY_PEM)" > $(Build.SourcesDirectory)/keys/binarydiff.pem chmod 600 $(Build.SourcesDirectory)/keys/binarydiff.pem stella scan diff \ --base $(REGISTRY)/$(IMAGE):${PREV_TAG} \ --target $(REGISTRY)/$(IMAGE):$(Build.SourceBranchName) \ --mode=elf \ --emit-dsse=$(Build.ArtifactStagingDirectory)/attestations/ \ --signing-key=$(Build.SourcesDirectory)/keys/binarydiff.pem \ --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 ```bash # 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 ```bash # For large images, increase timeout stella scan diff \ --base myapp:v1 \ --target myapp:v2 \ --timeout=600 ``` ### 4. Use Caching ```yaml # GitHub Actions with caching - uses: actions/cache@v4 with: path: ~/.stella/cache key: stella-${{ runner.os }}-${{ hashFiles('**/Dockerfile') }} ``` ### 5. Fail Fast on Critical Issues ```bash # 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 ```bash # Use Docker config stella scan diff \ --base myapp:v1 \ --target myapp:v2 \ --registry-auth=~/.docker/config.json ``` ### Platform Issues ```bash # Explicitly specify platform for multi-arch stella scan diff \ --base myapp:v1 \ --target myapp:v2 \ --platform=linux/amd64 ``` ### Timeout Issues ```bash # Increase timeout for slow registries stella scan diff \ --base myapp:v1 \ --target myapp:v2 \ --timeout=900 ```