feat(R4b): reproducible builds — pin requirements.lock checksum
- Dockerfile: build-arg REQUIREMENTS_CHECKSUM verifica lo sha256 del lockfile (fallisce la build se non coincide; vuoto = skip locale) - docker-compose.yml: default = hash corrente del lock - CI: nuovo job checksum.yml che fallisce se il pin non coincide - Fix YAML preesistente in ci.yml (name: con ':' non quotati) - Docs: README, .env.example, review aggiornati
This commit is contained in:
1 parent
0f15b7958f
commit
12d2018ac5
7 files changed
+78
-10
No files matched your search
@@ -11,3 +11,10 @@ BOOKISH_CLEANUP_HOURS=1
|
||||
|
||||
# RATE LIMIT upload per IP/ora (0 = disabilitato, default 20)
|
||||
#BOOKISH_RATE_LIMIT_UPLOAD=20
|
||||
|
||||
# BUILD REPRODUCIBILE (R4b): sha256 di requirements.lock. Il default in
|
||||
# docker-compose.yml è già il pin corrente; in dev sovrascrivi con:
|
||||
# REQUIREMENTS_CHECKSUM="" → skippa il check in build locale
|
||||
# quando aggiorni le dipendenze (pip-compile) aggiorna anche il pin:
|
||||
# sed -i "s/[a-f0-9]\{64\}/$(sha256sum requirements.lock | awk '{print $1}')/" docker-compose.yml
|
||||
#REQUIREMENTS_CHECKSUM=d13e4fb14a909e0bd06d20894860152d5334aa341dd9dec2e5e4ca368a0d2a62
|
||||
@@ -0,0 +1,34 @@
|
||||
name: deps-checksum
|
||||
|
||||
# R4b: il lockfile è "pinnato" per costruzioni riproducibili. Questo job
|
||||
# fallisce se l'hash in docker-compose.yml non coincide con requirements.lock.
|
||||
# Quando aggiorni le dipendenze:
|
||||
# pip-compile --output-file=requirements.lock requirements.in
|
||||
# sed -i "s/d13e4fb14a909e0bd06d20894860152d5334aa341dd9dec2e5e4ca368a0d2a62/$(sha256sum requirements.lock | awk '{print $1}')/" docker-compose.yml
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
checksum:
|
||||
runs-on: ubuntu-latest
|
||||
name: checksum lockfile
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Calcola hash attuale
|
||||
id: hash
|
||||
run: |
|
||||
H=$(sha256sum requirements.lock | awk '{print $1}')
|
||||
echo "hash=$H" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: hash pinned in docker-compose.yml
|
||||
run: |
|
||||
PIN=$(grep -oP 'REQUIREMENTS_CHECKSUM: \$\{REQUIREMENTS_CHECKSUM:-\K[a-f0-9]{64}' docker-compose.yml | head -1)
|
||||
[ -n "$PIN" ] || { echo "hash non pinnato in docker-compose.yml"; exit 1; }
|
||||
echo "pinned=$PIN"
|
||||
echo "atteso=${{ steps.hash.outputs.hash }}"
|
||||
[ "$PIN" = "${{ steps.hash.outputs.hash }}" ] || { echo "checksum non aggiornato — rivedi docker-compose.yml"; exit 1; }
|
||||
echo "checksum OK"
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
docker compose logs bookish
|
||||
exit 1
|
||||
|
||||
- name: Smoke: upload epub -> epub (conversione, SSE)
|
||||
- name: "Smoke: upload epub -> epub (conversione, SSE)"
|
||||
run: |
|
||||
# finto epub (zip minimo vuoto accettato per estensione; la conversione
|
||||
# può fallire per formato, ma l'upload deve tornare 200 con task_id)
|
||||
@@ -67,11 +67,11 @@ jobs:
|
||||
echo "$RESP"
|
||||
echo "$RESP" | grep -q '"task_id"' || { echo "task_id mancante"; exit 1; }
|
||||
|
||||
- name: Smoke: CSP header presente
|
||||
- name: "Smoke: CSP header presente"
|
||||
run: |
|
||||
curl -sI http://127.0.0.1:5000/ | grep -q "Content-Security-Policy" || { echo "CSP assente"; exit 1; }
|
||||
|
||||
- name: Smoke: auth opzionale funziona (override)
|
||||
- name: "Smoke: auth opzionale funziona (override)"
|
||||
run: |
|
||||
cat > /tmp/override.yml << 'EOF'
|
||||
services:
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
FROM python:3.12-slim-bookworm AS builder
|
||||
|
||||
# Build riproducibile (R4b): se REQUIREMENTS_CHECKSUM è valorizzato, la
|
||||
# build fallisce se requirements.lock non coincide — nessuna dipendenza può
|
||||
# cambiare senza aggiornare il pin. Genera con:
|
||||
# sha256sum requirements.lock | awk '{print $1}'
|
||||
ARG REQUIREMENTS_CHECKSUM=""
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY requirements.txt requirements.lock ./
|
||||
RUN test -z "$REQUIREMENTS_CHECKSUM" \
|
||||
-o "$(sha256sum requirements.lock | awk '{print $1}')" = "$REQUIREMENTS_CHECKSUM" \
|
||||
|| { echo "FATAL: requirements.lock non corrisponde a REQUIREMENTS_CHECKSUM" >&2; exit 1; }
|
||||
RUN python -m venv /opt/venv \
|
||||
&& /opt/venv/bin/pip install --no-cache-dir --upgrade pip \
|
||||
&& /opt/venv/bin/pip install --no-cache-dir -r requirements.lock
|
||||
|
||||
@@ -61,6 +61,17 @@ pip install pip-tools
|
||||
pip-compile --strip-extras --no-hash --output-file requirements.lock requirements.txt
|
||||
```
|
||||
|
||||
La build Docker verifica che `requirements.lock` coincida con l'hash pinnato
|
||||
in `docker-compose.yml` (`REQUIREMENTS_CHECKSUM`, R4b): se aggiorni le
|
||||
dipendenze, aggiorna anche il pin:
|
||||
|
||||
```bash
|
||||
sed -i "s/[a-f0-9]\{64\}/$(sha256sum requirements.lock | awk '{print $1}')/" docker-compose.yml
|
||||
```
|
||||
|
||||
(trova la riga `REQUIREMENTS_CHECKSUM:` — primo hash del file). La CI
|
||||
(`.github/workflows/checksum.yml`) fallisce se il pin non coincide col lock.
|
||||
|
||||
## Note sul container
|
||||
|
||||
- Gunicorn, 1 worker + thread: i task SSE vivono in memoria del processo.
|
||||
|
||||
+6
-1
@@ -1,6 +1,11 @@
|
||||
services:
|
||||
bookish:
|
||||
build: .
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
# Riproducibilita (R4b): sha256 di requirements.lock. Default = hash
|
||||
# corrente; in dev puoi sovrascriverlo con REQUIREMENTS_CHECKSUM=""
|
||||
REQUIREMENTS_CHECKSUM: ${REQUIREMENTS_CHECKSUM:-d13e4fb14a909e0bd06d20894860152d5334aa341dd9dec2e5e4ca368a0d2a62}
|
||||
image: bookish:local
|
||||
ports:
|
||||
- "${PORT:-5000}:5000"
|
||||
|
||||
@@ -21,8 +21,9 @@ vanno chiuse prima di considerare il progetto "pronto" per un uso reale.
|
||||
Esito revisione: **❌ non pronta per produzione — servono correzioni**.
|
||||
|
||||
> Aggiornamento (sessione corrente): la P0 è chiusa (**R1, R2, R3, R6**) e
|
||||
> parte della P1 (**R4** lock + **CI** smoke test + **self-host JS offline**).
|
||||
> Restano P1 R4b e P2 (R5, R7).
|
||||
> parte della P1 (**R4** lock + **CI** smoke test + **self-host JS offline** +
|
||||
> **R4b** checksum lock nel Dockerfile/CI).
|
||||
> Restano P2 (R5, R7).
|
||||
|
||||
---
|
||||
|
||||
@@ -227,10 +228,10 @@ rimosse, trust proxy ristretto via env.
|
||||
| R1 | Reader 404 su file convertiti | Alta | Alta | ✅ risolto |
|
||||
| R2 | Disk/CPU DoS via upload non autenticato | Alta | Media | ✅ mitigato (auth+rate) |
|
||||
| R3 | XSS da file HTML malevoli | Alta | Bassa | ✅ mitigato (CSP+DOMPurify) |
|
||||
| R4 | Build non riproducibile | Media | Media | ✅ lockfile |
|
||||
| R4 | Build non riproducibile | Media | Media | ✅ lockfile + checksum pinned |
|
||||
| R5 | Errori timeout conversioni lunghe | Media | Media | ⏳ P2 |
|
||||
| R6 | Trust proxy / opzioni fantasma | Bassa | Media | ✅ risolto |
|
||||
| R7 | /tmp tmpfs vs conversioni grandi | Bassa | Bassa | ⏳ P1 |
|
||||
| R7 | /tmp tmpfs vs conversioni grandi | Bassa | Bassa | ⏳ P2 |
|
||||
|
||||
---
|
||||
|
||||
@@ -247,8 +248,9 @@ rimosse, trust proxy ristretto via env.
|
||||
✅ `requirements.lock` (pip-compile, 112 righe) + Dockerfile lo usa.
|
||||
- [x] Self-hosting dei JS frontend (per offline).
|
||||
✅ epub.js/pdf.js+worker/JSZip vendored in `static/vendor/`, CSP senza CDN.
|
||||
- [ ] **R4b** `git` revision pin nel Dockerfile build-arg `--build-arg
|
||||
requirements_checksum` (advanced).
|
||||
- [x] **R4b** Hash del lockfile pinnato nel Dockerfile (build-arg
|
||||
`REQUIREMENTS_CHECKSUM`, default in compose) + job CI di verifica
|
||||
(`.github/workflows/checksum.yml`). ✅ fatto
|
||||
- [x] **R6** Cleanup gunicorn.conf (opzioni inesistenti; `forwarded_allow_ips`
|
||||
solo per proxy fidato noto). ✅ già fatto
|
||||
- [x] Smoke test Docker nella CI (`docker build` + `docker compose up` +
|
||||
|
||||
Reference in new issue
Block a user