diff --git a/.dockerignore b/.dockerignore index ccbc40df..d739dfcc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,18 +1,44 @@ -.dockerignore -.git -.github +# Version control and CI +.git/ +.github/ .gitignore -.travis.yml -CONTRIBUTING.md -Dockerfile -README.md -config.cfg -configs -docs + +# Development environment .env -logo.png -tests +.venv/ +.ruff_cache/ +__pycache__/ +*.pyc +*.pyo +*.pyd + +# Documentation and metadata +docs/ +tests/ +README.md CHANGELOG.md +CONTRIBUTING.md PULL_REQUEST_TEMPLATE.md +SECURITY.md +logo.png +.travis.yml + +# Build artifacts and configs +configs/ +Dockerfile +.dockerignore Vagrantfile -Makefile + +# User configuration (should be bind-mounted) +config.cfg + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +Thumbs.db diff --git a/Dockerfile b/Dockerfile index dfc0839a..e6fad061 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,58 @@ -FROM python:3.11-alpine +# syntax=docker/dockerfile:1 +FROM python:3.12-alpine ARG VERSION="git" -ARG PACKAGES="bash libffi openssh-client openssl rsync tini gcc libffi-dev linux-headers make musl-dev openssl-dev rust cargo" +# Removed rust/cargo (not needed with uv), simplified package list +ARG PACKAGES="bash openssh-client openssl rsync tini" LABEL name="algo" \ version="${VERSION}" \ description="Set up a personal IPsec VPN in the cloud" \ - maintainer="Trail of Bits " + maintainer="Trail of Bits " \ + org.opencontainers.image.source="https://github.com/trailofbits/algo" \ + org.opencontainers.image.description="Algo VPN - Set up a personal IPsec VPN in the cloud" \ + org.opencontainers.image.licenses="AGPL-3.0" -RUN apk --no-cache add ${PACKAGES} -RUN adduser -D -H -u 19857 algo -RUN mkdir -p /algo && mkdir -p /algo/configs +# Install system packages in a single layer +RUN apk --no-cache add ${PACKAGES} && \ + adduser -D -H -u 19857 algo && \ + mkdir -p /algo /algo/configs WORKDIR /algo + +# Copy dependency files first for better layer caching COPY pyproject.toml uv.lock ./ -# Copy uv binary from official distroless image (more secure than curl) -COPY --from=ghcr.io/astral-sh/uv:0.8.5 /uv /bin/uv +# Copy uv binary from official image (using latest tag for automatic updates) +COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv -RUN uv sync +# Install Python dependencies +RUN uv sync --frozen --no-dev + +# Copy application code COPY . . -RUN chmod 0755 /algo/algo-docker.sh -# Because of the bind mounting of `configs/`, we need to run as the `root` user -# This may break in cases where user namespacing is enabled, so hopefully Docker -# sorts out a way to set permissions on bind-mounted volumes (`docker run -v`) -# before userns becomes default -# Note that not running as root will break if we don't have a matching userid -# in the container. The filesystem has also been set up to assume root. +# Set executable permissions and prepare runtime +RUN chmod 0755 /algo/algo-docker.sh && \ + chown -R algo:algo /algo && \ + # Create volume mount point with correct ownership + mkdir -p /data && \ + chown algo:algo /data + +# Multi-arch support metadata +ARG TARGETPLATFORM +ARG BUILDPLATFORM +RUN printf "Built on: %s\nTarget: %s\n" "${BUILDPLATFORM}" "${TARGETPLATFORM}" > /algo/build-info + +# Note: Running as root for bind mount compatibility with algo-docker.sh +# The script handles /data volume permissions and needs root access +# This is a Docker limitation with bind-mounted volumes USER root + +# Health check to ensure container is functional +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD /bin/uv --version || exit 1 + +VOLUME ["/data"] CMD [ "/algo/algo-docker.sh" ] ENTRYPOINT [ "/sbin/tini", "--" ] diff --git a/Makefile b/Makefile deleted file mode 100644 index 0ef19ce7..00000000 --- a/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -## docker-build: Build and tag a docker image -.PHONY: docker-build - -IMAGE := trailofbits/algo -TAG := latest -DOCKERFILE := Dockerfile -CONFIGURATIONS := $(shell pwd) - -docker-build: - docker build \ - -t $(IMAGE):$(TAG) \ - -f $(DOCKERFILE) \ - . - -## docker-deploy: Mount config directory and deploy Algo -.PHONY: docker-deploy - -# '--rm' flag removes the container when finished. -docker-deploy: - docker run \ - --cap-drop=all \ - --rm \ - -it \ - -v $(CONFIGURATIONS):/data \ - $(IMAGE):$(TAG) - -## docker-clean: Remove images and containers. -.PHONY: docker-prune - -docker-prune: - docker images \ - $(IMAGE) |\ - awk '{if (NR>1) print $$3}' |\ - xargs docker rmi - -## docker-all: Build, Deploy, Prune -.PHONY: docker-all - -docker-all: docker-build docker-deploy docker-prune diff --git a/PERFORMANCE.md b/PERFORMANCE.md deleted file mode 100644 index 6b756d86..00000000 --- a/PERFORMANCE.md +++ /dev/null @@ -1,196 +0,0 @@ -# Algo VPN Performance Optimizations - -This document describes performance optimizations available in Algo to reduce deployment time. - -## Overview - -By default, Algo deployments can take 10+ minutes due to sequential operations like system updates, certificate generation, and unnecessary reboots. These optimizations can reduce deployment time by 30-60%. - -## Performance Options - -### Skip Optional Reboots (`performance_skip_optional_reboots`) - -**Default**: `true` -**Time Saved**: 0-5 minutes per deployment - -```yaml -# config.cfg -performance_skip_optional_reboots: true -``` - -**What it does**: -- Analyzes `/var/log/dpkg.log` to detect if kernel packages were updated -- Only reboots if kernel was updated (critical for security and functionality) -- Skips reboots for non-kernel package updates (safe for VPN operation) - -**Safety**: Very safe - only skips reboots when no kernel updates occurred. - -### Parallel Cryptographic Operations (`performance_parallel_crypto`) - -**Default**: `true` -**Time Saved**: 1-3 minutes (scales with user count) - -```yaml -# config.cfg -performance_parallel_crypto: true -``` - -**What it does**: -- **StrongSwan certificates**: Generates user private keys and certificate requests in parallel -- **WireGuard keys**: Generates private and preshared keys simultaneously -- **Certificate signing**: Remains sequential (required for CA database consistency) - -**Safety**: Safe - maintains cryptographic security while improving performance. - -### Cloud-init Package Pre-installation (`performance_preinstall_packages`) - -**Default**: `true` -**Time Saved**: 30-90 seconds per deployment - -```yaml -# config.cfg -performance_preinstall_packages: true -``` - -**What it does**: -- **Pre-installs universal packages**: Installs core system tools (`git`, `screen`, `apparmor-utils`, `uuid-runtime`, `coreutils`, `iptables-persistent`, `cgroup-tools`) during cloud-init phase -- **Parallel installation**: Packages install while cloud instance boots, adding minimal time to boot process -- **Skips redundant installs**: Ansible skips installing these packages since they're already present -- **Universal compatibility**: Only installs packages that are always needed regardless of VPN configuration - -**Safety**: Very safe - same packages installed, just earlier in the process. - -### Batch Package Installation (`performance_parallel_packages`) - -**Default**: `true` -**Time Saved**: 30-60 seconds per deployment - -```yaml -# config.cfg -performance_parallel_packages: true -``` - -**What it does**: -- **Collects all packages**: Gathers packages from all roles (common tools, strongswan, wireguard, dnscrypt-proxy) -- **Single apt operation**: Installs all packages in one `apt` command instead of multiple sequential installs -- **Reduces network overhead**: Single package list download and dependency resolution -- **Maintains compatibility**: Falls back to individual installs when disabled - -**Safety**: Very safe - same packages installed, just more efficiently. - -## Expected Time Savings - -| Optimization | Time Saved | Risk Level | -|--------------|------------|------------| -| Skip optional reboots | 0-5 minutes | Very Low | -| Parallel crypto | 1-3 minutes | None | -| Cloud-init packages | 30-90 seconds | None | -| Batch packages | 30-60 seconds | None | -| **Combined** | **2-9.5 minutes** | **Very Low** | - -## Performance Comparison - -### Before Optimizations -``` -System updates: 3-8 minutes -Package installs: 1-2 minutes (sequential per role) -Certificate gen: 2-4 minutes (sequential) -Reboot wait: 0-5 minutes (always) -Other tasks: 2-3 minutes -──────────────────────────────── -Total: 8-22 minutes -``` - -### After Optimizations -``` -System updates: 3-8 minutes -Package installs: 0-30 seconds (pre-installed + batch) -Certificate gen: 1-2 minutes (parallel) -Reboot wait: 0 minutes (skipped when safe) -Other tasks: 2-3 minutes -──────────────────────────────── -Total: 6-13 minutes -``` - -## Disabling Optimizations - -To disable performance optimizations (for maximum compatibility): - -```yaml -# config.cfg -performance_skip_optional_reboots: false -performance_parallel_crypto: false -performance_preinstall_packages: false -performance_parallel_packages: false -``` - -## Technical Details - -### Reboot Detection Logic - -```bash -# Checks for kernel package updates -if grep -q "linux-image\|linux-generic\|linux-headers" /var/log/dpkg.log*; then - echo "kernel-updated" # Always reboot -else - echo "optional" # Skip if performance_skip_optional_reboots=true -fi -``` - -### Parallel Certificate Generation - -**StrongSwan Process**: -1. Generate all user private keys + CSRs simultaneously (`async: 60`) -2. Wait for completion (`async_status` with retries) -3. Sign certificates sequentially (CA database locking required) - -**WireGuard Process**: -1. Generate all private keys simultaneously (`wg genkey` in parallel) -2. Generate all preshared keys simultaneously (`wg genpsk` in parallel) -3. Derive public keys from private keys (fast operation) - -## Troubleshooting - -### If deployments fail with performance optimizations: - -1. **Check certificate generation**: Look for `async_status` failures -2. **Disable parallel crypto**: Set `performance_parallel_crypto: false` -3. **Force reboots**: Set `performance_skip_optional_reboots: false` - -### Performance not improving: - -1. **Cloud provider speed**: Optimizations don't affect cloud resource provisioning -2. **Network latency**: Slow connections limit all operations -3. **Instance type**: Low-CPU instances benefit most from parallel operations - -## Future Optimizations - -Additional optimizations under consideration: - -- **Package pre-installation via cloud-init** (saves 1-2 minutes) -- **Pre-built cloud images** (saves 5-15 minutes) -- **Skip system updates flag** (saves 3-8 minutes, security tradeoff) -- **Bulk package installation** (saves 30-60 seconds) - -## Contributing - -To contribute additional performance optimizations: - -1. Ensure changes are backwards compatible -2. Add configuration flags (don't change defaults without discussion) -3. Document time savings and risk levels -4. Test with multiple cloud providers -5. Update this documentation - -## Compatibility - -These optimizations are compatible with: -- ✅ All cloud providers (DigitalOcean, AWS, GCP, Azure, etc.) -- ✅ All VPN protocols (WireGuard, StrongSwan) -- ✅ Existing Algo installations (config changes only) -- ✅ All supported Ubuntu versions -- ✅ Ansible 9.13.0+ (latest stable collections) - -**Limited compatibility**: -- ⚠️ Environments with strict reboot policies (disable `performance_skip_optional_reboots`) -- ⚠️ Very old Ansible versions (<2.9) (upgrade recommended) \ No newline at end of file diff --git a/Vagrantfile b/Vagrantfile deleted file mode 100644 index 69fc17f4..00000000 --- a/Vagrantfile +++ /dev/null @@ -1,36 +0,0 @@ -Vagrant.configure("2") do |config| - config.vm.box = "bento/ubuntu-20.04" - - config.vm.provider "virtualbox" do |v| - v.name = "algo-20.04" - v.memory = "512" - v.cpus = "1" - end - - config.vm.synced_folder "./", "/opt/algo", create: true - - config.vm.provision "ansible_local" do |ansible| - ansible.playbook = "/opt/algo/main.yml" - - # https://github.com/hashicorp/vagrant/issues/12204 - ansible.pip_install_cmd = "sudo apt-get install -y python3-pip python-is-python3 && sudo ln -s -f /usr/bin/pip3 /usr/bin/pip" - ansible.install_mode = "pip_args_only" - ansible.pip_args = "ansible==11.8.0 jinja2>=3.1.6 netaddr==1.3.0 pyyaml>=6.0.2" - ansible.inventory_path = "/opt/algo/inventory" - ansible.limit = "local" - ansible.verbose = "-vvvv" - ansible.extra_vars = { - provider: "local", - server: "localhost", - ssh_user: "", - endpoint: "127.0.0.1", - ondemand_cellular: true, - ondemand_wifi: false, - dns_adblocking: true, - ssh_tunneling: true, - store_pki: true, - tests: true, - no_log: false - } - end -end diff --git a/pyproject.toml b/pyproject.toml index e43fdcd0..cb7277ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "algo" description = "Set up a personal IPSEC VPN in the cloud" -version = "0.1.0" +version = "2.0.0-beta" requires-python = ">=3.11" dependencies = [ "ansible==11.8.0", diff --git a/requirements.yml b/requirements.yml index 243a621c..137c8932 100644 --- a/requirements.yml +++ b/requirements.yml @@ -1,10 +1,10 @@ --- collections: - name: ansible.posix - version: ">=2.1.0" + version: "==2.1.0" - name: community.general - version: ">=11.1.0" + version: "==11.1.0" - name: community.crypto - version: ">=3.0.3" + version: "==3.0.3" - name: openstack.cloud - version: ">=2.4.1" + version: "==2.4.1"