Commit Graph

2793 Commits

Author SHA1 Message Date
Brad Fitzpatrick
d0fcb668d5 cmd/tailscale/cli: add 'tailscale configure flash-appliance'
Adds a CLI subcommand that downloads a signed Tailscale appliance
image (Gokrazy archive format, GAF) from pkgs.tailscale.com,
constructs a fresh GPT-partitioned disk from it (mbr.img + a
synthesized partition table + boot.img + root.img), formats /perm
as ext4 in pure Go via go-diskfs, and ejects the disk so a user
running on a regular workstation can flash an SD card or homelab
VM disk in one command without installing e2fsprogs.

On macOS the target disk is auto-discovered via diskutil, skipping
the boot disk and anything bigger than 256 GB out of paranoia. On
Linux the user passes --disk=/dev/sdX explicitly. Windows is not
supported yet and the command returns an error.

The GPT layout matches monogok's full-disk layout via the new
public github.com/bradfitz/monogok/disklayout package; a drift-
guard test inside monogok asserts the two implementations stay
byte-identical so OTA updates against monogok-built images keep
working.

Behind a ts_omit_flashappliance build tag (on by default).

Updates #1866

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ic1a8cd185e7039edccb7702ab4104544fcb58d29
2026-07-01 08:09:50 -07:00
Brad Fitzpatrick
b6e17df646 cmd/tailscaled, util/syspolicy: add JSON syspolicy file support
Tailscaled had no way to seed device-scope syspolicy settings short of
environment variables or a custom store wired up out of tree. Add a
--syspolicy-file flag whose default points at a well-known JSON file
that, when present, is parsed as a map[string]any and registered as a
device-scope policy source. The default path is
/etc/tailscale/syspolicy.json on every non-Windows platform (Linux, the
BSDs, illumos/Solaris, and tailscaled-without-the-GUI on macOS) and
%ProgramData%\Tailscale\syspolicy.json on Windows. The flag lets users
running tailscaled by hand (development, custom installs) point it at
an alternate file, and "" disables the load entirely.

JSON values map to setting types as expected: strings to
StringValue/PreferenceOptionValue/VisibilityValue/DurationValue (e.g.
"24h" parsed by time.ParseDuration), booleans to BooleanValue, numbers
to IntegerValue, and string arrays to StringListValue. The file is
validated against the registered setting definitions at load time so
unknown keys and value/type mismatches fail startup loudly rather than
producing surprising defaults at first read.

When HuJSON support is linked into the build (default; opt out with
ts_omit_hujsonconf), the file may use HuJSON (comments, trailing
commas). With ts_omit_hujsonconf it must be pure standard JSON. This
mirrors the pattern used by ipn/conffile.

On Windows the JSON file and the existing HKLM registry store both
register at DeviceScope. rsop merges later-registered same-scope
sources over earlier ones, so per-key values in the file override the
registry while keys absent from the file fall back to the registry.

The loader is registered via a feature.Hook from a file gated by
!ts_omit_syspolicy, and called from main after flag parsing. tsnet
still does not depend on the root syspolicy package, so embedders
don't pick this up implicitly.

Fixes #20305

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ie6326461c14efb226979ac162998a9c6373ce493
2026-06-30 13:10:16 -07:00
kari-ts
07cefc083d ipn/{ipn,ipnlocal}: add per-user policy snapshots to IPN bus (#20135)
This adds the NotifyInitialPolicy watch option and the Policy field in
Notify so that clients can receive the effective policy snapshot via IPN
bus.

This extends policyclient.Client so ipnlocal can get and watch policy
snapshots, which is used by sysPolicyChanged to notify watchers.

User-scoped policy store registration, management, and cleanup will be
added in a follow-up

Updates tailscale/corp#42259

Signed-off-by: kari <kari@tailscale.com>
2026-06-30 12:44:29 -07:00
Brad Fitzpatrick
fad8b9b8a9 clientupdate, cmd/tailscale: verify signed GAFs, wire up tailscale update for Gokrazy
Builds on top of the unsigned URL-based GAF update flow added previously
(see referenced issue for context). The pkgs.tailscale.com server now
publishes signed GAFs for the unstable track, with detached ed25519
signatures produced by pkgsign's signdist path (the same distsign scheme
used for every other release artifact). This change consumes them.

The URL-based path (tailscale update --gokrazy-update-from-url=URL) now
verifies the signature by default using clientupdate/distsign.Client,
which fetches distsign.pub from the root of the host serving the GAF and
checks the .sig against the root keys embedded in this binary. The
--unsigned flag stays for TestGokrazyUpdatesItselfToSameImage, whose
in-test fileserver does not publish distsign.pub.

The bare tailscale update path is now wired up for the Tailscale
appliance image. It fetches <pkgs>/<track>/?mode=json, picks the GAF
whose key matches the local device (vm-amd64, vm-arm64, or pi-arm64,
where arm64 is split via /sys/firmware/devicetree/base/model), confirms
the version with the user, and reuses the verified download path above.

To avoid wiping a user's custom Gokrazy build that happens to include
tailscaled, the bare update path is gated on hostinfo.Package == "tsapp",
which is only set when the new ts_appliance build tag is present
(mirroring the existing ts_package_container tag). The
gokrazy/tsapp*/config.json files now pass GoBuildTags ["ts_appliance"]
for the tailscale and tailscaled packages so monogok bakes the tag into
the official appliance builds. The TS_FORCE_ALLOW_TSAPP_UPDATE env var
is an escape hatch for callers who want to force the appliance update
path on a non-appliance build. The URL-based path stays ungated since it
requires explicit user intent (and is exercised by the natlab vmtest).

Updates #20002

Change-Id: I7c7856a88bf3dffb9eb8d3e9111fad0b3906743c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-06-30 07:09:25 -07:00
Brad Fitzpatrick
66af25733c tstest/natlab/vmtest, client/web: add web client integration tests
Adds two Gokrazy-based vmtests covering the tailscaled web client at
port 5252:

* TestWebClientLocalAccess enables the web client on a single node
  and exercises the canonical owner session flow against the node's
  own Tailscale IP: an unauthenticated GET /api/auth that identifies
  the caller, a GET /api/auth/session/new that issues a
  TS-Web-Session cookie, and a final GET /api/auth that reports
  authorized=true with the cookie.

* TestWebClientRemoteAccess runs the same session flow from a peer
  node on the same tailnet against a second target node's web
  client, exercising netstack interception of incoming :5252
  traffic, cross-node WhoIs, and the same-user "owner" path. It
  then flips the test control server's AllNodesSameUser off,
  re-logs in the client under a fresh identity, and asserts that
  GET /api/auth/session/new returns 401 with body "not-owner" --
  exercising the cross-user rejection in client/web/auth.go.

To make the natlab test environment exercise the same code path
as production (check mode, where the web client posts to
/machine/webclient/init via Noise and waits on a control-issued
auth URL), this also:

* Allowlists the natlab fake control hostname "control.tailscale"
  in client/web/auth.go's controlSupportsCheckMode so the web
  client follows the check-mode branch rather than the
  no-check-mode shortcut that immediately marks new sessions
  authenticated.

* Adds /machine/webclient/{init,wait} handlers to testcontrol.
  init returns a placeholder auth ID and URL; wait returns
  Complete=true immediately, so the web client's awaitUserAuth
  resolves on its first call. Together these let the tests drive
  the full check-mode session lifecycle without a real
  browser-click loop.

To support the multi-request HTTP flows from the test harness,
this also adds:

* vmtest.Env.HTTPGetStatus, a sister of HTTPGet that returns the
  upstream status code, body, and Set-Cookie cookies (as a
  vmtest.HTTPResponse) and accepts cookies on the outgoing
  request, so tests can drive flows that depend on cookie
  continuity.

* Cookie pass-through in cmd/tta's /http-get handler: it forwards
  the Cookie request header upstream and surfaces upstream
  Set-Cookie response headers downstream. This is what lets
  HTTPGetStatus carry a session cookie across requests.

Previously the only tests of the web client were in-process
httptest-based handler tests in client/web/web_test.go; nothing
exercised the actual port 5252 listener wiring, the cross-node
auth path, cookie-driven session state transitions through the
check-mode control round-trip, or the not-owner rejection end
to end.

Updates #13038

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Idb01486a89b53ac02c6ad3358bcfcceca90dbc36
2026-06-30 06:55:12 -07:00
Brad Fitzpatrick
1c77079fd7 ipn/ipnlocal, feature/acme: move most remaining cert code into feature/acme
f5eac39ea ("feature/acme, ipn/ipnlocal: start moving ACME/cert state
into an extension") started to move the cert code into feature/acme
but was meant as a baby step.

This goes further, moving almost everything, leaving only some hooks
in ipnlocal.

When we later move "serve" support out to feature/serve, this will
look a bit different in that the hooks currently in ipnlocal will move
to feature/serve (cert support already depends on serve).

As part of this, cert-related tests move to feaure/acme too, which
means some test infra from ipnlocal now moves to shared ipnlocaltest.
(it's not big at the moment, but I imagine it growing)

Updates #12614

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I9ea89aa9754f12d54b81751b6bd830f2664241ff
2026-06-29 12:57:22 -07:00
Brad Fitzpatrick
5ebc7497ea tsnet: link in feature/acme by default
This was missing in the earlier f5eac39ea7 and meant that tsnet users weren't
getting (all of) acme support.

Thanks to @ChaosInTheCRD and @BeckyPauley for debugging.

Updates #12614
Updates #20252

Change-Id: I176a7b179b2ad3726aca484057f0aae7cc3561c8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-06-29 09:04:57 -07:00
Brad Fitzpatrick
f5eac39ea7 feature/acme, ipn/ipnlocal: start moving ACME/cert state into an extension
The ACME serialization mutex (acmeMu) was a package-level global, and
several ACME-related fields lived on LocalBackend even though the
cert code is conditional and not linked into every binary. With
multiple tsnet.Servers in one process (each its own LocalBackend),
a process-wide acmeMu also serialized unrelated backends.

Introduce a new feature/acme extension that owns the per-LocalBackend
ACME/cert state in an ipnlocal.CertState value:

  - acmeMu, renewMu, renewCertAt (previously package globals)
  - pendingACMETLSALPNCerts, pendingCertDomains{,Mu},
    getCertForTest, certRefreshCancel (previously LocalBackend
    fields, only meaningful when ACME was compiled in)

ipnlocal/cert.go now reaches the state through b.certState(), which
is routed by a feature.Hook installed at init by feature/acme. The
CertState type lives in ipnlocal so cert.go can access its fields
directly without a method explosion; the extension in feature/acme
constructs and owns it.

This is a baby step. The end goal is for the entire cert/ACME code
to live in feature/acme, with ipnlocal only retaining whatever thin
hooks the rest of LocalBackend needs to call into it. The current
split (CertState and most of cert.go in ipnlocal, extension wrapper
in feature/acme) is a deliberately temporary middle ground that
keeps this PR small while making the next moves mechanical.

The package is named feature/acme to match the existing HasACME /
ts_omit_acme naming. condregister/maybe_acme.go wires it in for
non-js builds.

Updates #12614
Updates #20248
Updates #20249

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I520909f24ad11a9622ef33c2290fe36ad44d6f71
2026-06-26 09:48:24 -07:00
Alex Chan
8379d5955f ipn: remove the last traces of Prefs.AllowSingleHosts
We stopped reading this field nearly two years ago, with a TODO comment
to remove it sometime in 2025.

It is now 2026.

Updates #12058

Change-Id: I8ddf1c2e4c3c428e8d45a6491d3899368ec52c30
Signed-off-by: Alex Chan <alexc@tailscale.com>
2026-06-26 15:31:38 +01:00
Alex Freestone
af999f05cf k8s-operator/dnsrecords: fix dnsRR dropping reconcile events on lock err (#19968)
On optimistic lock error, requeue the event after a short duration.

Resolves a case where a failure to acquire an optimistic lock on the
dnsrecords configmap will cause the operator to drop a reconcile event
and leave the configmap in an undesirable state.

Updates tailscale/tailscale#19946

Signed-off-by: Alex Freestone <freestone.alex@gmail.com>
2026-06-26 13:52:05 +01:00
Tom Meadows
6e1de5b651 cmd/containerboot: refresh DNS config on SelfChange (#20236)
364b952d6 switched containerboot to partial netmap fetching, but
stopped refreshing `DNS.ExtraRecords`, so Tailscale Services created
after pod boot were invisible to resolveTailnetFQDN. To fix we watch
for SelfChange ipn bus notifies, and refetch dns-config via LocalAPI
to get a fresh set of `DNS.ExtraRecords`.

Fixes #20233

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-06-25 14:50:25 +01:00
Brad Fitzpatrick
aefb1531d1 net/tsdial, ipn/ipnlocal: stop using netmap.NetworkMap in Dialer
tsdial.Dialer.SetNetMap rebuilt an O(n peers) map of MagicDNS names on
every netmap change. As we move toward per-peer incremental deltas,
this becomes quadratic. This removes it and replaces it with
SetResolveMagicDNS, a callback into LocalBackend that looks up
hostnames from nodeBackend's new nodeByName index (populated alongside
nodeByAddr/nodeByKey on both full and delta paths). The index stores
both FQDNs and short names as keys.

This is the same treatment applied to netlog (8f210454d), wglog
(988b0905b), and drive (1d6989408): stop pushing *netmap.NetworkMap
into subsystems and instead have them pull from LocalBackend's live
data via callbacks.

Updates #12542

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I24557ab0c8a27636e08e4779bcfd3ec633db0a78
2026-06-24 13:14:45 -07:00
Alex Chan
281404e9e3 cmd/tailscale/cli: fix capitalisation of flags
Most of our flag descriptions start with a lowercase word (except proper
nouns); fix the handful which do not.

Fixes #20230

Change-Id: I00aaac171254c050ad0b75c2cf8746590c8c4d8f
Signed-off-by: Alex Chan <alexc@tailscale.com>
2026-06-24 16:56:49 +01:00
tsushanth
0b551986fe cmd/k8s-operator: scope HA Service hostname check per-tailnet (#20114)
The ProxyGroup HA Service reconciler's validateService scanned every
Service in the cluster with shouldExpose=true for duplicate hostnames.
With multi-tailnet (Tailnet CRD) support, that scan reaches across
tailnet boundaries:

  * A Service exposed via the single-proxy path (tailscale.com/expose)
    on the primary tailnet would block a ProxyGroup ingress Service
    for the same hostname on a secondary tailnet, even though the two
    live in different reconcilers and different tailnet DNS namespaces.

  * Two ProxyGroups joined to different tailnets via spec.tailnet
    would also block one another for shared hostnames, again despite
    living in separate DNS namespaces.

In both cases the ProxyGroup ingress Service was silently dropped
(IngressSvcInvalid event raised, queue cleared, ConfigMap never
written, ProxyGroup never serves the backend).

This change tightens the check in two ways:

  * Skip Services that aren't themselves managed by the ProxyGroup
    reconciler (use isTailscaleService instead of shouldExpose).
  * For ProxyGroup-managed Services attached to a different
    ProxyGroup, look up that ProxyGroup and skip the duplicate
    report when spec.Tailnet differs from the current one. Fall
    through and flag the collision on lookup failure so genuine
    duplicates are not silently allowed.

Adds regression tests covering both the single-proxy and the
different-tailnet cases. Updates the existing TestValidateService
expected error to reflect the rephrased message.

Updates #20069

Signed-off-by: tsushanth <78000697+tsushanth@users.noreply.github.com>
2026-06-23 14:25:11 +01:00
Anton Tolchanov
f442cda999 ipn/ipnlocal: consider all DERP regions for exit node recommendations
When recommending an exit node, suggestExitNodeLocked ranks candidates by
the latency to their home DERP region, taken from the most recent netcheck
report. But netcheck alternates between full reports, which probe every
region, and incremental reports, which only re-probe the home region and a
handful of the fastest regions. When the most recent report is incremental,
the suggestion fell back to a random for exit nodes that are far away.

Now we rank candidates against the best recent latency, tracked by the
`netcheck.Client` - the same data that is used to pick the preferred
DERP. It uses a history of measurements which includes a full netcheck
report, so should cover all DERP regions.

Updates tailscale/corp#17516

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2026-06-22 12:28:09 +02:00
Alex Chan
c3c2aa7093 all: don't repeat the the word "the" unnecessarily
Updates #cleanup

Change-Id: Ic1f430cd5dbf6cc1a385c59074a5d5cabe6fca57
Signed-off-by: Alex Chan <alexc@tailscale.com>
2026-06-18 16:32:08 +01:00
BeckyPauley
35a1a413f9 cmd/{containerboot,k8s-operator}: add 4via6 support in singleton egress (#19983)
Add support for configuring egress to destinations reachable via 4via6
subnet routes, using either the synthesized 4via6 address or the MagicDNS
name (in the form <IPv4-with-hyphens>-via-<siteID>[.*]).

Also update the Connector to validate and advertise 4via6 subnet routes.

Export net/netutil.ValidateViaPrefix so it can be reused by the Connector
validation logic.

This change only affects standalone egress proxies — ProxyGroup egress
requires IPv6 support before it can use 4via6.

Updates #19334

Change-Id: I6faecd6eb61ab55fc0cd97fe417af6b6a12fe7fc

Signed-off-by: Becky Pauley <becky@tailscale.com>
2026-06-18 16:13:10 +01:00
Brad Fitzpatrick
8f210454dd wgengine/netlog: stop using netmap.NetworkMap type, use LocalBackend
The Logger previously took a *netmap.NetworkMap at Startup and on every
ReconfigNetworkMap call, denormalizing it into per-IP and self lookup
maps. That denormalization is O(n) over all peers and ran on every
netmap update, contributing to the broader quadratic behavior we want
to eliminate when a single peer is added or removed.

Instead, this makes netlog ask LocalBackend (well, nodeBackend) for
the info it needs, letting us remove the netmap.NetworkMap type
entirely from the netlog package.

This is a dependency to removing the netmap.NetworkMap type from
upstream callers, like wgengine.Engine in general.

Updates #12542

Change-Id: Ib5f2de96e788a667332c0a6f7ac833b3d0053b5c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-06-17 15:11:57 -07:00
Bobi Gunardi
ca20611d11 util: add parse fallback helpers (#20022)
util/def: add def.Bool and def.Duration default parse helpers

Replace multiple instances of def.Bool and def.Duration with a new util/def
package.

Updates #20018

Co-authored-by: Bobby <boby@codelabs.co.id>
Co-authored-by: Simon Law <sfllaw@tailscale.com>
Signed-off-by: Bobby <boby@codelabs.co.id>
Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-06-15 15:58:51 -07:00
Brad Fitzpatrick
6596d237a3 ipn/ipnlocal: add wireguard session state metrics + publish on IPN bus
Updates #19989
Updates tailscale/corp#42874

Change-Id: I843ed95bc7b0f5cd38ba1467332c6b022901e254
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-06-15 11:41:18 -07:00
Brendan Creane
c48f953840 cmd/tailscale/cli, ipn/conffile: accept legacy serve config in set-config (#20056)
tailscale serve set-config now also accepts the legacy raw ipn.ServeConfig
format (as emitted by `tailscale serve status --json` and consumed via
TS_SERVE_CONFIG, which has no "version" field), so the common
serve-status-edit-set workflow stops failing. Only the services-oriented
content is applied; any node-level fields are skipped with a warning to
stderr pointing users at get-config to migrate.

Fixes tailscale/corp#39793

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-06-12 18:52:17 -07:00
Alex Valiushko
7d18a06292 go.mod,wgengine/magicsock: pull wireguard-go fix for roaming endpoints (#20118)
Bumps wireguard-go pin to include the roaming endpoints fix, and
two internal enhancements.

Pulls stock wireguard-go for non-tailscale simulation in tests,
to use its endpoint discovery mechanism.

Updates #20082

Change-Id: I2ff282cb7fe4ab099ce5e780a1d40ae86a6a6964
Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com>
2026-06-12 10:50:35 -07:00
James Tucker
b6713e9bc8 cmd/tailscale/cli: check kubeconfig writability instead of refusing $KUBECONFIG (#20009)
When running under the macOS sandbox, "tailscale configure kubeconfig"
refused outright whenever $KUBECONFIG was set, assuming the path would
not be writable. Yet when $KUBECONFIG was unset it happily relied on the
home-relative-path entitlement to write to ~/.kube/config, so the two
paths made inconsistent assumptions about what the sandbox can reach.

Resolve the kubeconfig path first, then check whether the target file
(or the nearest existing parent directory) is actually writable. Only
report an error if it is not, and include macOS sandbox guidance in that
error since a path outside the home directory is the likely cause. This
lets a $KUBECONFIG that does point under the home directory work, rather
than being rejected unconditionally.

Fixes #20007

Change-Id: I9880363c38b981efaed7e97367851ddacf647be1

Signed-off-by: James Tucker <james@tailscale.com>
2026-06-12 10:48:07 +01:00
David Bond
7fb6751ddd cmd/k8s-operator: rework [unexpected] log lines (#20065)
* cmd/k8s-operator: rework [unexpected] log lines

This commit modifies several places in the operator logs where we
prepend `[unexpected]` to instead use an appropriate logging level.

The `[unexpected]` prefix is intended to be used when the program
violates some internal invariant (or for example, a database has
become corrupted). Many of these cases were simply log lines that
then fell back to a default value/behaviour. These have been releveled
to warnings.

Some of these log lines also seemed extraeneous as for the example of
service reconcilers logging when there is no proxy group annotation. As
far as I can tell we've never had any predicates for limiting the
services reconciled to ones with that annotation, so they can just
be removed to reduce log spam.

Fixes: #cleanup

Signed-off-by: David Bond <davidsbond93@gmail.com>

* Update cmd/k8s-operator/egress-services-readiness.go

Co-authored-by: BeckyPauley <64131207+BeckyPauley@users.noreply.github.com>
Signed-off-by: David Bond <davidsbond@users.noreply.github.com>

* Update cmd/k8s-operator/operator.go

Co-authored-by: BeckyPauley <64131207+BeckyPauley@users.noreply.github.com>
Signed-off-by: David Bond <davidsbond@users.noreply.github.com>

---------

Signed-off-by: David Bond <davidsbond93@gmail.com>
Signed-off-by: David Bond <davidsbond@users.noreply.github.com>
Co-authored-by: BeckyPauley <64131207+BeckyPauley@users.noreply.github.com>
2026-06-11 14:48:48 +01:00
Örjan Fors
be44e66e99 cmd/tailscale: stop defaulting ssh username to local username (#19358)
Prevent tailscale ssh from automatically adding a username when
connecting to a server, only forward one if provided. The previous
behaviour prevented username overrides in the ssh configuration, since
the provided username takes precedence to the configured one.

This also keeps the tailscale ssh a thin wrapper around ssh by not
adding any extra arguments unless required.

Fixes #19357

Signed-off-by: Örjan Fors <o@42mm.org>
2026-06-11 12:11:37 +01:00
Brad Fitzpatrick
6ab5d91071 go.mod: bump some deps to match corp
Updates tailscale/corp#43243
Updaets #20067

Change-Id: I27e19f34e2216f3ac1a4e2a6b38c0ac473b8c7ad
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-06-10 21:29:22 -05:00
David Bond
e4ea65d32d cmd/k8s-operator: workload identity support for multi-tailnet (#20016)
This commit modifies the reconciler for the `Tailnet` custom resource
to allow referenced secrets to specify an `audience` field. If a
referenced secret contains both an `audience` and `client_id` we assume
the user's intention is to use workload identity.

In that case, we configure the tailscale API client to authenticate
using the Kubernetes token request API against the operator's service
account. This requires the operator to be aware of its own service
account name.

A small change has also been made to the messages added to the `Tailnet`
CRD's status field in the even that it is missing scopes to make it
clearer that certain scopes may not be applied.

Closes: #19090
Updates: #19471

Signed-off-by: David Bond <davidsbond93@gmail.com>
2026-06-10 10:22:19 +01:00
Adriano Sela Aviles
913df7e6ea cmd/tailscale/cli: unit tests for tailscale ip
Updates #20035

Signed-off-by: Adriano Sela Aviles <adriano@tailscale.com>
2026-06-09 11:43:24 -07:00
Anthony
819f3ba7c1 cmd/k8s-operator: allow custom annotations on deployment (#17143)
Fixes #17188

Signed-off-by: Anthony SCHWARTZ <antho.schwartz@gmail.com>
Signed-off-by: Anthony SCHWARTZ <anthony.schwartz@ext.ec.europa.eu>
2026-06-09 15:25:40 +01:00
Alex Chan
65a117184b all: rename NetworkLock functions/types to TailnetLock
To avoid breaking downstream code, add deprecated aliases for all the
old names.

Updates tailscale/corp#37904

Change-Id: I86d0b0d7da371946440b181c665448f91c3ef8d2
Signed-off-by: Alex Chan <alexc@tailscale.com>
2026-06-08 13:14:28 +01:00
Adriano Sela Aviles
83c8440834 cmd/tailscale/cli: add service support to tailscale ip
Fixes #20035

Signed-off-by: Adriano Sela Aviles <adriano@tailscale.com>
2026-06-05 18:49:17 -07:00
Nick Khyl
c0d0621417 logpolicy,tsnet: remove syspolicy dependency
tsnet depends on logpolicy, which in turn depended on util/syspolicy
because of a single LogTarget policy setting it uses.

In this commit, we replace that dependency with a feature.Hook,
which only tailscaled or its platform-specific alternatives should set.

Updates #20031

Signed-off-by: Nick Khyl <nickk@tailscale.com>
2026-06-05 16:21:27 -05:00
Andrew Lytvynov
c07bf57eba cmd/tailscaled: only warn about unsupported attestation when enabled (#20028)
We don't need to log if the policy doesn't actually say that hardware
attestation must be enabled.

Updates #cleanup

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2026-06-05 14:00:07 -07:00
Simon Law
84ffcd2759 cmd/tailscale/cli/jsonoutput: provide examples for jsonoutput.DNS* (#19998)
This patch adds examples for unmarshalling the JSON outputs of the
following commands:

	tailscale dns query --json
	tailscale dns status --json

It also adds an example usage of `tailscale dns` to both
jsonoutput.DNSQueryResult and jsonoutput.DNSStatusResult.

Updates #13326
Updates #18750

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-06-05 02:02:15 -07:00
Brad Fitzpatrick
772be1b0cc gokrazy, clientupdate: add start of Gokrazy auto-updates, tests
This adds support for Gokrazy GAF (Gokrazy Archive Format) zip
auto-updates, starting to wire up Tailscale's clientupdate mechanism
to Gokrazy's update mechanism.

Currently there's just a CLI command to update from a GAF URL,
with an --unsigned flag for use in a new natlab vmtest.

Next step would be publishing unstable track GAF files on
pkgs.tailscale.com, with detached signatures, and then making the
clientupdate mechanism also download those and check signatures.

Updates #20002

Change-Id: Ib03c56f17a57f8a4638398ef83549dac4813323d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-06-04 11:20:14 -07:00
Simon Law
6ff761c5f8 cmd/tailscale/cli/jsonoutput: fix flag parsing for boolean values (#19996)
SchemaVersion didn’t actually parse boolean values properly, so
calling `tailscale lock status --json=false` would fail with:

	invalid boolean value "false" for -json: invalid integer value passed to --json: "false"

This patch makes SchemaVersion.Set delegate to flag.FlagSet for its
argument parsing, with accompanying tests that ensure that both
boolean and integer values are parsed properly.

It also removes the restriction that prevented the flag from appearing
multiple times in the arguments list. Now, the final flag clobbers all
previous ones, aligning this behaviour with the standard flag package.

We also change the SchemaVersion.String output for the zero value to
"false", so that the default help message doesn’t change when we
switch other commands over from their boolean representations:

	user@host:~$ tailscale whoami --help
	FLAGS
	  --json, --json=false
	        output in JSON format (default false)

Updates #17613

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-06-04 10:18:47 -07:00
Simon Law
0bbaed6af4 cmd/tailscale/cli/jsonoutput: rename exported identifiers (#19994)
Since we don’t think anyone has actually imported the jsonoutput
package yet, we still have a chance to rename its fundamental types:

1. Rename the JSONSchemaVersion struct to SchemaVersion because
   it is a flag.Value that can represent any schema version.

2. Rename the JSONSchemaVersion.Value field to SchemaVersion.Version
   so the struct reads better:

	if args.json.IsSet && args.json.Version == 1 {
		// ...
	}

Updates #17613

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-06-04 09:55:48 -07:00
Simon Law
f05e145d7a cmd/tailscale/cli/jsonoutput: improve doc comments and add examples (#19993)
This patch:

1. Removes hardcoded mentions of a `--json` flag from the
   documentation for JSONSchemaVersion, because the type could be used
   for anything.

2. Removes `code` formatting because Go doc comments don’t support
   this syntax.

3. Fixes [links] in doc comments so they link to the types’
   online documentation.

4. Checks that JSONSchemaVersion satisfies the flag.Value interface.

5. Adds documentation examples for using both JSONSchemaVersion and
   ResponseEnvelope.

Updates #17613
Updates #18750

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-06-04 09:23:49 -07:00
Brad Fitzpatrick
dfb605db4a cmd/ssh-auth-none-demo: update SSH demo a bit
Per chat with an SSH client author who wanted a URL in the output.

And then make it more clear what parts are banners.

Updates #cleanup

Change-Id: If5033ad9dc0dba3d833f24ea39e117a455010492
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-06-03 19:36:19 -07:00
BeckyPauley
98f1ac0880 cmd/k8s-operator, net/netutil: revert 4via6 changes (#19990)
Reverts support 4via6 in egress proxy and connector (#19863)

Updates #19334

Signed-off-by: Becky Pauley <becky@tailscale.com>
2026-06-03 20:20:36 +01:00
Kabir
01c59d84a0 cmd/tailscale/cli: show services in serve status (#19600)
The "tailscale serve status" human-readable output previously showed
only serve-based proxies, not services.

Fixes https://github.com/tailscale/corp/issues/34163

Change-Id: Ie48858a8d8afd7184979d0fe2ab21ebd6fd0d4a0

Signed-off-by: Kabir Sikand <kabir@tailscale.com>
2026-06-02 17:09:54 -04:00
Simon Law
b47dd932f3 cmd/tailscale/cli: use tstime constant for tailscale routecheck (#19957)
Updates #19928

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-06-01 17:42:18 -07:00
ferrumclaudepilgrim
3f70abdc6f cmd/tailscaled, version/distro: default to userspace-networking on Crostini
cros-garcon NULL-derefs on cold-boot netlink enumeration when
tailscale0 is present, preventing the Crostini container and
ChromeOS Terminal from starting cleanly. This is an upstream
ChromiumOS bug in cros-garcon; tailscaled can work around it
by defaulting to userspace-networking mode on Crostini.

Tailscale SSH continues to work via tailscaled's netstack.
Users can override with --tun=tailscale0 on ChromeOS builds
where cros-garcon is fixed.

Crostini is detected via /opt/google/cros-containers/bin/garcon,
which is present in every Crostini penguin container.

ssh/tailssh extends the existing Debian default-PATH case to
cover Crostini, since Crostini is Debian-based and benefits
from the same SSH PATH defaults.

RELNOTE: Crostini now defaults to userspace-networking.

Fixes #19488
Updates #12090

Signed-off-by: ferrumclaudepilgrim <ferrumclaudepilgrim@users.noreply.github.com>
2026-06-01 17:40:07 -07:00
Brad Fitzpatrick
a6ab7efa4f ipn/ipnlocal, cmd/tailscale/cli: auto-renew TLS certs and warn while pending
The Tailscale daemon only refreshed TLS certs as a side effect of inbound
TLS handshakes or "tailscale cert" CLI calls. A node that doesn't see
inbound traffic during the renewal window silently rolls past expiry.

Add a once-per-hour background loop on LocalBackend that enumerates Serve
and Funnel HTTPS hostnames (filtered against the netmap's CertDomains so
we don't poke ACME for other nodes' service hostnames) and calls the
existing GetCertPEM path. The renewal decision (ARI window, then 2/3
expiry fallback) is unchanged; the loop just guarantees it runs.

For visibility during initial issuance or restart with a long-expired
cached cert, add a "tls-cert-pending" health Warnable that's set while
ACME is in flight and no usable cached cert exists. Async renewal of a
still-valid cert intentionally doesn't fire it. And then make the CLI "cert"
subcommand print out a warning if it's blocking due to a cert fetch
in flight, using that health info.

Fixes #19911
Fixes #19912

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I144e46c40e957b2e879587decace32a523a6eade
2026-06-01 16:31:54 -07:00
Simon Law
92bfda580c cmd/tailscale/cli: fix time in tailscale routecheck (#19956)
When running `tailscale netcheck`, the reported timestamp used to be
in UTC and formatted according to RFC 3339 with a `T` to separate the
date from the time:

	sfllaw@h2co3:~$ tailscale netcheck | head -n3

	Report:
		* Time: 2026-06-01T21:12:32.252620138Z

This is machine-readable time leaking out to the user interface. Times
in normal commands are formatted for humans to read:

	sfllaw@h2co3:~$ date
	Mon 01 Jun 2026 02:39:14 PM PDT
	sfllaw@h2co3:~$ journalctl -t tailscaled | tail -n1
	Jun 01 14:35:21 h2co3 tailscaled[3328921]: wgengine: sending TSMP disco key advertisement to 100.90.144.102
	sfllaw@h2co3:~$ timedatectl show
	Timezone=America/Los_Angeles
	LocalRTC=no
	CanNTP=yes
	NTP=yes
	NTPSynchronized=yes
	TimeUSec=Mon 2026-06-01 14:38:32 PDT
	RTCTimeUSec=Mon 2026-06-01 14:38:32 PDT
	sfllaw@h2co3:~$ uptime --since
	2026-05-15 07:37:45

This PR makes the times printed by the CLI commands consistent:

- For `tailscale routecheck`, it now prints local time as
  `2026-05-15 07:37:45-07:00`.
- For `netlogfmt`, it has always printed local time with a space,
  but now includes the time zone.
- All machine-readable outputs continue to be standard RFC 3339 in
  UTC, i.e. `--format=json`.

As part of a general cleanup, this PR also adds standard common
time.Format layouts as tstime constants.

Fixes #19928

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-06-01 16:12:08 -07:00
Achille Roussel
7f3bbc9865 net/netutil: add NewDefaultTransport to avoid http.DefaultTransport panics
Several packages built their HTTP transports with

    http.DefaultTransport.(*http.Transport).Clone()

The standard library only documents http.DefaultTransport as an
http.RoundTripper, so an application is free to replace it with a
RoundTripper that is not a *http.Transport (e.g. an instrumented or
tracing wrapper). When such an application embeds tsnet.Server, the
unchecked type assertion panics as soon as tsnet brings up its control
connection, DNS bootstrap, or log uploader.

Add netutil.NewDefaultTransport, which returns a clone of the global
when it is still the standard *http.Transport (preserving existing
behavior) and otherwise returns a fresh transport mirroring the stdlib
defaults. Route every clone site through it.

Updates #19937

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
2026-06-01 12:28:36 -07:00
Brad Fitzpatrick
0d92a69259 cmd/tailscale/cli: add "tailscale get" command
This adds @alexwlchan's proposed "tailscale get" command that reads
current preference values, complementing "tailscale set". It uses the
same flag names as set.

  tailscale get              # show all settings as a table
  tailscale get all          # same
  tailscale get accept-dns   # show a single value
  tailscale get --json       # output as JSON object
  tailscale get --set-flags  # output as tailscale set argv

Fixes #11389
Fixes tailscale/corp#38702

Change-Id: Ie366f27f11ccc56c76fff9a94ed8a9de9c835bd0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-06-01 11:59:33 -07:00
Simon Law
2d6844c565 cmd/tailscale/cli: add routecheck command (#19641)
Introduce a new `tailscale routecheck` command which prints a report
of high-availability routers that are reachable.

This command rhymes with the `tailscale netcheck` command and but
instead of reporting on local network conditions, `routecheck` reports
on remote connectivity.

Updates #17366
Updates tailscale/corp#33033

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-06-01 11:50:24 -07:00
Brad Fitzpatrick
d961e44856 cmd/testwrapper: auto-retry every failing test
Previously, testwrapper only retried tests explicitly annotated with
flakytest.Mark. Authors don't pre-emptively mark tests that haven't
flaked yet, so the first flake of a brand-new test failed CI even
when a re-run would have passed.

testwrapper now retries every failing test within a per-test wall-clock
budget (default: 5 minute per-attempt timeout capped at 1.5x the first
failure duration, 10 minute total). A test that fails and then passes
on retry is reported as flaky; a test that never passes within the
budget remains a real failure (exit non-zero).

For flakeapp's existing log scraping, the wire format is preserved:
the "flakytest failures JSON:" line is now emitted only for tests
that ultimately flaked (passed on retry). Unmarked tests get a fake
issue URL of the form https://github.com/{owner}/{repo}/issues/UNKNOWN
where owner/repo is detected from GITHUB_REPOSITORY, the local git
remote, or falls back to tailscale/tailscale. A new "permanent test
failures JSON:" line is emitted for tests that never passed; flakeapp
ignores it for now (a follow-up can teach it to record real failures
separately).

flakytest.Mark stays as an opt-in API: still useful for tracking a
known-flaky test against a real issue and for TS_SKIP_FLAKY_TESTS.

Updates tailscale/corp#38960

Change-Id: I56dfc9b023486d239f60793a53e9690578ce8017
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-06-01 11:07:56 -07:00
Simon Law
2ee9eacb94 client/local,ipn/localapi: add /localapi/v0/routecheck endpoint (#19640)
In order to support a `tailscale routecheck` command, we introduce the
`/localapi/v0/routecheck` endpoint to the local API. This endpoint
returns the most recent report collected by the routecheck client.
If `force=true` is an argument in the query string, then this endpoint
will actively probe before returning the report.

Updates #17366
Updates tailscale/corp#33033

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-06-01 11:06:14 -07:00