chore: clean .gitignore cache
This commit is contained in:
@@ -1,327 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Security SLSA Provenance - Execution Script
|
||||
#
|
||||
# This script generates and verifies SLSA provenance attestations.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Source helper scripts
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SKILLS_SCRIPTS_DIR="$(cd "${SCRIPT_DIR}/../scripts" && pwd)"
|
||||
|
||||
# shellcheck source=../scripts/_logging_helpers.sh
|
||||
source "${SKILLS_SCRIPTS_DIR}/_logging_helpers.sh"
|
||||
# shellcheck source=../scripts/_error_handling_helpers.sh
|
||||
source "${SKILLS_SCRIPTS_DIR}/_error_handling_helpers.sh"
|
||||
# shellcheck source=../scripts/_environment_helpers.sh
|
||||
source "${SKILLS_SCRIPTS_DIR}/_environment_helpers.sh"
|
||||
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
||||
|
||||
# Set defaults
|
||||
set_default_env "SLSA_LEVEL" "2"
|
||||
|
||||
# Parse arguments
|
||||
ACTION="${1:-}"
|
||||
TARGET="${2:-}"
|
||||
SOURCE_URI="${3:-}"
|
||||
PROVENANCE_FILE="${4:-}"
|
||||
|
||||
if [[ -z "${ACTION}" ]] || [[ -z "${TARGET}" ]]; then
|
||||
log_error "Usage: security-slsa-provenance <action> <target> [source_uri] [provenance_file]"
|
||||
log_error " action: generate, verify, inspect"
|
||||
log_error " target: Docker image, file path, or provenance file"
|
||||
log_error " source_uri: Source repository URI (for verify)"
|
||||
log_error " provenance_file: Path to provenance file (for verify with file)"
|
||||
log_error ""
|
||||
log_error "Examples:"
|
||||
log_error " security-slsa-provenance verify ghcr.io/user/charon:latest github.com/user/charon"
|
||||
log_error " security-slsa-provenance verify ./dist/binary github.com/user/repo provenance.json"
|
||||
log_error " security-slsa-provenance inspect provenance.json"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Validate action
|
||||
case "${ACTION}" in
|
||||
generate|verify|inspect)
|
||||
;;
|
||||
*)
|
||||
log_error "Invalid action: ${ACTION}"
|
||||
log_error "Action must be one of: generate, verify, inspect"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check required tools
|
||||
log_step "ENVIRONMENT" "Validating prerequisites"
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
log_error "jq is not installed"
|
||||
log_error "Install from: https://stedolan.github.io/jq/download/"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ "${ACTION}" == "verify" ]] && ! command -v slsa-verifier >/dev/null 2>&1; then
|
||||
log_error "slsa-verifier is not installed"
|
||||
log_error "Install from: https://github.com/slsa-framework/slsa-verifier"
|
||||
log_error "Quick install:"
|
||||
log_error " go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest"
|
||||
log_error "Or:"
|
||||
log_error " curl -sLO https://github.com/slsa-framework/slsa-verifier/releases/download/v2.6.0/slsa-verifier-linux-amd64"
|
||||
log_error " sudo install slsa-verifier-linux-amd64 /usr/local/bin/slsa-verifier"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ "${ACTION}" == "verify" ]] && [[ "${TARGET}" =~ ^ghcr\.|^docker\.|: ]]; then
|
||||
# Docker image verification requires gh CLI
|
||||
if ! command -v gh >/dev/null 2>&1; then
|
||||
log_error "gh (GitHub CLI) is not installed (required for Docker image verification)"
|
||||
log_error "Install from: https://cli.github.com/"
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
cd "${PROJECT_ROOT}"
|
||||
|
||||
# Execute action
|
||||
case "${ACTION}" in
|
||||
generate)
|
||||
log_step "GENERATE" "Generating SLSA provenance for ${TARGET}"
|
||||
log_warning "This generates a basic provenance for testing only"
|
||||
log_warning "Production provenance must be generated by CI/CD build platform"
|
||||
|
||||
if [[ ! -f "${TARGET}" ]]; then
|
||||
log_error "File not found: ${TARGET}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Calculate digest
|
||||
DIGEST=$(sha256sum "${TARGET}" | awk '{print $1}')
|
||||
ARTIFACT_NAME=$(basename "${TARGET}")
|
||||
OUTPUT_FILE="provenance-${ARTIFACT_NAME}.json"
|
||||
|
||||
# Generate basic provenance structure
|
||||
cat > "${OUTPUT_FILE}" <<EOF
|
||||
{
|
||||
"_type": "https://in-toto.io/Statement/v1",
|
||||
"subject": [
|
||||
{
|
||||
"name": "${ARTIFACT_NAME}",
|
||||
"digest": {
|
||||
"sha256": "${DIGEST}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"predicateType": "https://slsa.dev/provenance/v1",
|
||||
"predicate": {
|
||||
"buildDefinition": {
|
||||
"buildType": "https://github.com/user/local-build",
|
||||
"externalParameters": {
|
||||
"source": {
|
||||
"uri": "git+https://github.com/user/charon@local",
|
||||
"digest": {
|
||||
"sha1": "0000000000000000000000000000000000000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"internalParameters": {},
|
||||
"resolvedDependencies": []
|
||||
},
|
||||
"runDetails": {
|
||||
"builder": {
|
||||
"id": "https://github.com/user/local-builder@v1.0.0"
|
||||
},
|
||||
"metadata": {
|
||||
"invocationId": "local-$(date +%s)",
|
||||
"startedOn": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"finishedOn": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
log_success "Generated provenance: ${OUTPUT_FILE}"
|
||||
log_warning "This provenance is NOT cryptographically signed"
|
||||
log_warning "Use only for local testing, not for production"
|
||||
;;
|
||||
|
||||
verify)
|
||||
log_step "VERIFY" "Verifying SLSA provenance for ${TARGET}"
|
||||
|
||||
if [[ -z "${SOURCE_URI}" ]]; then
|
||||
log_error "Source URI is required for verification"
|
||||
log_error "Usage: security-slsa-provenance verify <target> <source_uri> [provenance_file]"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Determine if target is Docker image or file
|
||||
# Match: ghcr.io/user/repo:tag, docker.io/user/repo:tag, user/repo:tag, simple:tag, registry.io:5000/app:v1
|
||||
# Avoid: ./file, /path/to/file, file.ext, http://url
|
||||
# Strategy: Images have "name:tag" format and don't start with ./ or / and aren't files
|
||||
if [[ ! -f "${TARGET}" ]] && \
|
||||
[[ ! "${TARGET}" =~ ^\./ ]] && \
|
||||
[[ ! "${TARGET}" =~ ^/ ]] && \
|
||||
[[ ! "${TARGET}" =~ ^https?:// ]] && \
|
||||
[[ "${TARGET}" =~ : ]]; then
|
||||
# Looks like a Docker image
|
||||
log_info "Target appears to be a Docker image"
|
||||
|
||||
if [[ -n "${PROVENANCE_FILE}" ]]; then
|
||||
log_warning "Provenance file parameter ignored for Docker images"
|
||||
log_warning "Provenance will be downloaded from registry"
|
||||
fi
|
||||
|
||||
# Verify image with slsa-verifier
|
||||
log_info "Verifying image with slsa-verifier..."
|
||||
if slsa-verifier verify-image "${TARGET}" \
|
||||
--source-uri "github.com/${SOURCE_URI}" \
|
||||
--print-provenance 2>&1 | tee slsa-verify.log; then
|
||||
log_success "Provenance verification passed"
|
||||
|
||||
# Parse SLSA level from output
|
||||
if grep -q "SLSA" slsa-verify.log; then
|
||||
LEVEL=$(grep -oP 'SLSA Level: \K\d+' slsa-verify.log || echo "unknown")
|
||||
log_info "SLSA Level: ${LEVEL}"
|
||||
|
||||
if [[ "${LEVEL}" =~ ^[0-9]+$ ]] && [[ "${LEVEL}" -lt "${SLSA_LEVEL}" ]]; then
|
||||
log_warning "SLSA level ${LEVEL} is below minimum required level ${SLSA_LEVEL}"
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f slsa-verify.log
|
||||
exit 0
|
||||
else
|
||||
log_error "Provenance verification failed"
|
||||
cat slsa-verify.log >&2 || true
|
||||
rm -f slsa-verify.log
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# File artifact
|
||||
log_info "Target appears to be a file artifact"
|
||||
|
||||
if [[ ! -f "${TARGET}" ]]; then
|
||||
log_error "File not found: ${TARGET}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${PROVENANCE_FILE}" ]]; then
|
||||
log_error "Provenance file is required for file verification"
|
||||
log_error "Usage: security-slsa-provenance verify <file> <source_uri> <provenance_file>"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ ! -f "${PROVENANCE_FILE}" ]]; then
|
||||
log_error "Provenance file not found: ${PROVENANCE_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Verifying artifact with slsa-verifier..."
|
||||
if slsa-verifier verify-artifact "${TARGET}" \
|
||||
--provenance-path "${PROVENANCE_FILE}" \
|
||||
--source-uri "github.com/${SOURCE_URI}" \
|
||||
--print-provenance 2>&1 | tee slsa-verify.log; then
|
||||
log_success "Provenance verification passed"
|
||||
|
||||
# Parse SLSA level from output
|
||||
if grep -q "SLSA" slsa-verify.log; then
|
||||
LEVEL=$(grep -oP 'SLSA Level: \K\d+' slsa-verify.log || echo "unknown")
|
||||
log_info "SLSA Level: ${LEVEL}"
|
||||
|
||||
if [[ "${LEVEL}" =~ ^[0-9]+$ ]] && [[ "${LEVEL}" -lt "${SLSA_LEVEL}" ]]; then
|
||||
log_warning "SLSA level ${LEVEL} is below minimum required level ${SLSA_LEVEL}"
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f slsa-verify.log
|
||||
exit 0
|
||||
else
|
||||
log_error "Provenance verification failed"
|
||||
cat slsa-verify.log >&2 || true
|
||||
rm -f slsa-verify.log
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
inspect)
|
||||
log_step "INSPECT" "Inspecting SLSA provenance"
|
||||
|
||||
if [[ ! -f "${TARGET}" ]]; then
|
||||
log_error "Provenance file not found: ${TARGET}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate JSON
|
||||
if ! jq empty "${TARGET}" 2>/dev/null; then
|
||||
log_error "Invalid JSON in provenance file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " SLSA PROVENANCE DETAILS"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Extract and display key fields
|
||||
PREDICATE_TYPE=$(jq -r '.predicateType // "unknown"' "${TARGET}")
|
||||
echo "Predicate Type: ${PREDICATE_TYPE}"
|
||||
|
||||
# Builder
|
||||
BUILDER_ID=$(jq -r '.predicate.runDetails.builder.id // .predicate.builder.id // "unknown"' "${TARGET}")
|
||||
echo ""
|
||||
echo "Builder:"
|
||||
echo " ID: ${BUILDER_ID}"
|
||||
|
||||
# Source
|
||||
SOURCE_URI_FOUND=$(jq -r '.predicate.buildDefinition.externalParameters.source.uri // .predicate.materials[0].uri // "unknown"' "${TARGET}")
|
||||
SOURCE_DIGEST=$(jq -r '.predicate.buildDefinition.externalParameters.source.digest.sha1 // "unknown"' "${TARGET}")
|
||||
echo ""
|
||||
echo "Source Repository:"
|
||||
echo " URI: ${SOURCE_URI_FOUND}"
|
||||
if [[ "${SOURCE_DIGEST}" != "unknown" ]]; then
|
||||
echo " Digest: ${SOURCE_DIGEST}"
|
||||
fi
|
||||
|
||||
# Subject
|
||||
SUBJECT_NAME=$(jq -r '.subject[0].name // "unknown"' "${TARGET}")
|
||||
SUBJECT_DIGEST=$(jq -r '.subject[0].digest.sha256 // "unknown"' "${TARGET}")
|
||||
echo ""
|
||||
echo "Subject:"
|
||||
echo " Name: ${SUBJECT_NAME}"
|
||||
echo " Digest: sha256:${SUBJECT_DIGEST:0:12}..."
|
||||
|
||||
# Build metadata
|
||||
STARTED=$(jq -r '.predicate.runDetails.metadata.startedOn // .predicate.metadata.buildStartedOn // "unknown"' "${TARGET}")
|
||||
FINISHED=$(jq -r '.predicate.runDetails.metadata.finishedOn // .predicate.metadata.buildFinishedOn // "unknown"' "${TARGET}")
|
||||
echo ""
|
||||
echo "Build Metadata:"
|
||||
if [[ "${STARTED}" != "unknown" ]]; then
|
||||
echo " Started: ${STARTED}"
|
||||
fi
|
||||
if [[ "${FINISHED}" != "unknown" ]]; then
|
||||
echo " Finished: ${FINISHED}"
|
||||
fi
|
||||
|
||||
# Materials/Dependencies
|
||||
MATERIALS_COUNT=$(jq '.predicate.buildDefinition.resolvedDependencies // .predicate.materials // [] | length' "${TARGET}")
|
||||
if [[ "${MATERIALS_COUNT}" -gt 0 ]]; then
|
||||
echo ""
|
||||
echo "Materials (Dependencies): ${MATERIALS_COUNT}"
|
||||
jq -r '.predicate.buildDefinition.resolvedDependencies // .predicate.materials // [] | .[] | " - \(.uri // .name // "unknown")"' "${TARGET}" | head -n 5
|
||||
if [[ "${MATERIALS_COUNT}" -gt 5 ]]; then
|
||||
echo " ... and $((MATERIALS_COUNT - 5)) more"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
log_success "Provenance inspection complete"
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user