harden: run unprivileged, read-only rootfs, no capabilities
The app is the only process on this box accepting unauthenticated input from the internet and it was running as root in a writable container. Now uid 10001, read_only with a tmpfs /tmp, cap_drop ALL and no-new-privileges. Host-side /srv/scout-website/data and the service account key are chowned to 10001. Verified on a throwaway container: every route, the admin API, spam rejection, and a real submission writing to both the jsonl and SQLite.
This commit is contained in:
+4
-1
@@ -2,5 +2,8 @@ FROM python:3.12-slim
|
|||||||
RUN pip install --no-cache-dir fastapi "uvicorn[standard]" python-multipart gspread
|
RUN pip install --no-cache-dir fastapi "uvicorn[standard]" python-multipart gspread
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . /app
|
COPY . /app
|
||||||
RUN mkdir -p /app/static/img
|
RUN mkdir -p /app/static/img && useradd -r -u 10001 app
|
||||||
|
# Unprivileged: nothing here needs root, and the process is the one thing on
|
||||||
|
# this box that takes unauthenticated input from the internet.
|
||||||
|
USER 10001
|
||||||
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
|
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./app
|
context: ./app
|
||||||
container_name: scout-website
|
container_name: scout-website
|
||||||
|
read_only: true
|
||||||
|
tmpfs:
|
||||||
|
- /tmp
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
logging:
|
logging:
|
||||||
driver: json-file
|
driver: json-file
|
||||||
|
|||||||
Reference in New Issue
Block a user