Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ Human-readable summary of user-facing changes. **Detailed release notes:** [docs

## Unreleased (`main`)

Empty while the working tree carries **`1.8.0-rc`** (see section below).
Migration notes on the **`1.8.0-rc`** working line (no new semver cut):

- **FastAPI floor (#2089):** `fastapi>=0.143.0`. Native OpenTelemetry auto-config is opt-in (`FASTAPI_OTEL_AUTO_CONFIGURE=true` or `telemetry={"auto_configure": True}`). The app passes `telemetry={"auto_configure": False}` and still exports only when `DATA_BOAR_OTEL_ENABLED` is set.
- **Credential env names (#2086):** `pass_from_env`, `password_from_env`, `user_from_env`, `token_from_env`, and `client_secret_from_env` fail at load when the name is outside the `DATA_BOAR_` / `REST_API_` / `API_` prefixes, or when the destination is missing from that target’s `allowed_hosts`. Shipped examples now use prefixed names (`DATA_BOAR_DEMO_DB_PASSWORD`, `DATA_BOAR_LAB_*`).
- **`GET /health` (#2077):** the unauthenticated body is only `{"status":"ok"}`. License, trust, transport, and integrity moved to authenticated `GET /status`.
- **Notification `public_base_url` (#2087):** `public_base_url: ${VAR}` raises `ValueError` and is not expanded. Other notification `${VAR}` values still expand only for the same prefixes.

---

Expand Down
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,13 @@ The application adds the following headers to all web and API responses by defau
- **Permissions-Policy** – disables browser features not needed by the app (camera, microphone, geolocation, etc.).
- **Strict-Transport-Security (HSTS)** – set only when the request is considered HTTPS (direct or via `X-Forwarded-Proto: https` from a trusted proxy), so HTTP-only deployments are not locked out. When present, it uses `max-age=31536000; includeSubDomains; preload`.

When the app is behind a reverse proxy (e.g. nginx, Caddy, load balancer), ensure the proxy sets **X-Forwarded-Proto: https** for TLS-terminated requests so HSTS is applied correctly. Configure **`api.trusted_proxy_cidrs`** to the CIDR(s) of the **direct** proxy peer; without a match, `X-Forwarded-*` headers are ignored (fail-safe). When the peer matches and the forwarded proto is trusted `https`, the dashboard suppresses the plaintext-risk banner for that request and **`GET /status`** / **`GET /health`** expose **`effective_external_transport`** with `tls_termination: trusted_proxy` — process-level **`dashboard_transport`** still reports upstream HTTP honestly. Do not enable HSTS at the app layer for plain HTTP; the proxy can add HSTS when serving over HTTPS.
When the app is behind a reverse proxy (e.g. nginx, Caddy, load balancer), ensure the proxy sets **X-Forwarded-Proto: https** for TLS-terminated requests so HSTS is applied correctly. Configure **`api.trusted_proxy_cidrs`** to the CIDR(s) of the **direct** proxy peer; without a match, `X-Forwarded-*` headers are ignored (fail-safe). When the peer matches and the forwarded proto is trusted `https`, the dashboard suppresses the plaintext-risk banner for that request and **`GET /status`** exposes **`effective_external_transport`** with `tls_termination: trusted_proxy` — process-level **`dashboard_transport`** still reports upstream HTTP honestly. Do not enable HSTS at the app layer for plain HTTP; the proxy can add HSTS when serving over HTTPS.

## Optional API key (enterprise)

The API does not implement authentication by default; secure the app at the reverse proxy or network level when exposed. For enterprises that want a simple shared-secret gate without changing the “secure at proxy” model, the application supports an **optional API key**:

- In config, set `api.require_api_key: true` and either `api.api_key` (literal — avoid committing secrets) or `api.api_key_from_env: "VAR"` (read key from environment at startup). When enabled, **GET /health** stays **unauthenticated** on purpose: it returns liveness JSON (`status`, public `license` summary, `dashboard_transport`) for probes. **Every other route** must include **X-API-Key** or **Authorization: Bearer <key>** when a key is successfully resolved from config/env. **401** = missing or wrong key. **503** = `require_api_key` is true but no key could be resolved (misconfiguration). **`main.py --web` exits with code 2** before listening if the key is required but missing, so you do not accidentally run an open API.
- In config, set `api.require_api_key: true` and either `api.api_key` (literal — avoid committing secrets) or `api.api_key_from_env: "VAR"` (read key from environment at startup). When enabled, **GET /health** stays **unauthenticated** on purpose: it returns liveness JSON `{"status":"ok"}` for probes (#2077). License, trust, and `dashboard_transport` are on **GET /status**. **Every other route** must include **X-API-Key** or **Authorization: Bearer <key>** when a key is successfully resolved from config/env. **401** = missing or wrong key. **503** = `require_api_key` is true but no key could be resolved (misconfiguration). **`main.py --web` exits with code 2** before listening if the key is required but missing, so you do not accidentally run an open API.
- **Good practice:** Use a strong, random key and store it in an environment variable (e.g. `api_key_from_env: "AUDIT_API_KEY"`). Do not log the key or commit it to version control. This is a simple gate only; for full authentication and authorization, continue to use the reverse proxy or an identity provider.
- **Concrete operator steps** (shell, systemd, Docker/K8s patterns, `curl` checks, synthetic example key): [API_KEY_FROM_ENV_OPERATOR_STEPS.md](docs/ops/API_KEY_FROM_ENV_OPERATOR_STEPS.md). For ordering (inventory clients, staging first, monitor `dashboard_transport` and audit export), see [SECURE_BY_DEFAULT_BLOCKERS_AND_MIGRATION.md](docs/ops/SECURE_BY_DEFAULT_BLOCKERS_AND_MIGRATION.md).
- **End-to-end technician guide (API key + TLS paths, Let’s Encrypt, lab self-signed, Docker):** [SECURE_DASHBOARD_AUTH_AND_HTTPS_HOWTO.md](docs/ops/SECURE_DASHBOARD_AUTH_AND_HTTPS_HOWTO.md) ([pt-BR](docs/ops/SECURE_DASHBOARD_AUTH_AND_HTTPS_HOWTO.pt_BR.md)).
Expand Down
4 changes: 2 additions & 2 deletions SECURITY.pt_BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,13 @@ A aplicação adiciona os seguintes cabeçalhos a todas as respostas web e da AP
- **Permissions-Policy** — desabilita recursos do navegador não usados pelo app (câmera, microfone, geolocalização, etc.).
- **Strict-Transport-Security (HSTS)** — definido apenas quando a requisição é considerada HTTPS (direta ou via `X-Forwarded-Proto: https` de um proxy confiável), para que implantações somente HTTP não fiquem bloqueadas. Quando presente, usa `max-age=31536000; includeSubDomains; preload`.

Quando o app estiver atrás de um proxy reverso (ex.: nginx, Caddy, load balancer), garanta que o proxy defina **X-Forwarded-Proto: https** para requisições com TLS terminado para que o HSTS seja aplicado corretamente. Configure **`api.trusted_proxy_cidrs`** com o(s) CIDR(s) do peer **direto** do proxy; sem correspondência, cabeçalhos `X-Forwarded-*` são ignorados (fail-safe). Quando o peer corresponde e o proto forwarded confiável é `https`, o painel suprime o banner de risco de texto claro nessa requisição e **`GET /status`** / **`GET /health`** expõem **`effective_external_transport`** com `tls_termination: trusted_proxy` — o **`dashboard_transport`** a nível de processo continua reportando HTTP upstream com honestidade. Não habilite HSTS na camada do app para HTTP puro; o proxy pode adicionar HSTS ao servir via HTTPS.
Quando o app estiver atrás de um proxy reverso (ex.: nginx, Caddy, load balancer), garanta que o proxy defina **X-Forwarded-Proto: https** para requisições com TLS terminado para que o HSTS seja aplicado corretamente. Configure **`api.trusted_proxy_cidrs`** com o(s) CIDR(s) do peer **direto** do proxy; sem correspondência, cabeçalhos `X-Forwarded-*` são ignorados (fail-safe). Quando o peer corresponde e o proto forwarded confiável é `https`, o painel suprime o banner de risco de texto claro nessa requisição e **`GET /status`** expõe **`effective_external_transport`** com `tls_termination: trusted_proxy` — o **`dashboard_transport`** a nível de processo continua reportando HTTP upstream com honestidade. Não habilite HSTS na camada do app para HTTP puro; o proxy pode adicionar HSTS ao servir via HTTPS.

## Chave de API opcional (empresarial)

A API não implementa autenticação por padrão; proteja o app no proxy reverso ou no nível de rede quando exposto. Para empresas que desejam um gate de segredo compartilhado simples sem alterar o modelo “proteger no proxy”, a aplicação suporta uma **chave de API opcional**:

- No config, defina `api.require_api_key: true` e `api.api_key` (literal — evite commit de segredo) ou `api.api_key_from_env: "VAR"` (lê a chave do ambiente na subida do processo). Quando habilitado, **GET /health** permanece **sem autenticação** de propósito: devolve JSON de liveness (`status`, resumo **público** de `license`, `dashboard_transport`) para probes. **Todas as outras rotas** precisam de **X-API-Key** ou **Authorization: Bearer** quando a chave foi resolvida a partir do config/ambiente. **401** = chave ausente ou inválida. **503** = `require_api_key` true porém nenhuma chave resolvida (misconfig). O **`main.py --web` encerra com código 2** antes de escutar se a chave é obrigatória e está ausente, evitando API aberta por engano.
- No config, defina `api.require_api_key: true` e `api.api_key` (literal — evite commit de segredo) ou `api.api_key_from_env: "VAR"` (lê a chave do ambiente na subida do processo). Quando habilitado, **GET /health** permanece **sem autenticação** de propósito: devolve JSON de liveness `{"status":"ok"}` para probes (#2077). Licença, confiança e `dashboard_transport` ficam em **GET /status**. **Todas as outras rotas** precisam de **X-API-Key** ou **Authorization: Bearer** quando a chave foi resolvida a partir do config/ambiente. **401** = chave ausente ou inválida. **503** = `require_api_key` true porém nenhuma chave resolvida (misconfig). O **`main.py --web` encerra com código 2** antes de escutar se a chave é obrigatória e está ausente, evitando API aberta por engano.
- **Boa prática:** Use uma chave forte e aleatória e armazene-a em uma variável de ambiente (ex.: `api_key_from_env: "AUDIT_API_KEY"`). Não registre em log nem faça commit da chave no controle de versão. Isso é apenas um gate simples; para autenticação e autorização completas, use o proxy reverso ou um provedor de identidade.
- **Passos concretos (EN, para evitar ambiguidade):** nome da variável no YAML vs segredo no SO, precedência, exemplos `curl` — [API_KEY_FROM_ENV_OPERATOR_STEPS.md](docs/ops/API_KEY_FROM_ENV_OPERATOR_STEPS.md). Ordem sugerida (inventário, staging, monitorar `dashboard_transport` / audit export): [SECURE_BY_DEFAULT_BLOCKERS_AND_MIGRATION.pt_BR.md](docs/ops/SECURE_BY_DEFAULT_BLOCKERS_AND_MIGRATION.pt_BR.md).
- **Guia ponta a ponta para técnico (chave de API + caminhos TLS, Let’s Encrypt, autoassinado em lab, Docker):** [SECURE_DASHBOARD_AUTH_AND_HTTPS_HOWTO.pt_BR.md](docs/ops/SECURE_DASHBOARD_AUTH_AND_HTTPS_HOWTO.pt_BR.md) ([EN](docs/ops/SECURE_DASHBOARD_AUTH_AND_HTTPS_HOWTO.md)).
Expand Down
2 changes: 1 addition & 1 deletion api/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@
"run_cli_demo": "Zero-config demo (synthetic config + corpus under /tmp/data_boar_demo/; ignores config.yaml in the CWD; loopback dashboard; GET /logs/{session_id} when audit_logs enabled):",
"run_cli_api": "Start the API / dashBOARd (no scan until you use the browser or HTTP):",
"run_cli_tls": "You must either terminate TLS at the process (--https-cert-file + --https-key-file, or the same under api: in config) or explicitly accept plaintext with --allow-insecure-http (or api.allow_insecure_http: true). The official Docker image passes --allow-insecure-http by default; mount certs and remove it for HTTPS.",
"run_cli_bind": "Bind order when using --web: --host overrides api.host in config and API_HOST; if none are set, the default is 127.0.0.1 (Docker image often sets API_HOST=0.0.0.0). Port: api.port in config vs --port. Transport: GET /status and GET /health include dashboard_transport — see docs/USAGE.md.",
"run_cli_bind": "Bind order when using --web: --host overrides api.host in config and API_HOST; if none are set, the default is 127.0.0.1 (Docker image often sets API_HOST=0.0.0.0). Port: api.port in config vs --port. Transport: GET /status includes dashboard_transport; GET /health is liveness only — see docs/USAGE.md.",
"run_cli_danger": "Dangerous maintenance (lab/demo only): --reset-data wipes SQLite sessions/findings and deletes generated reports under report.output_dir. See SECURITY.md before using in production.",
"run_cli_integrity": "After an official pip/pipx upgrade, re-baseline integrity hashes with --reconcile-integrity-anchor --confirm-upgrade-to=<installed-version> (must match the running package; does not auto-reconcile on semver).",
"run_cli_audit": "Audit trail export (CLI only): --export-audit-trail or --export-audit-trail path.json writes a JSON snapshot (wipe log, session summary; future: integrity rows). Does not modify the database. Omit the path or use - for stdout.",
Expand Down
2 changes: 1 addition & 1 deletion api/locales/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@
"run_cli_demo": "Demonstração zero-config (config + corpus sintéticos em /tmp/data_boar_demo/; ignora o config.yaml do diretório atual; dashboard em loopback; GET /logs/{session_id} com audit_logs habilitado):",
"run_cli_api": "Subir a API / dashBOARd (sem varredura até usar o navegador ou HTTP):",
"run_cli_tls": "É preciso terminar TLS no processo (--https-cert-file + --https-key-file, ou o mesmo em api: na config) ou aceitar explicitamente texto claro com --allow-insecure-http (ou api.allow_insecure_http: true). A imagem Docker oficial passa --allow-insecure-http por padrão; monte certificados e remova para HTTPS.",
"run_cli_bind": "Ordem de bind com --web: --host sobrescreve api.host na config e API_HOST; se nada estiver definido, o padrão é 127.0.0.1 (a imagem Docker costuma definir API_HOST=0.0.0.0). Porta: api.port na config vs --port. Transporte: GET /status e GET /health incluem dashboard_transport — veja docs/USAGE.md.",
"run_cli_bind": "Ordem de bind com --web: --host sobrescreve api.host na config e API_HOST; se nada estiver definido, o padrão é 127.0.0.1 (a imagem Docker costuma definir API_HOST=0.0.0.0). Porta: api.port na config vs --port. Transporte: GET /status inclui dashboard_transport; GET /health é só liveness — veja docs/USAGE.md.",
"run_cli_danger": "Manutenção perigosa (só lab/demo): --reset-data apaga sessões/achados no SQLite e remove relatórios gerados em report.output_dir. Veja SECURITY.md antes de usar em produção.",
"run_cli_integrity": "Depois de um upgrade oficial pip/pipx, re-baseline os hashes de integridade com --reconcile-integrity-anchor --confirm-upgrade-to=<versão-instalada> (tem que casar com o pacote em execução; não reconcilia sozinho por semver).",
"run_cli_audit": "Exportação de trilha de auditoria (só CLI): --export-audit-trail ou --export-audit-trail caminho.json grava um snapshot JSON (log de wipe, resumo de sessão; futuro: linhas de integridade). Não modifica o banco. Omita o caminho ou use - para stdout.",
Expand Down
29 changes: 11 additions & 18 deletions api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class LocaleSlug(str, Enum):


def _integrity_snapshot() -> dict:
"""#856: integrity anchor snapshot for /health and /status (ensure once, lazily)."""
"""#856: integrity anchor snapshot for /status (ensure once, lazily)."""
from core.integrity_anchor import ensure_integrity_anchor, get_integrity_snapshot

snap = get_integrity_snapshot()
Expand Down Expand Up @@ -801,6 +801,10 @@ async def _lifespan(app: FastAPI):
title="LGPD/GDPR/CCPA Audit API",
version=get_about_info()["version"],
lifespan=_lifespan,
# Native OTel is opt-in from FastAPI 0.143 (floor in pyproject). Keep the
# kwarg explicit so a later default cannot export ahead of
# DATA_BOAR_OTEL_ENABLED (core.otel_setup, #1500 / #2089).
telemetry={"auto_configure": False},
)

# OpenTelemetry: opt-in via DATA_BOAR_OTEL_ENABLED (see core/otel_setup.py / #1500).
Expand Down Expand Up @@ -1337,27 +1341,15 @@ async def locale_html_middleware(request: Request, call_next):


@app.get("/health")
async def health(request: Request):
async def health():
"""
Liveness/readiness probe for Docker, Swarm and Kubernetes.

Semantics: **always unauthenticated** (no API key). Returns minimal JSON for
orchestrators; see SECURITY.md / USAGE.md for difference vs protected routes.
Semantics: **always unauthenticated** (no API key). Body is only
``{"status": "ok"}`` (#2077). License, trust, and transport live on
authenticated ``GET /status``.
"""
cfg = _get_config()
canonical = get_canonical_trust_snapshot(cfg)
eff = effective_dashboard_transport(request, cfg)
body: dict = {"status": "ok"}
body["license"] = _license_public_dict()
body["trust_state"] = canonical["trust_state"]
body["trust_reasons"] = canonical["trust_reasons"]
body["output_confidence"] = canonical["output_confidence"]
body["dashboard_transport"] = get_dashboard_transport_snapshot()
body["forwarded_headers"] = eff["forwarded"]
body["effective_external_transport"] = eff["effective_external_transport"]
body["enterprise_surface"] = get_enterprise_surface_posture(cfg)
body["integrity"] = _integrity_snapshot()
return body
return {"status": "ok"}


@app.get("/about/json")
Expand Down Expand Up @@ -1575,6 +1567,7 @@ async def get_status(request: Request):
"enterprise_surface": get_enterprise_surface_posture(cfg),
"maturity_assessment_integrity": maturity_integrity,
"integrity": _integrity_snapshot(),
"license": _license_public_dict(),
}


Expand Down
Loading
Loading