# deploy/gitlab/examples/.gitlab-ci-stellaops.yml # StellaOps Keyless Signing Templates for GitLab CI # # Include this file in your .gitlab-ci.yml to enable keyless signing: # # include: # - project: 'stella-ops/templates' # file: 'deploy/gitlab/examples/.gitlab-ci-stellaops.yml' # # sign-image: # extends: .stellaops-sign # variables: # ARTIFACT_DIGEST: $CI_REGISTRY_IMAGE@sha256:... # ARTIFACT_TYPE: image # # See: docs/modules/signer/guides/keyless-signing.md # ============================================================================== # Base Configuration # ============================================================================== variables: STELLAOPS_URL: "https://api.stella-ops.org" STELLAOPS_CLI_VERSION: "latest" # ============================================================================== # Keyless Signing Job Template # ============================================================================== .stellaops-sign: image: stella-ops/cli:${STELLAOPS_CLI_VERSION} id_tokens: STELLAOPS_OIDC_TOKEN: aud: sigstore variables: # Required - must be set by extending job ARTIFACT_DIGEST: "" # Optional - defaults to 'image' ARTIFACT_TYPE: "image" # Optional - include in Rekor transparency log INCLUDE_REKOR: "true" # Optional - push attestation to registry PUSH_ATTESTATION: "true" before_script: - | if [[ -z "${ARTIFACT_DIGEST}" ]]; then echo "ERROR: ARTIFACT_DIGEST must be set" exit 1 fi script: - | set -euo pipefail SIGN_ARGS=( --keyless --artifact "${ARTIFACT_DIGEST}" --type "${ARTIFACT_TYPE}" --output json ) if [[ "${INCLUDE_REKOR}" == "true" ]]; then SIGN_ARGS+=(--rekor) fi echo "Signing artifact: ${ARTIFACT_DIGEST}" RESULT=$(stella attest sign "${SIGN_ARGS[@]}") # Extract outputs for downstream jobs ATTESTATION_DIGEST=$(echo "$RESULT" | jq -r '.attestationDigest') REKOR_UUID=$(echo "$RESULT" | jq -r '.rekorUuid // empty') CERT_IDENTITY=$(echo "$RESULT" | jq -r '.certificateIdentity // empty') echo "ATTESTATION_DIGEST=${ATTESTATION_DIGEST}" >> sign.env echo "REKOR_UUID=${REKOR_UUID}" >> sign.env echo "CERTIFICATE_IDENTITY=${CERT_IDENTITY}" >> sign.env echo "Attestation created: ${ATTESTATION_DIGEST}" if [[ -n "${REKOR_UUID}" ]]; then echo "Rekor UUID: ${REKOR_UUID}" fi # Push attestation if requested if [[ "${PUSH_ATTESTATION}" == "true" ]]; then echo "Pushing attestation to registry..." stella attest push \ --attestation "${ATTESTATION_DIGEST}" \ --registry "${CI_REGISTRY_IMAGE}" fi artifacts: reports: dotenv: sign.env # ============================================================================== # Verification Job Template # ============================================================================== .stellaops-verify: image: stella-ops/cli:${STELLAOPS_CLI_VERSION} variables: # Required - must be set by extending job ARTIFACT_DIGEST: "" CERTIFICATE_IDENTITY: "" CERTIFICATE_OIDC_ISSUER: "https://gitlab.com" # Optional - verification settings REQUIRE_REKOR: "true" STRICT: "true" REQUIRE_SBOM: "false" REQUIRE_VERDICT: "false" before_script: - | if [[ -z "${ARTIFACT_DIGEST}" ]]; then echo "ERROR: ARTIFACT_DIGEST must be set" exit 1 fi if [[ -z "${CERTIFICATE_IDENTITY}" ]]; then echo "ERROR: CERTIFICATE_IDENTITY must be set" exit 1 fi script: - | set -euo pipefail VERIFY_ARGS=( --artifact "${ARTIFACT_DIGEST}" --certificate-identity "${CERTIFICATE_IDENTITY}" --certificate-oidc-issuer "${CERTIFICATE_OIDC_ISSUER}" --output json ) if [[ "${REQUIRE_REKOR}" == "true" ]]; then VERIFY_ARGS+=(--require-rekor) fi if [[ "${REQUIRE_SBOM}" == "true" ]]; then VERIFY_ARGS+=(--require-sbom) fi if [[ "${REQUIRE_VERDICT}" == "true" ]]; then VERIFY_ARGS+=(--require-verdict) fi echo "Verifying artifact: ${ARTIFACT_DIGEST}" echo "Expected identity: ${CERTIFICATE_IDENTITY}" set +e RESULT=$(stella attest verify "${VERIFY_ARGS[@]}" 2>&1) EXIT_CODE=$? set -e VERIFIED=$(echo "$RESULT" | jq -r '.valid // false') ATTESTATION_COUNT=$(echo "$RESULT" | jq -r '.attestationCount // 0') echo "VERIFIED=${VERIFIED}" >> verify.env echo "ATTESTATION_COUNT=${ATTESTATION_COUNT}" >> verify.env echo "Verified: ${VERIFIED}" echo "Attestations found: ${ATTESTATION_COUNT}" if [[ "$VERIFIED" != "true" ]]; then echo "Verification issues:" echo "$RESULT" | jq -r '.issues[]? | " - \(.code): \(.message)"' if [[ "${STRICT}" == "true" ]]; then echo "ERROR: Verification failed in strict mode" exit 1 fi fi artifacts: reports: dotenv: verify.env # ============================================================================== # SBOM Generation and Signing Template # ============================================================================== .stellaops-sbom: image: stella-ops/cli:${STELLAOPS_CLI_VERSION} id_tokens: STELLAOPS_OIDC_TOKEN: aud: sigstore variables: # Required - image to generate SBOM for IMAGE: "" # Optional - SBOM format SBOM_FORMAT: "cyclonedx-json" # Optional - output file SBOM_OUTPUT: "sbom.json" before_script: - | if [[ -z "${IMAGE}" ]]; then echo "ERROR: IMAGE must be set" exit 1 fi script: - | set -euo pipefail echo "Generating SBOM for: ${IMAGE}" # Generate SBOM stella sbom generate \ --image "${IMAGE}" \ --format "${SBOM_FORMAT}" \ --output "${SBOM_OUTPUT}" # Calculate digest SBOM_DIGEST="sha256:$(sha256sum "${SBOM_OUTPUT}" | cut -d' ' -f1)" echo "SBOM digest: ${SBOM_DIGEST}" # Sign SBOM echo "Signing SBOM..." RESULT=$(stella attest sign \ --keyless \ --artifact "${SBOM_DIGEST}" \ --type sbom \ --rekor \ --output json) ATTESTATION_DIGEST=$(echo "$RESULT" | jq -r '.attestationDigest') REKOR_UUID=$(echo "$RESULT" | jq -r '.rekorUuid // empty') echo "SBOM_DIGEST=${SBOM_DIGEST}" >> sbom.env echo "SBOM_ATTESTATION_DIGEST=${ATTESTATION_DIGEST}" >> sbom.env echo "SBOM_REKOR_UUID=${REKOR_UUID}" >> sbom.env # Attach to image echo "Attaching SBOM to image..." stella attest attach \ --image "${IMAGE}" \ --attestation "${ATTESTATION_DIGEST}" \ --type sbom echo "SBOM signed and attached successfully" artifacts: paths: - ${SBOM_OUTPUT} reports: dotenv: sbom.env # ============================================================================== # Policy Verdict Template # ============================================================================== .stellaops-verdict: image: stella-ops/cli:${STELLAOPS_CLI_VERSION} id_tokens: STELLAOPS_OIDC_TOKEN: aud: sigstore variables: # Required - image to evaluate IMAGE: "" # Optional - policy pack ID POLICY: "default" # Optional - fail on block verdict FAIL_ON_BLOCK: "true" before_script: - | if [[ -z "${IMAGE}" ]]; then echo "ERROR: IMAGE must be set" exit 1 fi script: - | set -euo pipefail echo "Evaluating policy '${POLICY}' for: ${IMAGE}" RESULT=$(stella policy evaluate \ --image "${IMAGE}" \ --policy "${POLICY}" \ --output json) VERDICT=$(echo "$RESULT" | jq -r '.verdict') VERDICT_DIGEST=$(echo "$RESULT" | jq -r '.verdictDigest') PASSED=$(echo "$RESULT" | jq -r '.passed') echo "Verdict: ${VERDICT}" echo "Passed: ${PASSED}" # Sign verdict echo "Signing verdict..." SIGN_RESULT=$(stella attest sign \ --keyless \ --artifact "${VERDICT_DIGEST}" \ --type verdict \ --rekor \ --output json) ATTESTATION_DIGEST=$(echo "$SIGN_RESULT" | jq -r '.attestationDigest') REKOR_UUID=$(echo "$SIGN_RESULT" | jq -r '.rekorUuid // empty') echo "VERDICT=${VERDICT}" >> verdict.env echo "VERDICT_DIGEST=${VERDICT_DIGEST}" >> verdict.env echo "VERDICT_PASSED=${PASSED}" >> verdict.env echo "VERDICT_ATTESTATION_DIGEST=${ATTESTATION_DIGEST}" >> verdict.env echo "VERDICT_REKOR_UUID=${REKOR_UUID}" >> verdict.env # Check if we should fail if [[ "${PASSED}" != "true" && "${FAIL_ON_BLOCK}" == "true" ]]; then echo "ERROR: Policy verdict is ${VERDICT} - blocking deployment" exit 1 fi artifacts: reports: dotenv: verdict.env