diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4be59c1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,55 @@ +# Ignore build artifacts +target/ +dist/ + +# Ignore IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Ignore OS-specific files +.DS_Store +Thumbs.db + +# Ignore git files +.git/ +.gitignore + +# Note: Cargo.lock is included for reproducible builds + +# Ignore temporary files +*.tmp +*.temp + +# Ignore documentation build artifacts +book/ + +# Ignore CI/CD artifacts +.github/ + +# Ignore test artifacts +*.profraw + +# Ignore local configuration +.env +.env.local + +# Ignore cargo registry cache +.cargo/registry/ +.cargo/git/ + +# Ignore benchmark results +criterion/ + +# Ignore log files +*.log + +# Ignore backup files +*.bak +*.backup + +# Ignore package files +*.tar.gz +*.zip \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2507dbc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,59 @@ +# Multi-stage build for safe-hash-rs +# Use the official Rust image as the build environment +FROM rust:1.83-slim-bookworm AS builder + +# Set the working directory +WORKDIR /usr/src/app + +# Install required build dependencies +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Copy the workspace files +COPY Cargo.toml Cargo.lock ./ +COPY rust-toolchain.toml ./ +COPY dist-workspace.toml ./ +COPY rustfmt.toml ./ + +# Copy the source code +COPY crates/ ./crates/ + +# Build the application in release mode +RUN cargo build --release --bin safe-hash + +# Runtime stage - use a minimal base image +FROM debian:bookworm-slim AS runtime + +# Install runtime dependencies +RUN apt-get update && apt-get install -y \ + ca-certificates \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* \ + && update-ca-certificates + +# Create a non-root user for security +RUN useradd --create-home --shell /bin/bash --user-group --uid 1000 safeuser + +# Copy the binary from the builder stage +COPY --from=builder /usr/src/app/target/release/safe-hash /usr/local/bin/safe-hash + +# Ensure the binary is executable +RUN chmod +x /usr/local/bin/safe-hash + +# Create a directory for input files +RUN mkdir -p /app/input && chown -R safeuser:safeuser /app + +# Switch to non-root user +USER safeuser + +# Set the working directory +WORKDIR /app + +# Set the entrypoint +ENTRYPOINT ["safe-hash"] + +# Default command (show help) +CMD ["--help"] \ No newline at end of file diff --git a/README.md b/README.md index a9ada86..485ab60 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ A tool to verify Safe{Wallet} transaction data and EIP-712 messages before signi # Security practices for using `safe-hash` 1. **Use a separate device for running this script**, totally different from what you use to send/sign your Safe{Wallet} transactions. This is to add some resilience in case your main device is compromised. - 1. For extra security, run this inside a docker container or a secure operating system like Tails or Qubes OS. + 1. For extra security, run this inside a docker container or a secure operating system like Tails or Qubes OS. **This project includes Podman/Docker support for enhanced security isolation.** 2. **Manually verify what you expect, and then compare to what you get**. This tool shows you what you get from you wallet based on your input, you should compare it to what you expect to get 3. **Understand the parameters you're signing**. This tool is useless if you don't know how to read the output! To get familiar, you can play the [wise-signer](https://wise-signer.cyfrin.io/) game to learn how calldata should be interpreted (with this tool too). 4. **Read/watch these resources**: @@ -68,6 +68,78 @@ brew install cyfrin/tap/safe-hash npm install -g @cyfrin/safe-hash ``` +### Podman/Docker + +For enhanced security, you can run `safe-hash` in a container using Podman or Docker. This provides additional isolation and is especially recommended when running on potentially compromised systems. + +#### Quick Start with Podman + +1. Build the container image: +```bash +./scripts/podman-build.sh +``` + +2. Run safe-hash in the container: +```bash +# Show help (note: use 'help' subcommand, not --help flag) +./scripts/safe-hash-rs help + +# Verify a transaction +./scripts/safe-hash-rs tx --chain ethereum --nonce 63 --safe-address 0x1c694Fc3006D81ff4a56F97E1b99529066a23725 --safe-version 1.4.1 + +# Mount a directory to access local files +./scripts/safe-hash-rs -v ./test msg --chain sepolia --safe-address 0x657ff0D4eC65D82b2bC1247b0a558bcd2f80A0f1 --input-file /app/input/test_message.txt --safe-version 1.4.1 +``` + +#### Manual Podman Commands + +If you prefer to run commands manually: + +```bash +# Build the image +podman build -t localhost/safe-hash:latest . + +# Run with basic security +podman run --rm --cap-drop ALL --read-only --tmpfs /tmp --network host localhost/safe-hash:latest --help + +# Mount files for processing +podman run --rm --cap-drop ALL --read-only --tmpfs /tmp --network host -v ./test:/app/input:ro,Z localhost/safe-hash:latest msg --chain sepolia --safe-address 0x657ff0D4eC65D82b2bC1247b0a558bcd2f80A0f1 --input-file /app/input/test_message.txt --safe-version 1.4.1 +``` + +#### Docker Compatibility + +The same Dockerfile works with Docker: + +```bash +# Build with Docker +docker build -t safe-hash:latest . + +# Run with Docker (basic help) +docker run --rm --cap-drop ALL --read-only --tmpfs /tmp --network host safe-hash:latest --help + +# Mount files for processing with Docker +docker run --rm --cap-drop ALL --read-only --tmpfs /tmp --network host -v ./test:/app/input:ro safe-hash:latest msg --chain sepolia --safe-address 0x657ff0D4eC65D82b2bC1247b0a558bcd2f80A0f1 --input-file /app/input/test_message.txt --safe-version 1.4.1 + +The container setup includes several security enhancements: +- **Minimal base image**: Uses Debian slim for reduced attack surface +- **Non-root user**: Runs as unprivileged user inside container +- **Read-only filesystem**: Container filesystem is mounted read-only +- **Dropped capabilities**: All Linux capabilities are dropped +- **Network isolation**: Can be run with restricted networking +- **Resource limits**: Easily configurable resource constraints + +#### Docker Compatibility + +The same Dockerfile works with Docker: + +```bash +# Build with Docker +docker build -t safe-hash:latest . + +# Run with Docker +docker run --rm --cap-drop ALL --read-only --tmpfs /tmp --network host safe-hash:latest --help +``` + # Usage This tool helps protect against possible phishing or compromised UI attacks by allowing local verification of transaction and message hashes before signing. @@ -176,10 +248,15 @@ Verify the above value as the Safe Tx Hash when signing the message from the led ## Trust Assumptions * You trust this codebase * You trust the Safe Wallet contracts -* You trust your OS +* You trust your OS (or container runtime when using Podman/Docker) * You trust the [transaction service API](https://docs.safe.global/core-api/transaction-service-overview) * Unless you use the `--offline` flag. +When using containers: +* You trust the container runtime (Podman/Docker) +* You trust the base container images (Rust and Debian) +* The container provides additional isolation but doesn't eliminate all trust assumptions + # Community-Maintained User Interface Implementations > [!IMPORTANT] diff --git a/docs/PODMAN.md b/docs/PODMAN.md new file mode 100644 index 0000000..864c290 --- /dev/null +++ b/docs/PODMAN.md @@ -0,0 +1,227 @@ +# Podman Setup for safe-hash-rs + +This document provides comprehensive instructions for running safe-hash-rs using Podman containers for enhanced security. + +## Prerequisites + +- Podman installed on your system +- Basic understanding of container concepts + +## Quick Start + +1. **Build the container:** + ```bash + ./scripts/podman-build.sh + ``` + +2. **Run safe-hash:** + ```bash + ./scripts/safe-hash-rs help + ``` + +## Detailed Usage + +### Building the Container + +The build script creates a multi-stage container with: +- Rust build environment for compilation +- Minimal Debian runtime environment +- Security hardening (non-root user, minimal dependencies) + +```bash +# Build with automatic version detection +./scripts/podman-build.sh + +# Or build manually +podman build -t localhost/safe-hash:latest . +``` + +### Running Commands + +#### Basic Usage + +```bash +# Show help (use 'help' subcommand to see safe-hash help) +./scripts/safe-hash-rs help + +# Verify a transaction +./scripts/safe-hash-rs tx \ + --chain ethereum \ + --nonce 63 \ + --safe-address 0x1c694Fc3006D81ff4a56F97E1b99529066a23725 \ + --safe-version 1.4.1 +``` + +#### Using Local Files + +Mount a directory to access local files: + +```bash +./scripts/safe-hash-rs -v ./test msg \ + --chain sepolia \ + --safe-address 0x657ff0D4eC65D82b2bC1247b0a558bcd2f80A0f1 \ + --input-file /app/input/test_message.txt \ + --safe-version 1.4.1 +``` + +#### Interactive Mode + +For debugging or exploring: + +```bash +./scripts/safe-hash-rs -i +``` + +### Manual Podman Commands + +For advanced users who prefer direct control: + +```bash +# Basic run +podman run --rm \ + --cap-drop ALL \ + --read-only \ + --tmpfs /tmp \ + --network host \ + localhost/safe-hash:latest --help + +# With volume mount +podman run --rm \ + --cap-drop ALL \ + --read-only \ + --tmpfs /tmp \ + --network host \ + -v ./test:/app/input:ro,Z \ + localhost/safe-hash:latest msg \ + --chain sepolia \ + --safe-address 0x657ff0D4eC65D82b2bC1247b0a558bcd2f80A0f1 \ + --input-file /app/input/test_message.txt \ + --safe-version 1.4.1 +``` + +## Security Features + +### Container Hardening + +- **Non-root execution**: Container runs as unprivileged user (uid 1000) +- **Read-only filesystem**: Container filesystem mounted read-only +- **Capability dropping**: All Linux capabilities dropped (`--cap-drop ALL`) +- **Minimal base**: Uses Debian slim for reduced attack surface +- **Temporary filesystem**: `/tmp` mounted as tmpfs for runtime files + +### Network Isolation + +By default, the container uses host networking for API access. For enhanced security: + +```bash +# No network access (offline mode only) +podman run --rm --network none localhost/safe-hash:latest tx --offline [OPTIONS] + +# Custom network +podman network create safe-hash-net +podman run --rm --network safe-hash-net localhost/safe-hash:latest [OPTIONS] +``` + +### Resource Limits + +Add resource constraints for additional security: + +```bash +podman run --rm \ + --memory 512m \ + --cpus 1 \ + --pids-limit 100 \ + localhost/safe-hash:latest [OPTIONS] +``` + +## Troubleshooting + +### Common Issues + +1. **Permission denied on volume mounts:** + ```bash + # Ensure the directory is readable + chmod +r ./test/* + + # Or use SELinux relabeling + podman run --rm -v ./test:/app/input:ro,Z localhost/safe-hash:latest [OPTIONS] + ``` + +2. **Network connectivity issues:** + ```bash + # Use host networking + podman run --rm --network host localhost/safe-hash:latest [OPTIONS] + ``` + +3. **Image not found:** + ```bash + # Rebuild the image + ./scripts/podman-build.sh + ``` + +### Debugging + +Run in interactive mode to debug issues: + +```bash +./scripts/safe-hash-rs -i +# Inside container: +safe-hash --help +``` + +## Best Practices + +1. **Always use the latest image**: Rebuild regularly for security updates +2. **Use read-only mounts**: Mount input directories as read-only (`:ro`) +3. **Limit resources**: Set memory and CPU limits for production use +4. **Network isolation**: Use `--offline` mode when possible +5. **Regular updates**: Keep base images updated + +## Docker Compatibility + +The same setup works with Docker: + +```bash +# Build with Docker +docker build -t safe-hash:latest . + +# Run with Docker +docker run --rm --cap-drop ALL --read-only --tmpfs /tmp --network host safe-hash:latest --help +``` + +## Integration Examples + +### CI/CD Pipeline + +```yaml +# Example GitHub Actions step +- name: Verify Safe Transaction + run: | + podman build -t safe-hash:latest . + podman run --rm --network host safe-hash:latest tx \ + --chain ethereum \ + --nonce ${{ env.NONCE }} \ + --safe-address ${{ env.SAFE_ADDRESS }} \ + --safe-version 1.4.1 +``` + +### Automated Verification Script + +```bash +#!/bin/bash +# verify-transaction.sh + +# Build if image doesn't exist +if ! podman image exists localhost/safe-hash:latest; then + ./scripts/podman-build.sh +fi + +# Run verification +./scripts/safe-hash-rs tx \ + --chain "${CHAIN:-ethereum}" \ + --nonce "${NONCE}" \ + --safe-address "${SAFE_ADDRESS}" \ + --safe-version "${SAFE_VERSION:-1.4.1}" +``` + +This container setup provides a secure, isolated environment for running safe-hash while maintaining all the functionality of the native binary. \ No newline at end of file diff --git a/scripts/podman-build.sh b/scripts/podman-build.sh new file mode 100755 index 0000000..7d2387a --- /dev/null +++ b/scripts/podman-build.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# Build script for safe-hash using Podman +# This script builds the container image with proper tags and labels + +set -e + +# Configuration +IMAGE_NAME="safe-hash" +IMAGE_TAG="latest" +REGISTRY_NAME="localhost" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}Building safe-hash container image with Podman...${NC}" + +# Get version from Cargo.toml if available +VERSION=$(grep -E '^version = "' crates/safe-hash/Cargo.toml | sed 's/version = "\(.*\)"/\1/' | head -1) +if [ -n "$VERSION" ]; then + echo -e "${GREEN}Detected version: $VERSION${NC}" + VERSION_TAG="$VERSION" +else + echo -e "${YELLOW}Could not detect version, using 'latest'${NC}" + VERSION_TAG="latest" +fi + +# Build the image +echo -e "${BLUE}Building container image...${NC}" +podman build \ + --tag "${REGISTRY_NAME}/${IMAGE_NAME}:${IMAGE_TAG}" \ + --tag "${REGISTRY_NAME}/${IMAGE_NAME}:${VERSION_TAG}" \ + --label "org.opencontainers.image.title=safe-hash" \ + --label "org.opencontainers.image.description=Verify Safe Wallet Transactions and Messages" \ + --label "org.opencontainers.image.version=${VERSION:-unknown}" \ + --label "org.opencontainers.image.source=https://github.com/Cyfrin/safe-hash-rs" \ + --label "org.opencontainers.image.vendor=Cyfrin" \ + --build-arg BUILDKIT_INLINE_CACHE=1 \ + . + +if [ $? -eq 0 ]; then + echo -e "${GREEN}✅ Build completed successfully!${NC}" + echo -e "${GREEN}Image tags:${NC}" + echo -e " - ${REGISTRY_NAME}/${IMAGE_NAME}:${IMAGE_TAG}" + echo -e " - ${REGISTRY_NAME}/${IMAGE_NAME}:${VERSION_TAG}" + + # Show image details + echo -e "\n${BLUE}Image details:${NC}" + podman images "${REGISTRY_NAME}/${IMAGE_NAME}" + + echo -e "\n${GREEN}To run the container, use:${NC}" + echo -e " podman run --rm ${REGISTRY_NAME}/${IMAGE_NAME}:${IMAGE_TAG} --help" + echo -e "\n${GREEN}Or use the safe-hash-rs script:${NC}" + echo -e " ./scripts/safe-hash-rs --help" +else + echo -e "${RED}❌ Build failed!${NC}" + exit 1 +fi \ No newline at end of file diff --git a/scripts/safe-hash-rs b/scripts/safe-hash-rs new file mode 100755 index 0000000..5984433 --- /dev/null +++ b/scripts/safe-hash-rs @@ -0,0 +1,142 @@ +#!/bin/bash + +# Run script for safe-hash using Podman +# This script runs the container with proper volume mounts and security settings + +set -e + +# Configuration +IMAGE_NAME="safe-hash" +IMAGE_TAG="latest" +REGISTRY_NAME="localhost" +CONTAINER_NAME="safe-hash-$(date +%s)" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Default values +INTERACTIVE=false +VOLUME_MOUNT="" +WORKING_DIR="/app" + +# Function to show usage +show_usage() { + echo "Usage: $0 [OPTIONS] [SAFE_HASH_ARGS...]" + echo "" + echo "Container wrapper for safe-hash - Verify Safe Wallet Transactions and Messages" + echo "" + echo "Options:" + echo " -i, --interactive Run in interactive mode with shell access" + echo " -v, --volume DIR Mount a host directory to /app/input in the container" + echo " -h, --help Show this help message" + echo "" + echo "Examples:" + echo " # Show safe-hash help (use 'help' subcommand)" + echo " $0 help" + echo "" + echo " # Check version" + echo " $0 --version" + echo "" + echo " # Verify a transaction" + echo " $0 tx --chain ethereum --nonce 63 --safe-address 0x1c694Fc3006D81ff4a56F97E1b99529066a23725 --safe-version 1.4.1" + echo "" + echo " # Mount a directory and use a file from it" + echo " $0 -v ./test msg --chain sepolia --safe-address 0x657ff0D4eC65D82b2bC1247b0a558bcd2f80A0f1 --input-file /app/input/test_message.txt --safe-version 1.4.1" + echo "" + echo " # Run in interactive mode" + echo " $0 -i" + echo "" + echo "Note: This script runs safe-hash inside a secure Podman container." + echo "Use '$0 --help' to see this message, or '$0 help' to see safe-hash help." +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -i|--interactive) + INTERACTIVE=true + shift + ;; + -v|--volume) + if [ -z "$2" ]; then + echo -e "${RED}Error: --volume requires a directory argument${NC}" >&2 + exit 1 + fi + VOLUME_MOUNT="$2" + shift 2 + ;; + -h|--help) + show_usage + exit 0 + ;; + *) + # All remaining arguments are passed to safe-hash + break + ;; + esac +done + +# Check if image exists +if ! podman image exists "${REGISTRY_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"; then + echo -e "${YELLOW}Image ${REGISTRY_NAME}/${IMAGE_NAME}:${IMAGE_TAG} not found.${NC}" + echo -e "${BLUE}Building the image first...${NC}" + + # Check if build script exists + if [ -f "./scripts/podman-build.sh" ]; then + ./scripts/podman-build.sh + else + echo -e "${RED}Build script not found. Please run the build script first.${NC}" + exit 1 + fi +fi + +# Prepare podman run command +PODMAN_ARGS=( + "run" + "--rm" + "--name" "${CONTAINER_NAME}" + "--security-opt" "label=disable" + "--cap-drop" "ALL" + "--read-only" + "--tmpfs" "/tmp" + "--network" "host" +) + +# Add volume mount if specified +if [ -n "$VOLUME_MOUNT" ]; then + # Convert to absolute path + VOLUME_MOUNT=$(realpath "$VOLUME_MOUNT") + + if [ ! -d "$VOLUME_MOUNT" ]; then + echo -e "${RED}Error: Directory $VOLUME_MOUNT does not exist${NC}" >&2 + exit 1 + fi + + echo -e "${GREEN}Mounting $VOLUME_MOUNT to /app/input${NC}" + PODMAN_ARGS+=("--volume" "${VOLUME_MOUNT}:/app/input:ro,Z") +fi + +# Add image +PODMAN_ARGS+=("${REGISTRY_NAME}/${IMAGE_NAME}:${IMAGE_TAG}") + +# Run in interactive mode or with arguments +if [ "$INTERACTIVE" = true ]; then + echo -e "${BLUE}Starting interactive container...${NC}" + PODMAN_ARGS[2]="--rm" # Replace --rm with -it + podman run -it "${PODMAN_ARGS[@]:3}" /bin/bash +else + if [ $# -eq 0 ]; then + # No arguments provided, show help + PODMAN_ARGS+=("--help") + else + # Pass all remaining arguments to safe-hash + PODMAN_ARGS+=("$@") + fi + + echo -e "${BLUE}Running safe-hash in container...${NC}" + podman "${PODMAN_ARGS[@]}" +fi \ No newline at end of file