lablink_allocator_service.tunnel_manager¶
lablink_allocator_service.tunnel_manager
¶
Reverse-tunnel connectivity's server-side bookkeeping.
One wstunnel server runs for the whole deployment (started by start.sh),
and each client attaches to it over a WebSocket carrying its own path
prefix. This module owns the two things the allocator must control:
-
The restrictions file. wstunnel's reverse-tunnel bind address is chosen by the CLIENT, so without restrictions any client holding a valid client_secret could bind another client's loopback alias and receive that client's student sessions. Each rule pins one path prefix to one /32 alias and the two ports we tunnel. wstunnel reloads the file automatically when it changes, so no restart or signal is needed.
-
Which aliases actually have a client attached. A bound port is NOT evidence of an attached client: wstunnel keeps a reverse-tunnel listener bound for --remote-to-local-server-idle-timeout after the client leaves, and connections to an orphaned listener hang rather than being refused (measured: 12s, no response). Health must therefore observe the pairing, not the process or the port.
Two facts measured against the pinned wstunnel v10.6.2 binary, both of which the rendered file's shape depends on -- re-verify when bumping it:
allow[].porttakes a list of port-number STRINGS, each parsed as a single-port range (port: [6080..=6080, ...]under --log-lvl DEBUG). A[{start: N, end: N}]form is rejected outright.!PathPrefixcompares ONLY the client's first path segment, so a match value liketunnel/<prefix>can never fire and the failure is a silent deny-all. The match value must BE the whole first segment, which is whypath_prefix()returnstun-<client_id>-<digest>at the root rather than nesting under a shared "tunnel/". Confirmed end to end: a client presenting another client's alias in-Rwas refused with "Rejecting connection with not allowed destination".
Security note: client_id is the DB hostname, which arrives from
client-controlled registration input. Two independent guards, both
load-bearing: _is_safe() rejects the charset (what stops a multi-line
hostname reaching the renderer at all), and yaml.safe_dump does the
rendering (what stops a single-line-but-special value -- quotes, colons,
# -- forging YAML structure if it ever slipped past).
Attributes¶
RESTRICTIONS_PATH = TUNNEL_DIR / 'restrictions.yaml'
module-attribute
¶
TUNNEL_DIR = Path('/tmp/lablink-tunnel')
module-attribute
¶
TUNNEL_PORTS = (6080, 7070)
module-attribute
¶
TUNNEL_SERVER_PORT = 8080
module-attribute
¶
Functions:¶
attached_aliases()
¶
Alias octets with a listening reverse-tunnel socket right now.
Parses /proc/net/tcp because there is no wstunnel API for it. State
"0A" is TCP_LISTEN; local_address is <hex ip>:<hex port> with the IP
in network-reversed byte order, so 127.0.0.10 is 0A00007F.
Source code in packages/allocator/src/lablink_allocator_service/tunnel_manager.py
authorize_client(*, client_id, alias_octet, prefix)
¶
Permit exactly this client to bind exactly its own alias.
Raises ValueError -- before any file work -- if client_id/prefix carry characters this module can't safely render, or if alias_octet is outside the range a /32 loopback rule can express. This is the trust boundary: client_id is attacker-controlled (see the module docstring), so it's validated here rather than assumed clean by callers.
Source code in packages/allocator/src/lablink_allocator_service/tunnel_manager.py
is_valid_alias_octet(octet)
¶
True iff octet is in the range a /32 loopback rule can express.
Exposed so callers (routes/registration.py) can reject an out-of-range allocation with a clean error response before writing a client row, instead of finding out only when authorize_client raises.
Source code in packages/allocator/src/lablink_allocator_service/tunnel_manager.py
path_prefix(client_id, secret)
¶
Per-client value the client must dial as its first path segment.
tun-<client_id>-<digest>. Must BE the first path segment, not a
sub-path under some shared root: wstunnel's !PathPrefix restriction
matcher only inspects the first segment (see the module docstring), so
a value like "tunnel/tun- marker is
what lets nginx tell a tunnel attach apart from an application route
now that the prefix sits at the URL root instead of under a fixed
"tunnel/" segment.
Derived from the client's own secret so it is stable across restarts and needs no storage of its own. It identifies the client to /internal/tunnel_auth; the bearer token is what authenticates.
Source code in packages/allocator/src/lablink_allocator_service/tunnel_manager.py
revoke_client(client_id)
¶
Drop this client's permission. No-op if it was never a tunnel client.
No client_id validation here: an unsafe id can never have made it into _restrictions in the first place (authorize_client rejects it before writing), so the plain dict.pop miss already gives the right no-op behavior. This also has to stay a no-op, not raise -- it runs on every client unregister.
Source code in packages/allocator/src/lablink_allocator_service/tunnel_manager.py
tunnel_status()
¶
"ok" iff the shared tunnel server is accepting connections.