diff --git a/README.md b/README.md index d6e59488..a6b24417 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,32 @@ A network firewall for agentic workflows with domain whitelisting. This tool pro ### Installation +**Recommended: One-line installer with SHA verification** + +```bash +curl -sSL https://raw.githubusercontent.com/githubnext/gh-aw-firewall/main/install.sh | sudo bash +``` + +This installer automatically: +- Downloads the latest release binary +- Verifies SHA256 checksum to detect corruption or tampering +- Validates the file is a valid Linux executable +- Protects against 404 error pages being saved as binaries +- Installs to `/usr/local/bin/awf` + +**Alternative: Manual installation** + ```bash # Download the latest release binary -curl -L https://github.com/githubnext/gh-aw-firewall/releases/latest/download/awf-linux-x64 -o awf +curl -fL https://github.com/githubnext/gh-aw-firewall/releases/latest/download/awf-linux-x64 -o awf + +# Download checksums for verification +curl -fL https://github.com/githubnext/gh-aw-firewall/releases/latest/download/checksums.txt -o checksums.txt + +# Verify SHA256 checksum +sha256sum -c checksums.txt --ignore-missing + +# Install chmod +x awf sudo mv awf /usr/local/bin/ @@ -29,8 +52,6 @@ sudo mv awf /usr/local/bin/ sudo awf --help ``` -**Note:** Verify checksums after download by downloading `checksums.txt` from the release page. - **Docker Image Verification:** All published container images are cryptographically signed with cosign. See [docs/image-verification.md](docs/image-verification.md) for verification instructions. ### Basic Usage diff --git a/docs-site/src/content/docs/index.md b/docs-site/src/content/docs/index.md index 4971e585..0ed7684b 100644 --- a/docs-site/src/content/docs/index.md +++ b/docs-site/src/content/docs/index.md @@ -41,8 +41,13 @@ When AI agents like GitHub Copilot CLI run with access to tools and MCP servers, Download the latest release binary: ```bash -# Linux (x64) -curl -L https://github.com/githubnext/gh-aw-firewall/releases/latest/download/awf-linux-x64 -o awf +# One-line installer with SHA verification (recommended) +curl -sSL https://raw.githubusercontent.com/githubnext/gh-aw-firewall/main/install.sh | sudo bash + +# Or manual installation +curl -fL https://github.com/githubnext/gh-aw-firewall/releases/latest/download/awf-linux-x64 -o awf +curl -fL https://github.com/githubnext/gh-aw-firewall/releases/latest/download/checksums.txt -o checksums.txt +sha256sum -c checksums.txt --ignore-missing chmod +x awf sudo mv awf /usr/local/bin/ @@ -50,8 +55,8 @@ sudo mv awf /usr/local/bin/ sudo awf --version ``` -:::note -Verify checksums after download by checking `checksums.txt` from the release page. +:::tip[Automatic SHA Verification] +The one-line installer automatically verifies the SHA256 checksum to protect against corrupted or tampered downloads. ::: ### Your First Command diff --git a/docs/RELEASE_TEMPLATE.md b/docs/RELEASE_TEMPLATE.md index 044e733f..beef01e1 100644 --- a/docs/RELEASE_TEMPLATE.md +++ b/docs/RELEASE_TEMPLATE.md @@ -29,11 +29,31 @@ Everything below the `---` separator becomes the release notes. ## Installation -### Binary Installation (Recommended) +### One-Line Installer (Recommended) + +**Linux (x64) with automatic SHA verification:** +```bash +curl -sSL https://raw.githubusercontent.com/{{REPOSITORY}}/main/install.sh | sudo bash +``` + +This installer: +- Downloads the latest release binary +- Verifies SHA256 checksum against `checksums.txt` +- Validates the file is a valid ELF executable +- Installs to `/usr/local/bin/awf` + +### Manual Binary Installation (Alternative) **Linux (x64):** ```bash -curl -L https://github.com/{{REPOSITORY}}/releases/download/{{VERSION}}/awf-linux-x64 -o awf +# Download binary and checksums +curl -fL https://github.com/{{REPOSITORY}}/releases/download/{{VERSION}}/awf-linux-x64 -o awf +curl -fL https://github.com/{{REPOSITORY}}/releases/download/{{VERSION}}/checksums.txt -o checksums.txt + +# Verify checksum +sha256sum -c checksums.txt --ignore-missing + +# Install chmod +x awf sudo mv awf /usr/local/bin/ ``` @@ -45,19 +65,6 @@ sudo mv awf /usr/local/bin/ npm install -g https://github.com/{{REPOSITORY}}/releases/download/{{VERSION}}/awf.tgz ``` -### Requirements - -- Docker and Docker Compose must be installed -- For iptables manipulation, run with sudo: `sudo awf ...` -- Container images will be pulled automatically from GHCR on first run - -## Verification - -Verify checksums after download: -```bash -sha256sum -c checksums.txt -``` - ## Quick Start ```bash diff --git a/install.sh b/install.sh new file mode 100755 index 00000000..e622dd25 --- /dev/null +++ b/install.sh @@ -0,0 +1,247 @@ +#!/bin/bash +set -e + +# Install script for awf (Agentic Workflow Firewall) +# +# This script downloads, verifies, and installs the awf binary with SHA256 validation +# to protect against corrupted or tampered downloads. +# +# Usage: +# curl -sSL https://raw.githubusercontent.com/githubnext/gh-aw-firewall/main/install.sh | sudo bash +# +# Security features: +# - Uses curl -f to fail on HTTP errors (404, 403, etc.) +# - Verifies SHA256 checksum from official checksums.txt +# - Validates downloaded file is a valid ELF executable +# - Detects HTML error pages that may slip through +# +# Requirements: +# - curl +# - sha256sum +# - file +# - sudo/root access +# +# Repository: https://github.com/githubnext/gh-aw-firewall +# Issue #107: https://github.com/githubnext/gh-aw-firewall/issues/107 + +REPO="githubnext/gh-aw-firewall" +BINARY_NAME="awf-linux-x64" +INSTALL_DIR="/usr/local/bin" +INSTALL_NAME="awf" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Helper functions +info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +# Check if running as root +check_sudo() { + if [ "$EUID" -ne 0 ]; then + error "This script must be run with sudo or as root" + exit 1 + fi +} + +# Check required commands +check_requirements() { + local missing=() + + for cmd in curl sha256sum file; do + if ! command -v "$cmd" &> /dev/null; then + missing+=("$cmd") + fi + done + + if [ ${#missing[@]} -ne 0 ]; then + error "Missing required commands: ${missing[*]}" + error "Please install them and try again" + exit 1 + fi +} + +# Check OS and architecture +check_platform() { + local os arch + os=$(uname -s) + arch=$(uname -m) + + if [ "$os" != "Linux" ]; then + error "Unsupported OS: $os (this installer supports Linux only)" + exit 1 + fi + + case "$arch" in + x86_64|amd64) + ;; + *) + error "Unsupported architecture: $arch (supported: x86_64)" + exit 1 + ;; + esac +} + +# Get latest release version +get_latest_version() { + info "Fetching latest release version..." + + # Try GitHub API with -f to fail on HTTP errors + VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" 2>/dev/null | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + + if [ -z "$VERSION" ]; then + error "Failed to fetch latest version from GitHub API" + error "Please check your internet connection and try again" + exit 1 + fi + + info "Latest version: $VERSION" +} + +# Download file +download_file() { + local url="$1" + local output="$2" + + info "Downloading from $url..." + + # Use -f to fail on HTTP errors (like 404) + if ! curl -fsSL "$url" -o "$output"; then + error "Failed to download $url" + error "Please check if the release exists and try again" + exit 1 + fi + + # Check if file is not empty + if [ ! -s "$output" ]; then + error "Downloaded file is empty" + rm -f "$output" + exit 1 + fi + + # Check if file is HTML (common for 404 pages) + if file "$output" | grep -q "HTML"; then + error "Downloaded file appears to be an HTML page (possibly 404)" + error "Please check if the release exists: https://github.com/${REPO}/releases" + rm -f "$output" + exit 1 + fi +} + +# Verify checksum +verify_checksum() { + local file="$1" + local checksums_file="$2" + + info "Verifying SHA256 checksum..." + + # Extract the checksum for our binary from checksums.txt + # Format: "checksum filename" (two spaces) - use exact filename match at end of line + local expected_sum + expected_sum=$(awk -v fname="$BINARY_NAME" '$2 == fname {print $1; exit}' "$checksums_file") + + if [ -z "$expected_sum" ]; then + error "Could not find checksum for $BINARY_NAME in checksums.txt" + exit 1 + fi + + # Validate checksum format (64 hex characters, case-insensitive) + if ! echo "$expected_sum" | grep -qE '^[a-fA-F0-9]{64}$'; then + error "Invalid checksum format: $expected_sum" + exit 1 + fi + + # Normalize checksum case + expected_sum=$(echo "$expected_sum" | tr 'A-F' 'a-f') + + # Calculate actual checksum + local actual_sum + actual_sum=$(sha256sum "$file" | awk '{print $1}' | tr 'A-F' 'a-f') + + if [ "$expected_sum" != "$actual_sum" ]; then + error "Checksum verification failed!" + error "Expected: $expected_sum" + error "Got: $actual_sum" + error "The downloaded file may be corrupted or tampered with" + exit 1 + fi + + info "Checksum verification passed ✓" +} + +# Main installation function +main() { + info "Starting awf installation..." + + # Check requirements + check_sudo + check_requirements + check_platform + + # Get version + get_latest_version + + # Create temp directory with prefix for identification + # mktemp creates secure temporary directories with proper permissions (0700) + TEMP_DIR=$(mktemp -d -t awf-install.XXXXXX) + + # Validate temp directory was created + if [ -z "$TEMP_DIR" ] || [ ! -d "$TEMP_DIR" ]; then + error "Failed to create temporary directory" + exit 1 + fi + + # Set up cleanup trap (mktemp already ensures secure location) + trap 'rm -rf "$TEMP_DIR"' EXIT + + # Download URLs + BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}" + BINARY_URL="${BASE_URL}/${BINARY_NAME}" + CHECKSUMS_URL="${BASE_URL}/checksums.txt" + + # Download binary and checksums + download_file "$BINARY_URL" "$TEMP_DIR/$BINARY_NAME" + download_file "$CHECKSUMS_URL" "$TEMP_DIR/checksums.txt" + + # Verify checksum + verify_checksum "$TEMP_DIR/$BINARY_NAME" "$TEMP_DIR/checksums.txt" + + # Make binary executable + chmod +x "$TEMP_DIR/$BINARY_NAME" + + # Test if it's a valid ELF executable + if ! file "$TEMP_DIR/$BINARY_NAME" | grep -q "ELF.*executable"; then + error "Downloaded file is not a valid Linux executable" + exit 1 + fi + + # Install binary + info "Installing to $INSTALL_DIR/$INSTALL_NAME..." + mv "$TEMP_DIR/$BINARY_NAME" "$INSTALL_DIR/$INSTALL_NAME" + + # Verify installation + if [ -x "$INSTALL_DIR/$INSTALL_NAME" ]; then + info "Installation successful! ✓" + info "" + info "Run 'awf --help' to get started" + info "Note: awf requires Docker to be installed and running" + else + error "Installation failed - binary not found at $INSTALL_DIR/$INSTALL_NAME" + exit 1 + fi +} + +# Run main function +main