diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index d0720e2..2e8cdf8 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,13 +1,29 @@ #!/bin/sh -set -e +set -euo pipefail + +if ! command -v curl >/dev/null 2>&1; then + echo "Error: curl is required but was not found in PATH." >&2 + exit 1 +fi + +if ! command -v jq >/dev/null 2>&1; then + echo "Error: jq is required but was not found in PATH." >&2 + exit 1 +fi + +# Allow operators to override the genesis URL and expected checksum. +GENESIS_URL=${GENESIS_URL:-"https://raw.githubusercontent.com/bettergovph/govchain/refs/heads/main/genesis.json"} +GENESIS_SHA256=${GENESIS_SHA256:-""} # Exit if the MONIKER environment variable is not set. -if [ -z "$MONIKER" ]; then +if [ -z "${MONIKER:-}" ]; then echo "Error: The MONIKER environment variable is not set." echo "Please set it using -e MONIKER='your-moniker' with docker run, or in your docker-compose.yml." exit 1 fi +EXTERNAL_IP=${EXTERNAL_IP:-""} + # Ensure the config directory exists mkdir -p "/home/nonroot/.govchain/config" chown nonroot:nonroot "/home/nonroot/.govchain/config" @@ -16,11 +32,30 @@ chown nonroot:nonroot "/home/nonroot/.govchain/config" if [ ! -f "/home/nonroot/.govchain/config/genesis.json" ]; then # Define the path for the actual genesis.json ACTUAL_GENESIS_PATH="/home/nonroot/.govchain/config/genesis.json" - TEMP_GENESIS_PATH="/tmp/genesis.json" # Use a temporary path for download + TEMP_GENESIS_PATH=$(mktemp /tmp/genesis.XXXXXX) + + cleanup() { + [ -f "$TEMP_GENESIS_PATH" ] && rm -f "$TEMP_GENESIS_PATH" + } + trap cleanup EXIT # Download the actual genesis.json to a temporary location - echo "📥 Downloading actual genesis file to extract chain-id..." - env 'HOME=/home/nonroot' curl -sL "https://raw.githubusercontent.com/bettergovph/govchain/refs/heads/main/genesis.json" -o "$TEMP_GENESIS_PATH" + echo "📥 Downloading actual genesis file from $GENESIS_URL to extract chain-id..." + if ! env 'HOME=/home/nonroot' curl -fSL --retry 3 --retry-delay 2 "$GENESIS_URL" -o "$TEMP_GENESIS_PATH"; then + echo "Error: failed to download genesis file from $GENESIS_URL" >&2 + exit 1 + fi + + if [ -n "$GENESIS_SHA256" ]; then + if ! command -v sha256sum >/dev/null 2>&1; then + echo "Error: sha256sum is required when GENESIS_SHA256 is provided." >&2 + exit 1 + fi + echo "🔐 Verifying genesis checksum..." + echo "$GENESIS_SHA256 $TEMP_GENESIS_PATH" | sha256sum -c - + else + echo "⚠️ Warning: GENESIS_SHA256 not provided. Skipping checksum verification." + fi # Extract chain-id from the downloaded genesis.json CHAIN_ID=$(jq -r '.chain_id' "$TEMP_GENESIS_PATH") @@ -37,6 +72,7 @@ if [ ! -f "/home/nonroot/.govchain/config/genesis.json" ]; then # Replace the dummy genesis.json created by init with the actual downloaded one echo "Replacing dummy genesis.json with the actual genesis file..." mv "$TEMP_GENESIS_PATH" "$ACTUAL_GENESIS_PATH" + trap - EXIT # Set the minimum gas price in app.toml env 'HOME=/home/nonroot' sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0stake"/' "/home/nonroot/.govchain/config/app.toml" diff --git a/docs/security_assessment.md b/docs/security_assessment.md new file mode 100644 index 0000000..4084904 --- /dev/null +++ b/docs/security_assessment.md @@ -0,0 +1,27 @@ +# Security Assessment Summary + +This document captures the primary security weaknesses identified during the review and the remediation steps implemented in this change-set. A hands-on proof-of-concept that recreates the original vulnerability and demonstrates the hardened mitigation is available in [`docs/poc/genesis_poisoning.md`](poc/genesis_poisoning.md). + +## 1. Insecure Genesis File Bootstrap + +### Issue +- Both the Docker entrypoint (`docker-entrypoint.sh`) and the volunteer helper script (`scripts/join-as-volunteer.sh`) downloaded the network genesis file directly from the `main` branch without integrity checks. +- The downloads used silent `curl` invocations that ignored HTTP errors and offered no tamper detection. Any man-in-the-middle attack, compromised CDN, or unexpected upstream change could silently deliver a malicious genesis file. A poisoned genesis file allows an attacker to modify the validator set or bootstrap nodes onto a hostile fork. + +### Remediation +- Added strict download options (`curl -fSL --retry 3 --retry-delay 2`) so HTTP failures surface immediately. +- Introduced optional `GENESIS_URL` and `GENESIS_SHA256` overrides and checksum verification in both scripts. +- Added dependency checks for `curl`, `jq`, and `sha256sum` to fail fast when prerequisites are missing. +- Hardened scripts with `set -euo pipefail`, temporary-file handling, and cleanup traps to avoid partial writes. + +### Operator Action Items +- Publish an official `GENESIS_SHA256` alongside every release and configure the environment variables (or script arguments) accordingly. +- When hosting alternative genesis mirrors, ensure the checksum matches the canonical release artifact. +- Consider pinning `GENESIS_URL` to an immutable tag or release asset rather than a moving branch reference. + +## 2. General Operational Guidance + +- Monitor CI/CD pipelines to ensure scripts remain executable and dependencies such as `jq` and `sha256sum` stay available in container images. +- Document the expected checksum management process in validator onboarding materials so volunteers consistently verify genesis integrity. + +The combination of these mitigations closes the immediate loophole around silent genesis tampering and establishes a clearer operational posture for secure node provisioning. diff --git a/scripts/join-as-volunteer.sh b/scripts/join-as-volunteer.sh index 5eddc48..f6e6542 100755 --- a/scripts/join-as-volunteer.sh +++ b/scripts/join-as-volunteer.sh @@ -3,7 +3,12 @@ # govchain Volunteer Node Setup # Allows volunteers to join the network as validators without tokens -set -e +set -euo pipefail + +if ! command -v curl >/dev/null 2>&1; then + echo "❌ Error: curl is required but was not found in PATH." >&2 + exit 1 +fi echo "================================" echo "govchain Volunteer Node Setup" @@ -11,16 +16,17 @@ echo "================================" echo "" if [ "$#" -lt 2 ]; then - echo "Usage: $0 " + echo "Usage: $0 [genesis-sha256]" echo "" echo "Example:" - echo " $0 volunteer-node-1 https://raw.githubusercontent.com/org/govchain/main/genesis.json" + echo " $0 volunteer-node-1 https://raw.githubusercontent.com/org/govchain/main/genesis.json " echo "" exit 1 fi NODE_NAME="$1" GENESIS_URL="$2" +GENESIS_SHA256="${3:-}" echo "📝 Node Name: $NODE_NAME" echo "🌐 Genesis URL: $GENESIS_URL" @@ -48,7 +54,31 @@ echo "🔧 Initializing volunteer node..." # Download genesis file echo "📥 Downloading genesis file..." -curl -s "$GENESIS_URL" -o "$HOME/.govchain/config/genesis.json" +TEMP_GENESIS=$(mktemp /tmp/genesis.XXXXXX) + +cleanup() { + [ -f "$TEMP_GENESIS" ] && rm -f "$TEMP_GENESIS" +} +trap cleanup EXIT + +if ! curl -fSL --retry 3 --retry-delay 2 "$GENESIS_URL" -o "$TEMP_GENESIS"; then + echo "❌ Error: failed to download genesis from $GENESIS_URL" >&2 + exit 1 +fi + +if [ -n "$GENESIS_SHA256" ]; then + if ! command -v sha256sum >/dev/null 2>&1; then + echo "❌ Error: sha256sum is required when providing a genesis checksum." >&2 + exit 1 + fi + echo "🔐 Verifying genesis checksum..." + echo "$GENESIS_SHA256 $TEMP_GENESIS" | sha256sum -c - +else + echo "⚠️ Warning: No genesis checksum provided. Skipping integrity verification." +fi + +mv "$TEMP_GENESIS" "$HOME/.govchain/config/genesis.json" +trap - EXIT # Create validator key echo "🔑 Creating validator key..."