distinct / distinct_agent /access.py
User1342's picture
Say how a worker is shared: one access code, any number of people
3d71a3b
Raw History Blame Contribute Delete
3.6 kB
"""The code this worker prints for the people allowed to use it.
**Why a worker hands out a code at all.** A server knows who is signed in; it
does not know whose machine an agent is. The operator of the machine does, and
this is how they say so: the worker prints one code when it starts, they give
it to the people who should be able to use it, and those people enter it on a
server they have signed in to. The server keeps a digest, never the code.
It is deliberately not derived from anything. A code computed from a hostname,
a port, a start time or an agent id would be guessable by anybody who knew
those, and every one of them is public or nearly so.
"""
from __future__ import annotations
import base64
import secrets
from collections.abc import Sequence
#: Mirrors `distinct_server.security.ACCESS_CODE_BYTES`. The two sides agree
#: on a format rather than sharing an implementation, so this is the number
#: that must not drift.
ACCESS_CODE_BYTES = 24
def new_access_code() -> str:
"""192 random bits, in groups of five.
Grouped because it is going to be read aloud and retyped at least once by
somebody who is not enjoying it, and base32 because a code containing
both `l` and `1` is a support request waiting to happen.
"""
raw = base64.b32encode(secrets.token_bytes(ACCESS_CODE_BYTES)).decode("ascii").rstrip("=")
return "-".join(raw[index : index + 5] for index in range(0, len(raw), 5))
def announce_access_code(code: str, servers: "Sequence[str]" = ()) -> str:
"""What the worker prints, once, when it starts.
In full rather than summarised, because this is the only moment it exists
in a readable form: the servers keep a digest of it and this process does
not write it to disk.
``servers`` are named in the invitation when they are known, because the
two halves of the instruction are useless apart: a code with no address is
a key to a door nobody can find. It is written as a sentence somebody can
forward as-is, since forwarding it is the entire point.
A sequence, not a string, because ``--server`` is repeatable and one
worker can serve several servers on the same code. An earlier version
took the argument straight from argparse and printed
``Sign in at ['http://…']``, which is what happens when a list is asked
to be a sentence.
THIS CODE IS DELIBERATELY NOT ONE-TO-ONE. It is not consumed by being
used and it is not tied to whoever redeems it first; every person who
enters it is added to the set allowed to see this worker. That is the
sharing model, and the wording says so, because a reader who assumed one
code meant one person would go looking for a way to mint more.
"""
addresses = [str(item).strip() for item in servers if str(item).strip()]
if len(addresses) == 1:
where = f" at {addresses[0]}"
elif addresses:
where = " at " + ", ".join(addresses[:-1]) + f" or {addresses[-1]}"
else:
where = ""
return (
"\n"
" Access code for this worker:\n"
f" {code}\n"
"\n"
f" Send this to anyone who should be able to use this machine:\n"
f" Sign in{where} and enter this code under \"Add a worker\":\n"
f" {code}\n"
"\n"
" The same code works for as many people as you give it to — it is\n"
" not used up by the first person to enter it. Anybody holding it\n"
" can send this worker requests, so treat it like a key. Restarting\n"
" the worker issues a new one and retires this.\n"
)