Repository navigation
Let a browser on another device sign in to a Windows host with a typed code - #2088
Open
Brian Krabach (bkrabach) wants to merge 6 commits into
Open
Brian Krabach (bkrabach) wants to merge 6 commits into
Brian Krabach (bkrabach) wants to merge 6 commits into
Conversation
…d code On a native Windows host a browser signs in only through a one-use ticket that the local launcher issues and a loopback page redeems. A browser on another device (a LAN or Tailnet address) could not sign in at all: Windows has no password sign-in, and the page told the person to use the launcher. `amplifier-unified browsers code` now prints a code at the PC. The owner types it on the sign-in page that the other device shows. The code has ten symbols from 32 (50 bits), lasts 120 seconds and works once. The host keeps only its SHA-256, and at most eight codes. The command uses the same pinned request as `open`: it pins the privately read leaf certificate before it reads or sends the control credential, and the host accepts that request from this computer only. The code is bound to the host instance, the data folder, the owner and one configured HTTPS address that is not this PC. The redeem check is separate and narrow: HTTPS, an explicit Origin equal to the address the browser used, and that address an exact configured remote one. It does not look at the peer address, because a Tailscale TCP forward connects from 127.0.0.1. There is no lockout and no delay: a wrong code is counted and never removes a real one, so nobody can keep the owner out by guessing. A session from a code is marked remote in its signed cookie. "Sign out all browsers" also drops pending codes. The local ticket sign-in, and password sign-in on Linux, macOS and WSL, do not change. `issue`, `redeem` and `owner_browser_url` now share their checks with the code paths through private helpers; their tests pass unedited. The remote page is sent with `Referrer-Policy: same-origin`, not `no-referrer`, because a browser posts a form from a `no-referrer` page with `Origin: null`. Not measured: a phone browser, a real second device, and native Windows. The new tests run on Linux and are added to the focused Windows job. Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
… only for a live code
… sign-out state in a holder - One test serves the real handlers over real TLS with the product certificate shape and signs in at the remote name from a loopback peer. - The documented address order keeps the local address first: the first configured address is the main one for callbacks of connected services. - The running host kept its sign-out time in application keys and changed them after start, which aiohttp deprecates. It now lives in a holder object. A watcher fault is said once in the host log.
…n-in # Conflicts: # .github/workflows/platform-mechanisms.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Change
Problem. On a native Windows host a browser signs in only through a one-use ticket that the local launcher issues and a loopback page redeems. A Windows host has no password sign-in. So a browser on another device (LAN or Tailnet address) could not sign in at all. Optional LAN or Tailnet access must be possible on a Windows host. Certificate renewal for a remote address (#2069) and sign-out of all browsers (#2080) are in place; this is the last piece.
Result. The owner starts the sign-in at the PC:
It prints a code such as
ABCDE-FGHJKand the address to open. On the other device the sign-in page for that address shows one field for the code.0,1,I,O): 50 bits. It lasts 120 seconds and works once. The host keeps only its SHA-256, at most eight at a time. It is bound to the host instance, the data folder, the owner SID and one address: an exact HTTPS entry ofpublic_originsthat is not this computer. Input is upper-cased; spaces and hyphens are removed; nothing else is changed, so a look-alike is a wrong code.open: it pins the privately read server certificate before it reads or sends the owner credential. The host accepts that request from this computer only, with the same checks as for a ticket.browser_entry.remote_origin): HTTPS, an explicitOriginequal to the address the browser used, and that address an exact configured remote one. It does not read the peer address. A Tailscale TCP forward connects from127.0.0.1(measured on Windows 11 with Tailscale 1.104.5), so the socket does not say where the browser is. The local ticket keeps its stricter check; its code path and its two test files are unchanged in behavior (tests/test_browser_entry.pyandtests/test_windows_auth.pyhave no edits).amplifier-unified browsers sign-out(Let the owner sign out all browsers at once #2080) signs out every browser and now also drops pending codes.Two independent security reviews of this design (typed code versus link, lockout, sign-out) are recorded with the program; their findings are applied.
Also in this PR: a fix to #2080. The running host kept its sign-out time in application keys and changed them after start. aiohttp deprecates that, and a later aiohttp would turn a sign-out into a silent no-op in the running host. The time now lives in a holder object (
auth.Sessions). A fault in the watcher is said once in the host log.Docs.
docs/DEPLOYMENT.mdhas a new section, "Windows host: reach Unified from another device", with the measured Tailnet steps. The local address stays first inpublic_origins, because the first address is the main one for callbacks of connected services. The README points to it. The design document replaces "remote browsers need a pairing design" with the design as built.Limits, stated plainly.
Not touched:
components/web-client/**,amplifier_web/static/**,scripts/install.ps1,scripts/windows_bootstrap_native.cs.Validation
Local, Linux:
tests/test_browser_code.py: 83 passed. Real aiohttp requests through the real authentication middleware and handlers. One case serves the real handlers over real TLS with the product certificate shape and signs in at the remote name from a loopback peer: certificate valid for the remote name and refused for another name; code page without script; owner makes a code over the local address; wrong code; the same form at the local address refused; right code gives a Secure, HttpOnly, SameSite=Strict session that the middleware accepts; the code works once.test_browser_entry,test_windows_auth,test_session_sign_out,test_server_auth,test_cli_deployment,test_device_pairing,test_device_connections,test_windows_user_trial_probe,test_windows_bootstrap_ci, with the new file): 1142 passed, 8 skipped.Native Windows (this PR's
windows-local-connectioncheck):tests/test_browser_code.pyandtests/test_session_sign_out.pyrun there, including the real-TLS case.Not proven:
Originon the form post. The local flow already depends on that on desktop browsers.