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
33 changes: 21 additions & 12 deletions skills/exapp-development/references/exapp-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,36 @@ Every HTTP surface the ExApp exposes must be covered by a `<route>`; unmatched r

| Field | Meaning |
|---|---|
| `url` | Case-insensitive regex matched against the request path (e.g. `^/api/.*`) |
| `url` | Regex matched against the request path including its leading `/` (e.g. `^/api/.*`); the PHP proxy ignores case, HaRP does not |
| `verb` | Comma-separated HTTP methods the route accepts |
| `access_level` | `PUBLIC` (0) anyone, `USER` (1) any logged-in user, `ADMIN` (2) admins only |
| `bruteforce_protection` | JSON array of response status codes that count as a bruteforce attempt (e.g. `[401,429]`) |
| `headers_to_exclude` | JSON array of request header names stripped before forwarding |

Enforcement happens in two places, depending on the path a request takes:

- **HaRP path** (browser to `/exapps/...`): AppAPI hands HaRP the route table (url + access_level +
- **HaRP path** (browser to `/exapps/...`): AppAPI hands HaRP the route table (url + verb + access_level +
bruteforce_protection) via the ExApp metadata endpoint, and HaRP resolves the caller's level per request
through the user-info endpoint (no/disabled user = PUBLIC, admin = ADMIN, else USER), enforcing the
comparison itself. `verb` and `headers_to_exclude` are not part of the HaRP checks. Observed replies: an
anonymous request to a `USER` or `ADMIN` route gets `403` (an admin session or basic auth gets through), an
undeclared path gets `404`, and the lifecycle endpoints (`/heartbeat`, `/init`, `/enabled`) get `502`.
- **PHP proxy path** (`/index.php/apps/app_api/proxy/...`): `ExAppProxyController` matches `url` (regex) and
`verb`, enforces the access level, strips `headers_to_exclude`, and applies bruteforce throttling on the
listed status codes.

Authoring notes: an empty element (`<headers_to_exclude></headers_to_exclude>`) is fine and means "none", but
nested sub-elements (`<bruteforce_protection><status>401</status></bruteforce_protection>`) are rejected; use
the JSON-in-text form shown above.
comparison itself. Since HaRP 0.4.8 the first route whose `url` matches the path and whose `verb` lists the
method decides, as in the PHP proxy; `HEAD` is accepted wherever `GET` is listed, and a method that no route
of the path lists gets `405` with an `Allow` header. HaRP 0.4.7 and older, and HaRP with an AppAPI that does
not send the verbs yet, match by `url` only: the first matching route decides for every method.
`headers_to_exclude` is not part of the HaRP checks. Observed replies: an anonymous request to a `USER` or
`ADMIN` route gets `403` (an admin session or basic auth gets through), an undeclared path gets `404`, and the
lifecycle endpoints (`/heartbeat`, `/init`, `/enabled`) get `502`.
- **PHP proxy path** (`/index.php/apps/app_api/proxy/...`): `ExAppProxyController` takes the first route whose
`url` (regex) matches and whose `verb` lists the method (`HEAD` also where `GET` is listed, since
nextcloud/app_api#1144), enforces the access level, strips `headers_to_exclude`, and applies bruteforce
throttling on the listed status codes. A request no route lets through gets `404`. Only `GET`, `HEAD`, `POST`,
`PUT` and `DELETE` reach the proxy at all: Nextcloud answers other methods, `PATCH` and `OPTIONS` included,
with `405`.

Authoring notes: list every method a route serves in its `verb` (`GET,POST`), and give methods that need
different access levels different URLs, as older HaRP versions decide by URL only. An empty element
(`<headers_to_exclude></headers_to_exclude>`) is fine and means "none", but nested sub-elements
(`<bruteforce_protection><status>401</status></bruteforce_protection>`) are rejected; use the JSON-in-text form
shown above.

## Environment variables: a declared allow-list

Expand Down
4 changes: 2 additions & 2 deletions skills/harp-operations/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ This skill is HaRP itself. Registering daemons, installing ExApps and the occ su
## Facts that save hours

- Read the status code first, it names the plane: `401` shared key, `403` route access level, `404` unknown
route or an app not on this daemon, `502` an infrastructure route or a proxy rule pointing nowhere, `503`
the agent under load.
route or an app not on this daemon, `405` a method the route's `verb` does not list (HaRP 0.4.8+), `502` an
infrastructure route or a proxy rule pointing nowhere, `503` the agent under load.
- `GET /` on the HTTP frontend answers `404` by design. Never use it as a health probe; use
`/exapps/app_api/info` with the `harp-shared-key` header.
- One HaRP serves one Nextcloud. `NC_INSTANCE_URL` is a single value and every request is authorised against
Expand Down
8 changes: 6 additions & 2 deletions skills/harp-operations/references/harp-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ cause.
| `401` | Shared key missing or wrong on a control route, or an unsigned request to `/exapps/app_api/...` |
| `403` | The caller's level is below the route's `access_level` (anonymous on a USER or ADMIN route) |
| `404` | Path not declared by the ExApp, or the app is not routable here (`<NOSRV>`), or `GET /` |
| `405` | No route of that path lists the method in its `verb` (HaRP 0.4.8+); the `Allow` header names the listed ones |
| `502` | An infrastructure route (`/heartbeat`, `/init`, `/enabled`) requested from outside, which is blocked by design, or a reverse-proxy rule pointing at nothing |
| `503` | No healthy backend: the agent is stalled or overloaded |

Expand All @@ -150,8 +151,10 @@ cause.
- Tags, from HaRP's publish workflow: `:release` on every GitHub release, `:vX.Y.Z` alongside it, `:latest`
and `:main` on every push to `main`. Production wants `:release` or a pinned `:vX.Y.Z`; `:latest` is a
development build.
- AppAPI enforces a floor of `0.3.0` (`Application::MINIMUM_HARP_VERSION`, same in NC33, NC34 and NC35) and
reports it through the `HarpVersionCheck` setup check.
- AppAPI enforces a floor (`Application::MINIMUM_HARP_VERSION`) and reports it through the `HarpVersionCheck`
setup check: `0.4.8` with nextcloud/app_api#1149 and its backports to NC33, NC34 and NC35, `0.3.0` before.
With #1149 the check caches the version it reads for an hour, so the warning can outlive a HaRP update by
that long.
- Before 0.4.3, `/info` reported the version as a float, which Nextcloud read as `0.4.0` whatever the real
patch level was. On 0.4.3 and later it is a real `x.y.z` string, so the admin overview shows the truth.
- Upgrading is `docker pull` plus recreate; ExApps reconnect their tunnels on their own. Verify with the
Expand Down Expand Up @@ -193,6 +196,7 @@ least 0.4.3 before investigating `503` bursts, and check host CPU pressure at th
| Every ExApp request is `401` | Shared key mismatch between `HP_SHARED_KEY` and the daemon's `--harp_shared_key`, or a non-ASCII key. Re-register the daemon with the exact value |
| One app is `404` with `<NOSRV>` in the log | That app is not deployed on this HaRP daemon (manual-install apps are served at `/index.php/apps/app_api/proxy/<appid>/...`) |
| A route answers `403` for normal users | The route declares `access_level` USER or ADMIN; anonymous callers get `403` |
| A request that used to work gets `405` | HaRP 0.4.8+ with an AppAPI that sends the route verbs checks the method: no route of that path lists it, and the reply's `Allow` header names the ones listed. The ExApp's `info.xml` must add the method to that route's `<verb>`; update the ExApp once it does |
| Browser gets `502` on `/heartbeat`, `/init` or `/enabled` | Working as intended: infrastructure routes are not reachable from outside |
| Every ExApp install ends in "heartbeat check failed" | The `/exapps/` rule on the Nextcloud reverse proxy is missing or wrong; AppAPI cannot reach `<nextcloud_url>/exapps/<appid>/heartbeat` |
| Bursts of `503` across all ExApps under load | Agent stall. Upgrade to 0.4.3+, then look for host CPU starvation at those timestamps |
Expand Down
Loading