Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/workflows/test-squid-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Squid Image Validation

on:
pull_request:
branches: [main]
paths:
- 'containers/squid/**'
workflow_dispatch:

permissions:
contents: read

jobs:
validate-squid-image:
name: Validate Squid Image (${{ matrix.platform }})
runs-on: ubuntu-latest
timeout-minutes: 20

strategy:
fail-fast: false
matrix:
platform: [linux/amd64, linux/arm64]

steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3

- name: Set up QEMU (for arm64 emulation)
uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
with:
platforms: arm64

- name: Build Squid image (${{ matrix.platform }})
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with:
context: ./containers/squid
push: false
load: true
platforms: ${{ matrix.platform }}
tags: awf-squid-test:pr

- name: Assert Berkeley DB / Sleepycat packages absent
run: |
# apk info lists all installed packages in the Alpine image.
# Any hit means a Berkeley DB package crept back in — fail fast.
FOUND=$(docker run --rm --user root --entrypoint sh awf-squid-test:pr -c \
'apk info 2>/dev/null | grep -iE "^(db|libdb|db5|sleepycat|berkeley)" || true')
if [ -n "$FOUND" ]; then
echo "::error::Berkeley DB / Sleepycat packages detected in squid image:"
echo "$FOUND"
exit 1
fi
echo "OK: No Berkeley DB / Sleepycat packages detected"

- name: Assert no Berkeley DB shared libraries in filesystem
run: |
FOUND=$(docker run --rm --user root --entrypoint sh awf-squid-test:pr -c \
'find / -xdev -name "libdb*.so*" 2>/dev/null || true')
if [ -n "$FOUND" ]; then
echo "::error::Berkeley DB shared libraries found in squid image filesystem:"
echo "$FOUND"
exit 1
fi
echo "OK: No libdb*.so* shared libraries found"

- name: Assert proxy user exists at UID 13
run: |
ENTRY=$(docker run --rm --user root --entrypoint sh awf-squid-test:pr -c \
'getent passwd proxy')
echo "passwd entry: $ENTRY"
# Format: proxy:x:13:13:...
UID_VAL=$(echo "$ENTRY" | cut -d: -f3)
if [ "$UID_VAL" != "13" ]; then
echo "::error::proxy user has UID $UID_VAL, expected 13"
exit 1
fi
echo "OK: proxy user has UID 13"

- name: Assert proxy group exists at GID 13
run: |
ENTRY=$(docker run --rm --user root --entrypoint sh awf-squid-test:pr -c \
'getent group proxy')
echo "group entry: $ENTRY"
# Format: proxy:x:13:
GID_VAL=$(echo "$ENTRY" | cut -d: -f3)
if [ "$GID_VAL" != "13" ]; then
echo "::error::proxy group has GID $GID_VAL, expected 13"
exit 1
fi
echo "OK: proxy group has GID 13"

- name: Assert squid binary is present and executable
run: |
docker run --rm --user root --entrypoint sh awf-squid-test:pr -c \
'squid --version 2>&1 | head -1'
echo "OK: squid binary is present"
80 changes: 20 additions & 60 deletions containers/squid/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,65 +1,25 @@
FROM ubuntu/squid:latest
FROM alpine:3.24

# Optionally switch to Azure apt mirror for faster package fetches in CI
# Only rewrite if azure.archive.ubuntu.com is resolvable (BuildKit DNS can fail)
# Falls back to default archive.ubuntu.com which is universally reachable
RUN if getent hosts azure.archive.ubuntu.com >/dev/null 2>&1; then \
echo "Using Azure apt mirror (DNS resolved successfully)"; \
if [ -f /etc/apt/sources.list ]; then \
sed -i 's|http://archive.ubuntu.com|http://azure.archive.ubuntu.com|g' /etc/apt/sources.list; \
sed -i 's|http://security.ubuntu.com|http://azure.archive.ubuntu.com|g' /etc/apt/sources.list; \
fi; \
if [ -d /etc/apt/sources.list.d ]; then \
find /etc/apt/sources.list.d -name '*.sources' -exec \
sed -i 's|http://archive.ubuntu.com|http://azure.archive.ubuntu.com|g' {} + 2>/dev/null || true; \
find /etc/apt/sources.list.d -name '*.sources' -exec \
sed -i 's|http://security.ubuntu.com|http://azure.archive.ubuntu.com|g' {} + 2>/dev/null || true; \
fi; \
else \
echo "Azure apt mirror not reachable, using default archive.ubuntu.com"; \
fi

# Install additional tools for debugging, healthcheck, and SSL Bump
# apt_update_retry: retries up to 3 times with backoff; if all fail, reverts to archive.ubuntu.com
# Install Squid and supporting tools needed by runtime health checks and diagnostics.
# Keep bash + shadow so the compose preflight can continue to drop privileges with:
# su -s /bin/bash proxy -c ...
RUN set -eux; \
force_archive_mirror() { \
echo "Falling back to archive.ubuntu.com mirror..." >&2; \
if [ -f /etc/apt/sources.list ]; then \
sed -i 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' /etc/apt/sources.list; \
sed -i 's|http://security.ubuntu.com|http://archive.ubuntu.com|g' /etc/apt/sources.list 2>/dev/null || true; \
fi; \
if [ -d /etc/apt/sources.list.d ]; then \
find /etc/apt/sources.list.d -name '*.sources' -exec \
sed -i -e 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' \
-e 's|http://security.ubuntu.com|http://archive.ubuntu.com|g' {} + 2>/dev/null || true; \
fi; \
rm -rf /var/lib/apt/lists/* && apt-get update; \
}; \
apt_update_retry() { \
local i; for i in 1 2 3; do \
rm -rf /var/lib/apt/lists/*; \
if apt-get update > /tmp/apt-update.log 2>&1; then \
cat /tmp/apt-update.log; \
if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \
else \
cat /tmp/apt-update.log; \
fi; \
echo "apt-get update attempt $i/3 failed or had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \
done; \
echo "All apt-get update retries failed, falling back to archive.ubuntu.com..." >&2; \
force_archive_mirror; \
}; \
apt_install_retry() { \
apt-get install -y --no-install-recommends "$@" && return 0; \
echo "apt-get install failed (likely mirror fetch timeout), forcing archive.ubuntu.com and retrying..." >&2; \
force_archive_mirror; \
apt-get install -y --no-install-recommends "$@"; \
}; \
PKGS="curl dnsutils net-tools netcat-openbsd openssl squid-openssl"; \
apt_update_retry && \
apt-get install -y --only-upgrade gpgv && \
apt_install_retry $PKGS && \
rm -rf /var/lib/apt/lists/*
apk add --no-cache \
bash \
bind-tools \
curl \
net-tools \
netcat-openbsd \
openssl \
shadow \
squid; \
# Alpine base assigns GID 13 to the 'news' group (and UID 9 to the 'news' user).
# AWF requires proxy user/group at UID/GID 13 for log-dir ownership compatibility.
# Remove the 'news' account first so those IDs are free to assign to 'proxy'.
userdel news 2>/dev/null || true; \
groupdel news 2>/dev/null || true; \
groupadd -r -g 13 proxy; \
useradd -r -u 13 -g proxy -M -s /sbin/nologin proxy

# Create log directory and SSL database directory, ensure proxy user owns them
RUN mkdir -p /var/log/squid /var/spool/squid /var/run/squid && \
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ The firewall uses a containerized architecture with Squid proxy for L7 (HTTP/HTT
## Container Architecture

### Squid Container (`containers/squid/`)
- Based on `ubuntu/squid:latest`
- Based on `alpine:3.24` with Squid installed from Alpine packages
- Mounts dynamically-generated `squid.conf` from work directory
- Exposes port 3128 for proxy traffic
- Logs to shared volume `squid-logs:/var/log/squid`
Expand Down
Loading