Compare commits
3 Commits
6a50e6db4d
...
3b92813491
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b92813491 | ||
|
|
7cab210674 | ||
|
|
24cb52d992 |
@@ -31,9 +31,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
SITE_PORT: "8080"
|
||||
KANBAN_PORT: "8081"
|
||||
API_PORT: "8000"
|
||||
# The runner shares the host's Docker daemon, so every published port is
|
||||
# taken on the machine itself. Known occupants of that host:
|
||||
# 8000, 9443 Portainer (the Edge tunnel and its UI)
|
||||
# 18080/18081 the production dtf-cloud stack (docker-compose.yml defaults)
|
||||
# 8080/8081 deploy/stack.yaml defaults
|
||||
# 9000/9001 MinIO defaults elsewhere
|
||||
# This block avoids all of them. Ephemeral ports are not an option: the
|
||||
# published port is baked into PUBLIC_ORIGIN, ALLOWED_ORIGINS and the CSP
|
||||
# when the containers start, so it has to be known beforehand.
|
||||
SITE_PORT: "28080"
|
||||
KANBAN_PORT: "28081"
|
||||
API_PORT: "28000"
|
||||
STORAGE_PORT: "29000"
|
||||
STORAGE_CONSOLE_PORT: "29001"
|
||||
COMPOSE: docker compose -f compose.local.yaml
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
26
ROADMAP.md
26
ROADMAP.md
@@ -240,11 +240,18 @@ records the same name for everyone. The meeting asked for traceability, and the
|
||||
`kanban/main.py` explicitly designed separation of duties (Mayana classifies,
|
||||
Thales/Alexandre authorise). Needs real per-person accounts with roles.
|
||||
|
||||
### `[ ]` 2.9 — No TLS in the stack `(F13)`
|
||||
### `[~]` 2.9 — TLS is terminated outside the repository `(F13)`
|
||||
|
||||
Ports publish plain HTTP on 18080/18081 while `COOKIE_SECURE: "true"` — cookies are
|
||||
silently dropped unless something external terminates TLS. Nothing in the repo
|
||||
provisions certificates; `TAREFAS.md` A2 still lists it as pending.
|
||||
Downgraded 2026-09-21. The stack publishes plain HTTP on 18080/18081 while
|
||||
`COOKIE_SECURE: "true"`, and nothing in the repo provisions certificates — but
|
||||
`nginx-proxy-manager` on the host owns 80/443 and terminates TLS in front of it,
|
||||
so cookies are not being dropped in practice. This is undocumented operational
|
||||
knowledge rather than a live defect.
|
||||
|
||||
What remains: record the proxy in `PORTAINER.md` as part of the deployment
|
||||
contract, so nobody moves the stack to a host without one and silently breaks
|
||||
every session cookie. `TAREFAS.md` A2 still lists the certificate as pending;
|
||||
confirm it is actually issued for the DTF subdomain.
|
||||
|
||||
### `[ ]` 2.10 — No email verification, no password recovery `(F14)`
|
||||
|
||||
@@ -463,6 +470,17 @@ charges. Fix as part of 1.1.
|
||||
nginx 1.31.6. With both images at zero CRITICAL, the image scan now **gates on
|
||||
CRITICAL** and reports HIGH.
|
||||
|
||||
### 2026-09-21 — from the runner host inventory
|
||||
|
||||
- `[x]` Fixed a regression in 2.1: the production gateway sits behind
|
||||
`nginx-proxy-manager`, so `$remote_addr` there is the proxy, not the customer.
|
||||
Overwriting `X-Forwarded-For` with it would have recorded the proxy's address for
|
||||
every request in production — the same bug 2.1 set out to fix. The gateway now
|
||||
uses `real_ip` to recover the customer's address from the proxy's header, trusting
|
||||
only private networks, so a request arriving directly at the published port
|
||||
cannot spoof it. Validated with `nginx -t` against the rendered config.
|
||||
- `[~]` 2.9 downgraded: TLS is terminated by that proxy, not missing.
|
||||
|
||||
### Reporting
|
||||
|
||||
- `[x]` Week-1 client report (`Relatorio-Semana-1-DTF.docx`), corrected 2026-09-18 to
|
||||
|
||||
@@ -14,7 +14,7 @@ x-app: &app
|
||||
APP_ENV: local
|
||||
DATABASE_URL: postgresql://${APP_DB_USER:-dtf_app}:${APP_DB_PASSWORD:-local-app-database-only}@db:5432/${POSTGRES_DB:-dtf_local}
|
||||
S3_ENDPOINT: http://storage:9000
|
||||
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:-http://localhost:9000}
|
||||
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:-http://localhost:${STORAGE_PORT:-9000}}
|
||||
S3_BUCKET: ${S3_BUCKET:-dtf-local-artwork}
|
||||
AWS_ACCESS_KEY_ID: ${S3_APP_USER:-dtf_app}
|
||||
AWS_SECRET_ACCESS_KEY: ${S3_APP_PASSWORD:-local-app-storage-only}
|
||||
@@ -74,7 +74,9 @@ services:
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-dtf_local}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-local-storage-only}
|
||||
ports: ["127.0.0.1:9000:9000", "127.0.0.1:9001:9001"]
|
||||
ports:
|
||||
- "127.0.0.1:${STORAGE_PORT:-9000}:9000"
|
||||
- "127.0.0.1:${STORAGE_CONSOLE_PORT:-9001}:9001"
|
||||
volumes: [storage-data:/data]
|
||||
networks: [local, edge]
|
||||
healthcheck:
|
||||
@@ -165,9 +167,13 @@ services:
|
||||
context: .
|
||||
dockerfile: local/Dockerfile.web
|
||||
environment:
|
||||
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:-http://localhost:9000}
|
||||
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:-http://localhost:${STORAGE_PORT:-9000}}
|
||||
ports:
|
||||
# Published ports are host-wide even bound to loopback, so on a shared
|
||||
# machine any of them can collide with something unrelated. CI overrides
|
||||
# every one; see .gitea/workflows/deploy.yml.
|
||||
- "127.0.0.1:${SITE_PORT:-8080}:80"
|
||||
# Convenience only: the API through its own gateway. No test uses it.
|
||||
- "127.0.0.1:${API_PORT:-8000}:81"
|
||||
networks: [local, edge]
|
||||
depends_on:
|
||||
@@ -184,7 +190,7 @@ services:
|
||||
dockerfile: local/Dockerfile.web
|
||||
environment:
|
||||
WEB_INDEX: kanban.html
|
||||
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:-http://localhost:9000}
|
||||
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:-http://localhost:${STORAGE_PORT:-9000}}
|
||||
ports: ["127.0.0.1:${KANBAN_PORT:-8081}:80"]
|
||||
networks: [local, edge]
|
||||
depends_on:
|
||||
|
||||
@@ -8,6 +8,17 @@ server {
|
||||
# unavailable or still creating its database schema.
|
||||
resolver 127.0.0.11 ipv6=off valid=10s;
|
||||
set $api_upstream api:8000;
|
||||
|
||||
# This gateway sits behind the host's reverse proxy, so $remote_addr is that
|
||||
# proxy, not the customer. Recover the real address from the header it sets,
|
||||
# and only when the connection comes from a private network: a request that
|
||||
# reaches the published port directly from the internet is not trusted, so
|
||||
# its X-Forwarded-For is ignored and $remote_addr stays the actual peer.
|
||||
set_real_ip_from 10.0.0.0/8;
|
||||
set_real_ip_from 172.16.0.0/12;
|
||||
set_real_ip_from 192.168.0.0/16;
|
||||
real_ip_header X-Forwarded-For;
|
||||
real_ip_recursive on;
|
||||
root /usr/share/nginx/html;
|
||||
index ${WEB_INDEX};
|
||||
|
||||
@@ -29,6 +40,7 @@ server {
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
# Overwrite, never append: $proxy_add_x_forwarded_for keeps any header the
|
||||
# client sent, and the leftmost value would then be attacker-controlled.
|
||||
# After real_ip above, $remote_addr is the customer even behind the proxy.
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_read_timeout 30s;
|
||||
|
||||
Reference in New Issue
Block a user