Public Access
Let a host marked for the relay actually be reached through it
The relay checkbox said it was not wired up, and the plan said the server half was built and shipped. It was not: there was ADR 0004, the contract records, RelayOptions and a CHECK constraint, and RelayOptions said in its own remarks that there was no endpoint and no dialler. So both halves are built here, as step 3 of docs/reaching-a-host-you-cannot-dial.md. Server, in Features/Relay: - POST /api/v1/relay/tickets makes every decision: the host, Connect on its vault, the relay switched on, and the stored name resolved once and judged by RelayAddressPolicy. Loopback, link-local, 0.0.0.0/8, multicast and the metadata endpoints are refused with no configuration escape, with IPv4-mapped and NAT64 spellings unwrapped first; then the operator's denials, allow-list and private-network switch. What passes goes into a Data Protection ticket, and nothing resolves the name again. Every target refusal is the same 403, so the endpoint cannot be used to map the operator's DNS; the real reason is logged. - GET /api/v1/relay/connect takes the ticket as a second subprotocol, checks its expiry against TimeProvider and spends it with a unique insert into relay_ticket_use before accepting the upgrade. After the upgrade a refusal is a close code: 4502 for a host the server could not reach, 4429 for a limit, 4408 idle, 4409 lifetime, 1001 after a drain. - relay_session is the audit row, written from short-lived scopes. Client: - RelayBridge is a loopback SOCKS5 listener that accepts once and pipes to the relay socket. It rides on the new SshLoopbackProxy.Lifetime, so the connection disposes it on every way a session ends and a failed connect disposes it before throwing. - ServerRelay, exposed as IVaultServer.Relay from /api/v1/meta, asks for the ticket, opens the socket, starts the bridge and turns every refusal into a sentence. - VaultViewModel and TransfersViewModel reach a relayed host through it, never fall back to dialling it directly, and replace SSH.NET's transport error with the relay's own reason when it gave one. Neither side ever cancels a WebSocket operation. Cancelling one aborts the socket, and an aborted socket sends no close frame, which is the only way the client learns the server could not reach the host. AnIdleSession_IsClosedWithTheIdleCode asserts that the code arrives. Three departures from ADR 0004, recorded in it: awaited copy loops rather than Pipelines (same backpressure, two 32 KiB buffers), no ?ticket= fallback, and no device id in the ticket. Tests: API 245, shell 582 (5 skipped), layout 170, SSH 110 including a real login through the bridge, API client 30, contracts 25. Not run: the Android build (its change is copy only) and the Alpine layout oracle.
This commit is contained in:
1 parent
54bc142dce
commit
b20f7cae04
51 files changed
+6455
-88
No files matched your search
@@ -636,16 +636,15 @@
|
||||
|
||||
<!--
|
||||
◆ THE RELAY CARD, restyled to the mock's nested-card shape — radius 12, a checkbox with the
|
||||
title beside it rather than under it — but NOT to the mock's copy. The sentence stays
|
||||
exactly what it was, "(not wired up yet)" included: the design's own wording implies a relay
|
||||
this client can dial, and it cannot. See the remark under the hint below, which is unchanged
|
||||
and still the reason this box says what it says.
|
||||
title beside it rather than under it. The "(not wired up yet)" this title carried while the
|
||||
client had no path to the relay is gone, because it has one: a relayed host is connected
|
||||
through a ticket, a WebSocket and a loopback bridge. See docs/reaching-a-host-you-cannot-dial.md.
|
||||
-->
|
||||
<Border CornerRadius="12" Background="{StaticResource Field}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="1" Padding="12">
|
||||
<StackPanel Spacing="6">
|
||||
<CheckBox IsChecked="{Binding EditorRelayEnabled}" VerticalContentAlignment="Center">
|
||||
<TextBlock Text="Connect through the server relay (not wired up yet)" FontSize="13"
|
||||
<TextBlock Text="Connect through the server relay" FontSize="13"
|
||||
FontWeight="SemiBold" Foreground="{StaticResource Text}"
|
||||
TextWrapping="Wrap" />
|
||||
</CheckBox>
|
||||
@@ -655,19 +654,13 @@
|
||||
to resolve the target, or it becomes an authenticated open proxy into the operator's
|
||||
network.
|
||||
|
||||
◆ AND IT SAYS, FIRST, THAT TICKING IT BUYS NOTHING TODAY. The server half of the relay
|
||||
is built and this client has no path to it: VaultViewModel dials the address directly
|
||||
whether this is ticked or not. So the box as it stood spent the one deliberate privacy
|
||||
concession in the design — the address in plaintext columns — and delivered a
|
||||
connection that failed exactly as it had before, with nothing saying why.
|
||||
|
||||
Left tickable rather than disabled, because a host that already carries the flag has to
|
||||
be able to lose it, and a disabled control would trap the concession on. See
|
||||
docs/reaching-a-host-you-cannot-dial.md, which is the plan that makes this sentence
|
||||
removable.
|
||||
It also says the two things a user would otherwise find out by failing: the server,
|
||||
not this machine, has to be able to reach the address — the relay is no help against a
|
||||
deployment outside the target's network — and a relayed host does not fall back to
|
||||
dialling directly, so it connects only while the server is reachable.
|
||||
-->
|
||||
<TextBlock Classes="hint" FontSize="11.5"
|
||||
Text="Not built yet: this client always dials the host itself, so ticking this stores the address on the server and changes nothing about how the host is reached. When it does work, the relay will dial on your behalf — which is why the address and port have to be stored in plain text. Everything else about the host stays encrypted either way." />
|
||||
Text="The server dials this host on your behalf, for a machine this computer cannot reach — so the server has to be able to reach it. That is why the address and port are stored on the server in plain text; everything else about the host stays encrypted. A relayed host connects only while you are online." />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
|
||||
Reference in new issue
Block a user