Skip to content

Instantly share code, notes, and snippets.

@Fang-
Last active May 20, 2023 03:32
Show Gist options
  • Save Fang-/7f142f73a585cf86f0ecf4f6f7da8a17 to your computer and use it in GitHub Desktop.
Save Fang-/7f142f73a585cf86f0ecf4f6f7da8a17 to your computer and use it in GitHub Desktop.
The Open Eyre

the open eyre

Some have bemoaned Urbit's absence on the clearnet. Everyone accesses their urbits via HTTP, but few apps use that same affordance to expose themselves to the public at large.

But that's not very strange given the current state of HTTP access. As long as you are authenticated as the host ship, you get first-class access to the ship through Eyre's "channels" system, letting you pretend you are in userspace by sending %pokes and %watches as the host. However, if you are not authenticated, none of that is available at all, leaving you at the mercy of whatever HTTP APIs apps on that ship expose to the public. Generally, because it requires additional implementation work, that is none at all.

Here, we propose a mechanism by which unauthenticated clients are assigned temporary, "fake" guest identities, and use those to interact with the host's userspace through the existing Eyre channels system.

guest comets

Presently, requests are authenticated by including an urbauth-~sampel-hoster cookie, where the ship name corresponds to the host ship, and the cookie contains a unique key that identifies an authenticated session. These sessions are created upon successful use of the login page, and expire after some time, but are extended upon usage.

This proposal suggests that, in the absence of that cookie, Eyre should automatically create a "guest session", and provide a cookie with the key for it to the requester. Every such session has a randomly generated comet-length @p associated with it. (For extra flair, and a very soft indication of provenance, Eyre may generate a comet name whose tail matches the host ship.)

Cookies for guest sessions follow the familiar behaviors: they expire after some time, but get renewed upon use. More importantly, they provide access to Eyre's channels. For userspace interactions made through channels, and for HTTP requests that get routed into userspace, the src.bowl gets set to the comet associated with the session.

If a request comes in that does not provide any session cookies, a new guest cookie is minted and will be returned in the response. The handling of the request proceeds as if the new guest cookie was provided by the client. In other words, if a request is not authenticated, it will always have a guest session associated with it.

guest channels

Channels created & used by guests behave exactly as normal channels, except that the pokes and watches they send will have the guest identity in the src.bowl when they get processed by the agent.

guest requests

Incoming HTTP requests may end up getting handled by a generator or userspace agent. Presently, the src.bowl in those cases will always be the host ship. The authenticated flag of the $inbound-request will be set to true only if the request included a valid authentication cookie.

For incoming requests that are not authenticated as the host ship, we set the src.bowl to be the identity of the guest session, but leave the authenticated flag as false. It may now be more appropriate to read that flag as "did the ship in src.bowl formally authenticate this request?", or, "is the ship in src.bowl real?".

This is, hopefully, the most minimally disruptive change. Presently, the authenticated flag is commonly used for checking if the request is coming from the user that owns the host ship. This remains unchanged. It is somewhat less common, but still not unheard of, to assert that =(src our):bowl when handling incoming HTTP requests. This too continues to behave as desired: not providing responses for unauthenticated sessions.

However, with an eye towards stronger urb-ification of Eyre, developers are now encouraged to use the src.bowl as the primary way to check the provenance of HTTP requests. The exact desired behavior may differ for specific cases, but most should be well-served by simply reading the src.bowl as one would with other userspace events.

guest name discovery

The /~/name endpoint already exists and can be used to request the @p the requester is currently authenticated as. This will be updated to respond with the guest identity for guest sessions.

Because this is no longer guaranteed to be equivalent to the host's @p, but there might still be a need to, say, poke an agent specifically on the host's ship (in fact, guests must), we may want to add a /~/host endpoint or similar, to expose the @p of the ship that is handling incoming requests.

use cases & prior art

Public access to userspace pokes and subscriptions has had real-world demand and implementations in the past.

The old urbit.org/stream1 did something along these lines, letting web randos post with comet identities into a designated chat channel. (I do not recall how exactly it worked. It preceded Lighter Than Eyre.)

The graph-stream-hook3 that powered pal.dev/lobby provided similar behavior, but did so by reimplementing parts of Eyre's channels system in userspace. Of course, it skips out on event queues and other reliability must-haves, because the stakes were low and the (re)implementation cost not worth it. It was backed by fakeid4, which implements guest cookies, approximately as described above. Podium5 was an attempt to generalize the "channels but in userspace" pattern.

notable omissions

Initially, using a separate urbauth-~sampel-hoster-guest cookie seemed appropriate. However, this makes it possible to receive multiple authentication cookies within a single request. The easy answer is to just have "real" authentication take precedence, but a stronger position is to say a client should only ever be authenticated into a single session. So, while the client may get assigned a guest session on first visit, logging in will overwrite the associated cookie with one for a normal session instead.

We intentionally do not change the behavior of the /~/scry endpoint, which currently gives a 403 response for unauthenticated requests, even though the ability to perform scries is very useful for clients. Scry authentication has historically not been a real thing (even though the kernel hints at it, see $gang), resulting in the assumption that all scry calls come from the local ship exclusively. This is bound to change in the future, but forcing the issue for this particular proposal seems undesirable.

risks

memory pressure

One can easily imagine memory pressure becoming a risk. But the channel system as it exists today already has logic for closing subscriptions in channels of uncooperative channels, and channels already get cleaned up after twelve hours of no use. It is possible to make these numbers tighter for guest channels, but unclear at this time whether that will actually be needed.

Additionally, trim events already prompt eyre to clean up presently-inactive channels, or even all channels if the trim is issued with maximum priority.

cpu hogging

Direct access into userspace broadens the ways in which an urbit can be DoS-ed. But this is not a new attack surface. Certainly HTTP requests of any kind can be used for this. Agents can and should take care to enforce =(src our):bowl where appropriate, both presently and after these changes.

guests causing network traffic

Given that Eyre channels support poking arbitrary agents on arbitrary ships, one might be concerned that this opens a vector through which malicious actors can get anyone's urbit to send network packets. However, Eyre will construct the $sock (that is, the [from=@p to=@p] for a Gall task) as [~authenticated-as ~target-ship], and Gall asserts that at least one of those must be our. So, outsider cannot interact with non-local agents.

Even with Gall's enforcement in place, it is probably nicest for Eyre to perform this check within its own logic, to save on the round-trip, and more easily provide an explicit error message.

interacting with fake ships

Agents may store (semi-)permanent state and associate it with the "fake" comet identity generated for a guest session. Alternatively, or as a consequence, agents may attempt to send ames packets to such a comet. But this is no different from the regular comet case, where it may pop into existence only briefly, flail around on the network, and then disappear forever. Ames already has a mechanism for expiring comms with comets. Agents might consider doing a similar clean-up on-trim (if only they were ever told about trim).

social abuse

In response to socially malicious behavior (that is, being mean against people as opposed to infrastructure), users may want to block the user associated with a specific guest identity. It is entirely possible for Eyre to track IP addresses alongside the generated guest identities, and provide an IP blacklist whose requests we will always 403. The latter does not currently exist, but would be part of an abuse protection pass regardless of this proposal. For best effect, extend it into the runtime. (And, again, this is not too different from the normal comet case. Arguably we even have stronger responses here.)

ambiguous language

By far the biggest risk here is ambiguous or confusing developer-facing language. Both "session" and "authentication" can now refer to two different kinds of themselves. Above, we have said "guest session" to disambiguate, but rarely clarified on "authentication". Worse, there is an authentication flag in $inbound-request that will be set to false when you are authenticated into a guest session.

We may say we just have normal "sessions", always, and every session has a @p associated with it. And instead of calling requests authenticated or not, we may instead specify whether their associated @p is authentic or not.

One may be slightly tempted to say the flag needs to be is-this-us, but that leaves it vulnerable to a future change we will probably want before too long:

further horizons

In this proposal we have described the "authentication" flag as now meaning whether the identity of the request sender is "real" or not. That seems like it is overfitting slightly, the identity is either a fake guest, or our real selves.

However, it is not entirely unreasonable to imagine wanting to extend the courtesy we now grant to guests, to real Urbit Identities as well. Supporting some form of cross-ship authentication6, letting Eyre associate channels with real identities, would enable more traditional client-server style architectures. We already have server-server, which is nicer in most cases, but does require both parties to install the relevant app. One can imagine a "demo mode" where I can pair with you, or play a game with you, without committing to installing the app, but while also maintaining my own identity.

Very much outside of scope for this proposal, but seems fairly desirable and a logical extension to the "fake" identities used here, so worth keeping in mind while we figure out the exact desired semantics.

--

@arthyn
Copy link

arthyn commented Apr 28, 2023

this is incredible. I actually think doing the final thing mentioned could make this worth it even more. perhaps authenticated should be ?(%us %guest %none)

@Fang-
Copy link
Author

Fang- commented May 5, 2023

@polwex
Copy link

polwex commented May 19, 2023

Allowing remote poke through eyre would be nice too! Could do server-client apps that way too. Clients would have to install the frontend but that has its advantages too.

@Fang-
Copy link
Author

Fang- commented May 19, 2023

@polwex If by "remote poke" you mean poking agents on other ships, Eyre already supports this, assuming you're logged in as the ship that's handling the Eyre request. It's what the ship field in the poke and subscribe actions is for.

@polwex
Copy link

polwex commented May 20, 2023

No, I mean being able to poke other ships from the frontend. I don't remember exactly, maybe it was disallowed in Gall instead, but I recall there being a check against %poke-as that meant remote pokes from eyre didn't work. It's been a while since I saw it though, has it changed since?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment