- 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.
306 lines
8.8 KiB
YAML
306 lines
8.8 KiB
YAML
# 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
|