feat(drivers): let a read-only driver sign in before it reads - #743
feat(drivers): let a read-only driver sign in before it reads#743HuggeK wants to merge 2 commits into
Conversation
A driver that reads a vendor cloud cannot read anything until it has
exchanged a token, and it exchanges one with a POST issued from
driver_init or driver_poll. allowWrite refuses those phases -- correctly,
since nothing there carries a command lease -- so a cloud telemetry
driver could not be published read-only at all. The only way to make it
work was to publish it control-capable, which is what srcfl/device-drivers
does for myuplink today: its catalog entry claims a control path, while
its driver_command has always been
function driver_command(_action, _power_w, _cmd)
-- Read-only: no actuation in Step 1.
return false
end
Authenticating is a precondition for reading rather than a write to the
device, so it is admitted separately: RuntimePolicy gains AuthPostPath,
carried from the signed manifest's driver metadata, and host.http_post
skips the write scope only for a URL whose path equals it.
The exemption is narrow by construction. It requires a policy that is
read-only, a non-empty declared path, http.post granted, and an exact
path match -- so it cannot become a general write channel, and a driver
that declares nothing behaves exactly as before. It does not consume the
write budget: a token refresh is driven by expiry rather than by a
caller, and spending the budget on it would leave the driver unable to
read once its token aged out.
Scope: the signed-channel path only, which is what srcfl/device-drivers
publishes. The package-v1 path is untouched and still rejects a write
permission on a read-only package; a read-only cloud driver shipped that
way would need the field in the package schema first.
Companion to srcfl/device-drivers, which declares auth_post_path in the
driver's DRIVER table and confines the same POST in the generated
artifact guard.
|
@frahlg flagging you for review. This is the host half of a two-repo change. The other half is srcfl/device-drivers#64 and its follow-up, which found that five drivers — Four of the five can simply be relabelled. Merge order matters — this must land and ship before device-drivers publishes a read-only I could not request you as a reviewer directly (no permission on this repo from a fork), hence the mention. |
A driver that reads a vendor cloud cannot read anything until it has exchanged
a token, and it exchanges one with a POST issued from
driver_initordriver_poll.allowWriterefuses exactly those phases:That is right for actuation — nothing in those phases carries a command lease —
but it means a cloud telemetry driver cannot be published read-only at all.
The only way to make one work is to publish it control-capable.
That is what
srcfl/device-driversdoes formyuplinktoday. Its catalog entryclaims a control path, while its command entrypoint has always been:
So the driver is published write-capable to preserve a token refresh, and an
owner reading the catalog is told it can control their heat pump.
What this changes
Authenticating is a precondition for reading rather than a write to the device,
so it is admitted separately from the write scope.
RuntimePolicygainsAuthPostPath, carried from the signed manifest's driver metadata, andhost.http_postskipsallowWriteonly for a URL whose path equals it.Why the exemption cannot widen
allowAuthPostrequires all of:RuntimePolicy(a bundled or local driver has none and is unaffected);ReadOnly— a controlling driver has no exemption to take;AuthPostPath, which only the signed manifest can set;http.poststill granted inPermissions;url.Parse(...).Pathequal to the declared path — not a prefix or suffix.A driver that declares nothing behaves exactly as before, which is every driver
in the channel today except
myuplink.Why it does not consume the write budget
A token refresh is driven by expiry, not by a caller. Charging it to
maxWriteswould let a driver's own token lifetime exhaust the budget andleave it unable to read — the failure this change exists to prevent.
Scope: signed channel only
This touches the signed-channel path (
runtimePolicyForInstalledSignedDriver),which is what
srcfl/device-driverspublishes. The package-v1 path isdeliberately untouched:
validateSourcefulReadOnlyContractstill rejects anywrite-capable permission on a read-only package. A read-only cloud driver
shipped that way would need the field in the package schema first, and no such
package exists today.
Companion change
srcfl/device-driversdeclaresauth_post_pathin the driver'sDRIVERtableand confines the same POST inside the generated artifact guard, so the
restriction holds in the Lua as well as in the host. That side must not merge
first — a read-only
myuplinkreaching a host without this change would loseits token refresh.
Testing
go/internal/drivers/read_only_auth_post_test.gocovers the exemption applyingoutside a write scope, staying off the write budget, being confined to the
declared path (including
/oauth/token/../device, a bare/oauth, a foreignhost, and a malformed URL), and being refused when undeclared, when the driver
controls, when
http.postwas never granted, and when there is no policy.go build ./...andgo vetare clean. On this machine./internal/driversand./internal/driverrepofail an identical set of testswith and without this change — 12 in
driversfrom a shallow clone lackingbundled driver assets ("not in catalog"), and
TestPublicationRejectsDangerousOutputDirectoriesindriverrepofrom aWindows path separator. Nothing is introduced; CI on Linux should be the
authority.
🤖 Generated with Claude Code