|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +##################################################################### |
| 4 | +# verify-image.sh |
| 5 | +# |
| 6 | +# Description: |
| 7 | +# This script verifies the authenticity and integrity of the Replicated SDK |
| 8 | +# container images by performing three key security checks: |
| 9 | +# 1. SLSA Provenance verification - Ensures build chain integrity |
| 10 | +# 2. Image signature verification - Validates image authenticity |
| 11 | +# 3. SBOM attestation verification - Checks software bill of materials |
| 12 | +# |
| 13 | +# Usage: |
| 14 | +# ./verify-image.sh [OPTIONS] |
| 15 | +# |
| 16 | +# Required Arguments: |
| 17 | +# -e, --env ENV Environment to verify (dev|stage|prod) |
| 18 | +# -v, --version VER Version to verify |
| 19 | +# -d, --digest DIG Image digest to verify |
| 20 | +# |
| 21 | +# Optional Arguments: |
| 22 | +# --show-sbom Show full SBOM content |
| 23 | +# -h, --help Show this help message |
| 24 | +# |
| 25 | +# Environment-specific Behavior: |
| 26 | +# - dev: Uses ttl.sh registry with dev signing keys |
| 27 | +# - stage: Uses staging registry with staging signing keys |
| 28 | +# - prod: Uses production registry with keyless verification |
| 29 | +# |
| 30 | +# Examples: |
| 31 | +# ./verify-image.sh --env dev \ |
| 32 | +# --version 1.5.3-beta.3 \ |
| 33 | +# --digest sha256:5b064832df6bfb934c081fa0263134bc9845525211f09a752d5684306310f3c5 |
| 34 | +# |
| 35 | +# ./verify-image.sh --env prod \ |
| 36 | +# --version 1.5.3 \ |
| 37 | +# --digest sha256:7cb8e0c8e0fba8e4a7157b4fcef9e7345538f7543a4e5925bb8b30c9c1375400 |
| 38 | +# |
| 39 | +# Exit Codes: |
| 40 | +# 0 - All verifications passed |
| 41 | +# 1 - Verification failed or invalid arguments |
| 42 | +# |
| 43 | +# Author: Replicated, Inc. |
| 44 | +# License: Apache-2.0 |
| 45 | +##################################################################### |
| 46 | + |
| 47 | +# Help function to display usage information and examples |
| 48 | +show_help() { |
| 49 | + echo "Usage: $0 [OPTIONS]" |
| 50 | + echo "Verify SLSA attestation and SBOM for Replicated SDK" |
| 51 | + echo |
| 52 | + echo "Options:" |
| 53 | + echo " -e, --env ENV Environment to verify (dev|stage|prod) [Required]" |
| 54 | + echo " -v, --version VER Version to verify [Required]" |
| 55 | + echo " -d, --digest DIG Image digest to verify [Required]" |
| 56 | + echo " --show-sbom Show full SBOM content" |
| 57 | + echo " -h, --help Show this help message" |
| 58 | + echo |
| 59 | + echo "Examples:" |
| 60 | + echo " $0 --env dev --version 1.5.3-beta.3 --digest sha256:5b064832df6bfb934c081fa0263134bc9845525211f09a752d5684306310f3c5" |
| 61 | + echo " $0 --env stage --version 1.5.3-beta.3 --digest sha256:7cb8e0c8e0fba8e4a7157b4fcef9e7345538f7543a4e5925bb8b30c9c1375400" |
| 62 | +} |
| 63 | + |
| 64 | +# Parse command line arguments using a while loop for flexibility |
| 65 | +while [[ $# -gt 0 ]]; do |
| 66 | + case $1 in |
| 67 | + -e|--env) |
| 68 | + ENV="$2" |
| 69 | + shift 2 |
| 70 | + ;; |
| 71 | + -v|--version) |
| 72 | + TEST_VERSION="$2" |
| 73 | + shift 2 |
| 74 | + ;; |
| 75 | + -d|--digest) |
| 76 | + DIGEST="$2" |
| 77 | + shift 2 |
| 78 | + ;; |
| 79 | + --show-sbom) |
| 80 | + SHOW_SBOM=true |
| 81 | + shift |
| 82 | + ;; |
| 83 | + -h|--help) |
| 84 | + show_help |
| 85 | + exit 0 |
| 86 | + ;; |
| 87 | + *) |
| 88 | + echo "Unknown option: $1" |
| 89 | + show_help |
| 90 | + exit 1 |
| 91 | + ;; |
| 92 | + esac |
| 93 | +done |
| 94 | + |
| 95 | +# Validate required arguments to ensure script can proceed |
| 96 | +if [ -z "$ENV" ] || [ -z "$TEST_VERSION" ] || [ -z "$DIGEST" ]; then |
| 97 | + echo "Error: Environment (-e), version (-v), and digest (-d) are required" |
| 98 | + show_help |
| 99 | + exit 1 |
| 100 | +fi |
| 101 | + |
| 102 | +# Validate environment value to prevent invalid configurations |
| 103 | +if [[ ! "$ENV" =~ ^(dev|stage|prod)$ ]]; then |
| 104 | + echo "Error: Environment (-e, --env) must be one of: dev, stage, prod" |
| 105 | + exit 1 |
| 106 | +fi |
| 107 | + |
| 108 | +# Set environment-specific variables for registry paths and image names |
| 109 | +case $ENV in |
| 110 | + dev) |
| 111 | + # Development environment uses ttl.sh for temporary image storage |
| 112 | + REGISTRY="ttl.sh" |
| 113 | + IMAGE="${REGISTRY}/replicated/replicated-sdk" |
| 114 | + ;; |
| 115 | + stage) |
| 116 | + # Staging environment uses staging registry for pre-release testing |
| 117 | + REGISTRY="registry.staging.replicated.com" |
| 118 | + IMAGE="${REGISTRY}/library/replicated-sdk-image" |
| 119 | + ;; |
| 120 | + prod) |
| 121 | + # Production environment uses main registry for released images |
| 122 | + REGISTRY="registry.replicated.com" |
| 123 | + IMAGE="${REGISTRY}/library/replicated-sdk-image" |
| 124 | + ;; |
| 125 | +esac |
| 126 | + |
| 127 | +# Construct full image reference with digest for unique identification |
| 128 | +IMAGE_WITH_DIGEST="${IMAGE}@${DIGEST}" |
| 129 | + |
| 130 | +# Define source repository for SLSA verification |
| 131 | +SOURCE_REPO=github.com/replicatedhq/replicated-sdk |
| 132 | + |
| 133 | +echo "===============================================" |
| 134 | +echo "Starting verification for ${IMAGE_WITH_DIGEST}" |
| 135 | +echo "===============================================" |
| 136 | + |
| 137 | +# Step 1: SLSA Provenance Verification |
| 138 | +# This step ensures the image was built through our secure build pipeline |
| 139 | +echo -e "\n📋 Step 1: Verifying SLSA provenance..." |
| 140 | +if [ "$ENV" != "dev" ]; then |
| 141 | + # SLSA verification is skipped for dev environment as it uses different build process |
| 142 | + if COSIGN_REPOSITORY=${REGISTRY}/library/replicated-sdk-image slsa-verifier verify-image "${IMAGE_WITH_DIGEST}" \ |
| 143 | + --source-uri ${SOURCE_REPO} \ |
| 144 | + --source-tag ${TEST_VERSION} \ |
| 145 | + --print-provenance | tee /tmp/slsa_output.json | jq -r ' |
| 146 | + # Format and display relevant build information from SLSA attestation |
| 147 | + "✅ SLSA Verification: SUCCESS", |
| 148 | + "Build Details:", |
| 149 | + " • Builder: \(.predicate.builder.id | split("@")[0])", |
| 150 | + " • Organization: \(.predicate.invocation.environment.github_event_payload.organization.login)", |
| 151 | + " • Last Commit Author: \(.predicate.invocation.environment.github_event_payload.head_commit.author.name)", |
| 152 | + " • Last Commit Email: \(.predicate.invocation.environment.github_event_payload.head_commit.author.email)", |
| 153 | + " • Release Created By: \(.predicate.invocation.environment.github_event_payload.pusher.name)", |
| 154 | + " • Source: \(.predicate.invocation.configSource.uri | split("@")[0])", |
| 155 | + " • Commit: \(.predicate.invocation.configSource.digest.sha1)", |
| 156 | + " • Built From: \(.predicate.invocation.environment.github_ref)", |
| 157 | + " • Image Digest: \(.subject[0].digest.sha256 // "N/A")", |
| 158 | + " • Commit Timestamp: \(.predicate.invocation.environment.github_event_payload.head_commit.timestamp)" |
| 159 | + '; then |
| 160 | + echo "✅ SLSA verification successful" |
| 161 | + else |
| 162 | + echo "❌ SLSA verification failed" |
| 163 | + exit 1 |
| 164 | + fi |
| 165 | +else |
| 166 | + echo "ℹ️ SLSA verification skipped in dev mode" |
| 167 | +fi |
| 168 | + |
| 169 | +# Step 2: Image Signature Verification |
| 170 | +# Validates the image signature using environment-specific keys |
| 171 | +echo -e "\n🔏 Step 2: Verifying image signature..." |
| 172 | +if [ "$ENV" = "dev" ]; then |
| 173 | + # Development environment uses a development signing key for verification |
| 174 | + if VERIFICATION_OUTPUT=$(cosign verify-attestation \ |
| 175 | + --key ./cosign-dev.pub \ |
| 176 | + --type spdxjson \ |
| 177 | + ${IMAGE_WITH_DIGEST} 2>/dev/null); then |
| 178 | + echo "✅ Image signature verification successful" |
| 179 | + echo "Signature details:" |
| 180 | + echo "$VERIFICATION_OUTPUT" | jq -r ' |
| 181 | + " • Attestation type: \(.payloadType)", |
| 182 | + " • Signature timestamp: \(.optional.sig.timestamp // "N/A")" |
| 183 | + ' |
| 184 | + else |
| 185 | + echo "❌ Image signature verification failed" |
| 186 | + exit 1 |
| 187 | + fi |
| 188 | +elif [ "$ENV" = "stage" ]; then |
| 189 | + # Staging environment uses staging-specific signing key |
| 190 | + if VERIFICATION_OUTPUT=$(cosign verify-attestation \ |
| 191 | + --key ./cosign-stage.pub \ |
| 192 | + --type spdxjson \ |
| 193 | + ${IMAGE_WITH_DIGEST} 2>/dev/null); then |
| 194 | + echo "✅ Image signature verification successful" |
| 195 | + echo "Signature details:" |
| 196 | + echo "$VERIFICATION_OUTPUT" | jq -r ' |
| 197 | + " • Attestation type: \(.payloadType)", |
| 198 | + " • Signature timestamp: \(.optional.sig.timestamp // "N/A")" |
| 199 | + ' |
| 200 | + else |
| 201 | + echo "❌ Image signature verification failed" |
| 202 | + exit 1 |
| 203 | + fi |
| 204 | +else |
| 205 | + # Production environment uses production-specific signing key |
| 206 | + if VERIFICATION_OUTPUT=$(cosign verify-attestation \ |
| 207 | + --key ./cosign-prod.pub \ |
| 208 | + --type spdxjson \ |
| 209 | + ${IMAGE_WITH_DIGEST} 2>/dev/null); then |
| 210 | + echo "✅ Image signature verification successful" |
| 211 | + echo "Signature details:" |
| 212 | + echo "$VERIFICATION_OUTPUT" | jq -r ' |
| 213 | + " • Attestation type: \(.payloadType)", |
| 214 | + " • Signature timestamp: \(.optional.sig.timestamp // "N/A")" |
| 215 | + ' |
| 216 | + else |
| 217 | + echo "❌ Image signature verification failed" |
| 218 | + exit 1 |
| 219 | + fi |
| 220 | +fi |
| 221 | + |
| 222 | +# Step 3: SBOM Attestation Verification |
| 223 | +# Verifies and displays the Software Bill of Materials attached to the image |
| 224 | +echo -e "\n📦 Step 3: Verifying SBOM attestation..." |
| 225 | + |
| 226 | +# Try both SPDX predicate types for compatibility with different SBOM formats |
| 227 | +if ! RAW_ATTESTATION=$(cosign download attestation \ |
| 228 | + --predicate-type spdxjson \ |
| 229 | + ${IMAGE_WITH_DIGEST} 2>/dev/null) && \ |
| 230 | + ! RAW_ATTESTATION=$(cosign download attestation \ |
| 231 | + --predicate-type https://spdx.dev/Document \ |
| 232 | + ${IMAGE_WITH_DIGEST} 2>/dev/null); then |
| 233 | + echo "❌ No SPDX attestation found on image" |
| 234 | + echo "This may indicate the SBOM wasn't properly attached during build" |
| 235 | + exit 1 |
| 236 | +fi |
| 237 | + |
| 238 | +# Ensure the attestation is not empty |
| 239 | +if [ -z "$RAW_ATTESTATION" ]; then |
| 240 | + echo "❌ Empty SPDX attestation found" |
| 241 | + echo "This may indicate an issue during the build process" |
| 242 | + exit 1 |
| 243 | +fi |
| 244 | + |
| 245 | +echo "✅ SBOM verification successful" |
| 246 | +DECODED_PAYLOAD=$(echo "$RAW_ATTESTATION" | jq -r '.payload' | base64 -d) |
| 247 | + |
| 248 | +# Display formatted SBOM information focusing on key details |
| 249 | +echo "SBOM details:" |
| 250 | +echo "$DECODED_PAYLOAD" | jq -r ' |
| 251 | + " • Document Name: \(.predicate.name // "N/A")", |
| 252 | + " • Created: \(.predicate.creationInfo.created // "N/A")", |
| 253 | + " • Created By: \(.predicate.creationInfo.creators | map(select(startswith("Organization: Replicated"))) | .[0] // "N/A")", |
| 254 | + " • Tool: \(.predicate.creationInfo.creators | map(select(startswith("Tool:"))) | .[0] // "N/A")", |
| 255 | + " • Total Packages: \(.predicate.packages | length) packages" |
| 256 | +' |
| 257 | + |
| 258 | +# Optionally display full SBOM content if requested |
| 259 | +if [ "$SHOW_SBOM" = "true" ]; then |
| 260 | + echo -e "\nFull SBOM Content:" |
| 261 | + echo "$DECODED_PAYLOAD" | jq '.' |
| 262 | +fi |
| 263 | + |
| 264 | +echo "ℹ️ Use '--show-sbom' flag to view full SBOM contents" |
| 265 | + |
| 266 | +echo -e "\n✨ All verifications completed successfully!" |
| 267 | +echo "===============================================" |
0 commit comments