151 lines
4.1 KiB
Bash
151 lines
4.1 KiB
Bash
#!/bin/bash
|
|
# -----------------------------------------------------------------------------
|
|
# add-target.sh
|
|
# Sprint: SPRINT_20260125_001_Attestor_tuf_trust_foundation
|
|
# Task: TUF-006 - Create TUF repository structure template
|
|
# Description: Add a new target file to the TUF repository
|
|
# -----------------------------------------------------------------------------
|
|
|
|
set -euo pipefail
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
usage() {
|
|
echo "Usage: $0 <source-file> <target-name> [options]"
|
|
echo ""
|
|
echo "Add a target file to the TUF repository."
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --repo DIR Repository directory (default: current directory)"
|
|
echo " --custom-hash HASH Override SHA256 hash (for testing only)"
|
|
echo " -h, --help Show this help message"
|
|
echo ""
|
|
echo "Example:"
|
|
echo " $0 /path/to/rekor-key.pub rekor-key-v1"
|
|
echo " $0 /path/to/services.json sigstore-services-v1 --repo /var/lib/tuf"
|
|
exit 1
|
|
}
|
|
|
|
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
|
|
SOURCE_FILE=""
|
|
TARGET_NAME=""
|
|
REPO_DIR="."
|
|
CUSTOM_HASH=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--repo)
|
|
REPO_DIR="$2"
|
|
shift 2
|
|
;;
|
|
--custom-hash)
|
|
CUSTOM_HASH="$2"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
;;
|
|
*)
|
|
if [[ -z "$SOURCE_FILE" ]]; then
|
|
SOURCE_FILE="$1"
|
|
elif [[ -z "$TARGET_NAME" ]]; then
|
|
TARGET_NAME="$1"
|
|
else
|
|
log_error "Unknown argument: $1"
|
|
usage
|
|
fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$SOURCE_FILE" ]] || [[ -z "$TARGET_NAME" ]]; then
|
|
log_error "Source file and target name are required"
|
|
usage
|
|
fi
|
|
|
|
if [[ ! -f "$SOURCE_FILE" ]]; then
|
|
log_error "Source file not found: $SOURCE_FILE"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$REPO_DIR/targets.json" ]]; then
|
|
log_error "Not a TUF repository: $REPO_DIR (targets.json not found)"
|
|
exit 1
|
|
fi
|
|
|
|
# Calculate file hash and size
|
|
FILE_SIZE=$(stat -f%z "$SOURCE_FILE" 2>/dev/null || stat -c%s "$SOURCE_FILE")
|
|
if [[ -n "$CUSTOM_HASH" ]]; then
|
|
FILE_HASH="$CUSTOM_HASH"
|
|
else
|
|
FILE_HASH=$(openssl dgst -sha256 -hex "$SOURCE_FILE" | awk '{print $2}')
|
|
fi
|
|
|
|
log_info "Adding target: $TARGET_NAME"
|
|
log_info " Source: $SOURCE_FILE"
|
|
log_info " Size: $FILE_SIZE bytes"
|
|
log_info " SHA256: $FILE_HASH"
|
|
|
|
# Copy file to targets directory
|
|
TARGETS_DIR="$REPO_DIR/targets"
|
|
mkdir -p "$TARGETS_DIR"
|
|
cp "$SOURCE_FILE" "$TARGETS_DIR/$TARGET_NAME"
|
|
|
|
# Update targets.json
|
|
# This is a simplified implementation - production should use proper JSON manipulation
|
|
TARGETS_JSON="$REPO_DIR/targets.json"
|
|
|
|
# Read current version
|
|
CURRENT_VERSION=$(grep -o '"version"[[:space:]]*:[[:space:]]*[0-9]*' "$TARGETS_JSON" | head -1 | grep -o '[0-9]*')
|
|
NEW_VERSION=$((CURRENT_VERSION + 1))
|
|
|
|
# Calculate new expiry (30 days from now)
|
|
NEW_EXPIRES=$(date -u -d "+30 days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v+30d +%Y-%m-%dT%H:%M:%SZ)
|
|
|
|
log_info "Updating targets.json (version $CURRENT_VERSION -> $NEW_VERSION)"
|
|
|
|
# Create new targets entry
|
|
python3 - "$TARGETS_JSON" "$TARGET_NAME" "$FILE_SIZE" "$FILE_HASH" "$NEW_VERSION" "$NEW_EXPIRES" << 'PYTHON_SCRIPT'
|
|
import json
|
|
import sys
|
|
|
|
targets_file = sys.argv[1]
|
|
target_name = sys.argv[2]
|
|
file_size = int(sys.argv[3])
|
|
file_hash = sys.argv[4]
|
|
new_version = int(sys.argv[5])
|
|
new_expires = sys.argv[6]
|
|
|
|
with open(targets_file, 'r') as f:
|
|
data = json.load(f)
|
|
|
|
data['signed']['version'] = new_version
|
|
data['signed']['expires'] = new_expires
|
|
data['signed']['targets'][target_name] = {
|
|
'length': file_size,
|
|
'hashes': {
|
|
'sha256': file_hash
|
|
}
|
|
}
|
|
|
|
# Clear signatures (need to re-sign)
|
|
data['signatures'] = []
|
|
|
|
with open(targets_file, 'w') as f:
|
|
json.dump(data, f, indent=2)
|
|
|
|
print(f"Updated {targets_file}")
|
|
PYTHON_SCRIPT
|
|
|
|
log_info ""
|
|
log_info "Target added successfully!"
|
|
log_warn "IMPORTANT: targets.json signatures have been cleared."
|
|
log_warn "Run the signing script to re-sign metadata before publishing."
|