summaryrefslogtreecommitdiffhomepage
path: root/ipn/localapi
AgeCommit message (Collapse)AuthorFilesLines
2024-11-07client/tailscale,ipn/{ipnlocal,localapi}: add a pre-shutdown localAPI ↵Irbe Krumina1-0/+17
endpoint that terminates control connections. (#14028) Adds a /disconnect-control local API endpoint that just shuts down control client. This can be run before shutting down an HA subnet router/app connector replica - it will ensure that all connection to control are dropped and control thus considers this node inactive and tells peers to switch over to another replica. Meanwhile the existing connections keep working (assuming that the replica is given some graceful shutdown period). Updates tailscale/tailscale#14020 Signed-off-by: Irbe Krumina <irbe@tailscale.com>
2024-10-31cmd/tailscale/cli, client/tailscale, ipn/localapi: add tailscale syspolicy ↵Nick Khyl1-0/+50
{list,reload} commands In this PR, we add the tailscale syspolicy command with two subcommands: list, which displays policy settings, and reload, which forces a reload of those settings. We also update the LocalAPI and LocalClient to facilitate these additions. Updates #12687 Signed-off-by: Nick Khyl <nickk@tailscale.com>
2024-10-29cmd/tailscale/cli: add 'tailscale metrics' commandAnton Tolchanov1-9/+2
- `tailscale metrics print`: to show metric values in console - `tailscale metrics write`: to write metrics to a file (with a tempfile & rename dance, which is atomic on Unix). Also, remove the `TS_DEBUG_USER_METRICS` envknob as we are getting more confident in these metrics. Updates tailscale/corp#22075 Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2024-10-18ipn/{ipnauth,ipnlocal,ipnserver}: send the auth URL to the user who started ↵Nick Khyl2-20/+3
interactive login We add the ClientID() method to the ipnauth.Actor interface and updated ipnserver.actor to implement it. This method returns a unique ID of the connected client if the actor represents one. It helps link a series of interactions initiated by the client, such as when a notification needs to be sent back to a specific session, rather than all active sessions, in response to a certain request. We also add LocalBackend.WatchNotificationsAs and LocalBackend.StartLoginInteractiveAs methods, which are like WatchNotifications and StartLoginInteractive but accept an additional parameter specifying an ipnauth.Actor who initiates the operation. We store these actor identities in watchSession.owner and LocalBackend.authActor, respectively,and implement LocalBackend.sendTo and related helper methods to enable sending notifications to watchSessions associated with actors (or, more broadly, identifiable recipients). We then use the above to change who receives the BrowseToURL notifications: - For user-initiated, interactive logins, the notification is delivered only to the user who initiated the process. If the initiating actor represents a specific connected client, the URL notification is sent back to the same LocalAPI client that called StartLoginInteractive. Otherwise, the notification is sent to all clients connected as that user. Currently, we only differentiate between users on Windows, as it is inherently a multi-user OS. - In all other cases (e.g., node key expiration), we send the notification to all connected users. Updates tailscale/corp#18342 Signed-off-by: Nick Khyl <nickk@tailscale.com>
2024-10-09clientupdate, ipn/localapi: don't use google/uuid, thin iOS depsBrad Fitzpatrick1-2/+1
We were using google/uuid in two places and that brought in database/sql/driver. We didn't need it in either place. Updates #13760 Updates tailscale/corp#20099 Change-Id: Ieed32f1bebe35d35f47ec5a2a429268f24f11f1f Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-09-25util/usermetrics: make usermetrics non-globalKristoffer Dalby2-3/+2
this commit changes usermetrics to be non-global, this is a building block for correct metrics if a go process runs multiple tsnets or in tests. Updates #13420 Updates tailscale/corp#22075 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-09-24cli: add `tailscale dns query` (#13368)Andrea Gottardo1-0/+46
Updates tailscale/tailscale#13326 Adds a CLI subcommand to perform DNS queries using the internal DNS forwarder and observe its internals (namely, which upstream resolvers are being used). Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
2024-09-04cli: implement `tailscale dns status` (#13353)Andrea Gottardo1-0/+39
Updates tailscale/tailscale#13326 This PR begins implementing a `tailscale dns` command group in the Tailscale CLI. It provides an initial implementation of `tailscale dns status` which dumps the state of the internal DNS forwarder. Two new endpoints were added in LocalAPI to support the CLI functionality: - `/netmap`: dumps a copy of the last received network map (because the CLI shouldn't have to listen to the ipn bus for a copy) - `/dns-osconfig`: dumps the OS DNS configuration (this will be very handy for the UI clients as well, as they currently do not display this information) My plan is to implement other subcommands mentioned in tailscale/tailscale#13326, such as `query`, in later PRs. Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
2024-08-28ipn/{ipnauth,ipnlocal,ipnserver,localapi}: start baby step toward moving ↵Nick Khyl2-109/+23
access checks from the localapi.Handler to the LocalBackend Currently, we use PermitRead/PermitWrite/PermitCert permission flags to determine which operations are allowed for a LocalAPI client. These checks are performed when localapi.Handler handles a request. Additionally, certain operations (e.g., changing the serve config) requires the connected user to be a local admin. This approach is inherently racey and is subject to TOCTOU issues. We consider it to be more critical on Windows environments, which are inherently multi-user, and therefore we prevent more than one OS user from connecting and utilizing the LocalBackend at the same time. However, the same type of issues is also applicable to other platforms when switching between profiles that have different OperatorUser values in ipn.Prefs. We'd like to allow more than one Windows user to connect, but limit what they can see and do based on their access rights on the device (e.g., an local admin or not) and to the currently active LoginProfile (e.g., owner/operator or not), while preventing TOCTOU issues on Windows and other platforms. Therefore, we'd like to pass an actor from the LocalAPI to the LocalBackend to represent the user performing the operation. The LocalBackend, or the profileManager down the line, will then check the actor's access rights to perform a given operation on the device and against the current (and/or the target) profile. This PR does not change the current permission model in any way, but it introduces the concept of an actor and includes some preparatory work to pass it around. Temporarily, the ipnauth.Actor interface has methods like IsLocalSystem and IsLocalAdmin, which are only relevant to the current permission model. It also lacks methods that will actually be used in the new model. We'll be adding these gradually in the next PRs and removing the deprecated methods and the Permit* flags at the end of the transition. Updates tailscale/corp#18342 Signed-off-by: Nick Khyl <nickk@tailscale.com>
2024-08-27usermetric: add initial user-facing metricsKristoffer Dalby1-0/+15
This commit adds a new usermetric package and wires up metrics across the tailscale client. Updates tailscale/corp#22075 Co-authored-by: Anton Tolchanov <anton@tailscale.com> Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-08-14ipn/localapi: only flush relevant data in multiFilePostResponseWriter.Flush()Percy Wegmann1-4/+11
This prevents two things: 1. Crashing if there's no response body 2. Sending a nonsensical 0 response status code Updates tailscale/corp#22357 Signed-off-by: Percy Wegmann <percy@tailscale.com>
2024-07-19cmd/tailscale: add --min-validity flag to the cert command (#12822)Andrew Lytvynov1-1/+11
Some users run "tailscale cert" in a cron job to renew their certificates on disk. The time until the next cron job run may be long enough for the old cert to expire with our default heristics. Add a `--min-validity` flag which ensures that the returned cert is valid for at least the provided duration (unless it's longer than the cert lifetime set by Let's Encrypt). Updates #8725 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2024-06-18proxymap, various: distinguish between different protocolsAndrew Dunham2-6/+6
Previously, we were registering TCP and UDP connections in the same map, which could result in erroneously removing a mapping if one of the two connections completes while the other one is still active. Add a "proto string" argument to these functions to avoid this. Additionally, take the "proto" argument in LocalAPI, and plumb that through from the CLI and add a new LocalClient method. Updates tailscale/corp#20600 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: I35d5efaefdfbf4721e315b8ca123f0c8af9125fb
2024-06-14ipn/localapi,client/tailscale,cmd/derper: add WhoIs lookup by nodekey, use ↵Brad Fitzpatrick2-16/+55
in derper Fixes #12465 Change-Id: I9b7c87315a3d2b2ecae2b8db9e94b4f5a1eef74a Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-06-10ipn/{ipnlocal,localapi},net/netkernelconf,client/tailscale,cmd/containerboot ↵Irbe Krumina1-0/+18
: optionally enable UDP GRO forwarding for containers (#12410) Add a new TS_EXPERIMENTAL_ENABLE_FORWARDING_OPTIMIZATIONS env var that can be set for tailscale/tailscale container running as a subnet router or exit node to enable UDP GRO forwarding for improved performance. See https://tailscale.com/kb/1320/performance-best-practices#linux-optimizations-for-subnet-routers-and-exit-nodes This is currently considered an experimental approach; the configuration support is partially to allow further experimentation with containerized environments to evaluate the performance improvements. Updates tailscale/tailscale#12295 Signed-off-by: Irbe Krumina <irbe@tailscale.com>
2024-06-02tsnet,wgengine/netstack: add ListenPacket and testsMaisem Ali1-1/+4
This adds a new ListenPacket function on tsnet.Server which acts mostly like `net.ListenPacket`. Unlike `Server.Listen`, this requires listening on a specific IP and does not automatically listen on both V4 and V6 addresses of the Server when the IP is unspecified. To test this, it also adds UDP support to tsdial.Dialer.UserDial and plumbs it through the localapi. Then an associated test to make sure the UDP functionality works from both sides. Updates #12182 Signed-off-by: Maisem Ali <maisem@tailscale.com>
2024-05-07ipn/ipnlocal, all: plumb health trackers in testsBrad Fitzpatrick1-1/+1
I saw some panics in CI, like: 2024-05-08T04:30:25.9553518Z ## WARNING: (non-fatal) nil health.Tracker (being strict in CI): 2024-05-08T04:30:25.9554043Z goroutine 801 [running]: 2024-05-08T04:30:25.9554489Z tailscale.com/health.(*Tracker).nil(0x0) 2024-05-08T04:30:25.9555086Z tailscale.com/health/health.go:185 +0x70 2024-05-08T04:30:25.9555688Z tailscale.com/health.(*Tracker).SetUDP4Unbound(0x0, 0x0) 2024-05-08T04:30:25.9556373Z tailscale.com/health/health.go:532 +0x2f 2024-05-08T04:30:25.9557296Z tailscale.com/wgengine/magicsock.(*Conn).bindSocket(0xc0003b4808, 0xc0003b4878, {0x1fbca53, 0x4}, 0x0) 2024-05-08T04:30:25.9558301Z tailscale.com/wgengine/magicsock/magicsock.go:2481 +0x12c5 2024-05-08T04:30:25.9559026Z tailscale.com/wgengine/magicsock.(*Conn).rebind(0xc0003b4808, 0x0) 2024-05-08T04:30:25.9559874Z tailscale.com/wgengine/magicsock/magicsock.go:2510 +0x16f 2024-05-08T04:30:25.9561038Z tailscale.com/wgengine/magicsock.NewConn({0xc000063c80, 0x0, 0xc000197930, 0xc000197950, 0xc000197960, {0x0, 0x0}, 0xc000197970, 0xc000198ee0, 0x0, ...}) 2024-05-08T04:30:25.9562402Z tailscale.com/wgengine/magicsock/magicsock.go:476 +0xd5f 2024-05-08T04:30:25.9563779Z tailscale.com/wgengine.NewUserspaceEngine(0xc000063c80, {{0x22c8750, 0xc0001976b0}, 0x0, {0x22c3210, 0xc000063c80}, {0x22c31d8, 0x2d3c900}, 0x0, 0x0, ...}) 2024-05-08T04:30:25.9564982Z tailscale.com/wgengine/userspace.go:389 +0x159d 2024-05-08T04:30:25.9565529Z tailscale.com/ipn/ipnlocal.newTestBackend(0xc000358b60) 2024-05-08T04:30:25.9566086Z tailscale.com/ipn/ipnlocal/serve_test.go:675 +0x2a5 2024-05-08T04:30:25.9566612Z ta Updates #11874 Change-Id: I3432ed52d670743e532be4642f38dbd6e3763b1b Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-29appc: setting AdvertiseRoutes explicitly discards app connector routesFran Bull1-0/+6
This fixes bugs where after using the cli to set AdvertiseRoutes users were finding that they had to restart tailscaled before the app connector would advertise previously learned routes again. And seems more in line with user expectations. Fixes #11006 Signed-off-by: Fran Bull <fran@tailscale.com>
2024-04-26ipn/{ipnlocal,localapi},wgengine{,/magicsock}: plumb health.TrackerBrad Fitzpatrick2-8/+6
Down to 25 health.Global users. After this remains controlclient & net/dns & wgengine/router. Updates #11874 Updates #4136 Change-Id: I6dd1856e3d9bf523bdd44b60fb3b8f7501d5dc0d Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-25health: add Tracker type, in prep for removing global variablesBrad Fitzpatrick1-1/+1
This moves most of the health package global variables to a new `health.Tracker` type. But then rather than plumbing the Tracker in tsd.System everywhere, this only goes halfway and makes one new global Tracker (`health.Global`) that all the existing callers now use. A future change will eliminate that global. Updates #11874 Updates #4136 Change-Id: I6ee27e0b2e35f68cb38fecdb3b2dc4c3f2e09d68 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-22ipn/local: log OS-specific diagnostic information as JSON (#11700)Joe Tsai1-1/+1
There is an undocumented 16KiB limit for text log messages. However, the limit for JSON messages is 256KiB. Even worse, logging JSON as text results in significant overhead since each double quote needs to be escaped. Instead, use logger.Logf.JSON to explicitly log the info as JSON. We also modify osdiag to return the information as structured data rather than implicitly have the package log on our behalf. This gives more control to the caller on how to log. Updates #7802 Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2024-04-16all: use Go 1.22 range-over-intBrad Fitzpatrick2-2/+2
Updates #11058 Change-Id: I35e7ef9b90e83cac04ca93fd964ad00ed5b48430 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-15ipn/ipnlocal: make StartLoginInteractive take (yet unused) contextBrad Fitzpatrick1-1/+1
In prep for future fix to undermentioned issue. Updates tailscale/tailscale#7036 Change-Id: Ide114db917dcba43719482ffded6a9a54630d99e Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-15cmd/tailscale, ipn/ipnlocal: add suggest exit node CLI option (#11407)Claire Wang1-0/+16
Updates tailscale/corp#17516 Signed-off-by: Claire Wang <claire@tailscale.com>
2024-04-14ipn/ipnlocal: remove some dead code (legacyBackend methods) from LocalBackendBrad Fitzpatrick1-14/+0
Nothing used it. Updates #11649 Change-Id: Ic1c331d947974cd7d4738ff3aafe9c498853689e Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-07ipn/{ipnlocal,localapi}: add API to toggle use of exit nodeBrad Fitzpatrick1-0/+27
This is primarily for GUIs, so they don't need to remember the most recently used exit node themselves. This adds some CLI commands, but they're disabled and behind the WIP envknob, as we need to consider naming (on/off is ambiguous with running an exit node, etc) as well as automatic exit node selection in the future. For now the CLI commands are effectively developer debug things to test the LocalAPI. Updates tailscale/corp#18724 Change-Id: I9a32b00e3ffbf5b29bfdcad996a4296b5e37be7e Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-05ipn/localapi: sort localapi handler map keysBrad Fitzpatrick2-20/+92
Updates #cleanup Change-Id: I750ed8d033954f1f8786fb35dd16895bb1c5af8e Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-05drive: move normalizeShareName into pkg drive and make func public (#11638)Charlotte Brandhorst-Satzkorn1-2/+2
This change makes the normalizeShareName function public, so it can be used for validation in control. Updates tailscale/corp#16827 Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2024-04-04tailscale: switch tailfs to drive syntax for api and logs (#11625)Charlotte Brandhorst-Satzkorn1-3/+3
This change switches the api to /drive, rather than the previous /tailfs as well as updates the log lines to reflect the new value. It also cleans up some existing tailfs references. Updates tailscale/corp#16827 Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2024-04-03tailscale: update tailfs functions and vars to use drive naming (#11597)Charlotte Brandhorst-Satzkorn1-10/+10
This change updates all tailfs functions and the majority of the tailfs variables to use the new drive naming. Updates tailscale/corp#16827 Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2024-04-02tailscale: update tailfs file and package names (#11590)Charlotte Brandhorst-Satzkorn1-3/+3
This change updates the tailfs file and package names to their new naming convention. Updates #tailscale/corp#16827 Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2024-03-27ipn/localapi: add support for multipart POST to file-putPercy Wegmann1-18/+11
This allows sending multiple files via Taildrop in one request. Progress is tracked via ipn.Notify. Updates tailscale/corp#18202 Signed-off-by: Percy Wegmann <percy@tailscale.com>
2024-03-27ipn/localapi: add support for multipart POST to file-putPercy Wegmann1-14/+203
This allows sending multiple files via Taildrop in one request. Progress is tracked via ipn.Notify. Updates tailscale/corp#18202 Signed-off-by: Percy Wegmann <percy@tailscale.com>
2024-03-21localapi: reduce garbage production in bus watcherJames Tucker1-5/+3
Updates #optimization Signed-off-by: James Tucker <james@tailscale.com>
2024-03-09ipn/{ipnlocal,localapi}: add debug verb to force spam IPN bus NetMapBrad Fitzpatrick1-0/+2
To force the problem in its worst case scenario before fixing it. Updates tailscale/corp#17859 Change-Id: I2c8b8e5f15c7801e1ab093feeafac52ec175a763 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-03-08ipn,cmd/tailscale,client/tailscale: add support for renaming TailFS sharesPercy Wegmann1-5/+38
- Updates API to support renaming TailFS shares. - Adds a CLI rename subcommand for renaming a share. - Renames the CLI subcommand 'add' to 'set' to make it clear that this is an add or update. - Adds a unit test for TailFS in ipnlocal Updates tailscale/corp#16827 Signed-off-by: Percy Wegmann <percy@tailscale.com>
2024-03-08ipn,tailfs: tie TailFS share configuration to user profilePercy Wegmann1-6/+2
Previously, the configuration of which folders to share persisted across profile changes. Now, it is tied to the user's profile. Updates tailscale/corp#16827 Signed-off-by: Percy Wegmann <percy@tailscale.com>
2024-03-06ipn/localapi: add set-gui-visible endpointAndrea Gottardo1-0/+22
Updates tailscale/corp#17859 Provides a local API endpoint to be called from the GUI to inform the backend when the client menu is opened or closed. cc @bradfitz Signed-off-by: Andrea Gottardo <andrea@gottardo.me> Signed-off-by: Andrea Gottardo <andrea@tailscale.com> Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
2024-02-09tailfs: clean up naming and package structurePercy Wegmann1-8/+8
- Restyles tailfs -> tailFS - Defines interfaces for main TailFS types - Moves implemenatation of TailFS into tailfsimpl package Updates tailscale/corp#16827 Signed-off-by: Percy Wegmann <percy@tailscale.com>
2024-02-09tailfs: initial implementationPercy Wegmann1-1/+123
Add a WebDAV-based folder sharing mechanism that is exposed to local clients at 100.100.100.100:8080 and to remote peers via a new peerapi endpoint at /v0/tailfs. Add the ability to manage folder sharing via the new 'share' CLI sub-command. Updates tailscale/corp#16827 Signed-off-by: Percy Wegmann <percy@tailscale.com>
2024-02-05ipn/localapi: more http status cleanup (#10995)kari-ts1-4/+4
Use Http.StatusOk instead of 200 Updates #cleanup
2024-01-05cmd/tailscale, ipn/ipnlocal: add 'debug dial-types' commandAndrew Dunham1-0/+71
This command allows observing whether a given dialer ("SystemDial", "UserDial", etc.) will successfully obtain a connection to a provided host, from inside tailscaled itself. This is intended to help debug a variety of issues from subnet routers to split DNS setups. Updates #9619 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: Ie01ebb5469d3e287eac633ff656783960f697b84
2023-12-06ipn/localapi: use clientupdate.CanAutoUpdate from serveUpdateCheckSonia Appasamy1-10/+2
Fixes #10486 Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
2023-11-16{client/web},{ipn/ipnlocal}: replace localapi debug-web-client endpointSonia Appasamy1-60/+0
This change removes the existing debug-web-client localapi endpoint and replaces it with functions passed directly to the web.ServerOpts when constructing a web.ManageServerMode client. The debug-web-client endpoint previously handled making noise requests to the control server via the /machine/webclient/ endpoints. The noise requests must be made from tailscaled, which has the noise connection open. But, now that the full client is served from tailscaled, we no longer need to proxy this request over the localapi. Updates tailscale/corp#14335 Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
2023-11-15wgengine/magicsock: add DERP homeless debug mode for testingBrad Fitzpatrick1-0/+4
In DERP homeless mode, a DERP home connection is not sought or maintained and the local node is not reachable. Updates #3363 Updates tailscale/corp#396 Change-Id: Ibc30488ac2e3cfe4810733b96c2c9f10a51b8331 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-11-09ipn/{ipnlocal,ipnstate,localapi}: add localapi endpoints for client ↵Naman Sood1-0/+73
self-update (#10188) * ipn/{ipnlocal,ipnstate,localapi}: add localapi endpoints for client self-update Updates #10187. Signed-off-by: Naman Sood <mail@nsood.in> * depaware Updates #10187. Signed-off-by: Naman Sood <mail@nsood.in> * address review feedback Signed-off-by: Naman Sood <mail@nsood.in> --------- Signed-off-by: Naman Sood <mail@nsood.in>
2023-11-09ipn/localapi: only perform local-admin check in serveServeConfig (#10163)Andrew Lytvynov2-63/+120
On unix systems, the check involves executing sudo, which is slow. Instead of doing it for every incoming request, move the logic into localapi serveServeConfig handler and do it as needed. Updates tailscale/corp#15405 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2023-11-09client/tailscale,ipn/{ipnlocal,localapi}: check UDP GRO config (#10071)Jordan Whited1-0/+18
Updates tailscale/corp#9990 Signed-off-by: Jordan Whited <jordan@tailscale.com>
2023-11-07ipn/localapi: require root or sudo+operator access for SetServeConfig (#10142)Andrew Lytvynov2-18/+61
For an operator user, require them to be able to `sudo tailscale` to use `tailscale serve`. This is similar to the Windows elevated token check. Updates tailscale/corp#15405 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2023-11-02ipn/localapi: make serveTKASign require write permission (#10094)Andrew Lytvynov1-2/+2
The existing read permission check looks like an oversight. Write seems more appropriate for sining new nodes. Updates https://github.com/tailscale/corp/issues/15506 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>