feat(appgate): apps with their own login can skip the node login
Demo images / Build & push demo images (push) Successful in 3m33s
Demo images / Build & push demo images (push) Successful in 3m33s
Some apps carry a complete account system and are broken by an upstream challenge: git clients speak basic-auth (not browser cookies), and a BTCPay checkout link handed to a customer must open for that customer. Both were behind the gate's login page — the "non-browser clients need an access token" gap disclosed in five consecutive releases. - New manifest port policy `auth: open`: the daemon still fronts the port exactly like `gated` (loopback pin, external binds, frame-header fixes, app-down retry page, Tor upstream) but serves it without the login challenge. Requires auth_rationale, same burden of proof as `none`. Gitea 3001 and BTCPay 23000 declare it. - Runtime operator override per app (security.set-app-gate → app-configs/ <id>.json "gateEnabled"), surfaced as Settings → app → Access control. Wins over the manifest in both directions and applies on the next request — no restart, and it works today on catalog-covered apps whose signed manifest still says `gated`. - The gate resolves policy per-request from the live port map, so a toggle takes effect without waiting for the 60s rebind sweep. "Off" never releases the port: gated apps are loopback-pinned, so releasing would strand them, not open them. - security.app-gate-status now reports gate_enabled + any override. - New guard test pins the `auth: open` set (both entries reviewed); the `auth: none` count moves 25 → 26, absorbing pre-existing drift from the phoenixd onboarding (loopback JSON API with its own generated password). - Docs: the manifest spec's ports row documented only host/container/ protocol — bind, auth, auth_rationale and session_passthrough were undocumented. Added a full "Ports & the app gate" section plus a developer-guide entry telling app authors to enforce their own auth regardless, since the operator can flip the gate either way. Verified live on archi-dev-box from an external address: gated → 401 gate page; override off → Gitea 200 own page, BTCPay 302 to its own login, git-over-HTTP info/refs 200; override on → 401 again; clear → default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9b789a64ad
commit
58cdea5e79
@@ -29,7 +29,7 @@ reusable manifest primitive.
|
||||
| `dependencies` | list | — | `- storage: "10GB"`, `- { app_id: bitcoin, version: … }`, or a bare string. |
|
||||
| `resources` | ResourceLimits | — | `cpu_limit` (int), `memory_limit` (e.g. `"512m"`), `disk_limit`. |
|
||||
| `security` | SecurityPolicy | — | See [Security](#security). |
|
||||
| `ports` | list of PortMapping | — | `- { host: 8080, container: 80, protocol: tcp }`. |
|
||||
| `ports` | list of PortMapping | — | See [Ports & the app gate](#ports--the-app-gate). |
|
||||
| `volumes` | list of Volume | — | See [Volumes](#volumes). |
|
||||
| `files` | list of GeneratedFile | — | Config files written before create: `{ path, content, overwrite }`. `path` must sit under a declared bind mount. |
|
||||
| `environment` | list of string | — | `- KEY=value` pairs (static). |
|
||||
@@ -82,6 +82,57 @@ Validation (enforced at `AppManifest::validate()`):
|
||||
`secret_env`/`generated_secrets` names must be bare filenames.
|
||||
- Hook steps are validated against the hook allow-list (below).
|
||||
|
||||
## Ports & the app gate
|
||||
|
||||
```yaml
|
||||
ports:
|
||||
- host: 3001 # host port your app is reachable on
|
||||
container: 3000
|
||||
protocol: tcp # default tcp
|
||||
bind: 127.0.0.1 # empty = all interfaces
|
||||
auth: gated # session | none | local | gated | open
|
||||
auth_rationale: "…" # REQUIRED for auth: none and auth: open
|
||||
session_passthrough: false
|
||||
```
|
||||
|
||||
| Field | Notes |
|
||||
|-------|-------|
|
||||
| `bind` | Host address for the publish. Empty = all interfaces. List the same host/container pair twice with different binds to serve several addresses. |
|
||||
| `auth` | The port's authentication policy — see below. **Absent means "no instruction"**: the daemon reports on the port but never changes how it is published. |
|
||||
| `auth_rationale` | Why the port is safe without the gate's login. Required for `none` and `open`, rejected elsewhere. |
|
||||
| `session_passthrough` | Forward the node session cookie to the app on authorised requests. First-party companion UIs only; the gate otherwise strips its own credential. Only meaningful on `auth: gated`. |
|
||||
|
||||
### `auth` policies
|
||||
|
||||
- **`session`** (default when declared): the app gate authenticates every
|
||||
connection — node session cookie or an app-scoped bearer token.
|
||||
- **`gated`**: the app publishes on loopback **only** (`bind: 127.0.0.1`) and
|
||||
the daemon owns the external addresses: it binds them, authenticates every
|
||||
connection, fixes frame-blocking headers so the app embeds in the
|
||||
dashboard, serves a retrying page while the app is down, and fronts the
|
||||
Tor onion for the port. This is the migrated end state for most apps.
|
||||
- **`open`**: same daemon takeover as `gated` — loopback pin, external
|
||||
binds, header fixes, retry page, Tor — but **no dashboard login
|
||||
challenge**. For apps that carry a complete login of their own and are
|
||||
broken by an upstream challenge: Gitea (git clients speak basic-auth, not
|
||||
cookies), BTCPay (checkout pages must be reachable by anonymous payers).
|
||||
Requires `auth_rationale`.
|
||||
- **`none`**: the gate does not touch the port at all. Only for protocols
|
||||
that authenticate themselves (LND macaroons, TLS client certs) or where a
|
||||
login page is meaningless (p2p gossip). Requires `auth_rationale`.
|
||||
- **`local`**: host-local by intent — the gate must never bind or expose
|
||||
this port anywhere (e.g. Bitcoin RPC).
|
||||
|
||||
### Runtime override
|
||||
|
||||
The manifest sets the **default**. The node operator can flip any
|
||||
gate-fronted app between `gated` and `open` behaviour at runtime from
|
||||
**Settings → app → Access control** (RPC `security.set-app-gate`), stored
|
||||
per-app on the node (`app-configs/<id>.json`, key `gateEnabled`). The
|
||||
override wins over the manifest in both directions and applies on the next
|
||||
request — your app cannot assume the gate is or isn't in front of it, so it
|
||||
must always enforce its own authorization for sensitive operations.
|
||||
|
||||
## Volumes
|
||||
|
||||
```yaml
|
||||
|
||||
Reference in New Issue
Block a user