diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 000000000..331174d73 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,226 @@ +## Signing Releases with Minisign + +Starting from **version 2.0.3**, all Volta release artifacts must be cryptographically signed using [Minisign](https://jedisct1.github.io/minisign/) to ensure authenticity and integrity. + +### Prerequisites + +Maintainers must have Minisign installed: + +```bash +# macOS +brew install minisign + +# Ubuntu/Debian +sudo apt install minisign + +# Fedora +sudo dnf install minisign + +# Or download from: https://jedisct1.github.io/minisign/ +``` + +--- + +### One-Time Setup: Generate Release Signing Keys + +**This only needs to be done once** (or when rotating keys): + +```bash +# Generate a key pair for signing Volta releases +minisign -G -p volta-release.pub -s volta-release.key + +# You'll be prompted to create a password - use a strong one! +# This creates: +# volta-release.pub - Public key (will be embedded in install.sh) +# volta-release.key - Private key (keep this SECRET and secure) +``` + +**IMPORTANT:** + +- **Never commit `volta-release.key` to the repository** +- Store the private key in a secure, encrypted location +- Share the password securely among authorized maintainers only +- Commit `volta-release.pub` to the repository for reference +- Update the `Volta_PUBLIC_KEY` constant in `dev/unix/volta-install.sh` with the public key + +--- + +### Signing a Release + +After building release artifacts for all platforms, sign each one: + +#### 1. Build Release Artifacts + +```bash +# Example for v2.0.3 +# (Adjust based on your actual build process) +./build-release.sh 2.0.3 +``` + +This should produce tarballs for all supported platforms: + +- `volta-2.0.3-macos.tar.gz` +- `volta-2.0.3-linux.tar.gz` +- `volta-2.0.3-linux-arm.tar.gz` + +#### 2. Sign Each Artifact + +```bash +# Navigate to the directory with your release artifacts +cd target/release # or wherever your builds are + +# Sign each platform's tarball +minisign -Sm volta-2.0.3-macos.tar.gz +minisign -Sm volta-2.0.3-linux.tar.gz +minisign -Sm volta-2.0.3-linux-arm.tar.gz + +# You'll be prompted for the private key password for each file +# This creates .minisig files: +# volta-2.0.3-macos.tar.gz.minisig +# volta-2.0.3-linux.tar.gz.minisig +# volta-2.0.3-linux-arm.tar.gz.minisig +``` + +#### 3. Verify Signatures Locally + +**Always verify signatures before uploading** to catch any issues: + +```bash +# Verify each signature +for file in volta-2.0.3-*.tar.gz; do + echo "Verifying $file..." + if minisign -Vm "$file" -p volta-release.pub; then + echo " $file verified successfully" + else + echo " FAILED: $file" + exit 1 + fi +done +``` + +#### 4. Upload to GitHub Releases + +1. Go to https://github.com/volta-cli/volta/releases +2. Click "Draft a new release" +3. Tag: `v2.0.3` +4. Upload **BOTH** the tarballs and their signatures: + - `volta-2.0.3-macos.tar.gz` + - `volta-2.0.3-macos.tar.gz.minisig` + - `volta-2.0.3-linux.tar.gz` + - `volta-2.0.3-linux.tar.gz.minisig` + - `volta-2.0.3-linux-arm.tar.gz` + - `volta-2.0.3-linux-arm.tar.gz.minisig` + +#### 5. Verify Public Accessibility + +Before publishing the release, verify signatures are downloadable: + +```bash +# Test each signature URL (replace v2.0.3 with your version) +curl -I https://github.com/volta-cli/volta/releases/download/v2.0.3/volta-2.0.3-macos.tar.gz.minisig +curl -I https://github.com/volta-cli/volta/releases/download/v2.0.3/volta-2.0.3-linux.tar.gz.minisig +curl -I https://github.com/volta-cli/volta/releases/download/v2.0.3/volta-2.0.3-linux-arm.tar.gz.minisig + +# All should return: HTTP/2 200 +``` + +#### 6. Publish the Release + +Once verified, publish the GitHub release. Users will now get automatic signature verification! + +--- + +### Key Management + +#### Storing the Private Key + +**Option 1: Local Secure Storage (Recommended for individual maintainers)** + +- Store in an encrypted drive/partition +- The key is password-protected by default (minisign feature) +- Keep offline backups in secure locations + +**Option 2: CI/CD Secrets (Recommended for automated releases)** + +- Store the private key in GitHub Secrets +- Automate signing in the release workflow +- Limit access to authorized maintainers only + +#### Key Rotation + +If the private key is compromised or as part of regular security hygiene: + +1. Generate new keys (follow "One-Time Setup" above) +2. Update `Volta_PUBLIC_KEY` in `dev/unix/volta-install.sh` +3. Create a new release with the updated install script +4. Announce the key rotation to users +5. Consider re-signing recent releases with the new key + +#### Public Key Location + +- **Embedded**: The public key is hardcoded in `dev/unix/volta-install.sh` as `Volta_PUBLIC_KEY` +- **Repository**: Also stored as `volta-release.pub` in the repo for reference +- **Documentation**: Listed in README.md security section + +--- + +### Troubleshooting + +#### "Signature verification failed" during local testing + +```bash +# Make sure you're using the correct public key +cat volta-release.pub + +# Verify the key in install.sh matches +grep Volta_PUBLIC_KEY dev/unix/volta-install.sh +``` + +#### "Permission denied" when signing + +```bash +# Make sure the private key has correct permissions +chmod 600 volta-release.key +``` + +#### Forgot the private key password + +Unfortunately, there's no way to recover it. You'll need to: + +1. Generate new keys +2. Update the public key in `install.sh` +3. Re-sign all future releases with the new key + +--- + +### Quick Reference + +```bash +# Generate keys (one-time) +minisign -G -p volta-release.pub -s volta-release.key + +# Sign a release +minisign -Sm volta-VERSION-PLATFORM.tar.gz + +# Verify a signature +minisign -Vm volta-VERSION-PLATFORM.tar.gz -p volta-release.pub + +# Verify all signatures +for f in volta-*.tar.gz; do minisign -Vm "$f" -p volta-release.pub || echo "Failed: $f"; done +``` + +--- + +### Security Notes + +- The private key password adds an extra layer of security +- Signatures prove authenticity (from Volta maintainers) and integrity (not modified) +- Users' install script automatically verifies signatures before installation +- If verification fails, installation is aborted for security +- Old releases (< v2.0.3) don't have signatures and skip verification + +--- + +### Questions? + +If you have questions about the signing process, please reach out to the core maintainers or open a discussion in the repository. diff --git a/dev/unix/volta-install.sh b/dev/unix/volta-install.sh index fddaaa616..2695ba139 100755 --- a/dev/unix/volta-install.sh +++ b/dev/unix/volta-install.sh @@ -5,6 +5,8 @@ # has, fetch and install the appropriate build of Volta, and modify the user's # profile. +readonly Volta_PUBLIC_KEY="RWTf5is8+3rdT2AIPYiQqEkdqqbIJGnRZzoq6ztC6mQaoTDIxfiSSozL" + # NOTE: to use an internal company repo, change how this determines the latest version get_latest_release() { curl --silent "https://volta.sh/latest-version" @@ -22,8 +24,20 @@ download_release_from_repo() { local filename="volta-$version-$os_info.tar.gz" local download_file="$tmpdir/$filename" local archive_url="$(release_url)/download/v$version/$filename" + local signature_url="${archive_url}.minisig" + local signature_file="${download_file}.minisig" + + curl --progress-bar --show-error --location --fail "$archive_url" --output "$download_file" --write-out "$download_file" || return 1 + + #download the signature file + info 'Fetching' "Signature file" + if ! curl --silent --show-error --location --fail "$signature_url" --output "$signature_file"; then + error "Could not download signature file from $signature_url" + eprintf "Signature verification cannot proceed without the signature file." + return 1 + fi - curl --progress-bar --show-error --location --fail "$archive_url" --output "$download_file" --write-out "$download_file" + echo "$download_file" } usage() { @@ -70,6 +84,85 @@ bold() { command printf '\033[1m%s\033[0m' "$1" } +# check if Minisign is available +check_minisign() { + if command -v minisign >/dev/null 2>&1; then + return 0 + fi + + warning "minisign not found. Attempting to install" + + if [[ "$OSTYPE" == "darwin"* ]]; then + #macOS + if command -v brew >/dev/null 2>&1; then + brew install minisign + else + error "Homebrew not found. Please install minsign manually" + eprintf " Visit: https://jedisct1.github.io/minisign/" + return 1 + fi + elif [[ "$OSTYPE" == "linux"* ]]; then + #Linux + if command -v apt >/dev/null 2>&1; then + sudo apt update && sudo apt install -y minisign + elif command -v dnf >/dev/null 2>&1; then + sudo dnf install -y minisign + elif command -v yum >/dev/null 2>&1; then + sudo yum install -y minisign + else + error "Unsupported package manager. Please install minisign manually." + eprintf " Visit: https://jedisct1.github.io/minisign/" + return 1 + fi + else + error "Unsupported OS ($OSTYPE). Please install minisign manually." + eprintf " Visit: https://jedisct1.github.io/minisign/" + return 1 + fi + + #Verify Installation succeeded + if ! command -v minisign >/dev/null 2>&1; then + error "Failed to install minisign. Please install it manually." + return 1 + fi + + info "Minisign successfully Installed" + return 0 + +} + +#Verify the signature of a download release +verify_release_signature() { + local archive_file="$1" + local signature_file="${archive_file}.minisig" + + info 'Verifying' "release signature" + + #check if signature file exists + if [ ! -f "$signature_file" ]; then + error "Signature file not found: $signature_file" + eprintf "Cannot verify the release without a signature file." + return 1 + fi + + #verify using minisign + if minisign -Vm "$archive_file" -P "$Volta_PUBLIC_KEY" 2>/dev/null; then + info 'Verified' "release signature successfully" + return 0 + else + error "Signature verification failed for $(basename "$archive_file")" + eprintf "" + eprintf "This could mean:" + eprintf " . The file was corrupted during download" + eprintf " . The file has been tampered with" + eprintf " . You are using an outdated installer script" + eprintf "" + eprintf "Please try again or report this issue at:" + eprintf " https://github.com/volta-cli/volta/issues" + return 1 + fi +} + # check for issue with VOLTA_HOME # if it is set, and exists, but is not a directory, the install will fail volta_home_is_ok() { @@ -259,6 +352,28 @@ install_release() { info 'Checking' "for existing Volta installation" if upgrade_is_ok "$version" "$install_dir" "$is_dev_install" then + #Only verify signatures for versions that have them + ##Supposing signatures start from v2.0.3 (can be adjusted) + local should_verify=false + + #parse version number + local version_number="${version#v}" + + #check if version >= 2.0.3 + if printf '%s\n2.0.3\n' "$version_number" | sort -V -C 2>/dev/null; then + should_verify=true + fi + if [ "$should_verify" = true ]; then + + #ensure mnisign is available + if ! check_minisign; then + return 1 + fi + else + warning "Version $version predates signature verification" + info 'Skipping' "signature verification for older releases." + fi + download_archive="$(download_release "$version"; exit "$?")" exit_status="$?" if [ "$exit_status" != 0 ] @@ -266,7 +381,14 @@ install_release() { error "Could not download Volta version '$version'. See $(release_url) for a list of available releases" return "$exit_status" fi - + if [ "$should_verify" = true]; then + #verify signature before installing + if ! verify_release_signature "$download_archive"; then + #remove the download file if verification fails + rm -f "$download_archive" "${download_archive}.minisig" + return 1 + fi + fi install_from_file "$download_archive" "$install_dir" else # existing legacy install, or upgrade problem