Align healthchecks, API_READ_TOKEN policy, and OpenAPI coverage #9

Merged
quentinreytinas merged 2 commits from quentin/fix-healthchecks-and-align-api-documentation into main 2026-04-07 14:05:19 +02:00
quentinreytinas commented 2026-04-07 13:48:39 +02:00 (Migrated from github.com)

Motivation

  • Fix a runtime issue where container orchestrators expect an unauthenticated liveness probe by using the public health endpoint.
  • Resolve contradictory token requirements between runtime code, documentation and the example compose file by establishing a single fallback behavior for API_READ_TOKEN.
  • Ensure the OpenAPI spec accurately describes the routes the Flask app actually exposes so generated clients and documentation are correct.

Description

  • Change the container healthcheck target from /health to /health/public in the Dockerfile and docker-compose.example.yml (so liveness checks do not require authentication).
  • Make API_READ_TOKEN optional in the compose example by defaulting it to BORG_API_TOKEN when unset, remove the strict API_READ_TOKEN startup validation in borgmatic_api_app/config.py, and update README.md to document the fallback behavior.
  • Add missing endpoints to openapi.yaml to match the Flask routes, including /health/public, /metrics, /repositories/{label}/create, /security/exec-whitelist, /security/exec-whitelist/validate, and /security/audit-log, add the Security tag, and update the read-auth description to reflect that read endpoints accept READ_TOKEN or WRITE_TOKEN and also accept X-Progress-Token: READ_TOKEN.
  • Remove a duplicated external: true line for nextcloud_aio_nextcloud_data in docker-compose.example.yml and update example health check calls in README.md to use /health/public.

Testing

  • Ran a route/spec parity check with a small Python script that compares @bp.route(...) entries in borgmatic_api_app/routes/legacy.py against paths in openapi.yaml, which returned no missing routes (success).
  • Attempted docker compose -f docker-compose.example.yml config but it could not run in this environment because the docker command is not available (failure due to environment).
  • Ran pytest -q which failed in this environment due to missing runtime dependency (flask), causing test errors unrelated to the changed logic (failure due to missing dependency).

Codex Task

### Motivation - Fix a runtime issue where container orchestrators expect an unauthenticated liveness probe by using the public health endpoint. - Resolve contradictory token requirements between runtime code, documentation and the example compose file by establishing a single fallback behavior for `API_READ_TOKEN`. - Ensure the OpenAPI spec accurately describes the routes the Flask app actually exposes so generated clients and documentation are correct. ### Description - Change the container healthcheck target from `/health` to `/health/public` in the `Dockerfile` and `docker-compose.example.yml` (so liveness checks do not require authentication). - Make `API_READ_TOKEN` optional in the compose example by defaulting it to `BORG_API_TOKEN` when unset, remove the strict `API_READ_TOKEN` startup validation in `borgmatic_api_app/config.py`, and update `README.md` to document the fallback behavior. - Add missing endpoints to `openapi.yaml` to match the Flask routes, including `/health/public`, `/metrics`, `/repositories/{label}/create`, `/security/exec-whitelist`, `/security/exec-whitelist/validate`, and `/security/audit-log`, add the `Security` tag, and update the read-auth description to reflect that read endpoints accept `READ_TOKEN` or `WRITE_TOKEN` and also accept `X-Progress-Token: READ_TOKEN`. - Remove a duplicated `external: true` line for `nextcloud_aio_nextcloud_data` in `docker-compose.example.yml` and update example health check calls in `README.md` to use `/health/public`. ### Testing - Ran a route/spec parity check with a small Python script that compares `@bp.route(...)` entries in `borgmatic_api_app/routes/legacy.py` against paths in `openapi.yaml`, which returned no missing routes (success). - Attempted `docker compose -f docker-compose.example.yml config` but it could not run in this environment because the `docker` command is not available (failure due to environment). - Ran `pytest -q` which failed in this environment due to missing runtime dependency (`flask`), causing test errors unrelated to the changed logic (failure due to missing dependency). ------ [Codex Task](https://chatgpt.com/codex/tasks/task_e_69d4ecbb3d208332ab47eae111f12430)
chatgpt-codex-connector[bot] (Migrated from github.com) reviewed 2026-04-07 13:51:39 +02:00
chatgpt-codex-connector[bot] (Migrated from github.com) left a comment

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc1a0b16ca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

### 💡 Codex Review Here are some automated review suggestions for this pull request. **Reviewed commit:** `fc1a0b16ca` <details> <summary>ℹ️ About Codex in GitHub</summary> <br/> [Your team has set up Codex to review pull requests in this repo](http://chatgpt.com/codex/settings/general). Reviews are triggered when you - Open a pull request for review - Mark a draft as ready - Comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". </details>
@ -49,10 +49,6 @@ class Settings:
raise RuntimeError(
"API_TOKEN must be set and non empty to start the Borgmatic API"
)
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-04-07 13:51:39 +02:00

P1 Badge Keep API_READ_TOKEN non-empty at startup

Removing the API_READ_TOKEN validation lets the app start with an explicitly empty read token, which turns into an authentication bypass for every read-only endpoint: in AuthManager.verify() (borgmatic_api_app/auth.py), the read path accepts X-Progress-Token when it equals settings.read_token, so with read_token == "" a request with no auth token (and only X-From-NodeRed) is accepted because "" == "". Previously this misconfiguration failed fast at startup.

Useful? React with 👍 / 👎.

**<sub><sub>![P1 Badge](https://img.shields.io/badge/P1-orange?style=flat)</sub></sub> Keep API_READ_TOKEN non-empty at startup** Removing the `API_READ_TOKEN` validation lets the app start with an explicitly empty read token, which turns into an authentication bypass for every read-only endpoint: in `AuthManager.verify()` (`borgmatic_api_app/auth.py`), the read path accepts `X-Progress-Token` when it equals `settings.read_token`, so with `read_token == ""` a request with no auth token (and only `X-From-NodeRed`) is accepted because `"" == ""`. Previously this misconfiguration failed fast at startup. Useful? React with 👍 / 👎.
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-04-07 13:51:39 +02:00

P2 Badge Describe repo-create payload with actual accepted fields

The added /repositories/{label}/create schema documents stats/progress/verbosity, but the implementation repo_create() consumes different options (encryption, repository, source_repository, storage_quota, copy_crypt_key, append_only, make_parent_dirs, etc., in borgmatic_api_app/routes/legacy.py). This contract mismatch can cause generated clients to send ineffective payloads and miss the controls the endpoint really supports.

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Describe repo-create payload with actual accepted fields** The added `/repositories/{label}/create` schema documents `stats/progress/verbosity`, but the implementation `repo_create()` consumes different options (`encryption`, `repository`, `source_repository`, `storage_quota`, `copy_crypt_key`, `append_only`, `make_parent_dirs`, etc., in `borgmatic_api_app/routes/legacy.py`). This contract mismatch can cause generated clients to send ineffective payloads and miss the controls the endpoint really supports. Useful? React with 👍 / 👎.
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-04-07 13:51:39 +02:00

P2 Badge Mark exec-whitelist validation as read-auth in spec

The new OpenAPI entry requires AuthWrite, but the actual handler security_validate_exec() enforces _require_auth(read_only=True) (borgmatic_api_app/routes/legacy.py), so READ_TOKEN is valid at runtime. This mismatch makes generated clients and policy tooling over-request write credentials for a read-only dry-run endpoint, undermining least-privilege access and breaking callers that only have read tokens.

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Mark exec-whitelist validation as read-auth in spec** The new OpenAPI entry requires `AuthWrite`, but the actual handler `security_validate_exec()` enforces `_require_auth(read_only=True)` (`borgmatic_api_app/routes/legacy.py`), so `READ_TOKEN` is valid at runtime. This mismatch makes generated clients and policy tooling over-request write credentials for a read-only dry-run endpoint, undermining least-privilege access and breaking callers that only have read tokens. Useful? React with 👍 / 👎.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
quentinreytinas/borgmatic-api-nextcloud-aio!9
No description provided.