#!/bin/bash # Copyright (c) StellaOps. All rights reserved. # Licensed under the BUSL-1.1 license. # # generate-slsa-provenance.sh # Generates SLSA v1.0 provenance statements for release artifacts # # Usage: ./generate-slsa-provenance.sh --version --commit --output set -euo pipefail # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Default values VERSION="" COMMIT="" OUTPUT_DIR="provenance" ARTIFACTS_DIR="artifacts" BUILDER_ID="${BUILDER_ID:-https://ci.stella-ops.org/builder/v1}" BUILD_TYPE="${BUILD_TYPE:-https://stella-ops.io/ReleaseBuilder/v1}" REPOSITORY_URI="${REPOSITORY_URI:-git+https://git.stella-ops.org/stella-ops.org/git.stella-ops.org}" # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --version) VERSION="$2" shift 2 ;; --commit) COMMIT="$2" shift 2 ;; --output) OUTPUT_DIR="$2" shift 2 ;; --artifacts) ARTIFACTS_DIR="$2" shift 2 ;; --builder-id) BUILDER_ID="$2" shift 2 ;; --build-type) BUILD_TYPE="$2" shift 2 ;; --help) echo "Usage: $0 --version --commit --output " echo "" echo "Options:" echo " --version Release version (required)" echo " --commit Git commit SHA (required)" echo " --output Output directory for provenance files (default: provenance)" echo " --artifacts Directory containing release artifacts (default: artifacts)" echo " --builder-id Builder ID URI (default: https://ci.stella-ops.org/builder/v1)" echo " --build-type Build type URI (default: https://stella-ops.io/ReleaseBuilder/v1)" exit 0 ;; *) echo -e "${RED}Unknown option: $1${NC}" exit 1 ;; esac done # Validate required arguments if [[ -z "$VERSION" ]]; then echo -e "${RED}Error: --version is required${NC}" exit 1 fi if [[ -z "$COMMIT" ]]; then echo -e "${RED}Error: --commit is required${NC}" exit 1 fi # Create output directory mkdir -p "$OUTPUT_DIR" # Get timestamps STARTED_ON="${BUILD_STARTED_ON:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" FINISHED_ON="$(date -u +%Y-%m-%dT%H:%M:%SZ)" # Get invocation ID from CI environment INVOCATION_ID="${CI_JOB_ID:-${GITHUB_RUN_ID:-$(uuidgen || cat /proc/sys/kernel/random/uuid 2>/dev/null || echo "local-build")}}" echo -e "${GREEN}Generating SLSA v1.0 provenance for version ${VERSION}${NC}" echo " Commit: ${COMMIT}" echo " Builder: ${BUILDER_ID}" echo " Output: ${OUTPUT_DIR}" # Function to generate provenance for a single artifact generate_provenance() { local artifact_path="$1" local artifact_name artifact_name=$(basename "$artifact_path") # Compute SHA-256 digest local sha256 sha256=$(sha256sum "$artifact_path" | cut -d' ' -f1) # Determine component name from artifact local component_name component_name=$(echo "$artifact_name" | sed -E 's/stella-([^-]+).*/\1/') local output_file="${OUTPUT_DIR}/${component_name}.slsa.intoto.jsonl" echo " Generating provenance for: ${artifact_name}" # Generate SLSA v1.0 provenance statement cat > "$output_file" << EOF { "_type": "https://in-toto.io/Statement/v1", "subject": [ { "name": "${artifact_name}", "digest": { "sha256": "${sha256}" } } ], "predicateType": "https://slsa.dev/provenance/v1", "predicate": { "buildDefinition": { "buildType": "${BUILD_TYPE}", "externalParameters": { "version": "${VERSION}", "repository": "${REPOSITORY_URI}", "ref": "refs/tags/v${VERSION}" }, "internalParameters": {}, "resolvedDependencies": [ { "uri": "${REPOSITORY_URI}@refs/tags/v${VERSION}", "digest": { "gitCommit": "${COMMIT}" } } ] }, "runDetails": { "builder": { "id": "${BUILDER_ID}", "version": { "stellaOps": "${VERSION}" } }, "metadata": { "invocationId": "${INVOCATION_ID}", "startedOn": "${STARTED_ON}", "finishedOn": "${FINISHED_ON}" }, "byproducts": [] } } } EOF echo -e " ${GREEN}Created: ${output_file}${NC}" } # Find and process artifacts artifact_count=0 for artifact in "${ARTIFACTS_DIR}"/stella-*.tar.gz "${ARTIFACTS_DIR}"/stella-*.zip; do if [[ -f "$artifact" ]]; then generate_provenance "$artifact" ((artifact_count++)) fi done if [[ $artifact_count -eq 0 ]]; then echo -e "${YELLOW}Warning: No artifacts found in ${ARTIFACTS_DIR}${NC}" exit 0 fi echo "" echo -e "${GREEN}Generated ${artifact_count} provenance statement(s)${NC}" echo "Files written to: ${OUTPUT_DIR}/"