From 79d9c797ae249c499ce1fb06465b39a63cde9cbe Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Tue, 23 Jun 2026 11:29:54 +0200 Subject: [PATCH 1/6] ova enhancements --- .github/workflows/test-ova.yml | 58 ++++++++++++++++++++++++++ flake.nix | 1 + ova/defguard.pkr.hcl | 1 + ova/files/defguard-init.service | 3 ++ ova/files/generate-env.sh | 7 ++-- ova/files/start.sh | 11 ++--- ova/tests/config.bats | 49 ++++++++++++++++++++++ ova/tests/generate-env.bats | 52 +++++++++++++++++++++++ ova/tests/helpers.bash | 45 ++++++++++++++++++++ ova/tests/integration.bats | 51 +++++++++++++++++++++++ ova/tests/start.bats | 73 +++++++++++++++++++++++++++++++++ ova/tests/stub/docker | 9 ++++ 12 files changed, 352 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/test-ova.yml create mode 100644 ova/tests/config.bats create mode 100644 ova/tests/generate-env.bats create mode 100644 ova/tests/helpers.bash create mode 100644 ova/tests/integration.bats create mode 100644 ova/tests/start.bats create mode 100755 ova/tests/stub/docker diff --git a/.github/workflows/test-ova.yml b/.github/workflows/test-ova.yml new file mode 100644 index 0000000..32b6a66 --- /dev/null +++ b/.github/workflows/test-ova.yml @@ -0,0 +1,58 @@ +name: Test OVA scripts + +on: + push: + branches: + - main + paths: + - "ova/**" + - ".github/workflows/test-ova.yml" + pull_request: + branches: + - main + paths: + - "ova/**" + - ".github/workflows/test-ova.yml" + +jobs: + logic: + name: Logic + compose config + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install bats + run: | + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends bats + + - name: Run logic and config tests + run: bats ova/tests/start.bats ova/tests/generate-env.bats ova/tests/config.bats + + integration: + name: Real bring-up + runs-on: [self-hosted, Linux, X64] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install bats + run: | + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends bats + + - name: Login to GitHub container registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run integration tests + env: + RUN_INTEGRATION: "1" + CORE_TAG: "2" + PROXY_TAG: "2" + GATEWAY_TAG: "2" + run: bats ova/tests/integration.bats diff --git a/flake.nix b/flake.nix index 15868e0..5f2d05d 100644 --- a/flake.nix +++ b/flake.nix @@ -21,6 +21,7 @@ kubectl kubernetes-helm kubeconform + bats ]; }; }); diff --git a/ova/defguard.pkr.hcl b/ova/defguard.pkr.hcl index 20ceb38..c41c078 100644 --- a/ova/defguard.pkr.hcl +++ b/ova/defguard.pkr.hcl @@ -109,6 +109,7 @@ build { "sudo mv /tmp/defguard-init.service /etc/systemd/system/defguard-init.service", "sudo systemctl daemon-reload", "sudo systemctl enable docker.service", + "sudo systemctl enable defguard-init.service", "sudo chown -R ubuntu:ubuntu /opt/stacks/defguard", "sudo rm -f /etc/netplan/00-installer-config.yaml /etc/netplan/50-cloud-init.yaml", "sudo cloud-init clean --logs", diff --git a/ova/files/defguard-init.service b/ova/files/defguard-init.service index f7ed544..b759004 100644 --- a/ova/files/defguard-init.service +++ b/ova/files/defguard-init.service @@ -10,3 +10,6 @@ StandardOutput=append:/var/log/defguard-startup.log StandardError=append:/var/log/defguard-startup.log ExecStart=/bin/bash /opt/stacks/defguard/generate-env.sh ExecStart=/bin/bash /opt/stacks/defguard/start.sh + +[Install] +WantedBy=multi-user.target diff --git a/ova/files/generate-env.sh b/ova/files/generate-env.sh index 1de8c52..be6634f 100644 --- a/ova/files/generate-env.sh +++ b/ova/files/generate-env.sh @@ -2,7 +2,8 @@ # Generates /opt/stacks/defguard/.env with random secrets on first boot. # If .env already exists (e.g. provided via cloud-init), this script does nothing. -ENV_FILE="/opt/stacks/defguard/.env" +STACK_DIR="${DEFGUARD_STACK_DIR:-/opt/stacks/defguard}" +ENV_FILE="$STACK_DIR/.env" if [ -f "$ENV_FILE" ]; then echo "DefGuard: .env already exists, skipping generation." @@ -13,8 +14,8 @@ echo "DefGuard: generating .env with random secrets..." DB_PASSWORD=$(openssl rand -hex 16) -if [ -f "/opt/stacks/defguard/.image-tags" ]; then - source "/opt/stacks/defguard/.image-tags" +if [ -f "$STACK_DIR/.image-tags" ]; then + source "$STACK_DIR/.image-tags" fi : "${DEFGUARD_CORE_TAG:?DEFGUARD_CORE_TAG is required}" diff --git a/ova/files/start.sh b/ova/files/start.sh index bacd81e..9f5a1e8 100644 --- a/ova/files/start.sh +++ b/ova/files/start.sh @@ -11,8 +11,9 @@ # - path: /opt/stacks/defguard/enable-docker-management # content: "" -PROFILES_FILE="/opt/stacks/defguard/active-profiles" -ENABLE_DOCKER_MGMT_FILE="/opt/stacks/defguard/enable-docker-management" +STACK_DIR="${DEFGUARD_STACK_DIR:-/opt/stacks/defguard}" +PROFILES_FILE="$STACK_DIR/active-profiles" +ENABLE_DOCKER_MGMT_FILE="$STACK_DIR/enable-docker-management" # Append the dockge profile if the opt-in flag file is present _maybe_add_dockge() { @@ -33,7 +34,7 @@ if [ ! -f "$PROFILES_FILE" ]; then if [ -n "$COMPOSE_PROFILES" ]; then export COMPOSE_PROFILES fi - docker compose -f /opt/stacks/defguard/docker-compose.yaml up -d + docker compose -f "$STACK_DIR/docker-compose.yaml" up -d else COMPOSE_PROFILES=$(tr '[:space:]' ',' < "$PROFILES_FILE" | tr -s ',' | sed 's/,$//') if [ -z "$COMPOSE_PROFILES" ]; then @@ -44,10 +45,10 @@ else else unset COMPOSE_PROFILES fi - docker compose -f /opt/stacks/defguard/docker-compose.yaml up -d + docker compose -f "$STACK_DIR/docker-compose.yaml" up -d else COMPOSE_PROFILES=$(_maybe_add_dockge "$COMPOSE_PROFILES") export COMPOSE_PROFILES - docker compose -f /opt/stacks/defguard/docker-compose.standalone.yaml up -d + docker compose -f "$STACK_DIR/docker-compose.standalone.yaml" up -d fi fi diff --git a/ova/tests/config.bats b/ova/tests/config.bats new file mode 100644 index 0000000..0e80c79 --- /dev/null +++ b/ova/tests/config.bats @@ -0,0 +1,49 @@ +#!/usr/bin/env bats +# `docker compose config` evaluates profiles without pulling images, so the +# expected service set per profile combination can be checked offline. + +load helpers + +setup() { + command -v docker >/dev/null 2>&1 || skip "docker not installed" + docker compose version >/dev/null 2>&1 || skip "docker compose v2 not available" + make_stack + write_env +} + +teardown() { + teardown_stack +} + +# sorted, space-joined active services for a given file ($1) and profiles ($2) +services_for() { + COMPOSE_PROFILES="$2" docker compose -f "$STACK_DIR/$1" config --services 2>/dev/null | sort | xargs +} + +@test "all-in-one without profiles -> core db edge gateway" { + [ "$(services_for docker-compose.yaml "")" = "core db edge gateway" ] +} + +@test "all-in-one with dockge -> core db dockge edge gateway" { + [ "$(services_for docker-compose.yaml "dockge")" = "core db dockge edge gateway" ] +} + +@test "standalone core -> core db" { + [ "$(services_for docker-compose.standalone.yaml "core")" = "core db" ] +} + +@test "standalone core,gateway -> core db gateway" { + [ "$(services_for docker-compose.standalone.yaml "core,gateway")" = "core db gateway" ] +} + +@test "standalone core,edge,gateway -> core db edge gateway" { + [ "$(services_for docker-compose.standalone.yaml "core,edge,gateway")" = "core db edge gateway" ] +} + +@test "standalone core,edge,gateway,dockge -> core db dockge edge gateway" { + [ "$(services_for docker-compose.standalone.yaml "core,edge,gateway,dockge")" = "core db dockge edge gateway" ] +} + +@test "standalone with no profiles -> no services" { + [ "$(services_for docker-compose.standalone.yaml "")" = "" ] +} diff --git a/ova/tests/generate-env.bats b/ova/tests/generate-env.bats new file mode 100644 index 0000000..2575aea --- /dev/null +++ b/ova/tests/generate-env.bats @@ -0,0 +1,52 @@ +#!/usr/bin/env bats + +load helpers + +setup() { + make_stack +} + +teardown() { + teardown_stack +} + +@test "generates .env with tags sourced from .image-tags" { + write_image_tags aaa bbb ccc + run bash "$FILES_DIR/generate-env.sh" + [ "$status" -eq 0 ] + [ -f "$STACK_DIR/.env" ] + grep -qx 'DEFGUARD_CORE_TAG=aaa' "$STACK_DIR/.env" + grep -qx 'DEFGUARD_PROXY_TAG=bbb' "$STACK_DIR/.env" + grep -qx 'DEFGUARD_GATEWAY_TAG=ccc' "$STACK_DIR/.env" +} + +@test "db and postgres passwords match and are non-empty" { + write_image_tags + bash "$FILES_DIR/generate-env.sh" + dbp=$(grep '^DEFGUARD_DB_PASSWORD=' "$STACK_DIR/.env" | cut -d= -f2) + pgp=$(grep '^POSTGRES_PASSWORD=' "$STACK_DIR/.env" | cut -d= -f2) + [ -n "$dbp" ] + [ "$dbp" = "$pgp" ] +} + +@test ".env is created with 600 permissions" { + write_image_tags + bash "$FILES_DIR/generate-env.sh" + perm=$(stat -c '%a' "$STACK_DIR/.env" 2>/dev/null || stat -f '%Lp' "$STACK_DIR/.env") + [ "$perm" = "600" ] +} + +@test "existing .env is left untouched (idempotent)" { + echo "SENTINEL=keep-me" > "$STACK_DIR/.env" + write_image_tags + run bash "$FILES_DIR/generate-env.sh" + [ "$status" -eq 0 ] + grep -qx 'SENTINEL=keep-me' "$STACK_DIR/.env" +} + +@test "fails and writes nothing when .image-tags is missing" { + run bash "$FILES_DIR/generate-env.sh" + [ "$status" -ne 0 ] + [[ "$output" == *"DEFGUARD_CORE_TAG is required"* ]] + [ ! -f "$STACK_DIR/.env" ] +} diff --git a/ova/tests/helpers.bash b/ova/tests/helpers.bash new file mode 100644 index 0000000..a92775f --- /dev/null +++ b/ova/tests/helpers.bash @@ -0,0 +1,45 @@ +# shellcheck shell=bash + +OVA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +FILES_DIR="$OVA_DIR/files" +STUB_DIR="$OVA_DIR/tests/stub" + +# DEFGUARD_STACK_DIR redirects the scripts at this temp dir; compose files are +# copied in so `docker compose config` and real `up` see the actual definitions. +make_stack() { + STACK_DIR="$(mktemp -d)" + export DEFGUARD_STACK_DIR="$STACK_DIR" + cp "$FILES_DIR/docker-compose.yaml" "$STACK_DIR/" + cp "$FILES_DIR/docker-compose.standalone.yaml" "$STACK_DIR/" +} + +teardown_stack() { + [ -n "${STACK_DIR:-}" ] && rm -rf "$STACK_DIR" + return 0 +} + +# Bake image tags as the Packer build does; generate-env.sh sources this. +write_image_tags() { + cat > "$STACK_DIR/.image-tags" < "$STACK_DIR/.env" < "$DOCKER_STUB_LOG" +} + +teardown() { + teardown_stack +} + +@test "no active-profiles, no dockge -> all-in-one, profiles unset" { + run bash "$FILES_DIR/start.sh" + [ "$status" -eq 0 ] + [ "$(last_compose_file)" = "docker-compose.yaml" ] + [ "$(last_profiles)" = "" ] +} + +@test "no active-profiles, dockge enabled -> all-in-one, dockge profile" { + touch "$STACK_DIR/enable-docker-management" + run bash "$FILES_DIR/start.sh" + [ "$status" -eq 0 ] + [ "$(last_compose_file)" = "docker-compose.yaml" ] + [ "$(last_profiles)" = "dockge" ] +} + +@test "active-profiles=core -> standalone, core profile" { + echo "core" > "$STACK_DIR/active-profiles" + run bash "$FILES_DIR/start.sh" + [ "$status" -eq 0 ] + [ "$(last_compose_file)" = "docker-compose.standalone.yaml" ] + [ "$(last_profiles)" = "core" ] +} + +@test "active-profiles='core gateway' (space separated) -> core,gateway" { + printf 'core gateway\n' > "$STACK_DIR/active-profiles" + run bash "$FILES_DIR/start.sh" + [ "$status" -eq 0 ] + [ "$(last_compose_file)" = "docker-compose.standalone.yaml" ] + [ "$(last_profiles)" = "core,gateway" ] +} + +@test "active-profiles multiline + dockge -> profiles plus dockge appended" { + printf 'core\nedge\ngateway\n' > "$STACK_DIR/active-profiles" + touch "$STACK_DIR/enable-docker-management" + run bash "$FILES_DIR/start.sh" + [ "$status" -eq 0 ] + [ "$(last_compose_file)" = "docker-compose.standalone.yaml" ] + [ "$(last_profiles)" = "core,edge,gateway,dockge" ] +} + +@test "empty/whitespace active-profiles -> falls back to all-in-one" { + printf ' \n' > "$STACK_DIR/active-profiles" + run bash "$FILES_DIR/start.sh" + [ "$status" -eq 0 ] + [ "$(last_compose_file)" = "docker-compose.yaml" ] + [ "$(last_profiles)" = "" ] +} + +@test "empty active-profiles + dockge -> all-in-one with dockge" { + printf '\n' > "$STACK_DIR/active-profiles" + touch "$STACK_DIR/enable-docker-management" + run bash "$FILES_DIR/start.sh" + [ "$status" -eq 0 ] + [ "$(last_compose_file)" = "docker-compose.yaml" ] + [ "$(last_profiles)" = "dockge" ] +} diff --git a/ova/tests/stub/docker b/ova/tests/stub/docker new file mode 100755 index 0000000..2549f92 --- /dev/null +++ b/ova/tests/stub/docker @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Records `docker compose` invocations instead of running them, so start.sh's +# profile selection can be asserted without a daemon. +log="${DOCKER_STUB_LOG:?DOCKER_STUB_LOG must be set}" +{ + printf 'compose_profiles=%s\n' "${COMPOSE_PROFILES-}" + printf 'args=%s\n' "$*" +} >> "$log" +exit 0 From 8c0e9779dd9976020e058230456558c10af11611 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 25 Jun 2026 11:04:46 +0200 Subject: [PATCH 2/6] whitelist wg interface in docker rules --- .github/workflows/test-ova.yml | 2 +- ova/defguard.pkr.hcl | 14 +++++++ ova/files/defguard-firewall.service | 13 +++++++ ova/files/defguard-firewall.sh | 37 ++++++++++++++++++ ova/tests/firewall.bats | 58 ++++++++++++++++++++++++++++ ova/tests/stub/iptables-stub | 14 +++++++ ova/tests/stub/noop | 4 ++ terraform/modules/proxy/main.tf | 22 ----------- terraform/modules/proxy/outputs.tf | 9 ----- terraform/modules/proxy/setup.sh | 47 ---------------------- terraform/modules/proxy/variables.tf | 47 ---------------------- 11 files changed, 141 insertions(+), 126 deletions(-) create mode 100644 ova/files/defguard-firewall.service create mode 100755 ova/files/defguard-firewall.sh create mode 100644 ova/tests/firewall.bats create mode 100755 ova/tests/stub/iptables-stub create mode 100755 ova/tests/stub/noop delete mode 100644 terraform/modules/proxy/main.tf delete mode 100644 terraform/modules/proxy/outputs.tf delete mode 100644 terraform/modules/proxy/setup.sh delete mode 100644 terraform/modules/proxy/variables.tf diff --git a/.github/workflows/test-ova.yml b/.github/workflows/test-ova.yml index 32b6a66..970b319 100644 --- a/.github/workflows/test-ova.yml +++ b/.github/workflows/test-ova.yml @@ -28,7 +28,7 @@ jobs: sudo apt-get install -y --no-install-recommends bats - name: Run logic and config tests - run: bats ova/tests/start.bats ova/tests/generate-env.bats ova/tests/config.bats + run: bats ova/tests/start.bats ova/tests/generate-env.bats ova/tests/config.bats ova/tests/firewall.bats integration: name: Real bring-up diff --git a/ova/defguard.pkr.hcl b/ova/defguard.pkr.hcl index c41c078..c7f3eaa 100644 --- a/ova/defguard.pkr.hcl +++ b/ova/defguard.pkr.hcl @@ -92,6 +92,16 @@ build { destination = "/tmp/defguard-init.service" } + provisioner "file" { + source = "files/defguard-firewall.sh" + destination = "/tmp/defguard-firewall.sh" + } + + provisioner "file" { + source = "files/defguard-firewall.service" + destination = "/tmp/defguard-firewall.service" + } + provisioner "shell" { inline = [ "sudo bash /tmp/docker-setup.sh", @@ -107,9 +117,13 @@ build { "echo 'DEFGUARD_GATEWAY_TAG=${var.gateway_tag}' | sudo tee -a /opt/stacks/defguard/.image-tags > /dev/null", "sudo mv /tmp/99-defguard.cfg /etc/cloud/cloud.cfg.d/99-defguard.cfg", "sudo mv /tmp/defguard-init.service /etc/systemd/system/defguard-init.service", + "sudo mv /tmp/defguard-firewall.sh /opt/stacks/defguard/defguard-firewall.sh", + "sudo chmod +x /opt/stacks/defguard/defguard-firewall.sh", + "sudo mv /tmp/defguard-firewall.service /etc/systemd/system/defguard-firewall.service", "sudo systemctl daemon-reload", "sudo systemctl enable docker.service", "sudo systemctl enable defguard-init.service", + "sudo systemctl enable defguard-firewall.service", "sudo chown -R ubuntu:ubuntu /opt/stacks/defguard", "sudo rm -f /etc/netplan/00-installer-config.yaml /etc/netplan/50-cloud-init.yaml", "sudo cloud-init clean --logs", diff --git a/ova/files/defguard-firewall.service b/ova/files/defguard-firewall.service new file mode 100644 index 0000000..6913e26 --- /dev/null +++ b/ova/files/defguard-firewall.service @@ -0,0 +1,13 @@ +[Unit] +Description=DefGuard host forwarding rules for the WireGuard gateway +After=docker.service +Wants=docker.service + +[Service] +Type=oneshot +StandardOutput=append:/var/log/defguard-startup.log +StandardError=append:/var/log/defguard-startup.log +ExecStart=/bin/bash /opt/stacks/defguard/defguard-firewall.sh + +[Install] +WantedBy=multi-user.target diff --git a/ova/files/defguard-firewall.sh b/ova/files/defguard-firewall.sh new file mode 100755 index 0000000..a1a0b03 --- /dev/null +++ b/ova/files/defguard-firewall.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Docker sets the FORWARD policy to DROP and only accepts its own bridges, which +# drops the gateway's decrypted WireGuard traffic before it can be forwarded. + +set -u + +SYSCTL_DIR="${DEFGUARD_SYSCTL_DIR:-/etc/sysctl.d}" + +# Docker enables net.ipv4.ip_forward itself, but not IPv6 forwarding. +cat > "$SYSCTL_DIR/99-defguard-forward.conf" </dev/null + +# DOCKER-USER only exists once dockerd has set up its chains; this may race +# docker.service. Wait briefly, then bail cleanly (next boot re-applies). +for _ in $(seq 1 30); do + if iptables -n -L DOCKER-USER >/dev/null 2>&1; then + break + fi + sleep 1 +done + +if ! iptables -n -L DOCKER-USER >/dev/null 2>&1; then + echo "DefGuard: DOCKER-USER chain not present; skipping (Docker not ready)." + exit 0 +fi + +for ipt in iptables ip6tables; do + for dir in "-i" "-o"; do + "$ipt" -C DOCKER-USER "$dir" wg+ -j ACCEPT 2>/dev/null \ + || "$ipt" -I DOCKER-USER "$dir" wg+ -j ACCEPT + done +done + +echo "DefGuard: wg+ forwarding whitelisted in DOCKER-USER." diff --git a/ova/tests/firewall.bats b/ova/tests/firewall.bats new file mode 100644 index 0000000..33bb54c --- /dev/null +++ b/ova/tests/firewall.bats @@ -0,0 +1,58 @@ +#!/usr/bin/env bats +# iptables/ip6tables/sysctl/sleep are stubbed so only defguard-firewall.sh's +# rule logic is exercised, with no effect on the host firewall. + +load helpers + +setup() { + BIN="$(mktemp -d)" + cp "$STUB_DIR/iptables-stub" "$BIN/iptables" + cp "$STUB_DIR/iptables-stub" "$BIN/ip6tables" + cp "$STUB_DIR/noop" "$BIN/sysctl" + cp "$STUB_DIR/noop" "$BIN/sleep" + chmod +x "$BIN"/* + export PATH="$BIN:$PATH" + export IPT_STUB_LOG="$BIN/ipt.log" + : > "$IPT_STUB_LOG" + export DEFGUARD_SYSCTL_DIR="$BIN/sysctl.d" + mkdir "$DEFGUARD_SYSCTL_DIR" +} + +teardown() { + rm -rf "$BIN" +} + +inserts() { + grep -c -- '-I DOCKER-USER' "$IPT_STUB_LOG" +} + +@test "whitelists wg+ in/out for both iptables and ip6tables" { + run bash "$FILES_DIR/defguard-firewall.sh" + [ "$status" -eq 0 ] + [ "$(inserts)" -eq 4 ] + grep -q -- 'iptables -I DOCKER-USER -i wg+ -j ACCEPT' "$IPT_STUB_LOG" + grep -q -- 'iptables -I DOCKER-USER -o wg+ -j ACCEPT' "$IPT_STUB_LOG" + grep -q -- 'ip6tables -I DOCKER-USER -i wg+ -j ACCEPT' "$IPT_STUB_LOG" + grep -q -- 'ip6tables -I DOCKER-USER -o wg+ -j ACCEPT' "$IPT_STUB_LOG" +} + +@test "writes the forwarding sysctl drop-in" { + run bash "$FILES_DIR/defguard-firewall.sh" + [ "$status" -eq 0 ] + conf="$DEFGUARD_SYSCTL_DIR/99-defguard-forward.conf" + [ -f "$conf" ] + grep -qx 'net.ipv4.ip_forward = 1' "$conf" + grep -qx 'net.ipv6.conf.all.forwarding = 1' "$conf" +} + +@test "idempotent: no inserts when the rules already exist" { + IPT_RULE_EXISTS=1 run bash "$FILES_DIR/defguard-firewall.sh" + [ "$status" -eq 0 ] + [ "$(inserts)" -eq 0 ] +} + +@test "exits cleanly without inserts when DOCKER-USER is absent" { + IPT_CHAIN_EXISTS=0 run bash "$FILES_DIR/defguard-firewall.sh" + [ "$status" -eq 0 ] + [ "$(inserts)" -eq 0 ] +} diff --git a/ova/tests/stub/iptables-stub b/ova/tests/stub/iptables-stub new file mode 100755 index 0000000..7dc3f9d --- /dev/null +++ b/ova/tests/stub/iptables-stub @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Records iptables/ip6tables calls without touching the host. Copy onto PATH as +# both `iptables` and `ip6tables` (it reports $0). Toggle simulated state with: +# IPT_CHAIN_EXISTS=1 -> `-L DOCKER-USER` probe succeeds (default) +# IPT_RULE_EXISTS=0 -> `-C` rule check fails, i.e. rule absent (default) +name="$(basename "$0")" +log="${IPT_STUB_LOG:?IPT_STUB_LOG must be set}" +printf '%s %s\n' "$name" "$*" >> "$log" + +case "$*" in + *"-L DOCKER-USER"*) [ "${IPT_CHAIN_EXISTS:-1}" = "1" ] && exit 0 || exit 1 ;; + *"-C "*) [ "${IPT_RULE_EXISTS:-0}" = "1" ] && exit 0 || exit 1 ;; +esac +exit 0 diff --git a/ova/tests/stub/noop b/ova/tests/stub/noop new file mode 100755 index 0000000..c5441de --- /dev/null +++ b/ova/tests/stub/noop @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +# On PATH as `sysctl`/`sleep`: swallow the call so tests neither touch the host +# nor wait on the retry loop. +exit 0 diff --git a/terraform/modules/proxy/main.tf b/terraform/modules/proxy/main.tf deleted file mode 100644 index 2019f13..0000000 --- a/terraform/modules/proxy/main.tf +++ /dev/null @@ -1,22 +0,0 @@ -resource "aws_instance" "defguard_proxy" { - ami = var.ami - instance_type = var.instance_type - - user_data = templatefile("${path.module}/setup.sh", { - proxy_url = var.proxy_url - grpc_port = var.grpc_port - arch = var.arch - package_version = var.package_version - http_port = var.http_port - log_level = var.log_level - }) - user_data_replace_on_change = true - - primary_network_interface { - network_interface_id = var.network_interface_id - } - - tags = { - Name = "defguard-proxy-instance" - } -} diff --git a/terraform/modules/proxy/outputs.tf b/terraform/modules/proxy/outputs.tf deleted file mode 100644 index 3ed793b..0000000 --- a/terraform/modules/proxy/outputs.tf +++ /dev/null @@ -1,9 +0,0 @@ -output "proxy_private_address" { - description = "Private IP address of Defguard Proxy instance" - value = aws_instance.defguard_proxy.private_ip -} - -output "instance_id" { - description = "ID of Defguard Proxy instance" - value = aws_instance.defguard_proxy.id -} diff --git a/terraform/modules/proxy/setup.sh b/terraform/modules/proxy/setup.sh deleted file mode 100644 index 0f1db73..0000000 --- a/terraform/modules/proxy/setup.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash -set -e - -LOG_FILE="/var/log/defguard.log" - -log() { - echo "$(date '+%Y-%m-%d %H:%M:%S') $1" -} - -( -log "Updating apt repositories..." -apt update - -log "Installing curl..." -apt install -y curl - -log "Downloading defguard-proxy package..." -curl -fsSL -o /tmp/defguard-proxy.deb https://github.com/DefGuard/proxy/releases/download/v${package_version}/defguard-proxy-${package_version}-${arch}-unknown-linux-gnu.deb - -log "Installing defguard-proxy package..." -dpkg -i /tmp/defguard-proxy.deb - -log "Writing proxy configuration to /etc/defguard/proxy.toml..." -tee /etc/defguard/proxy.toml <&1 | tee -a "$LOG_FILE" diff --git a/terraform/modules/proxy/variables.tf b/terraform/modules/proxy/variables.tf deleted file mode 100644 index 21a7eeb..0000000 --- a/terraform/modules/proxy/variables.tf +++ /dev/null @@ -1,47 +0,0 @@ -variable "ami" { - description = "AMI ID for the instance" - type = string -} - -variable "instance_type" { - description = "Instance type for the instance" - type = string - default = "t3.micro" -} - -variable "proxy_url" { - description = "URL of the Proxy instance" - type = string -} - -variable "grpc_port" { - description = "Port to be used to communicate with Defguard Core" - type = string -} - -variable "network_interface_id" { - description = "Network interface ID for the instance" - type = string -} - -variable "arch" { - description = "Architecture of the Defguard Proxy package to be installed" - type = string -} - -variable "package_version" { - description = "Version of the Defguard Proxy package to be installed" - type = string -} - -variable "http_port" { - description = "Port to be used to access Defguard Proxy via HTTP" - type = number - default = 8080 -} - -variable "log_level" { - description = "Log level for Defguard Proxy. Possible values: trace, debug, info, warn, error" - type = string - default = "info" -} From 64892745b02d2e11ebfd1b30f884f8b8dcf3d3ee Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 25 Jun 2026 11:09:19 +0200 Subject: [PATCH 3/6] test build --- .github/workflows/build-ova.yml | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-ova.yml b/.github/workflows/build-ova.yml index 3ce52bd..4e1a1d9 100644 --- a/.github/workflows/build-ova.yml +++ b/.github/workflows/build-ova.yml @@ -1,6 +1,13 @@ name: Build OVF Image on: + # Push to this branch builds a test OVA (tags default to "2", never publishes latest). + push: + branches: + - ova-enhancements + paths: + - "ova/**" + - ".github/workflows/build-ova.yml" workflow_dispatch: inputs: core_tag: @@ -12,6 +19,10 @@ on: gateway_tag: description: "defguard gateway image tag" required: true + publish_latest: + description: "Also overwrite defguard-latest.ova (off = test build)" + type: boolean + default: false jobs: build: @@ -62,9 +73,9 @@ jobs: env: PACKER_LOG: 1 run: | - CORE_TAG="${{ github.event.inputs.core_tag }}" - PROXY_TAG="${{ github.event.inputs.proxy_tag }}" - GATEWAY_TAG="${{ github.event.inputs.gateway_tag }}" + CORE_TAG="${{ github.event.inputs.core_tag || '2' }}" + PROXY_TAG="${{ github.event.inputs.proxy_tag || '2' }}" + GATEWAY_TAG="${{ github.event.inputs.gateway_tag || '2' }}" packer build \ -var "iso_url=file://$PWD/ubuntu-24.04.4-live-server-amd64.iso" \ -var "core_tag=${CORE_TAG}" \ @@ -77,14 +88,23 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: eu-central-1 - CORE_TAG: ${{ github.event.inputs.core_tag }} - PROXY_TAG: ${{ github.event.inputs.proxy_tag }} - GATEWAY_TAG: ${{ github.event.inputs.gateway_tag }} + CORE_TAG: ${{ github.event.inputs.core_tag || '2' }} + PROXY_TAG: ${{ github.event.inputs.proxy_tag || '2' }} + GATEWAY_TAG: ${{ github.event.inputs.gateway_tag || '2' }} run: | TIMESTAMP=$(date +%Y%m%d-%H%M%S) FILENAME="defguard_${TIMESTAMP}_core-${CORE_TAG}_edge-${PROXY_TAG}_gateway-${GATEWAY_TAG}.ova" ls -lh output/defguard/defguard.ova aws s3 cp output/defguard/defguard.ova "s3://defguard-downloads/ova/${FILENAME}" echo "Uploaded: s3://defguard-downloads/ova/${FILENAME}" + + - name: Publish as latest + if: ${{ inputs.publish_latest }} + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: eu-central-1 + run: | aws s3 cp output/defguard/defguard.ova "s3://defguard-downloads/ova/defguard-latest.ova" \ --cache-control "no-cache" + echo "Updated: s3://defguard-downloads/ova/defguard-latest.ova" From 824b35f3711ac50a1804190b54927edad18683de Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 25 Jun 2026 15:21:29 +0200 Subject: [PATCH 4/6] remove test builds --- .github/workflows/build-ova.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/build-ova.yml b/.github/workflows/build-ova.yml index 4e1a1d9..2a2cfdf 100644 --- a/.github/workflows/build-ova.yml +++ b/.github/workflows/build-ova.yml @@ -1,13 +1,6 @@ name: Build OVF Image on: - # Push to this branch builds a test OVA (tags default to "2", never publishes latest). - push: - branches: - - ova-enhancements - paths: - - "ova/**" - - ".github/workflows/build-ova.yml" workflow_dispatch: inputs: core_tag: From a8f994e73d078595910342febd3e9d1cef5aa569 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 25 Jun 2026 15:32:38 +0200 Subject: [PATCH 5/6] restore accidentally deleted files --- terraform/modules/proxy/main.tf | 22 +++++++++++++ terraform/modules/proxy/outputs.tf | 9 ++++++ terraform/modules/proxy/setup.sh | 47 ++++++++++++++++++++++++++++ terraform/modules/proxy/variables.tf | 47 ++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 terraform/modules/proxy/main.tf create mode 100644 terraform/modules/proxy/outputs.tf create mode 100644 terraform/modules/proxy/setup.sh create mode 100644 terraform/modules/proxy/variables.tf diff --git a/terraform/modules/proxy/main.tf b/terraform/modules/proxy/main.tf new file mode 100644 index 0000000..2019f13 --- /dev/null +++ b/terraform/modules/proxy/main.tf @@ -0,0 +1,22 @@ +resource "aws_instance" "defguard_proxy" { + ami = var.ami + instance_type = var.instance_type + + user_data = templatefile("${path.module}/setup.sh", { + proxy_url = var.proxy_url + grpc_port = var.grpc_port + arch = var.arch + package_version = var.package_version + http_port = var.http_port + log_level = var.log_level + }) + user_data_replace_on_change = true + + primary_network_interface { + network_interface_id = var.network_interface_id + } + + tags = { + Name = "defguard-proxy-instance" + } +} diff --git a/terraform/modules/proxy/outputs.tf b/terraform/modules/proxy/outputs.tf new file mode 100644 index 0000000..3ed793b --- /dev/null +++ b/terraform/modules/proxy/outputs.tf @@ -0,0 +1,9 @@ +output "proxy_private_address" { + description = "Private IP address of Defguard Proxy instance" + value = aws_instance.defguard_proxy.private_ip +} + +output "instance_id" { + description = "ID of Defguard Proxy instance" + value = aws_instance.defguard_proxy.id +} diff --git a/terraform/modules/proxy/setup.sh b/terraform/modules/proxy/setup.sh new file mode 100644 index 0000000..0f1db73 --- /dev/null +++ b/terraform/modules/proxy/setup.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -e + +LOG_FILE="/var/log/defguard.log" + +log() { + echo "$(date '+%Y-%m-%d %H:%M:%S') $1" +} + +( +log "Updating apt repositories..." +apt update + +log "Installing curl..." +apt install -y curl + +log "Downloading defguard-proxy package..." +curl -fsSL -o /tmp/defguard-proxy.deb https://github.com/DefGuard/proxy/releases/download/v${package_version}/defguard-proxy-${package_version}-${arch}-unknown-linux-gnu.deb + +log "Installing defguard-proxy package..." +dpkg -i /tmp/defguard-proxy.deb + +log "Writing proxy configuration to /etc/defguard/proxy.toml..." +tee /etc/defguard/proxy.toml <&1 | tee -a "$LOG_FILE" diff --git a/terraform/modules/proxy/variables.tf b/terraform/modules/proxy/variables.tf new file mode 100644 index 0000000..21a7eeb --- /dev/null +++ b/terraform/modules/proxy/variables.tf @@ -0,0 +1,47 @@ +variable "ami" { + description = "AMI ID for the instance" + type = string +} + +variable "instance_type" { + description = "Instance type for the instance" + type = string + default = "t3.micro" +} + +variable "proxy_url" { + description = "URL of the Proxy instance" + type = string +} + +variable "grpc_port" { + description = "Port to be used to communicate with Defguard Core" + type = string +} + +variable "network_interface_id" { + description = "Network interface ID for the instance" + type = string +} + +variable "arch" { + description = "Architecture of the Defguard Proxy package to be installed" + type = string +} + +variable "package_version" { + description = "Version of the Defguard Proxy package to be installed" + type = string +} + +variable "http_port" { + description = "Port to be used to access Defguard Proxy via HTTP" + type = number + default = 8080 +} + +variable "log_level" { + description = "Log level for Defguard Proxy. Possible values: trace, debug, info, warn, error" + type = string + default = "info" +} From 225d7c01d76fb33cac2634b90e1400fd5589bc75 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:40:31 +0200 Subject: [PATCH 6/6] bump actions --- .github/workflows/test-ova.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-ova.yml b/.github/workflows/test-ova.yml index 970b319..367a560 100644 --- a/.github/workflows/test-ova.yml +++ b/.github/workflows/test-ova.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install bats run: | @@ -35,7 +35,7 @@ jobs: runs-on: [self-hosted, Linux, X64] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install bats run: | @@ -43,7 +43,7 @@ jobs: sudo apt-get install -y --no-install-recommends bats - name: Login to GitHub container registry - uses: docker/login-action@v2 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }}