- Implement `SbomVexOrderingDeterminismProperties` for testing component list and vulnerability metadata hash consistency. - Create `UnicodeNormalizationDeterminismProperties` to validate NFC normalization and Unicode string handling. - Add project file for `StellaOps.Testing.Determinism.Properties` with necessary dependencies. - Introduce CI/CD template validation tests including YAML syntax checks and documentation content verification. - Create validation script for CI/CD templates ensuring all required files and structures are present.
307 lines
9.4 KiB
YAML
307 lines
9.4 KiB
YAML
# -----------------------------------------------------------------------------
|
|
# 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
|