Commit Graph
11118 Commits
Author SHA1 Message Date
Lee Briggs 687671143a cmd/k8s-operator,k8s-proxy: improve debug msgs
Signed-off-by: Lee Briggs <lee@leebriggs.co.uk>
2026-08-19 18:05:26 -07:00
Evan Lowry 2b8a980f1f ipn/ipnlocal: deduplicate RoutableIPs / RequestTags in hostinfo (#20759)
Both RoutableIPs and RequestTags act as a set of values. There have been
cases of misconfigured IAC populating duplicate values, resulting in
more data being sent to control than needs to be.

Updates tailscale/corp#44607

Signed-off-by: Evan Lowry <evan@tailscale.com>
2026-08-05 12:28:47 -03:00
Adrian Dewhurst 616dcd5378 feature/conn25: add app name to DoH query string
To prepare for enforcing permission to access an app, it is extremely
helpful if the client reports which app it expects the query is for,
instead of making the connector check all possible matching apps.

Updates tailscale/corp#40076

Change-Id: Ib41e0af4c0134d06dc4acda97a39c0a3adb88968
Signed-off-by: Adrian Dewhurst <adrian@tailscale.com>
2026-08-04 14:43:31 -04:00
chaosinthecrd 21a3f6413a cmd/k8s-operator/deploy/chart: support nameOverride and fullnameOverride
The Helm chart hardcoded the operator's Deployment, ServiceAccount,
Role, and RoleBinding names to "operator".

Adds a standard _helpers.tpl with name/fullname template functions and
use them for the operator's resources, including the oauth Secret and
the cluster-scoped ClusterRole/ClusterRoleBinding. When neither
nameOverride nor fullnameOverride is set, the fullname resolves to the
historical hardcoded names rather than the release name, so existing
installations upgrade with no resource renames; rendering with default
values is byte-identical to before.

Fixes tailscale/tailscale#18232

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-08-04 19:08:48 +01:00
chaosinthecrd 37aca4aa2e cmd/k8s-operator: support imagePullSecrets on DNSConfig nameserver
The nameserver Deployment created for a DNSConfig was the only
operator-managed workload with no way to configure imagePullSecrets, so its
pods could not pull the nameserver image from a private registry.

Adds imagePullSecrets to NameserverPod and thread it through to the nameserver
Deployment pod spec.

Updates #16772

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-08-04 17:14:01 +01:00
chaosinthecrd 15b03cf5bc cmd/k8s-operator: set imagePullSecrets on proxy ServiceAccounts
The Helm chart only applied imagePullSecrets to the operator Deployment's
pod spec, so proxy pods (which use a different ServiceAccount) never
received them and could not pull images from private registries.

Apply imagePullSecrets to the operator, proxies, and kube-apiserver-auth-proxy
ServiceAccounts.

Updates #16772

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-08-04 17:14:01 +01:00
Brad Fitzpatrick 0dfe672b32 ipn/ipnlocal: allow the ingress peer capability for unsigned peers (#20745)
0eb38dc2e (#20561) made peer capability resolution return nothing for
peers with UnsignedPeerAPIOnly set, so that a possibly malicious
control server can't grant capabilities to peers outside the tailnet
lock authority. But Tailscale Funnel ingress nodes are unsigned by
design, and control intentionally grants them
PeerCapabilityIngress, which the peerapi /v0/ingress handler requires.
The result was that every Funnel connection was rejected with a 403
"denied; no ingress cap".

Instead of denying all capabilities to unsigned peers, allowlist
PeerCapabilityIngress specifically. It only permits ingress requests
over the PeerAPI, which unsigned peers can already reach, and the
node only serves them for targets explicitly configured for Funnel.

The tsnet TestFunnel didn't catch the regression because its fake
ingress peer was a normal signed peer. Teach testcontrol to mark a
node as UnsignedPeerAPIOnly (excluding such nodes from traffic-
permitting filter rules, as real control does, so clients don't
discard the packet filter) and make TestFunnel use it so the test
now exercises the same capability checks as production Funnel
traffic.

Updates tailscale/corp#46053
Updates #20739


Change-Id: I3f6b8e2a94d1c07b5a2e9d84f16c30aa79e5d21b

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-08-04 08:43:45 -07:00
David Bond ca79c1e09b cmd/k8s-operator,k8s-operator/reconciler: add PeerRelay e2e test (#20663)
This commit adds a new end-to-end test for the `PeerRelay` custom
resource.

This test is currently quite limited due to the fact that we are running
our tests within a kind cluster within github actions. This means it's
not really possible to give the peer relays a proper public IP address
via the `LoadBalancer` type services that we spin up.

That being said, we intend to expand our e2e test suite with actual
real clusters in future so this can be expanded upon at a later date.

For now, this test spins up a single and multi-replica deployment of
a peer relay and confirms that it has been registered with control
and is configured to act as a peer relay.

This test also caught a small bug where the server URL was not being
passed into the peer relay's configuration, which has been fixed here.

Closes: https://github.com/tailscale/corp/issues/45731
Closes: https://github.com/tailscale/corp/issues/45737

Signed-off-by: David Bond <davidsbond93@gmail.com>
2026-08-04 14:29:03 +01:00
Brad Fitzpatrick 12e4462ba3 root: make TestLicenseHeaders skip files unknown to git
The license header test walked the whole tree, so scratch files,
worktrees, and other untracked local files made "go test ." fail in a
developer checkout. Ask git for the set of tracked files and only
check those, falling back to checking everything (as before) when the
tree is not a git checkout, such as in a release tarball.

Fixes #20740

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I9f3a7c21e5b804d6a2f4c8e19d7b53a6e0c412fd
2026-08-04 10:47:35 +01:00
Brad Fitzpatrick 91c1bbecb1 util/httpm: narrow test cache git dependency
Be cacheable in git worktrees.

Revision to earlier b39ee0445d

Updates tailscale/corp#40359

Change-Id: Ib374d90bf6ee833caabaa3c159ec9f09d991aa41
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-08-04 10:47:04 +01:00
Brad Fitzpatrick 4c4d1c35f8 ipn/ipnlocal: avoid deadlocks during shutdown
LocalBackend.Shutdown waits for the ACME refresh loop and active SSH
sessions. Both can be blocked acquiring LocalBackend.mu, so waiting while
holding that mutex deadlocks shutdown.

Detach the SSH server under the mutex, then stop both subsystems after
releasing it. Prevent their work from restarting once shutdown begins, and
serialize repeated Shutdown calls with sync.Once.

Add regression tests that verify subsystem shutdown runs without
LocalBackend.mu held.

Updates tailscale/corp#45964

Change-Id: I37ead4f26fbfb5703a83882668d98a8862ba7d67
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-08-01 13:57:38 -07:00
loowr d645114dd4 ipnauth: set isUnixSock in the ts_omit_unixsocketidentity GetConnIdentity
The ts_omit_unixsocketidentity variant of GetConnIdentity never
performs the *net.UnixConn type assertion, so ConnIdentity.isUnixSock
stays false. ipnserver's Permissions only grants local API access to
unix socket connections, so with this build tag every local API request
is denied read and write access ("status access denied") and the CLI
cannot talk to the daemon at all in --extra-small/--min builds.

Mirror the type assertion from the peercred variant so the omitted
identity build behaves as intended (everyone is an admin when unix
socket identities are compiled out).

Signed-off-by: loowr <loowr@proton.me>
2026-07-31 17:30:11 -07:00
Brad Fitzpatrick 9f7d3f91cd go.mod: bump json-experiment for ExperimentalGlobalSupportFormatTag
We'll need this in corp at least, which means we need this here.

Updates #20220

Change-Id: I5dbf93f2b9ce05658193fc8ba61eb61185637521
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-31 16:38:23 -07:00
Brad Fitzpatrick a5d6c80f86 go.mod: re-bump staticcheck to v0.8.0-rc.1
Commit 33042fb97 (go.mod: bump sigs.k8s.io/controller-runtime to
v0.23.3) was based on a stale tree and accidentally reverted the
staticcheck bump from 7eeb62415 back to v0.7.0. That version's IR
builder panics on the Go 1.27 standard library (unexpected expr:
*ast.KeyValueExpr), breaking staticcheck CI on the Go 1.27 test
branch. Everything else in that commit was intentional k8s ecosystem
upgrades; staticcheck was the only collateral revert.

Updates #20220

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I763a3dcc29e592adcf6979c80d6e720b02c0bb09
2026-07-31 14:42:34 -07:00
Brad Fitzpatrick f520f85d3b go.mod: reorganize with Go 1.27's go mod tidy
Go 1.27's go mod tidy merges and reorganizes the require blocks once
the go directive in go.mod is 1.27 or newer. Land that mechanical
reorganization now (generated with a temporary 1.27 directive, with
the directive then restored) so the eventual toolchain switch doesn't
carry a 300+ line go.mod diff. Both Go 1.26's and Go 1.27's go mod
tidy leave the reorganized file unchanged, and go.sum and the selected
module versions are identical, so this is purely cosmetic.

Updates #20220

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: If7af51ca71ec30026034cf736f992e731c0e0531
2026-07-31 14:14:55 -07:00
chaosinthecrd 33042fb97b go.mod: bump sigs.k8s.io/controller-runtime to v0.23.3
PR #20388 (commit ca9f6971e) bumped k8s.io/* from v0.34 to v0.35 as a
transitive effect of pulling in helm v3.21 (for containerd CVE fixes).
The old controller-runtime v0.19.4 is aligned with k8s 1.31 APIs and
regresses at runtime against v0.35 client-go (informer reflectors stall
on Watch responses), leaving the operator unable to reconcile.

v0.23.3 is the release aligned with k8s.io/* v0.35 / Kubernetes 1.35.
The operator uses a narrow slice of controller-runtime (manager +
builder + client + reconcile + handler + source), so this bump is a
lift-and-shift: no reconciler wiring changes needed.

Test scaffolding fix: controller-runtime v0.20+ populates TypeMeta
(Kind/APIVersion) on objects returned by the fake client. The shared
expectEqual helper now strips TypeMeta before diffing so existing
tests continue to work without needing every 'want' object updated.

Tested: deployed to live clusters across Kubernetes 1.33, 1.34, 1.35,
and 1.36 (identical build per cluster). Verified the operator starts and
reconciles cleanly on all versions with no RBAC/forbidden errors, CRDs
establish and enforce validation, and the field indexers resolve
correctly under annotation churn (no stale-index misrouting). Exercised
the Service, ProxyClass, Connector, egress (simple + ProxyGroup, incl.
the EndpointSlice reconciler), and HA ingress-for-pg reconcilers.
Confirmed informer/watch recovery after an API server restart forces a
re-list/re-watch (the exact regression this bump fixes).

Updates tailscale/corp#44898

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-07-31 18:36:38 +01:00
Jordan Whited 014d5bd9e4 net/packet,wgengine: update TSMPDiscoKeyAdvertisement docs
Updates #20081
Updates #20494

Signed-off-by: Jordan Whited <jordan@tailscale.com>
2026-07-31 09:53:25 -07:00
Jordan Whited b87203b836 tailcfg: bump CapVer for TSMPDiscoKeyAdvertisement on WireGuard handshake
TSMPDiscoKeyAdvertisement transmission on WireGuard handshake was
implemented in 3799eaf.

Updates #20081
Updates #20494

Signed-off-by: Jordan Whited <jordan@tailscale.com>
2026-07-31 08:48:25 -07:00
Brad Fitzpatrick 0e0ee1ad04 tool/gocross: fix gocross-wrapper.ps1 toolchain staleness check for rc versions
The check strips the rc suffix from the installed toolchain's minor
version (VERSION file, e.g. "go1.27rc2" becomes 27) but not from
go.mod's go directive ("1.27rc2" stays 27rc2). The string comparison
27 -lt 27rc2 is then true, so the wrapper considered an up-to-date rc
toolchain stale and tried to delete it while its go.exe was running,
failing with access denied and breaking the gocross build on Windows.
Strip the rc suffix from both sides.

Updates #20220

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ifa74cdfc4f9095133b96579de2f804c74cd9aeab
2026-07-31 08:11:27 -07:00
Brad Fitzpatrick 7eeb62415e go.mod: bump staticcheck in prep for Go 1.27, address fallout
Go 1.27 requires this new v0.8.0-rc.1.

But staticcheck 0.8's SA4023 gets stricter and points out that
modifiedExternallyError and handleListenersAccept always return
non-nil errors, and that MonitorHealth's callers don't need a separate
nil check before errors.Is. Simplify all three call sites; no behavior
change.

But then a handful of other places that SA4023 is angry about are
wrong (because it's not considering build tags) and can't be addressed
by ignore directives (again not considering build tags), so we just
disable SA4023 for now.

Updates #20220

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I2fefe3b986b5798c2e01624a0e9820839d21a569
2026-07-31 08:00:40 -07:00
chaosinthecrd 165622bc0f cmd/k8s-operator: add auth key reissuance to PeerRelay
PeerRelay replicas got a single-use auth key minted on config Secret
creation and never again, so a replica that lost its login (device
deleted, state Secret lost, key expiry) could never re-auth despite
containerboot writing the reissue_authkey signal.

Port the shouldReissueAuthKey pattern already used by ProxyGroup and
Recorder: track in-flight reissues per replica, rate-limit re-issuance
per PeerRelay, and delete the stale device before minting a new key.

Updates #20544

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-07-31 15:58:55 +01:00
Brad Fitzpatrick 9c8ddab2b4 go.toolchain.next.{branch,rev}: start testing Go 1.27 (go1.27rc2)
Point the TS_GO_NEXT=1 toolchain at the tailscale.go1.27 branch so we
can start testing Go 1.27 ahead of its release.

Updates #20220

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I7947f1e79b713ff84e0418aef4ed3b17f7fbdbd5
2026-07-31 07:38:38 -07:00
Adriano Sela Aviles 47e52e5f1f util/dnsname: add utility function ValidLabelLike
Updates tailscale/corp#45901

Signed-off-by: Adriano Sela Aviles <adriano@tailscale.com>
2026-07-30 17:01:49 -07:00
Jordan Whited 7dc437768e go.mod: bump wireguard-go
Updates #20590

Signed-off-by: Jordan Whited <jordan@tailscale.com>
2026-07-30 15:49:42 -07:00
Simon Law 42a3f151d3 cmd/tailscale/jsonoutput: expand on package documentation
Updates #17613

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-07-30 16:23:09 -04:00
Simon Law 7cf85bdaad cmd/tailscale/tslockjsonv1: add examples to the docs
This patch adds examples of unmarshalling tslockjsonv1.LogResponse
and tslockjsonv1.StatusResponse to the documentation.

Updates #17613

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-07-30 16:23:09 -04:00
Simon Law 3093e4523c feature/tailnetlock/tslockjsonv1: turn print functions into JSON converters
This patch pulls the printing and JSON-encoding out of
feature/tailnetlock/tslockjsonv1 into their callers, so that this
package only handles type conversions.

In cmd/tailscale/cli/tailnet-lock.go, it extracts the
printTailnetLockStatus function from runTailnetLockStatus to mirror
printTailnetLockLog and runTailnetLockLog.

Updates #17613

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-07-30 16:23:09 -04:00
Simon Law 1f57abd00b feature/tailnetlock/tstestjsonv1: rename functions with simpler names
This patch renames the functions in feature/tailnetlock/tstestjsonv1
to remove stuttering. It also adds doc comments.

Updates #17613

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-07-30 16:23:09 -04:00
Simon Law de53f2111f feature/tailnetlock/tslockjsonv1: extract JSON marshalers for tailscale lock
This patch extracts the functions used to marshal the JSON output of
the `tailscale lock` subcommand.

Updates #17613

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-07-30 16:23:09 -04:00
Simon Law 2b879a6e95 cmd/tailscale/tslockjsonv1: export structs to unmarshal JSON for tailscale lock
This patch exports the structs used to unmarshal the JSON output of
the `tailscale lock` subcommand.

Updates #17613

Change-Id: I615723ff9dbc631e452dbf6bbdeab7b97e96dbb3
Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-07-30 16:23:09 -04:00
Simon Law 965e2f7d0d cmd/tailscale/tslockjsonv1: extract handling of TKA types
This patch extracts the handling of the TKA types for the
`tailscale lock` subcommand into its own tka.go file.

Updates #17613

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-07-30 16:23:09 -04:00
Simon Law eccbbaa14d cmd/tailscale/tslockjsonv1: extract JSON handling for tailscale lock
This patch extracts the JSON handling for the `tailscale lock`
subcommand from the jsonoutput package into its own tslockjsonv1
package.

Updates #17613

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-07-30 16:23:09 -04:00
Naman Sood b3c9f289ae feature/conn25: clear address assignments, flow tables on profile switch
On switching from one tailnet with Connectors 2025 enabled to another,
clear the address assignments and flow tables from the previous tailnet.
They will not be useful in the new one (since the tailnet configuration
and nodes are different), and could blackhole traffic if both tailnets
happen to have a connector for the same domain.

Fixes tailscale/corp#45619.

Signed-off-by: Naman Sood <mail@nsood.in>
2026-07-30 16:20:36 -04:00
Brad Fitzpatrick 3bf0149b29 util/zstdframe, util/testenv: don't pool coders within synctest bubbles
The zstd Encoder and Decoder types use channels internally, created
when the coder is constructed. A coder constructed by a goroutine
inside a testing/synctest bubble therefore owns bubble-associated
channels, and if it lands in zstdframe's process-wide pools and is
later reused outside that bubble, the Go runtime kills the process:

    fatal error: receive on synctest channel from outside bubble

This has been crashing test binaries that mix synctest-based tests
with regular tests exercising zstd compression in parallel, taking
out every other test in the package with it.

Add testenv.InSynctestBubble and use it in zstdframe to construct a
fresh coder per call within a bubble instead of using the pools.
Pooling behavior outside of bubbles (including in benchmarks) is
unchanged.

As of Go 1.26 there is no public API to query bubble membership, so
InSynctestBubble looks for the "synctest bubble N" annotation that
the runtime renders in the current goroutine's runtime.Stack header.
That annotation is not covered by the Go compatibility promise, so
tests fail loudly (in util/testenv directly, and in util/zstdframe by
reintroducing the pooled-coder crash) if a future Go release changes
it. The check costs ~2us and runs only in test binaries, detected by
an uncached flag.Lookup("test.v") rather than testenv.InTest: this
path is reachable from package init functions (before testing has
registered its flags), where InTest would permanently latch a false
result into its cache, breaking later InTest and AssertInTest calls.

Fixes tailscale/corp#45861

Change-Id: I7d89e2d0de51e30098ceda25c12d27918acc46e8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-30 12:28:22 -07:00
Brad Fitzpatrick 70a03faafe .github/workflows: enforce cherry-pick -x markers on release branch PRs
Company policy requires that all cherry-picks onto release branches be
made with "git cherry-pick -x" so the commit message records which
commit it came from, but nothing enforced that. Add a GitHub Actions
check that requires every commit in a PR targeting release-branch/* to
have a "(cherry picked from commit ...)" line referencing a commit
that's an ancestor of main. PRs that intentionally aren't cherry-picks
(version bumps, release-only fixes) can be exempted with the
"not-a-cherry-pick" label.

Updates tailscale/corp#45854

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: If463d6ecaefd855594d345c733606b411b6fd387
2026-07-30 11:17:04 -07:00
Naman Sood 78e7e89be6 ipn/ipnlocal: fix comment typo
This function used to have the suffix LockedOnEntry, but no longer has
that since #17804.

Updates #cleanup

Signed-off-by: Naman Sood <mail@nsood.in>
2026-07-30 14:16:53 -04:00
Michael Ben-Ami 0aba4d9460 feature/conn25,ipn/ipnlocal: remove UseWIPCode() guards
Removing this check installs conn25 instance-level hooks whenever the
feature is built in. init-level hooks were always installed, but used
this guard to exit early.

Now all hooks, both instance-level and init-level rely on netmap
configuration (populated tailscale.com/app-connectors-experimental node
attribute, with non-empty apps) to not exit early.

The conn25-shutoff feature flag tells control not to send that node
attribute.

Fixes tailscale/corp#39033

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-07-30 12:14:49 -04:00
Patrick O'DohertyandMike Jensen 9d48dbd561 ssh/tailssh: keep acceptEnv values and names out of the incubator cmdline (#20552)
Previously the acceptEnv variables forwarded to the incubator child were
JSON-encoded onto its command line (--encoded-env), so their values were
visible in /proc/<pid>/cmdline to any other local user and were logged in
the session-start argv (locally and to log.tailscale.com except where
--no-logs-no-support was specified).

This change now carries those variables through an os.Pipe file
descriptor as a json encoded payload. Added end-to-end testing
helps validate secrets reach the session but are not in flags or logged.

Fixes tailscale/corp#44903

Change-Id: I5b137b20e9c06feec6b70aaf4e6925e6db74017e

Signed-off-by: Mike Jensen <mikej@tailscale.com>
Co-authored-by: Mike Jensen <mikej@tailscale.com>
2026-07-30 09:57:55 -06:00
Brad Fitzpatrick 77948cdce4 client/systray: fix crash when suggested exit node has no location
The suggest-exit-node LocalAPI response omits the Location field when
the suggested exit node has no location, such as a regular tailnet exit
node. Building the exit node menu called CountryCode and City on the
invalid view unconditionally, crashing the app on startup. The crash
was introduced in aa21b0c00 (#19627), which added those calls.

Add an integration test that starts the systray app against a private
dbus session bus, a fake StatusNotifierWatcher, and a fake LocalAPI
serving such a suggestion, then verifies the StatusNotifierItem is
registered and the initial menu is built. On Linux the systray is pure
dbus (StatusNotifierItem and dbusmenu), so no X server or desktop
environment is needed and the test runs in existing CI.

Fixes #20678

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I262ae0f724cb7c41aa0f531fcbded967e5bee432
2026-07-30 07:55:18 -07:00
Claus Lensbøl 3799eaf264 ipn/ipnlocal,wgengine: implement wg-go SetPriorityMessageOnEstablishmentFunc (#20606)
Implements a way to send TSMPDiscoAdverts based on a trigger from
wireguard-go when a rekey happens. This lets us distribute disco keys
consistently, but also sets us up for a minimal message that can be
distributed to other clients.

A benchmark is implemented to make it easier to keep the call cheap and
to avoid locking up anything in wireguard-go.

Updates #20081

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
2026-07-30 09:20:43 -04:00
David Bond eaf1b56ffe cmd/k8s-operator: move proxyclass reconciler into its own package (#19695)
This commit moves the proxyclass reconciliation logic from its original
home in the rather large main package of the k8s-operator to a dedicated
package within `k8s-operator/reconciler/proxyclass`.

Closes: https://github.com/tailscale/corp/issues/37085

Signed-off-by: David Bond <davidsbond93@gmail.com>
2026-07-30 11:37:54 +01:00
Kristoffer Dalby dd426dbb51 ssh/tailssh: hold CHANNEL_EOF until both output streams drain
The previous commit sent CHANNEL_EOF from the stdout copier as soon as
stdout hit EOF. With the process exited and the send window exhausted by
a slow client, the stderr copier could still be draining its backlog;
x/crypto/ssh fails every write after EOF (WriteExtended checks sentEOF),
so the tail of stderr was silently dropped.

Send CloseWrite from run() itself, after cmd.Wait, exit-status and both
output copiers have finished. Wire order is unchanged: exit-status,
remaining output, EOF, CHANNEL_CLOSE.

The test withholds stderr window credit until the process has exited
server-side, then releases it and checks the final bytes arrived. It
also pins the harness login shell to /bin/sh; fish forks -c commands and
stays resident, changing pipe fd ownership under test.

Updates #18256

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2026-07-30 10:18:00 +02:00
James Tucker 92f43003fc ssh/tailssh: fix exit-status frame ordering
Send exit-status before EOF before CHANNEL_CLOSE per RFC 4254 §6.10.
The old code raced CloseWrite against Exit; macOS OpenSSH often saw
EOF first and dropped the exit code.

Replace atomic.Bool/atomic.Int32/channel sync with a sync.WaitGroup
over the output goroutines and emit:

    cmd.Wait -> ss.Exit -> closeAll(childPipes) -> wg.Wait -> ss.Close

The stdin copier stays out of wg: it blocks reading from the SSH
channel until the client half-closes, and many clients (go-scp,
plain `ssh host cmd`, scp, sftp) don't half-close before they
receive CHANNEL_CLOSE from us. Including it deadlocks. It self-
cleans via deferred ss.Close.

Align exit codes with shell convention:
  255 SSH-internal error (OpenSSH ssh.c:1693)
  254 recording-infrastructure failure
  127 command-not-found (POSIX)

Switch session termination from SIGKILL to SIGHUP, matching
OpenSSH PTY-master-close (session.c:2246). Process-group delivery
follows in a later commit.

Relies on the gliderssh wire-order change (tailscale/gliderssh#8),
which go.mod already pins since 3f5eb3199.

Based on tailscale/tailscale#18331.

Updates #18256

Signed-off-by: James Tucker <james@tailscale.com>
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2026-07-30 10:18:00 +02:00
Kristoffer Dalby 12fcb2217b ssh/tailssh: test exit-status frame ordering and stderr drain
Added before the fix so CI records the failure. Frame order is asserted
off the session interface rather than through a client, so it holds on
any GOOS instead of depending on how a particular client reacts.

Measured on CI against this commit, no fix yet:

  TestExitStatusPrecedesEOF   FAIL on darwin, linux/amd64 and the
        privileged linux job, 10/10 retries, classified permanent:
        got [eof exit-status], want [exit-status eof close]
  TestExitCodePassthrough     PASS on both
  TestStderrTailNotTruncated  PASS on both (guards a 2022 fix)

The ordering assertion is the only thing that pins this bug. Exit-code
assertions do not: OpenSSH 10.2p1 on darwin reads the pending
exit-status request after EOF just as linux does, so the wrong frame
order is invisible to the client's exit status over the direct-exec
path. The exit-status loss reported in #18256 comes from
/usr/bin/login -pq in the incubator path, which is a separate defect.

Updates #18256

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2026-07-30 10:18:00 +02:00
Kristoffer Dalby ea3f83216b ssh/tailssh: add exit-status test harness
Extract the in-process SSH server harness out of tailssh_test.go into
tailssh_exitcodes_test.go so the exit-status tests can share it, and add
the integrationtest-tagged exit-code suite (Go and OpenSSH clients).

Updates #18256

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2026-07-30 10:18:00 +02:00
Brad Fitzpatrick e17e38c290 go.mod: bump x/net for hpack memory reduction change
Bump x/net for just-merged hpack memory reduction change
https://go-review.googlesource.com/c/net/+/807260

Updates tailscale/corp#29053

Change-Id: Ica7fbc760a5e90e2791b90fd153fc12f0c8022fa
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-29 16:53:44 -07:00
Brad Fitzpatrick 406aeea174 cmd/tailscale/cli: add debug ts2021 --force-port flag
By default the controlhttp dialer tries the plaintext port 80 upgrade
path first with a port 443 TLS fallback, so it's not deterministic
which of the two server code paths a debugging session exercises. Add
a --force-port flag to pin the noise connection to one or the other:
443 uses the dialer's existing TS_FORCE_NOISE_443 knob, and both
values are also enforced at the dial layer so the other port can't be
used by a silent fallback.

Updates tailscale/corp#29053

Change-Id: I5641db9bd2083d458ba7e0e766c7f9da535ba3e0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-29 15:36:40 -07:00
Brad Fitzpatrick 0fec0e75a0 cmd/tailscale/cli: add debug ts2021 --connect-ip flag
Add a flag to dial a specific IP for the noise connection while
keeping the --host value for the /key fetch, TLS SNI, and Host
header. This permits testing a single control plane frontend (such
as a staging or canary trunkd instance with no DNS record) end to
end with a real ts2021 upgrade and noise handshake.

Updates tailscale/corp#29053

Change-Id: I8cd0208d6b01219eabd33f8fdd77cc490fffe29e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-29 15:36:40 -07:00
Simon Law bb498a8393 net/netcheck: extract Compare function for region latencies (#20658)
There are two places in the code where we need to compare the
latencies of a netcheck report. In both cases, the comparison wasn’t
well tested.

This PR extracts that logic into a Compare method of the new
RegionLatency type. This type wraps the map of latency measurements
keyed by region ID.

Updates #cleanup

Change-Id: I7f248988973007c2f452283c1b82f84b03068f77

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-07-29 16:51:55 -04:00
BeckyPauley d4dce80927 cmd/k8s-operator/e2e: use Let's Encrypt staging environment (#20666)
When running k8s operator e2e tests against real tailnets, use Let's
Encrypt's staging environment to avoid production rate limits.

Updates tailscale/corp#45571

Signed-off-by: Becky Pauley <becky@tailscale.com>
2026-07-29 15:15:45 +01:00