# ----------------------------------------------------------------------------- # stellaops-gate-example.gitlab-ci.yml # Sprint: SPRINT_20251226_001_BE_cicd_gate_integration # Task: CICD-GATE-08 - GitLab CI example workflow using stella gate evaluate # Description: Example GitLab CI configuration for StellaOps release gate integration # ----------------------------------------------------------------------------- # # This configuration demonstrates how to integrate StellaOps release gates into # your GitLab CI/CD pipeline. The gate evaluates security drift between your # current build and the approved baseline, blocking releases that introduce new # reachable vulnerabilities. # # Usage: # Include this file in your .gitlab-ci.yml: # include: # - project: 'stellaops/ci-templates' # file: '/templates/stellaops-gate.gitlab-ci.yml' # # Prerequisites: # 1. STELLAOPS_API_TOKEN variable configured in CI/CD settings # 2. STELLAOPS_BACKEND_URL variable configured (or use default) # 3. Container image built and pushed to registry # # Exit codes: # 0 = Pass - Release may proceed # 1 = Warn - Release may proceed with warnings (configurable) # 2 = Fail - Release blocked due to security policy violation # variables: STELLAOPS_BACKEND_URL: ${STELLAOPS_BACKEND_URL:-https://stellaops.internal} STELLAOPS_CLI_VERSION: "latest" # Registry configuration REGISTRY: ${CI_REGISTRY} IMAGE_NAME: ${CI_REGISTRY_IMAGE} stages: - build - scan - gate - deploy # ----------------------------------------------------------------------------- # Build Stage: Build and push container image # ----------------------------------------------------------------------------- build: stage: build image: docker:24 services: - docker:24-dind variables: DOCKER_TLS_CERTDIR: "/certs" before_script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - | # Build with BuildKit for better caching export DOCKER_BUILDKIT=1 # Generate image tag based on commit IMAGE_TAG="${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}" # Build and push docker build \ --label "org.opencontainers.image.revision=${CI_COMMIT_SHA}" \ --label "org.opencontainers.image.source=${CI_PROJECT_URL}" \ -t "${IMAGE_TAG}" \ . docker push "${IMAGE_TAG}" # Get the digest IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "${IMAGE_TAG}" | cut -d'@' -f2) echo "IMAGE_DIGEST=${IMAGE_DIGEST}" >> build.env echo "IMAGE_REF=${CI_REGISTRY_IMAGE}@${IMAGE_DIGEST}" >> build.env artifacts: reports: dotenv: build.env # ----------------------------------------------------------------------------- # Gate Stage: Evaluate StellaOps release gate # ----------------------------------------------------------------------------- .stellaops-gate-base: stage: gate image: alpine:3.19 variables: # Baseline strategy: auto-detect based on branch BASELINE_STRATEGY: "auto" # Allow warnings to pass by default ALLOW_WARNINGS: "true" before_script: - | # Install dependencies apk add --no-cache curl jq bash # Install StellaOps CLI curl -sSL https://get.stella-ops.org/cli | bash export PATH="$HOME/.stellaops/bin:$PATH" # Verify installation stella --version stellaops-gate: extends: .stellaops-gate-base needs: - job: build artifacts: true script: - | # Determine baseline strategy based on branch if [ "$BASELINE_STRATEGY" = "auto" ]; then case "$CI_COMMIT_REF_NAME" in main|master) BASELINE="production" ;; release/*) BASELINE="last-approved" ;; *) BASELINE="previous-build" ;; esac else BASELINE="$BASELINE_STRATEGY" fi echo "============================================" echo "StellaOps Release Gate Evaluation" echo "============================================" echo "Image Digest: ${IMAGE_DIGEST}" echo "Baseline Strategy: ${BASELINE}" echo "Branch: ${CI_COMMIT_REF_NAME}" echo "============================================" # Run gate evaluation set +e RESULT=$(stella gate evaluate \ --image "${IMAGE_DIGEST}" \ --baseline "${BASELINE}" \ --output json \ --ci-context "gitlab-ci" \ --repository "${CI_PROJECT_PATH}" \ --tag "${CI_COMMIT_SHORT_SHA}" \ 2>&1) EXIT_CODE=$? set -e # Parse results DECISION_ID=$(echo "$RESULT" | jq -r '.decisionId // "unknown"') STATUS=$(echo "$RESULT" | jq -r '.status // "unknown"') SUMMARY=$(echo "$RESULT" | jq -r '.summary // "No summary"') # Store for downstream jobs echo "GATE_DECISION_ID=${DECISION_ID}" >> gate.env echo "GATE_STATUS=${STATUS}" >> gate.env echo "GATE_EXIT_CODE=${EXIT_CODE}" >> gate.env # Display results echo "" echo "============================================" echo "Gate Result: ${STATUS}" echo "Decision ID: ${DECISION_ID}" echo "============================================" echo "${SUMMARY}" echo "============================================" # Handle exit codes case $EXIT_CODE in 0) echo "Gate PASSED - Release may proceed" ;; 1) echo "Gate PASSED WITH WARNINGS" if [ "$ALLOW_WARNINGS" = "true" ]; then echo "Warnings allowed - continuing pipeline" exit 0 else echo "Warnings not allowed - blocking pipeline" exit 1 fi ;; 2) echo "Gate BLOCKED - Security policy violation" echo "Review the gate decision for details:" echo "${STELLAOPS_BACKEND_URL}/gates/decisions/${DECISION_ID}" exit 2 ;; *) echo "Gate evaluation error (exit code: $EXIT_CODE)" exit $EXIT_CODE ;; esac artifacts: reports: dotenv: gate.env rules: - if: $CI_COMMIT_BRANCH - if: $CI_MERGE_REQUEST_IID # ----------------------------------------------------------------------------- # Gate Override: Manual override for blocked releases # ----------------------------------------------------------------------------- stellaops-gate-override: extends: .stellaops-gate-base needs: - job: build artifacts: true - job: stellaops-gate artifacts: true script: - | if [ "$GATE_STATUS" != "Fail" ]; then echo "Override not needed - gate status is ${GATE_STATUS}" exit 0 fi echo "============================================" echo "StellaOps Gate Override Request" echo "============================================" echo "Original Decision ID: ${GATE_DECISION_ID}" echo "Override requested by: ${GITLAB_USER_LOGIN}" echo "Justification: ${OVERRIDE_JUSTIFICATION}" echo "============================================" if [ -z "$OVERRIDE_JUSTIFICATION" ]; then echo "ERROR: OVERRIDE_JUSTIFICATION variable must be set" exit 1 fi # Request override with justification stella gate evaluate \ --image "${IMAGE_DIGEST}" \ --baseline "last-approved" \ --allow-override \ --justification "${OVERRIDE_JUSTIFICATION}" \ --ci-context "gitlab-ci-override" \ --repository "${CI_PROJECT_PATH}" \ --tag "${CI_COMMIT_SHORT_SHA}" rules: - if: $CI_COMMIT_BRANCH when: manual allow_failure: true environment: name: security-override action: prepare # ----------------------------------------------------------------------------- # Deploy Stage: Deploy to staging (only if gate passed) # ----------------------------------------------------------------------------- deploy-staging: stage: deploy image: alpine:3.19 needs: - job: build artifacts: true - job: stellaops-gate artifacts: true script: - | echo "Deploying ${IMAGE_REF} to staging..." # Verify gate passed if [ "$GATE_STATUS" != "Pass" ] && [ "$GATE_STATUS" != "Warn" ]; then echo "ERROR: Gate did not pass (status: ${GATE_STATUS})" exit 1 fi # Add your deployment commands here # Example: kubectl set image deployment/app app=${IMAGE_REF} echo "Deployment complete!" environment: name: staging url: https://staging.example.com rules: - if: $CI_COMMIT_BRANCH == "main" - if: $CI_COMMIT_BRANCH =~ /^release\// # ----------------------------------------------------------------------------- # Deploy Stage: Deploy to production (requires manual approval) # ----------------------------------------------------------------------------- deploy-production: stage: deploy image: alpine:3.19 needs: - job: build artifacts: true - job: stellaops-gate artifacts: true script: - | echo "Deploying ${IMAGE_REF} to production..." # Verify gate passed (warnings not allowed for production) if [ "$GATE_STATUS" != "Pass" ]; then echo "ERROR: Production deployment requires Pass status (got: ${GATE_STATUS})" exit 1 fi # Add your production deployment commands here echo "Production deployment complete!" environment: name: production url: https://example.com rules: - if: $CI_COMMIT_BRANCH == "main" when: manual