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>
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>
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.
Fixestailscale/tailscale#18232
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
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>
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>
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>
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>
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
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>
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>
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>
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
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
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>
TSMPDiscoKeyAdvertisement transmission on WireGuard handshake was
implemented in 3799eaf.
Updates #20081
Updates #20494
Signed-off-by: Jordan Whited <jordan@tailscale.com>
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
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
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>
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
This patch adds examples of unmarshalling tslockjsonv1.LogResponse
and tslockjsonv1.StatusResponse to the documentation.
Updates #17613
Signed-off-by: Simon Law <sfllaw@tailscale.com>
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>
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>
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>
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>
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>
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>
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.
Fixestailscale/corp#45619.
Signed-off-by: Naman Sood <mail@nsood.in>
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.
Fixestailscale/corp#45861
Change-Id: I7d89e2d0de51e30098ceda25c12d27918acc46e8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
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
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.
Fixestailscale/corp#39033
Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
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.
Fixestailscale/corp#44903
Change-Id: I5b137b20e9c06feec6b70aaf4e6925e6db74017e
Signed-off-by: Mike Jensen <mikej@tailscale.com>
Co-authored-by: Mike Jensen <mikej@tailscale.com>
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
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>