Compare commits
3 Commits
6a50e6db4d
...
3b92813491
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b92813491 | ||
|
|
7cab210674 | ||
|
|
24cb52d992 |
@@ -31,9 +31,20 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
env:
|
env:
|
||||||
SITE_PORT: "8080"
|
# The runner shares the host's Docker daemon, so every published port is
|
||||||
KANBAN_PORT: "8081"
|
# taken on the machine itself. Known occupants of that host:
|
||||||
API_PORT: "8000"
|
# 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
|
COMPOSE: docker compose -f compose.local.yaml
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- 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,
|
`kanban/main.py` explicitly designed separation of duties (Mayana classifies,
|
||||||
Thales/Alexandre authorise). Needs real per-person accounts with roles.
|
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
|
Downgraded 2026-09-21. The stack publishes plain HTTP on 18080/18081 while
|
||||||
silently dropped unless something external terminates TLS. Nothing in the repo
|
`COOKIE_SECURE: "true"`, and nothing in the repo provisions certificates — but
|
||||||
provisions certificates; `TAREFAS.md` A2 still lists it as pending.
|
`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)`
|
### `[ ]` 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
|
nginx 1.31.6. With both images at zero CRITICAL, the image scan now **gates on
|
||||||
CRITICAL** and reports HIGH.
|
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
|
### Reporting
|
||||||
|
|
||||||
- `[x]` Week-1 client report (`Relatorio-Semana-1-DTF.docx`), corrected 2026-09-18 to
|
- `[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
|
APP_ENV: local
|
||||||
DATABASE_URL: postgresql://${APP_DB_USER:-dtf_app}:${APP_DB_PASSWORD:-local-app-database-only}@db:5432/${POSTGRES_DB:-dtf_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_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}
|
S3_BUCKET: ${S3_BUCKET:-dtf-local-artwork}
|
||||||
AWS_ACCESS_KEY_ID: ${S3_APP_USER:-dtf_app}
|
AWS_ACCESS_KEY_ID: ${S3_APP_USER:-dtf_app}
|
||||||
AWS_SECRET_ACCESS_KEY: ${S3_APP_PASSWORD:-local-app-storage-only}
|
AWS_SECRET_ACCESS_KEY: ${S3_APP_PASSWORD:-local-app-storage-only}
|
||||||
@@ -74,7 +74,9 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-dtf_local}
|
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-dtf_local}
|
||||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-local-storage-only}
|
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]
|
volumes: [storage-data:/data]
|
||||||
networks: [local, edge]
|
networks: [local, edge]
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -165,9 +167,13 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: local/Dockerfile.web
|
dockerfile: local/Dockerfile.web
|
||||||
environment:
|
environment:
|
||||||
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:-http://localhost:9000}
|
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:-http://localhost:${STORAGE_PORT:-9000}}
|
||||||
ports:
|
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"
|
- "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"
|
- "127.0.0.1:${API_PORT:-8000}:81"
|
||||||
networks: [local, edge]
|
networks: [local, edge]
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -184,7 +190,7 @@ services:
|
|||||||
dockerfile: local/Dockerfile.web
|
dockerfile: local/Dockerfile.web
|
||||||
environment:
|
environment:
|
||||||
WEB_INDEX: kanban.html
|
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"]
|
ports: ["127.0.0.1:${KANBAN_PORT:-8081}:80"]
|
||||||
networks: [local, edge]
|
networks: [local, edge]
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -8,6 +8,17 @@ server {
|
|||||||
# unavailable or still creating its database schema.
|
# unavailable or still creating its database schema.
|
||||||
resolver 127.0.0.11 ipv6=off valid=10s;
|
resolver 127.0.0.11 ipv6=off valid=10s;
|
||||||
set $api_upstream api:8000;
|
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;
|
root /usr/share/nginx/html;
|
||||||
index ${WEB_INDEX};
|
index ${WEB_INDEX};
|
||||||
|
|
||||||
@@ -29,6 +40,7 @@ server {
|
|||||||
proxy_set_header X-Forwarded-Proto https;
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
# Overwrite, never append: $proxy_add_x_forwarded_for keeps any header the
|
# Overwrite, never append: $proxy_add_x_forwarded_for keeps any header the
|
||||||
# client sent, and the leftmost value would then be attacker-controlled.
|
# 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_set_header X-Forwarded-For $remote_addr;
|
||||||
proxy_connect_timeout 5s;
|
proxy_connect_timeout 5s;
|
||||||
proxy_read_timeout 30s;
|
proxy_read_timeout 30s;
|
||||||
|
|||||||
Reference in New Issue
Block a user