Commit Graph

6 Commits

Author SHA1 Message Date
cproudlock
7ac1ee3077 Refuse a kiosk caller wearing an address that belongs to the path
Both kiosks admit a check-in or check-out with no login when the caller sits in
a configured network. The subtraction that makes that safe was missing.

A reverse proxy normally sits ON the plant network a site enters in the
allowlist. When the proxy forwards nothing - misconfigured, or something
bypassed it - the request arrives wearing the proxy's own address, which matches
the allowlist, while carrying no evidence whatever about who sent it. That is
the one case that has to refuse, and it was being admitted. The previous
fail-closed test only looked closed because the proxy in it happened to sit
outside the range.

Addresses that belong to the path are now subtracted before the allowlist is
consulted: loopback always, since no site's kiosk is the server itself, plus
whatever the site declares as its proxies. USB and printedparts get the same
treatment - they had diverged for no reason anybody chose.

Three more, same surface:

A range that admits everybody can no longer be saved by accident. 0.0.0.0/0 and
::/0 are refused outright, anything wider than a site plausibly needs is refused
unless an explicit override is set, and the refusal says why.

The kiosk token no longer travels in a URL. The provisioning link carries a
single-use setup code redeemed on first open, so the token stays out of browser
history, referrers and proxy logs. The route is declared public deliberately:
the secret is the code, the way a password-reset link is the secret.

The badge-to-name lookup is bounded. It answers without a login and is now
reachable from a whole network rather than one screen, so a source that keeps
missing gets refused rather than being allowed to walk the directory. On by
default, budget and kill switch are settings.

All four new settings carry controls on their settings pages. A setting that can
stop the kiosk, or widen it, must not be reachable only by hand-editing rows.

The installer's client-IP advice is corrected: it described the opposite of what
the shipped waitress flags do, and named only GE-Enforce as the thing that
breaks when the rule is absent, when the two kiosk allowlists depend on it too.
2026-09-18 13:30:24 -04:00
cproudlock
0f56653345 Say when a kiosk screen is not set up, and hand out the setup link
Two failures from the same root: an administrator cannot see the kiosk token,
and a screen that was provisioned wrongly does not say so.

The settings API masks every key whose name contains 'token', so the settings
page shows '********'. The real value is visible for one moment - after
Generate, before Save - and an administrator who copies the field at any other
time provisions a screen with the literal mask. The kiosk then answers 404,
exactly as it answers a caller it has never heard of, because the route
deliberately does not advertise itself. Nothing anywhere says what went wrong.

- Each kiosk gains a permission-gated provisioning-link route that returns the
  real setup URL, token included, to somebody who already holds settings.edit.
  The mask stays: a token has no business in an ordinary settings list. The
  copy step just stops depending on catching the one moment it is on screen.
  Revealing it writes an audit row, since that is when the value left the
  database. Both settings pages get a "Copy setup link" button, which falls
  back to showing the link when the clipboard is refused - it is, over plain
  http and in locked-down browsers.
- The USB kiosk asks the server on load whether this screen may be used, and
  says plainly when it may not. It rendered a keypad and only failed once
  somebody had typed their SSO, which reads as broken rather than as not set
  up. It cannot guess: the enabled flag is public and only says the site has a
  kiosk, and a caller admitted by network holds no token to look for, so the
  parts kiosk's localStorage check would warn exactly the callers the allowlist
  exists to serve. The probe is a badge-less session request - the gate runs
  before any badge handling, so refused is 404 and admitted is the validation
  error, with no lookup and nothing recorded. A test pins that ordering,
  because reordering it would start telling admitted screens they are not set
  up.

Verified in a browser, not only in tests: provisioning with the mask lands on
the refused screen, a correct token gets the drive list and survives a plain
revisit, and a tokenless caller inside the allowlist gets straight in.
2026-09-16 15:26:44 -04:00
cproudlock
da91a29155 Admit kiosk callers by network position, not only by device token
The device token assumes a handful of fixed screens an administrator
provisions once. That is not how these kiosks are used: most people open the
page on their own PC, and there is no login, so per-browser provisioning would
mean touching every PC on the floor and losing it again on the next reimage or
cleared profile. A screen in that state did not look refused either - the USB
kiosk rendered a working page whose every call answered 404.

Both kiosks now admit a caller on either ground, the same shape the GE-Enforce
client endpoints already use: the per-device token, or a source address inside
an allowlist. Wall screens keep the token and are not confined to the
allowlist; everyone else is admitted by where they are on the network. Still
gated on the site's enable flag, so nothing opens by itself, and both
allowlists ship empty - token-only until a site enters its ranges.

- shopdb.api gains ip_in_cidrs and request_ip_allowlisted (contract 0.35.0).
  On the surface because both plugins need the identical gate, and a CIDR
  parser copied into two of them is where a fail-open creeps in later. They
  fail closed on an empty setting, an unparseable caller address and a
  malformed entry, and they read remote_addr rather than raw X-Forwarded-For -
  so the IIS rewrite rule is what makes them meaningful, which the module says
  at length.
- Separate settings per kiosk, usb_kiosk_allowed_cidrs and
  printedparts_kiosk_allowed_cidrs. One must not grant the other, for the same
  reason the tokens are separate.
- Both settings pages get a picker listing the network plugin's subnets with
  their VLANs. It stores resolved CIDR strings, not subnet ids: the setting has
  to work at a site that does not run that plugin and must not break when an
  inventory row is deleted. Entries matching no subnet are shown rather than
  hidden, since a stale one left by an inventory edit looks exactly like a
  hand-typed range.
- USBKiosk says it was not admitted instead of pretending to work, which
  PartsKiosk already did.
- The USB settings page keyed text-vs-boolean off the token alone. Adding a
  second text setting to the boolean path would have saved it as the word
  'true' - the bug that replaced the kiosk token and 404'd every provisioned
  screen. Both load and save now key off a list, with a spec on it.

The trade, stated plainly in the admin UI: anyone on an allowed network can
check a drive in or out against a badge number they guess, and the record
carries that person's name. Badges are attribution, not authorization. Keeping
the ranges narrow is what bounds it; login is the alternative and was ruled out
because these users do not sign in.
2026-09-16 14:33:22 -04:00
cproudlock
408cf08628 Stop the USB settings page from replacing the kiosk token with "true"
Some checks failed
CI / backend (push) Has been cancelled
CI / naming (push) Has been cancelled
CI / gates (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / lean-build (push) Has been cancelled
The page saved every field as 'true'/'false', the kiosk device token
included, and loaded the token as a boolean. One save of Settings > USB
Devices therefore stored the literal word "true" (or "false") as the token,
and every kiosk provisioned with the real token got 404 on each request -
reported as the kiosk no longer accepting an SSO. Generate + Save never
stored the generated value either.

The token now loads and saves as text. The server already keeps the stored
token when it receives the mask back unchanged. The two switches still save
as booleans. The parts kiosk page was not affected.
2026-09-15 16:07:06 -04:00
4b21aa3887 Gate both kiosks behind a device token, and make an unreachable setting a test failure 2026-09-02 18:11:01 -04:00
376298dc02 Navigation sections declared, guide editor drag + references, USB settings, permission refresh 2026-08-27 09:30:08 -04:00