Builds the customer-facing distribution: a curated distribution/ directory containing only what an end customer needs (production docker-compose.yml with no build: sections, install.sh/update.sh/uninstall.sh/healthcheck.sh/ backup.sh/restore.sh, a customer-facing CHANGELOG.md, and README/LICENSE/ NOTICE/THIRD_PARTY_LICENSES) — pushed as the initial content of the new public alleyviper/argus repo. install.sh: detects OS, validates Docker/Compose, fetches all deployment files, generates a secure DB password, creates the required Docker network, pulls images, starts the stack, waits for health, and rotates the default admin/admin credentials via the auth API — printing the generated password once at the end. update.sh/healthcheck.sh/backup.sh/restore.sh/uninstall.sh formalize what was previously ad-hoc README snippets into real, safe-by-default scripts (uninstall.sh keeps data unless --remove-data is explicitly passed and confirmed; restore.sh requires typed confirmation and documents that it targets a fresh install, not a live merge). Added a sync-distribution job to release.yml: after a successful release, mirrors distribution/ into the public repo and creates a matching (customer-facing, commit-log-free) release marker there. Needs a one-time setup step — a fine-grained PAT scoped to alleyviper/argus added as the ARGUS_PUBLIC_REPO_TOKEN secret — the job cleanly no-ops until that's added. Not yet done: GHCR package visibility (argus-secure-api/-ui/-nginx) is still private, which blocks a genuine end-to-end curl-install test from a clean, unauthenticated environment — deferred at the user's request until they flip it manually (GitHub does not expose this via API).
98 lines
5.2 KiB
Plaintext
98 lines
5.2 KiB
Plaintext
global
|
|
log stdout format raw daemon "${LOG_LEVEL}"
|
|
|
|
pidfile /run/haproxy.pid
|
|
maxconn 4000
|
|
|
|
# Turn on stats unix socket
|
|
server-state-file /var/lib/haproxy/server-state
|
|
|
|
defaults
|
|
mode http
|
|
log global
|
|
option httplog
|
|
option dontlognull
|
|
option http-server-close
|
|
option redispatch
|
|
retries 3
|
|
timeout http-request 10s
|
|
timeout queue 1m
|
|
timeout connect 10s
|
|
timeout client 10m
|
|
timeout server 10m
|
|
timeout http-keep-alive 10s
|
|
timeout check 10s
|
|
maxconn 3000
|
|
|
|
# Allow seamless reloads
|
|
load-server-state-from-file global
|
|
|
|
# Use provided example error pages
|
|
errorfile 400 /usr/local/etc/haproxy/errors/400.http
|
|
errorfile 403 /usr/local/etc/haproxy/errors/403.http
|
|
errorfile 408 /usr/local/etc/haproxy/errors/408.http
|
|
errorfile 500 /usr/local/etc/haproxy/errors/500.http
|
|
errorfile 502 /usr/local/etc/haproxy/errors/502.http
|
|
errorfile 503 /usr/local/etc/haproxy/errors/503.http
|
|
errorfile 504 /usr/local/etc/haproxy/errors/504.http
|
|
|
|
backend dockerbackend
|
|
server dockersocket $SOCKET_PATH
|
|
|
|
backend docker-events
|
|
server dockersocket $SOCKET_PATH
|
|
timeout server 0
|
|
|
|
frontend dockerfrontend
|
|
bind ${BIND_CONFIG}
|
|
http-request deny unless METH_GET || { env(POST) -m bool }
|
|
|
|
# --- ARGUS-specific hardening, layered on top of the stock tecnativa
|
|
# image (see docker/docker-socket-proxy/README.md) ---
|
|
#
|
|
# The stock template's per-category CONTAINERS=1 gate is a path-prefix
|
|
# match on the whole /containers tree — once POST is globally unlocked
|
|
# (required here for restart/exec/pull/tag), CONTAINERS=1 alone would
|
|
# also let POST /containers/create through, which defeats the entire
|
|
# point of this proxy (an attacker who compromises the API could create
|
|
# a brand-new container with a host bind mount and escape to the host,
|
|
# the same class of privesc that raw docker.sock access has always
|
|
# allowed). Explicitly deny the dangerous, unused-by-ARGUS container
|
|
# lifecycle endpoints *before* the generic CONTAINERS allow rule below,
|
|
# regardless of which category env vars are set:
|
|
http-request deny if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/create(\?.*)?$ }
|
|
http-request deny if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/prune(\?.*)?$ }
|
|
http-request deny if METH_DELETE { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+$ }
|
|
|
|
# Allowed endpoints
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/((stop)|(restart)|(kill)) } { env(ALLOW_RESTARTS) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/start } { env(ALLOW_START) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/stop } { env(ALLOW_STOP) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/auth } { env(AUTH) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/build } { env(BUILD) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/commit } { env(COMMIT) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/configs } { env(CONFIGS) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers } { env(CONTAINERS) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/distribution } { env(DISTRIBUTION) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/events } { env(EVENTS) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/exec } { env(EXEC) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/grpc } { env(GRPC) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/images } { env(IMAGES) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/info } { env(INFO) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/networks } { env(NETWORKS) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/nodes } { env(NODES) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/_ping } { env(PING) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/plugins } { env(PLUGINS) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/secrets } { env(SECRETS) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/services } { env(SERVICES) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/session } { env(SESSION) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/swarm } { env(SWARM) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/system } { env(SYSTEM) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/tasks } { env(TASKS) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/version } { env(VERSION) -m bool }
|
|
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/volumes } { env(VOLUMES) -m bool }
|
|
http-request deny
|
|
default_backend dockerbackend
|
|
|
|
use_backend docker-events if { path,url_dec -m reg -i ^(/v[\d\.]+)?/events }
|