Task e4a7b18b: Feature 17 review remediation (firewall unit + pinned image/deps) (#218)
This commit was merged in pull request #218.
This commit is contained in:
+6
-2
@@ -14,12 +14,16 @@
|
|||||||
# the SQLite database lives on the mounted volume at
|
# the SQLite database lives on the mounted volume at
|
||||||
# /var/lib/nestquest (compose supplies NESTQUEST_DB_PATH pointing
|
# /var/lib/nestquest (compose supplies NESTQUEST_DB_PATH pointing
|
||||||
# there — configuration is environment-supplied, never baked in).
|
# there — configuration is environment-supplied, never baked in).
|
||||||
FROM python:3.12-slim AS builder
|
#
|
||||||
|
# Both stages use the same base image pinned by digest, and
|
||||||
|
# api/requirements.txt pins exact versions, so a rebuild reproduces
|
||||||
|
# the deployed image instead of floating to whatever is newest.
|
||||||
|
FROM python:3.12-slim@sha256:2f17fc044b579bab302c2e8054d3a686e2cb9a83de48e70534b94cd8ebbe06a9 AS builder
|
||||||
|
|
||||||
COPY api/requirements.txt /tmp/requirements.txt
|
COPY api/requirements.txt /tmp/requirements.txt
|
||||||
RUN pip install --no-cache-dir --prefix=/install -r /tmp/requirements.txt
|
RUN pip install --no-cache-dir --prefix=/install -r /tmp/requirements.txt
|
||||||
|
|
||||||
FROM python:3.12-slim
|
FROM python:3.12-slim@sha256:2f17fc044b579bab302c2e8054d3a686e2cb9a83de48e70534b94cd8ebbe06a9
|
||||||
|
|
||||||
# The staged dependency install from the builder (no pip, no wheels).
|
# The staged dependency install from the builder (no pip, no wheels).
|
||||||
COPY --from=builder /install /usr/local
|
COPY --from=builder /install /usr/local
|
||||||
|
|||||||
+29
-6
@@ -7,9 +7,32 @@
|
|||||||
# runtime dependencies HERE — this file is what the container image
|
# runtime dependencies HERE — this file is what the container image
|
||||||
# installs (see the Dockerfile at the repository root).
|
# installs (see the Dockerfile at the repository root).
|
||||||
#
|
#
|
||||||
# Compatible ranges mirroring the dev group's floors; the API is a
|
# EXACT pins: the versions installed in the deployed image
|
||||||
# server, so these are the only packages its runtime needs.
|
# (nestquest-api:latest on the API box, read with importlib.metadata),
|
||||||
fastapi>=0.115
|
# so a rebuild resolves the same packages. The four direct
|
||||||
uvicorn>=0.30
|
# dependencies come first; the rest is their resolved transitive
|
||||||
pyjwt[crypto]>=2.9
|
# closure, pinned too so nothing floats. The dev group's floors in
|
||||||
httpx>=0.28
|
# pyproject.toml are unchanged. Bump deliberately, together with the
|
||||||
|
# Dockerfile's digest-pinned base image.
|
||||||
|
fastapi==0.141.1
|
||||||
|
uvicorn==0.53.0
|
||||||
|
pyjwt[crypto]==2.15.0
|
||||||
|
httpx==0.28.1
|
||||||
|
|
||||||
|
# Transitive dependencies of the above.
|
||||||
|
annotated-doc==0.0.5
|
||||||
|
annotated-types==0.8.0
|
||||||
|
anyio==4.15.1
|
||||||
|
certifi==2026.7.22
|
||||||
|
cffi==2.1.1
|
||||||
|
click==8.5.0
|
||||||
|
cryptography==50.0.1
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
idna==3.20
|
||||||
|
pycparser==3.0
|
||||||
|
pydantic==2.13.5
|
||||||
|
pydantic_core==2.46.5
|
||||||
|
starlette==1.7.0
|
||||||
|
typing-inspection==0.4.4
|
||||||
|
typing_extensions==4.16.0
|
||||||
|
|||||||
+16
-6
@@ -266,8 +266,16 @@ committed `deploy/litestream.yml` (`host-key`, the NAS's ECDSA key), so no
|
|||||||
-A DOCKER-USER -p tcp --dport 8080 -j DROP # every other source
|
-A DOCKER-USER -p tcp --dport 8080 -j DROP # every other source
|
||||||
|
|
||||||
The rules are persisted by the systemd oneshot unit
|
The rules are persisted by the systemd oneshot unit
|
||||||
**`nestquest-api-firewall.service`** on the API box (enabled; ordered
|
**`nestquest-api-firewall.service`**, committed as
|
||||||
after `docker.service`, it applies the three rules at every boot).
|
`deploy/systemd/nestquest-api-firewall.service` (ordered after
|
||||||
|
`docker.service`, it idempotently applies the three rules at every
|
||||||
|
boot). Install and enable it on the API box, from the repository
|
||||||
|
checkout, **before** exposing the API:
|
||||||
|
|
||||||
|
sudo cp deploy/systemd/nestquest-api-firewall.service /etc/systemd/system/ \
|
||||||
|
&& sudo systemctl daemon-reload \
|
||||||
|
&& sudo systemctl enable --now nestquest-api-firewall.service
|
||||||
|
|
||||||
The HA box and every other client must use the Traefik hostname,
|
The HA box and every other client must use the Traefik hostname,
|
||||||
never `:8080` directly. Check it:
|
never `:8080` directly. Check it:
|
||||||
|
|
||||||
@@ -282,10 +290,12 @@ committed `deploy/litestream.yml` (`host-key`, the NAS's ECDSA key), so no
|
|||||||
# public path still works:
|
# public path still works:
|
||||||
curl -s -o /dev/null -w '%{http_code}\n' https://nestquest.cubecraftlabs.com/health # 200
|
curl -s -o /dev/null -w '%{http_code}\n' https://nestquest.cubecraftlabs.com/health # 200
|
||||||
|
|
||||||
On a rebuilt API box, recreate and enable the unit before exposing the
|
On a rebuilt API box, install the unit with the commands above before
|
||||||
API (`sudo systemctl enable --now nestquest-api-firewall.service`); the
|
exposing the API. The committed file is the authoritative copy; the
|
||||||
deployed unit is the authoritative copy
|
deployed unit must match it:
|
||||||
(`systemctl cat nestquest-api-firewall.service`).
|
|
||||||
|
diff deploy/systemd/nestquest-api-firewall.service \
|
||||||
|
/etc/systemd/system/nestquest-api-firewall.service # no output
|
||||||
|
|
||||||
## 7. Backup (Litestream to the Synology NAS)
|
## 7. Backup (Litestream to the Synology NAS)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ services:
|
|||||||
# (uid/gid 999, owner of the volume) with a read-only root filesystem,
|
# (uid/gid 999, owner of the volume) with a read-only root filesystem,
|
||||||
# no privilege escalation and no Linux capabilities.
|
# no privilege escalation and no Linux capabilities.
|
||||||
litestream:
|
litestream:
|
||||||
image: litestream/litestream:0.5.17
|
image: litestream/litestream:0.5.17@sha256:4b02b9859a6b6b4087d8b8944e15f7e984bd7957cba322bbeee38b0e27b9656a
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ["replicate", "-config", "/etc/litestream.yml"]
|
command: ["replicate", "-config", "/etc/litestream.yml"]
|
||||||
user: "999:999"
|
user: "999:999"
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Restrict NestQuest API port 8080 to Traefik (DOCKER-USER)
|
||||||
|
After=docker.service
|
||||||
|
Requires=docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
ExecStart=/bin/sh -c "iptables -C DOCKER-USER -s 10.60.1.33/32 -p tcp --dport 8080 -j RETURN 2>/dev/null || iptables -I DOCKER-USER 1 -s 10.60.1.33 -p tcp --dport 8080 -j RETURN; iptables -C DOCKER-USER -s 10.60.1.14/32 -p tcp --dport 8080 -j RETURN 2>/dev/null || iptables -I DOCKER-USER 2 -s 10.60.1.14 -p tcp --dport 8080 -j RETURN; iptables -C DOCKER-USER -p tcp --dport 8080 -j DROP 2>/dev/null || iptables -A DOCKER-USER -p tcp --dport 8080 -j DROP"
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in New Issue
Block a user