Compare commits

...

222 Commits

Author SHA1 Message Date
Will Norris
f0fc59b410 all: replace UserVisibleError with vizerror package
Updates tailscale/corp#9025

Signed-off-by: Will Norris <will@tailscale.com>
2023-02-16 11:22:24 -08:00
License Updater
300664f8ae licenses: update tailscale{,d} licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-02-16 18:01:08 +00:00
License Updater
4531be4406 licenses: update win/apple licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-02-15 13:59:22 +00:00
Flakes Updater
390db46aad go.mod.sri: update SRI hash for go.mod changes
Signed-off-by: Flakes Updater <noreply@tailscale.com>
2023-02-14 23:48:42 +00:00
Brad Fitzpatrick
607c3eb813 go.toolchain.rev: update to Go 1.20.1
And bump x/net for the HTTP/2 fixes.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-02-14 23:39:08 +00:00
James Tucker
ee471ca1c8 tstest/integration: enable go lookups from $PATH
Fixes tailscale/corp#9261

Signed-off-by: James Tucker <james@tailscale.com>
2023-02-14 23:06:58 +00:00
Maisem Ali
c01c84ea8e cmd/tailscale/cli: add "configure kubeconfig" subcommand
It takes in a node hostname and configures the local kubeconfig file to
point to that.

Updates #7220

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-02-14 06:06:42 +00:00
Maisem Ali
181a3da513 cmd/tailscale/cli: add a hidden configure subcommand
Also make `tailscale configure-host` an alias to `tailscale configure synology`

Updates #7220

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-02-14 06:06:42 +00:00
Andrew Dunham
6927a844b1 util/linuxfw: add build constraints excluding GOARCH=arm
This isn't currently supported due to missing support in upstream
dependencies, and also we don't use this package anywhere right now.
Just conditionally skip this for now.

Fixes #7268

Change-Id: Ie7389c2c0816b39b410c02a7276051a4c18b6450
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2023-02-14 06:00:03 +00:00
David Anderson
6de3459bc8 .github/workflows: fix typo
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-14 05:22:49 +00:00
David Anderson
f145c2b65b .github/workflows: add workflow to update go.mod Nix SRI hash
So that I just get a quick PR to approve and merge instead of
periodically discovering that the SRI hash has bitrotted.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-14 03:28:43 +00:00
David Anderson
f9667e4946 Dockerfile: fix docker build
The stamp vars got renamed and I forgot to update these scripts.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-14 00:59:09 +00:00
Denton Gentry
fdc2018d67 wgengine/magicsock: remove superfluous "discokey" from log
The stringification of the discokey type now prepends "discokey:"
Fixes https://github.com/tailscale/tailscale/issues/3074

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-02-12 21:05:05 +00:00
License Updater
10b20fd1c7 licenses: update android licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-02-12 03:46:15 +00:00
David Anderson
2eb25686d7 .github/workflows: simplify build-only go test invocation
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-12 02:37:24 +00:00
David Anderson
253333b8a3 .github/workflows: support disabling fuzz testing safely
OSS-Fuzz doesn't update their version of Go as quickly as we do, so
we sometimes end up with OSS-Fuzz being unable to build our code for
a few weeks. We don't want CI to be red for that entire time, but
we also don't want to forget to reenable fuzzing when OSS-Fuzz does
start working again.

This change makes two configurations worthy of a CI pass:
 - Fuzzing works, and we expected it to work. This is a normal
   happy state.
 - Fuzzing didn't compile, and we expected it to not compile. This
   is the "OSS-Fuzz temporarily broken" state.

If fuzzing is unexpectedly broken, or unexpectedly not broken, that's
a CI failure because we need to either address a fuzz finding, or
update TS_FUZZ_CURRENTLY_BROKEN to reflect the state of OSS-Fuzz.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-12 02:07:10 +00:00
David Anderson
5e186f9fbf .github/workflows: pin version of Windows we run on
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-12 01:17:16 +00:00
David Anderson
471053a054 .github/workflows: pin version of Ubuntu we run on
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-12 01:17:16 +00:00
David Anderson
2a43fa4421 .github/workflows: use variant=race instead of race=true for race test
Github's matrix runner formats the race variant as '(amd64, true)' if we
use race=true. So, change the way the variable is defined so that it says
'(amd64, race)' even if that makes the if statements a bit more complex.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-12 01:17:16 +00:00
David Anderson
9fc3d00c17 .github/workflows: add back forgotten android CI job
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-12 01:17:16 +00:00
David Anderson
4022796484 .github/workflows: unify and matrixify all our CI steps
Instead of having a dozen files that contribute CI steps with
inconsistent configs, this one file lists out everything that,
for us, constitutes "a CI run". It also enables the slack
notification webhook to notify us exactly once on a mass breakage,
rather than once for every sub-job that fails.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-12 01:17:16 +00:00
David Anderson
afe19d1d81 tool/go: don't use the names GOOS/GOARCH in script
The use of GOOS to mean "the compiler's host architecture" ends up
overriding whatever GOOS the user passed in, resulting in befuddling
errors like "unsupported GOOS/GOARCH pair linux/wasm" when the caller
requests js/wasm.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-12 01:17:16 +00:00
Jenny Zhang
fe5558094c cmd/tailscale/cli: add logout and debug info to web
Fixes #7238

Signed-off-by: Jenny Zhang <jz@tailscale.com>
2023-02-12 00:06:36 +00:00
David Anderson
ea8b896c6c .github/workflows: remove 'ci skip' boilerplate
We've never used the "[ci skip]" magic commit header in our history,
across all our repos. This seems to be boilerplate we imported years
ago and have since been copying around our CI configs.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-11 18:41:14 +00:00
License Updater
11fafdac8f licenses: update android licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-02-11 18:28:27 +00:00
Denton Gentry
01d58c9b61 scripts/installer.sh: add Mendel OS and OpenMandriva.
Fixes https://github.com/tailscale/tailscale/issues/6926
Fixes https://github.com/tailscale/tailscale/issues/7076

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-02-11 17:57:49 +00:00
David Anderson
bd81d520ab cmd/printdep: print correct toolchain URL
In the switch to static toolchains, we removed a legacy oddity from the
toolchain URL structure, but forgot to update printdep.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-11 17:57:36 +00:00
David Anderson
b64d900f0f version: fix version output for "go run"
Before (note attempted use of absent date and commit hash):

"short": "1.37.0-dev",
"long": "1.37.0-dev-t",

After:

"short": "1.37.0-ERR-BuildInfo",
"long": "1.37.0-ERR-BuildInfo",

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-11 07:29:55 +00:00
David Anderson
70a2929a12 version: make all exported funcs compile-time constant or lazy
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-11 07:29:55 +00:00
David Anderson
8b2ae47c31 version: unexport all vars, turn Short/Long into funcs
The other formerly exported values aren't used outside the package,
so just unexport them.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-11 07:29:55 +00:00
David Anderson
9e6b4d7ad8 types/lazy: helpers for lazily computed values
Co-authored-by: Maisem Ali <maisem@tailscale.com>
Co-authored-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-10 20:59:58 -08:00
Denton Gentry
5bca44d572 cmd/sync-containers: update latest and stable tags
Fixes https://github.com/tailscale/tailscale/issues/7251

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-02-10 20:47:18 -08:00
Mihai Parparita
fa932fefe7 net/interfaces: redo how we get the default interface on macOS and iOS
With #6566 we added an external mechanism for getting the default
interface, and used it on macOS and iOS (see tailscale/corp#8201).
The goal was to be able to get the default physical interface even when
using an exit node (in which case the routing table would say that the
Tailscale utun* interface is the default).

However, the external mechanism turns out to be unreliable in some
cases, e.g. when multiple cellular interfaces are present/toggled (I
have occasionally gotten my phone into a state where it reports the pdp_ip1
interface as the default, even though it can't actually route traffic).

It was observed that `ifconfig -v` on macOS reports an "effective interface"
for the Tailscale utn* interface, which seems promising. By examining
the ifconfig source code, it turns out that this is done via a
SIOCGIFDELEGATE ioctl syscall. Though this is a private API, it appears
to have been around for a long time (e.g. it's in the 10.13 xnu release
at https://opensource.apple.com/source/xnu/xnu-4570.41.2/bsd/net/if_types.h.auto.html)
and thus is unlikely to go away.

We can thus use this ioctl if the routing table says that a utun*
interface is the default, and go back to the simpler mechanism that
we had before #6566.

Updates #7184
Updates #7188

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-02-10 16:23:37 -08:00
Mihai Parparita
21fda7f670 net/routetable: include unknown flags in the routetable doctor output
As part of the work on #7248 I wanted to know all of the flags on the
RouteMessage struct that we get back from macOS. Though it doesn't turn
out to be useful (when using an exit node/Tailscale is the default route,
the flags for the physical interface routes are the same), it still seems
useful from a debugging/comprehensiveness perspective.

Adds additional Darwin flags that were output once I enabled this mode.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-02-10 15:54:31 -08:00
Mihai Parparita
7d204d89c2 ipn/ipnlocal: fix passthrough of formatting arguments in PeerAPI doctor output
Followup to #7235, we were not treating the formatting arguments as
variadic. This worked OK for single values, but stopped working when
we started passing multiple values (noticed while trying out #7244).

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-02-10 15:53:41 -08:00
David Anderson
9ad36d17a3 version: undo previous "optimization", do more work lazily
Commit 59c254579e moved a lot of work
from functions that could be eliminated at compile time (because
tests against runtime.GOOS are compile-time constant), into code
that must always run before main().

So, revert that, and instead optimize the package only by moving the
remaining string processing code behind sync.Onces.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-10 15:27:14 -08:00
Tom DNetto
2ca6dd1f1d wgengine: start logging DISCO frames to pcap stream
Signed-off-by: Tom DNetto <tom@tailscale.com>
2023-02-10 11:22:34 -10:00
David Anderson
da75e49223 version: return correct Meta.MajorMinorPatch in non-dev builds
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-10 13:00:44 -08:00
License Updater
78980a4ccf licenses: update win/apple licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-02-09 15:35:39 -08:00
Mihai Parparita
6799ef838f ipn/ipnlocal: add PeerAPI endpoint for doctor output
Useful when debugging issues (e.g. to see the full routing table), and
easier to refer to the output via a browser than trying to read it from
the logs generated by `bugreport --diagnose`.

Behind a canDebug() check, similar to the /magicsock and /interfaces
endpoints.

Updates #7184

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-02-09 15:09:56 -08:00
Brad Fitzpatrick
9e4d99305b go.toolchain.rev: bump Go toolchain
For tailscale/go#55 experimentation in another repo primarily,
but this is our source of truth, so we bump here.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-02-09 15:07:55 -08:00
David Anderson
0e4f2bdd0c pull-toolchain.sh: don't run update-flake.sh
We no longer carry an SRI hash for the toolchain, so flake
updating is no longer needed for toolchain changes.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-09 15:04:01 -08:00
David Anderson
33f29a1532 go.toolchain.rev: update toolchain to test iOS Go fix
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-09 12:59:51 -08:00
Andrew Dunham
ba48ec5e39 util/linuxfw: initial implementation of package
This package is an initial implementation of something that can read
netfilter and iptables rules from the Linux kernel without needing to
shell out to an external utility; it speaks directly to the kernel using
syscalls and parses the data returned.

Currently this is read-only since it only knows how to parse a subset of
the available data.

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
Change-Id: Iccadf5dcc081b73268d8ccf8884c24eb6a6f1ff5
2023-02-09 14:20:24 -05:00
Colin Adler
3c107ff301 net/connstats: fix ticker in NewStatistics (#7225)
`:=` was accidentally used, so `maxPeriod` never worked.

Signed-off-by: Colin Adler <colin1adler@gmail.com>
2023-02-09 01:24:52 -08:00
Will Norris
6ef834a6b7 get-authkey: require tags to be specified
Tailnet-owned auth keys (which all OAuth-created keys are) must include tags, since there is no user to own the registered devices.

Signed-off-by: Will Norris <will@tailscale.com>
2023-02-08 17:12:47 -08:00
Maisem Ali
89bd414be6 ipn/ipnstate: update field docs on PeerStatus.
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-02-08 15:23:05 -08:00
Mihai Parparita
2f4df30c75 .github/workflows: re-enable CIFuzz job
Having an empty `on` spec results in the job still running, but it
immediately fails with a "No jobs were run" message.

Go back to the original `on: [pull_request]` spec, and disable the
workflow in the GitHub UI instead.

This reverts commit f7b3156f16.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-02-08 13:51:14 -08:00
Mihai Parparita
62f4df3257 net/interfaces, net/netns: add node attributes to control default interface getting and binding
With #6566 we started to more aggressively bind to the default interface
on Darwin. We are seeing some reports of the wrong cellular interface
being chosen on iOS. To help with the investigation, this adds to knobs
to control the behavior changes:
- CapabilityDebugDisableAlternateDefaultRouteInterface disables the
  alternate function that we use to get the default interface on macOS
  and iOS (implemented in tailscale/corp#8201). We still log what it
  would have returned so we can see if it gets things wrong.
- CapabilityDebugDisableBindConnToInterface is a bigger hammer that
  disables binding of connections to the default interface altogether.

Updates #7184
Updates #7188

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-02-08 13:15:10 -08:00
Brad Fitzpatrick
fb84ccd82d control/controlhttp: don't require valid TLS cert for Noise connection
We don't require any cert at all for Noise-over-plaintext-port-80-HTTP,
so why require a valid cert chain for Noise-over-HTTPS? The reason we use
HTTPS at all is to get through firewalls that allow tcp/443 but not tcp/80,
not because we need the security properties of TLS.

Updates #3198

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-02-08 12:47:01 -08:00
Brad Fitzpatrick
2477fc4952 net/netutil: only check Linux sysctls w/ procfs, assume absent means false
Fixes #7217

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-02-08 12:23:36 -08:00
Maisem Ali
05adf22383 cmd/k8s-operator: add support for running an auth proxy
Updates #5055

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-02-08 11:45:10 -08:00
Mihai Parparita
31e2e9a300 ipn: remove unused NLKeyStateKey constant
We stopped writing network lock keys as separate items with #6315,
the constant is no longer used.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-02-08 11:19:28 -08:00
Mihai Parparita
f0f2b2e22b logtail: increase maximum log line size in low memory mode
The 255 byte limit was chosen more than 3 years ago (tailscale/corp@929635c9d9),
when iOS was operating under much more significant memory constraints.
With iOS 15 the network extension has an increased limit, so increasing
it to 4K should be fine.

The motivating factor was that the network interfaces being logged
by linkChange in wgengine/userspace.go were getting truncated, and it
would be useful to know why in some cases we're choosing the pdp_ip1
cell interface instead of the pdp_ip0 one.

Updates #7184
Updates #7188

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-02-07 22:00:14 -08:00
M. J. Fromberger
9be47f789c ipn/ipnlocal: fix the path for writing cert files (#7203)
Fixes #7202.

Change-Id: I1f8e9c59d5e42e7df7a3fbbd82ae2b4293845916
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
2023-02-07 14:34:04 -08:00
Andrew Dunham
cab2b2b59e ipn/localapi: print envknobs on bugreport
Previously, we only printed these at startup; print those when the user
generates a bugreport as we so we don't have to go spelunking through
the logs.

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: If5b0970f09fcb4cf8839958af5d37f84e0ba6ed2
2023-02-07 11:59:32 -05:00
David Anderson
59c254579e version: unify and optimize the various not-version funcs
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-06 22:24:14 -08:00
Maisem Ali
0fd2f71a1e ipn/ipnlocal: use presence of NodeID to identify logins
The profileManager was using the LoginName as a proxy to figure out if the profile
had logged in, however the LoginName is not present if the node was created with an
Auth Key that does not have an associated user.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-02-06 21:52:35 -08:00
Maisem Ali
2a094181df .github/workflows: use ./tool/go in go mod tidy
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-02-06 21:34:07 -08:00
Andrew Dunham
6d84f3409b ipn/ipnlocal: handle more edge cases in netmap expiry timer
We now handle the case where the NetworkMap.SelfNode has already expired
and do not return an expiry time in the past (which causes an ~infinite
loop of timers to fire).

Additionally, we now add an explicit check to ensure that the next
expiry time is never before the current local-to-the-system time, to
ensure that we don't end up in a similar situation due to clock skew.

Finally, we add more tests for this logic to ensure that we don't
regress on these edge cases.

Fixes #7193

Change-Id: Iaf8e3d83be1d133a7aab7f8d62939e508cc53f9c
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2023-02-06 20:32:25 -05:00
Tom DNetto
99b9d7a621 all: implement pcap streaming for datapath debugging
Updates: tailscale/corp#8470

Signed-off-by: Tom DNetto <tom@tailscale.com>
2023-02-04 15:54:20 -10:00
David Anderson
1acdcff63e go.toolchain.rev: update toolchain to test ios workaround
Updates tailscale/corp#9061

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-04 15:10:42 -08:00
Denton Gentry
4daba23cd4 cmd/get-authkey: add an OAuth API client to produce an authkey
Updates https://github.com/tailscale/tailscale/issues/3243

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-02-03 22:52:54 -08:00
Maisem Ali
6bae55e351 ipn/ipnlocal: add support to store certs in k8s secrets
Fixes #5676

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-02-03 19:12:21 -08:00
Mihai Parparita
0e3fb91a39 net/dns/resolver: remove maxDoHInFlight
It was originally added to control memory use on iOS (#2490), but then
was relaxed conditionally when running on iOS 15 (#3098). Now that we
require iOS 15, there's no need for the limit at all, so simplify back
to the original state.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-02-03 17:07:12 -08:00
Mihai Parparita
b6908181ff net/tshttpproxy: more directly use Transport proxy CONNECT hooks
GetProxyConnectHeader (golang/go#41048) was upstreamed in Go 1.16 and
OnProxyConnectResponse (golang/go#54299) in Go 1.20, thus we no longer
need to guard their use by the tailscale_go build tag.

Updates #7123

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-02-03 16:51:50 -08:00
License Updater
0e1403ec39 licenses: update win/apple licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-02-03 11:29:04 -08:00
David Crawshaw
8cf2805cca tailcfg, localapi: plumb device token to server
Updates tailscale/corp#8940

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2023-02-03 10:28:11 -08:00
Brad Fitzpatrick
38b32be926 tailcfg: add wire fields/docs for resuming streaming map sessions
No implementation yet.

Updates #7175

Change-Id: Id242d47cdd83afe4d254c8c5826f058fe39c8bfd
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-02-03 09:59:30 -08:00
Andrew Dunham
880a41bfcc net/dns/resolver: add envknob to debug exit node DNS queries on on Windows
Add the envknob TS_DEBUG_EXIT_NODE_DNS_NET_PKG, which enables more
verbose debug logging when calling the handleExitNodeDNSQueryWithNetPkg
function. This function is currently only called on Windows and Android.

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Ieb3ca7b98837d7dc69cd9ca47609c1c52e3afd7b
2023-02-03 12:09:00 -05:00
Andrew Dunham
d2301db49c ipn/localapi: print node IDs, pubkeys, and expiry on bugreport
Having this information near the "user bugreport" line makes it easier
to identify the node and expiry without spelunking through the rest of
the logs.

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I1597c783efc06574fa4c8f211e68d835f20b6ccb
2023-02-03 11:44:39 -05:00
License Updater
0aa7b495d5 licenses: update win/apple licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-02-02 21:19:33 -08:00
David Anderson
02a2dcfc86 go.toolchain.rev: use new statically built toolchain
Also removes the toolchain builds from flake.nix. For now the flake
build uses upstream Go 1.20, a followup change will switch it back to
our custom toolchain.

Updates tailscale/corp#9005

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-02-02 20:44:12 -08:00
Andrew Dunham
2dc3dc21a8 util/multierr: implement Go 1.20+'s multiple error Unwrap
Now that Go 1.20 is released, multierr.Error can implement
Unwrap() []error

Updates #7123

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Ic28c2579de6799801836c447afbca8cdcba732cf
2023-02-02 14:03:51 -05:00
Andrew Dunham
5ba2543828 ipn/ipnlocal: print warning about DNS servers in bugreport --diagnose
If the user passes the --diagnose flag, print a warning if any of the
default or fallback DNS resolvers are Tailscale IPs. This can interfere
with the ability to connect to the controlplane, and is typically
something to pay attention to if there's a connectivity issue.

Change-Id: Ib14bf6228c037877fbdcd22b069212b1a4b2c456
Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2023-02-02 13:09:05 -05:00
License Updater
c4eb857405 licenses: update tailscale{,d} licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-02-02 07:51:07 -08:00
Brad Fitzpatrick
03645f0c27 net/{netns,netstat}: use new x/sys/cpu.IsBigEndian
See golang/go#57237

Change-Id: If47ab6de7c1610998a5808e945c4177c561eab45
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-02-02 07:41:49 -08:00
Andrew Dunham
2755f3843c health, net/tlsdial: add healthcheck for self-signed cert
When we make a connection to a server, we previously would verify with
the system roots, and then fall back to verifying with our baked-in
Let's Encrypt root if the system root cert verification failed.

We now explicitly check for, and log a health error on, self-signed
certificates. Additionally, we now always verify against our baked-in
Let's Encrypt root certificate and log an error if that isn't
successful. We don't consider this a health failure, since if we ever
change our server certificate issuer in the future older non-updated
versions of Tailscale will no longer be healthy despite being able to
connect.

Updates #3198

Change-Id: I00be5ceb8afee544ee795e3c7a2815476abc4abf
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2023-02-01 23:17:41 -05:00
Andrew Dunham
7393ce5e4f wgengine/magicsock: add envknob to print information about port selection
To aid in debugging where a customer has static port-forwards set up and
there are issues establishing a connection through that port.

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Ic5558bcdb40c9119b83f79dcacf2233b07777f2a
2023-02-01 22:44:22 -05:00
Brad Fitzpatrick
cf8dd7aa09 all: use Go 1.20's bytes.Clone
Updates #7123
Updates #6257 (more to do in other repos)

Change-Id: I073e2a6d81a5d7fbecc29caddb7e057ff65239d0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-02-01 17:39:18 -08:00
Brad Fitzpatrick
f7b3156f16 .github/workflows: delete CIFuzz job
It doesn't yet support Go 1.20. We can bring it back later.

Updates #7123

Change-Id: I6c4a4090e910d06f34c3f4d612e737989fe85812
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-02-01 17:39:04 -08:00
Brad Fitzpatrick
b1248442c3 all: update to Go 1.20, use strings.CutPrefix/Suffix instead of our fork
Updates #7123
Updates #5309

Change-Id: I90bcd87a2fb85a91834a0dd4be6e03db08438672
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-02-01 15:23:54 -08:00
Brad Fitzpatrick
623176ebc9 go.toolchain.branch: update to Go 1.20
Updates #7123

Change-Id: I64f6d8de5bb511a23318118b4ea1146247f1ad7c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-02-01 15:14:05 -08:00
Will Norris
10085063fb util/vizerror: add As function to get wrapped Error
Signed-off-by: Will Norris <will@tailscale.com>
2023-02-01 14:39:13 -08:00
Will Norris
51e1ab5560 fixup! util/vizerror: add new package for visible errors
Signed-off-by: Will Norris <will@tailscale.com>
2023-02-01 12:04:04 -08:00
Will Norris
648aa00a28 fixup! util/vizerror: add new package for visible errors
Signed-off-by: Will Norris <will@tailscale.com>
2023-02-01 12:04:04 -08:00
Will Norris
a6c6979b85 fixup! util/vizerror: add new package for visible errors
Signed-off-by: Will Norris <will@tailscale.com>
2023-02-01 12:04:04 -08:00
Will Norris
598ec463bc fixup! util/vizerror: add new package for visible errors
Signed-off-by: Will Norris <will@tailscale.com>
2023-02-01 12:04:04 -08:00
Will Norris
8e6a1ab175 util/vizerror: add new package for visible errors
Signed-off-by: Will Norris <will@tailscale.com>
2023-02-01 12:04:04 -08:00
Brad Fitzpatrick
27d146d4f8 .github/ISSUE_TEMPLATE: add link to wiki/OtherSoftwareInterop
Add question about https://github.com/tailscale/tailscale/wiki/OtherSoftwareInterop
in the issue template.

Change-Id: I6ca374654e9f67be9cb447bb5d5f66a9087fd945
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-02-01 11:44:13 -08:00
Brad Fitzpatrick
ca45fe2d46 cmd/tailscale/cli: delete ActLikeCLI
It's since been rewritten in Swift.

 #cleanup

Change-Id: I0860d681e8728697804ce565f63c5613b8b1088c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-02-01 09:37:31 -08:00
shayne
30e0156430 nix: update nixpkgs flake, override go_1_20 for tailscale_go (#7139)
Bleeding edge Tailscale Nix flake broke after updating to go1.20rc3.

Go 1.20 moved to Go 1.17 as a bootstarp toolchain. Fortunately nixpkgs
nixos-unstable already had a 1.20.nix with bootstrap117.nix.

```
❯ ./result/bin/tailscale version
1.37.0-dev
  track: unstable (dev); frequent updates and bugs are likely
  go version: go1.20rc3-ts6a17f14c05
```

Signed-off-by: Shayne Sweeney <shayne@tailscale.com>
2023-02-01 12:27:04 -05:00
David Anderson
f8fc3db59c flake.nix: update SRI hash.
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-01-31 22:58:09 -08:00
David Crawshaw
4136f27f35 ipn/localapi: fix validHost parsing logic
Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2023-01-31 15:04:51 -08:00
Mihai Parparita
0039993359 cmd/tsconnect: update to xterm.js 5.1
It includes xtermjs/xterm.js#4216, which improves handling of some
escape sequences. Unfortunately it's not enough to fix the issue
with `ponysay`, but it does not hurt to be up to date.

Updates #6090

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-01-31 13:07:28 -08:00
License Updater
d560a4697a licenses: update win/apple licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-01-31 13:06:29 -08:00
Andrew Dunham
d1146dc701 ipn/ipnlocal: rate-limit diagnose logs in bugreport
We can log too quickly for logtail to catch up, even when we opt out of
log rate-limiting. When the user passes the --diagnose flag to
bugreport, we use a token bucket to control how many logs per second are
printed and sleep until we're able to write more.

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: If27672d66b621b589280bd0fe228de367ffcbd8f
2023-01-31 14:29:21 -05:00
M. J. Fromberger
3eae75b1b8 ci: make gofmt check fail for a non-empty diff (#7131)
Fixes #7130.

Change-Id: If47eb472ea98a8d8b250c5c681c7862d252645fb
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
2023-01-31 11:16:39 -08:00
Anton Tolchanov
100d8e909e cmd/derpprobe: migrate to the prober framework
`prober.DERP` was created in #5988 based on derpprobe. Having used it
instead of derpprobe for a few months, I think we have enough confidence
that it works and can now migrate derpprobe to use the prober framework
and get rid of code duplication.

A few notable changes in behaviour:
- results of STUN probes over IPv4 and IPv6 are now reported separately;
- TLS probing now includes OCSP verification;
- probe names in the output have changed;
- ability to send Slack notification from the prober has been removed.
  Instead, the prober now exports metrics in Expvar (/debug/vars) and
  Prometheus (/debug/varz) formats.

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

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2023-01-31 10:47:42 +00:00
Nick Kirby
fac1632ed9 Update CodeQL action to v2
There's an error in the `Perform CodeQL Analysis` step saying to upgrade to v2 as v1 was deprecated on 18th January.

Signed-off-by: Nick Kirby <nrkirb@gmail.com>
2023-01-30 22:54:05 -08:00
Jordan Whited
39e52c4b0a wgengine/magicsock: fix de-dup disco ping handling for netmap endpoints (#7118)
Fixes #7116

Signed-off-by: Jordan Whited <jordan@tailscale.com>
2023-01-30 22:38:20 -08:00
Brad Fitzpatrick
01e736e1d5 go.toolchain.rev: update to Go 1.20rc3
Updates #7123

Change-Id: Ibdf53530251c120e7e20c24abcf4a05f2ff7ac97
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-30 21:55:13 -08:00
Maisem Ali
04b57a371e ipn/ipnlocal: drop not required StateKey parameter
This is #cleanup now that #7121 is merged.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-01-30 17:58:55 -08:00
Mihai Parparita
73d33e3f20 cmd/tsconnect: use empty string as the default state store key
Makes the Wasm client more similar to the others, and allows the default
profile to be correctly picked up when restarting the client in dev
mode (where we persist the state in sessionStorage).

Also update README to reflect that Go wasm changes can be picked up
with just a reload (as of #5383)

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-01-30 17:18:41 -08:00
Maisem Ali
5bba65e978 net/memnet: rename from net/nettest
This is just #cleanup to resolve a TODO

Also add a package doc.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-01-30 13:03:32 -08:00
Maisem Ali
4441609d8f safesocket: remove the now unused WindowsLocalPort
Also drop the port param from safesocket.Listen. #cleanup

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-01-30 10:33:02 -08:00
David Anderson
fede3cd704 flake.nix: update SRI hash for Go modules.
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-01-28 22:00:52 -08:00
David Anderson
e51cf1b09d cmd/k8s-operator: use unstable tailscale image as well
We need a post-1.36 tailscale image to handle custom hostnames correctly.

Updates #502

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-01-27 21:34:28 -08:00
Jordan Whited
7921198c05 wgengine/magicsock: de-dup disco pings (#7093)
Fixes #7078

Signed-off-by: Jordan Whited <jordan@tailscale.com>
2023-01-27 21:04:32 -08:00
David Anderson
0dc9cbc9ab cmd/k8s-operator: use the unstable operator image
There is no stable release yet, and for alpha we want people on the
unstable build while we iterate.

Updates #502

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-01-27 19:05:09 -08:00
David Anderson
4950e6117e build_docker.sh: use docker hub for base image hosting.
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-01-27 18:33:05 -08:00
David Anderson
969b9ed91f build_docker.sh: set good repo defaults based on the target.
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-01-27 17:39:19 -08:00
License Updater
5242e0f291 licenses: update win/apple licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-01-27 17:35:18 -08:00
David Anderson
f991288ceb build_docker.sh: don't push to ghcr by default.
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-01-27 16:36:12 -08:00
Mihai Parparita
4973956419 ipn/ipnlocal: add /reset-auth LocalAPI endpoint
The iOS has a command to reset the persisted state of the app, but it
was doing its own direct keychain manipulation. This proved to be
brittle (since we changed how preferences are stored with #6022), so
we instead add a LocalAPI endpoint to do do this, which can be updated
in tandem.

This clears the same state as the iOS implementation (tailscale/corp#3186),
that is the machine key and preferences (which includes the node key).
Notably this does not clear the logtail ID, so that logs from the device
still end up in the same place.

Updates tailscale/corp#8923

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-01-27 16:32:35 -08:00
Will Norris
947c14793a all: update tools that manage copyright headers
Update all code generation tools, and those that check for license
headers to use the new standard header.

Also update copyright statement in LICENSE file.

Fixes #6865

Signed-off-by: Will Norris <will@tailscale.com>
2023-01-27 15:36:29 -08:00
Will Norris
71029cea2d all: update copyright and license headers
This updates all source files to use a new standard header for copyright
and license declaration.  Notably, copyright no longer includes a date,
and we now use the standard SPDX-License-Identifier header.

This commit was done almost entirely mechanically with perl, and then
some minimal manual fixes.

Updates #6865

Signed-off-by: Will Norris <will@tailscale.com>
2023-01-27 15:36:29 -08:00
Walter Poupore
11f7f7d4a0 docs/k8s: Use TS_AUTHKEY instead of TS_AUTH_KEY (#7092)
Updates https://github.com/tailscale/tailscale-www/issues/2199.

Signed-off-by: Walter Poupore <walterp@tailscale.com>
2023-01-27 15:05:03 -08:00
Andrew Dunham
50da265608 net/netns: add post-review comments
Follow-up to #7065 with some comments from Brad's review.

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Ia1219f4fa25479b2dada38ffe421065b408c5954
2023-01-27 17:25:03 -05:00
Brad Fitzpatrick
2fc8de485c net/netstat: document the Windows netstat code a bit more
And defensively bound allocation.

Updates tailscale/corp#8878

Change-Id: Iaa07479ea2ea28ee1ac3326ab025046d6d785b00
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-27 10:12:04 -08:00
Mihai Parparita
8c20da2568 util/httpm: add another HTTP method
Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-01-27 10:07:18 -08:00
Aaron Klotz
4a869048bf net/netstat: add nil checks to Windows OSMetadata implementation
The API documentation does claim to output empty strings under certain
conditions, but we're sometimes seeing nil pointers in the wild, not empty
strings.

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

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2023-01-27 09:41:48 -07:00
License Updater
8c03a31d10 licenses: update tailscale{,d} licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-01-27 07:50:22 -08:00
Brad Fitzpatrick
0d794a10ff go.mod: bump wintun-go
For https://git.zx2c4.com/wintun-go/commit/?id=0fa3db229ce2036ae779de8ba03d336b7aa826bd

Updates #6647

Change-Id: I1686b2c77df331fcb9fa4fae88e08232bb95f10b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-27 07:29:35 -08:00
Brad Fitzpatrick
a1b4ab34e6 util/httpm: add new package for prettier HTTP method constants
See package doc.

Change-Id: Ibbfc8e1f98294217c56f3a9452bd93ffa3103572
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-26 19:44:07 -08:00
Andrew Dunham
2703d6916f net/netns: add functionality to bind outgoing sockets based on route table
When turned on via environment variable (off by default), this will use
the BSD routing APIs to query what interface index a socket should be
bound to, rather than binding to the default interface in all cases.

Updates #5719
Updates #5940

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Ib4c919471f377b7a08cd3413f8e8caacb29fee0b
2023-01-26 20:58:58 -05:00
Denton Gentry
7439bc7ba6 cmd/sync-containers: add github.Keychain
Running sync-containers in a GitHub workflow will be
simpler if we check github.Keychain, which uses the
GITHUB_TOKEN if present.

Updates https://github.com/tailscale/corp/issues/8461

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-01-25 19:55:15 -08:00
David Anderson
9bd6a2fb8d cmd/k8s-operator: support setting a custom hostname.
Updates #502

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-01-25 15:38:48 -08:00
Kris Brandow
d5cb016cef tailcfg: add caps and SSH action for SSH hauling
Add capabilities and an SSH action for SSH session hauling.

Updates #7069

Signed-off-by: Kris Brandow <kris@tailscale.com>
2023-01-25 17:41:45 -05:00
shayne
6f992909f0 hostinfo: [windows] check if running via Scoop (#7068)
You can now install Tailscale on Windows via [Scoop](https://scoop.sh).

This change adds a check to `packageTypeWindows()`, looking at the exe's path, and
checking if it starts with: `C:\User\<NAME>\scoop\apps\tailscale`. If so, it
returns `"scoop"` as the package type.

Fixes: #6988

Signed-off-by: Shayne Sweeney <shayne@tailscale.com>
2023-01-25 16:58:23 -05:00
Xe Iaso
038d25bd04 cmd/nginx-auth: update Expected-Tailnet documentation (#6055)
Signed-off-by: Xe Iaso <xe@tailscale.com>
2023-01-25 13:47:19 -05:00
phirework
2d29f77b24 cmd/tailscale/cli: fix TUNmode display on synology web page (#7064)
Fixes #7059

Signed-off-by: Jenny Zhang <jz@tailscale.com>

Signed-off-by: Jenny Zhang <jz@tailscale.com>
2023-01-25 13:22:19 -05:00
License Updater
2adbb48632 licenses: update android licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-01-24 20:26:21 -08:00
Denton Gentry
9af7e8a0ff VERSION.txt: this is 1.37
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-01-24 15:32:02 -08:00
Andrew Dunham
44d73ce932 ipn/ipnlocal, net/dnscache: allow configuring dnscache logging via capability
This allows users to temporarily enable/disable dnscache logging via a
new node capability, to aid in debugging strange connectivity issues.

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I46cf2596a8ae4c1913880a78d0033f8b668edc08
2023-01-24 17:21:43 -05:00
Brad Fitzpatrick
d8feeeee4c wgengine/magicsock: fix buggy fast path in Conn.SetNetworkMap
Spotted by Maisem.

Fixes #6680

Change-Id: I5fdc01de8b006a1c43a2a4848f69397f54b4453a
Co-authored-By: Maisem Ali <maisem@tailscale.com>
Co-authored-By: Andrew Dunham <andrew@tailscale.com>
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-24 14:19:15 -08:00
Vince Prignano
30380403d0 cmd/k8s-operator: remove use of InjectClient (deprecated)
The dependency injection functionality has been deprecated a while back
and it'll be removed in the 0.15 release of Controller Runtime. This
changeset sets the Client after creating the Manager, instead of using
InjectClient.

Signed-off-by: Vince Prignano <vince@prigna.com>
2023-01-24 09:11:45 -08:00
Anton Tolchanov
b49aa6ac31 scripts: explicitly install tailscale-archive-keyring
This will ensure that the `tailscale-archive-keyring` Debian package
gets installed by the installer script.

Updates #3151

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2023-01-24 17:05:23 +00:00
Anton Tolchanov
586a88c710 hostinfo: add an environment type for Replit
Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2023-01-24 14:43:06 +00:00
Anton Tolchanov
e8b695626e cmd/mkpkg: allow specifying recommended dependencies
Updates #3151

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2023-01-24 12:43:24 +00:00
Harry Bowron
c1daa42c24 client/tailscale/keys: fix client.Keys unmarshalling
Signed-off-by: Author Name hbowron@gmail.com
Signed-off-by: Harry Bowron <harry@bolt.com>

Fixes #7020
2023-01-24 12:01:47 +00:00
Brad Fitzpatrick
6e5faff51e ipn/ipnlocal: add health warning for Tailscale SSH + SELinux
Updates #4908

Change-Id: If46be5045b13dd5c3068c334642f89b5917ec861
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-23 20:12:46 -08:00
Brad Fitzpatrick
c8db70fd73 cmd/tailscale/cli: add debug set-expire command for testing
Updates tailscale/corp#8811
Updates tailscale/corp#8613

Change-Id: I1c87806ca3ccc5c43e7ddbd6b4d521f73f7d29f1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-23 19:12:26 -08:00
Andrew Dunham
140b9aad5c ipn/ipnlocal: fire expiry timer when the current node expires
The current node isn't in NetMap.Peers, so without this we would not
have fired this timer on self expiry.

Updates #6932

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Id57f96985397e372f9226802d63b42ff92c95093
2023-01-23 20:23:11 -05:00
James Tucker
e002260b62 wgengine/wglog: add a prefix for all wireguard logs
Fixes #7041

Signed-off-by: James Tucker <james@tailscale.com>
2023-01-23 16:38:10 -08:00
Brad Fitzpatrick
06fff461dc ipn/ipnstate: add PeerStatus.KeyExpiry for tailscale status --json
Fixes #6712

Change-Id: I817cd5342fac8a956fcefda2d63158fa488f3395
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-23 12:45:09 -08:00
Brad Fitzpatrick
b6aa1c1f22 envknob, hostinfo, ipn/ipnlocal: add start of opt-in remote update support
Updates #6907

Change-Id: I85db4f6f831dd5ff7a9ef4bfa25902607e0c1558
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-23 12:12:42 -08:00
Andrew Dunham
b74db24149 tstest/integration: mark all integration tests as flaky
Updates #7036

Change-Id: I3aec5ad680078199ba984bf8afc20b2f2eb37257
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2023-01-23 14:36:16 -05:00
License Updater
65c9ce5a1b licenses: update tailscale{,d} licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-01-23 10:32:34 -08:00
Brad Fitzpatrick
64547b2b86 tailcfg,hostinfo: add Hostinfo.Machine and Hostinfo.GoArchVar
For detecting a non-ideal binary running on the current CPU.

And for helping detect the best Synology package to update to.

Updates #6995

Change-Id: I722f806675b60ce95364471b11c388150c0d4aea
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-22 14:13:56 -08:00
Brad Fitzpatrick
fd92fbd69e cmd/tailscale/cli: only give systemctl hint on systemd systems
Per recent user confusion on a QNAP issue.

Change-Id: Ibda00013df793fb831f4088b40be8a04dfad17c2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-21 13:19:54 -08:00
Brad Fitzpatrick
d5100e0910 net/connstats: mark TestConcurrent as flaky
Updates #7030

Change-Id: Ic46da5e5690b90b95028a68a3cf967ad86881e28
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-21 11:04:31 -08:00
Brad Fitzpatrick
ba5aa2c486 version, cmd/tailscale: add version.Meta, tailscale version --json
Add `tailscale version --json` JSON output mode. This will be used
later for a double-opt-in (per node consent like Tailscale SSH +
control config) to let admins do remote upgrades via `tailscale
update` via a c2n call, which would then need to verify the
cmd/tailscale found on disk for running tailscale update corresponds
to the running tailscaled, refusing if anything looks amiss.

Plus JSON output modes are just nice to have, rather than parsing
unstable/fragile/obscure text formats.

Updates #6995
Updates #6907

Change-Id: I7821ab7fbea4612f4b9b7bdc1be1ad1095aca71b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-20 21:04:30 -08:00
Brad Fitzpatrick
5ca22a0068 cmd/tailscale/cli: make 'tailscale update' support Debian/Ubuntu apt
Updates #6995

Change-Id: I3355435db583755e0fc73d76347f6423b8939dfb
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-20 13:22:19 -08:00
shayne
4471e403aa ipn/ipnlocal: [serve] listen on all-interfaces for macOS sandboxed (#6771)
On macOS (AppStore and macsys), we need to bind to ""/all-interfaces
due to the network sandbox. Ideally we would only bind to the
Tailscale interface, but macOS errors out if we try to
to listen on privileged ports binding only to a specific
interface.

We also implement the lc.Control hook, same as we do for
peerapi. It doesn't solve our problem but it's better that
we do and would likely be required when Apple gets around to
fixing per-interface priviliged port binding.

Fixes: #6364

Signed-off-by: Shayne Sweeney <shayne@tailscale.com>
2023-01-20 13:40:56 -05:00
Brad Fitzpatrick
6793685bba go.mod: bump AWS SDK past a breaking API change of theirs
They changed a type in their SDK which meant others using the AWS APIs
in their Go programs (with newer AWS modules in their caller go.mod)
and then depending on Tailscale (for e.g. tsnet) then couldn't compile
ipn/store/awsstore.

Thanks to @thisisaaronland for bringing this up.

Fixes #7019

Change-Id: I8d2919183dabd6045a96120bb52940a9bb27193b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-20 10:14:56 -08:00
shayne
73399f784b cmd/tailscale/cli: use mock impl of LocalClient for serve cmd (#6422)
Create an interface and mock implementation of tailscale.LocalClient for
serve command tests.

Updates #6304
Closes #6372

Signed-off-by: Shayne Sweeney <shayne@tailscale.com>
2023-01-20 12:36:25 -05:00
Jordan Whited
fec888581a wgengine/magicsock: retry failed single packet ops across rebinds (#6990)
The single packet WriteTo() through RebindingUDPConn.WriteBatch() was
not checking for a rebind between loading the PacketConn and writing to
it. Same with ReadFrom()/ReadBatch().

Fixes #6989

Signed-off-by: Jordan Whited <jordan@tailscale.com>
2023-01-20 09:15:32 -08:00
Brad Fitzpatrick
6edf357b96 all: start groundwork for using capver for localapi & peerapi
Updates #7015

Change-Id: I3d4c11b42a727a62eaac3262a879f29bb4ce82dd
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-19 14:53:47 -08:00
Brad Fitzpatrick
c129bf1da1 cmd/tailscale/cli: un-alpha login+switch in ShortUsage docs
Change-Id: I580d4417cf03833dfa8f6a295fb416faa667c477
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-19 11:59:27 -08:00
Brad Fitzpatrick
71a7b8581d cmd/tailscale/cli: make "update" work on Windows
Updates #6995

Co-authored-by: Aaron Klotz <aaron@tailscale.com>
Change-Id: I16622f43156a70b6fbc8205239fd489d7378d57b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-19 10:02:19 -08:00
Andrew Dunham
4fb663fbd2 various: mark more tests as flaky
Updates #2855
Updates #3598
Updates #7008

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I2b849e04646456b9f0c8a01563f2add752f4b2a4
2023-01-19 10:17:43 -05:00
Andrew Dunham
58ad21b252 wgengine/netstack: fix data race in tests
This uses the helper function added in #6173 to avoid flakes like:
    https://github.com/tailscale/tailscale/actions/runs/3826912237/jobs/6511078024

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: If3f1d3b9c0f64ffcb4ba9a30d3522ec49484f993
2023-01-19 10:17:34 -05:00
Andrew Dunham
dd7057682c tailcfg: bump capver for Node.Expired
Updates #6932

Change-Id: I96c2467fa49201eb3d8df5cb36486370f598928c
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2023-01-18 13:17:55 -08:00
Andrew Dunham
aea251d42a cmd/testwrapper: move from corp; mark magicsock test as flaky
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Ibab5860f5797b3db151d3c27855333e43a9088a4
2023-01-18 12:08:23 -05:00
Brad Fitzpatrick
2df38b1feb wgengine/magicsock: quiet log flood at tailscaled shutdown
When you hit control-C on a tailscaled (notably in dev mode, but
also on any systemctl stop/restart), there is a flood of messages like:

magicsock: doing cleanup for discovery key d:aa9c92321db0807f
magicsock: doing cleanup for discovery key d:bb0f16aacadbfd46
magicsock: doing cleanup for discovery key d:b5b2d386296536f2
magicsock: doing cleanup for discovery key d:3b640649f6796c91
magicsock: doing cleanup for discovery key d:71d7b1afbcce52cd
magicsock: doing cleanup for discovery key d:315b61d7e0111377
magicsock: doing cleanup for discovery key d:9301f63dce69bf45
magicsock: doing cleanup for discovery key d:376141884d6fe072
....

It can be hundreds or even tens of thousands.

So don't do that. Not a useful log message during shutdown.

Change-Id: I029a8510741023f740877df28adff778246c18e5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-17 19:05:59 -08:00
Brad Fitzpatrick
3addcacfe9 net/dns: fix recently added URL scheme from http to https
I typoed/brainoed in the earlier 3582628691

Change-Id: Ic198a6f9911f195d9da9fc5259b5784a4b15e5e3
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-17 18:50:04 -08:00
salman
eec734a578 ipn/{ipnlocal,localapi}: ensure watcher is installed before /watch-ipn-bus/ responds with 200
This change delays the first flush in the /watch-ipn-bus/ handler
until after the watcher has been successfully installed on the IPN
bus. It does this by adding a new onWatchAdded callback to
LocalBackend.WatchNotifications().

Without this, the endpoint returns a 200 almost immediatly, and
only then installs a watcher for IPN events.  This means there's a
small window where events could be missed by clients after calling
WatchIPNBus().

Fixes tailscale/corp#8594.

Signed-off-by: salman <salman@tailscale.com>
2023-01-17 22:59:39 +00:00
Brad Fitzpatrick
3eb986fe05 control/controlhttp: add TS_FORCE_NOISE_443, TS_DEBUG_NOISE_DIAL envknobs
Updates tailscale/docker-extension#49

Change-Id: I99a154c16c92228bfdf4d2cf6c58cda00e22d72f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-17 11:30:22 -08:00
Tom DNetto
ee6d18e35f cmd/tailscale/cli: implement --json for lock status and lock log cmds
Signed-off-by: Tom DNetto <tom@tailscale.com>
2023-01-17 10:10:35 -08:00
License Updater
287fe83f91 licenses: update android licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-01-17 09:35:45 -08:00
License Updater
ef1c902c21 licenses: update win/apple licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-01-17 09:35:26 -08:00
Brad Fitzpatrick
b657187a69 cmd/tailscale, logtail: add 'tailscale debug daemon-logs' logtail mechanism
Fixes #6836

Change-Id: Ia6eb39ff8972e1aa149aeeb63844a97497c2cf04
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-15 11:23:28 -08:00
andig
5f96d6211a Remove redundant type declaration
Signed-off-by: andig <cpuidle@gmx.de>
2023-01-15 07:32:02 -08:00
David Anderson
72cc70ebfc flake.nix: update vendor hash.
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-01-14 18:12:25 -08:00
Brad Fitzpatrick
3582628691 net/dns/resolvconffile: link to FAQ about resolv.conf being overwritten
Add link to new http://tailscale.com/s/resolvconf-overwrite page,
added in tailscale/tailscale-www#2243

Change-Id: I9718399487f2ed18bf1a112581fd168aea30f232
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-14 13:54:45 -08:00
Andrew Dunham
3a018e51bb ipn/ipnlocal: move handling of expired nodes to LocalBackend
In order to be able to synthesize a new NetMap when a node expires, have
LocalBackend start a timer when receiving a new NetMap that fires
slightly after the next node expires. Additionally, move the logic that
updates expired nodes into LocalBackend so it runs on every netmap
(whether received from controlclient or self-triggered).

Updates #6932

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I833390e16ad188983eac29eb34cc7574f555f2f3
2023-01-14 16:35:02 -05:00
Brad Fitzpatrick
6d85a94767 net/{packet,tstun}: fix typo in test helper docs
Change-Id: Ifc1684fe77c7d2585e049e0dfd7340910c47a67a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-14 13:01:15 -08:00
Brad Fitzpatrick
c1a2e2c380 net/{packet,tstun},wgengine/filter: fix unknown IP protocol handling
01b90df2fa added SCTP support before
(with explicit parsing for ports) and
69de3bf7bf tried to add support for
arbitrary IP protocols (as long as the ACL permited a port of "*",
since we might not know how to find ports from an arbitrary IP
protocol, if it even has such a concept). But apparently that latter
commit wasn't tested end-to-end enough. It had a lot of tests, but the
tests made assumptions about layering that either weren't true, or
regressed since 1.20. Notably, it didn't remove the (*Filter).pre
bidirectional filter that dropped all "unknown" protocol packets both
leaving and entering, even if there were explicit protocol matches
allowing them in.

Also, don't map all unknown protocols to 0. Keep their IP protocol
number parsed so it's matchable by later layers. Only reject illegal
things.

Fixes #6423
Updates #2162
Updates #2163

Change-Id: I9659b3ece86f4db51d644f9b34df78821758842c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-14 10:32:18 -08:00
Brad Fitzpatrick
3386a59cf1 wgengine/filter: include IP proto number in unknown protocol errors
Updates #6423

Change-Id: I9e363922e2c24fdc42687707c069af5bba68b93e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-14 08:35:14 -08:00
Brad Fitzpatrick
006ec659e6 wgengine/filter: reorder RunOut disjunctive cases to match RunIn above
Change-Id: Ia422121cde1687044b18be7bea9e7bf51a4183b9
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-14 08:35:14 -08:00
Brad Fitzpatrick
d9144c73a8 cmd/tailscale: add start of "tailscale update" command
Goal: one way for users to update Tailscale, downgrade, switch tracks,
regardless of platform (Windows, most Linux distros, macOS, Synology).

This is a start.

Updates #755, etc

Change-Id: I23466da1ba41b45f0029ca79a17f5796c2eedd92
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-14 07:53:41 -08:00
Mihai Parparita
67f82e62a1 ipn/ipnlocal: add Expired to PeerStatus
Needed for clients that get information via the /v0/status LocalAPI
endpoint (e.g. to not offer expired exit nodes as options).

Updates tailscale/corp#8702

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-01-13 18:21:56 -08:00
phirework
f011a0923a cmd/tailscale/cli: style synology outgoing access info (#6959)
Follow-up to #6957.

Updates #4015

Signed-off-by: Jenny Zhang <jz@tailscale.com>
2023-01-13 20:01:28 -05:00
Andrew Dunham
11ce5b7e57 ipn/ipnlocal, wgengine/magicsock: check Expired bool on Node; print error in Ping
Change-Id: Ic5f533f175a6e1bb73d4957d8c3f970add42e82e
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2023-01-13 16:56:34 -05:00
Brad Fitzpatrick
5eded58924 cmd/tailscale/cli: make web show/link Synology outgoing connection mode/docs
Fixes #4015

Change-Id: I8230bb0cc3d621b6fa02ab2462cea104fa1e9cf9
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-13 13:44:41 -08:00
Matthias Gabriel
355c3b2be7 control/controlhttp: fix header case-sensitivity
Change-Id: I49269bc969a80382997ec5c9de33c4f56d9dc787
Signed-off-by: Matthias Gabriel <matthias.gabriel@etit.tu-chemnitz.de>
2023-01-13 11:31:57 -08:00
Brad Fitzpatrick
61dfbc0a6e cmd/tailscale/cli: plumb TUN mode into tailscale web template
UI works remains, but data is there now.

Updates #4015

Change-Id: Ib91e94718b655ad60a63596e59468f3b3b102306
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-13 07:59:40 -08:00
salman
8a1201ac42 cmd/tailscale: correct order for -terminate-tls flag in serve tcp usage
The -terminate-tls flag is for the tcp subsubcommand, not the serve
subcommand like the usage example suggests.

Signed-off-by: salman <salman@tailscale.com>
2023-01-13 14:43:42 +00:00
Brad Fitzpatrick
faf2d30439 version: advertise unstable track in CLI, daemon start-up
Fixes #865

Change-Id: I166e56c3744b0a113973682b9a5327d7aec189f1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-13 06:36:58 -08:00
Jordan Whited
25a0091f69 net/portmapper: relax handling of UPnP resp (#6946)
Gateway devices operating as an HA pair w/VRRP or CARP may send UPnP
replies from static addresses rather than the floating gateway address.
This commit relaxes our source address verification such that we parse
responses from non-gateway IPs, and re-point the UPnP root desc
URL to the gateway IP. This ensures we are still interfacing with the
gateway device (assuming L2 security intact), even though we got a
root desc from a non-gateway address.

This relaxed handling is required for ANY port mapping to work on certain
OPNsense/pfsense distributions using CARP at the time of writing, as
miniupnpd may only listen on the static, non-gateway interface address
for PCP and PMP.

Fixes #5502

Signed-off-by: Jordan Whited <jordan@tailscale.com>
2023-01-12 16:57:02 -08:00
License Updater
b76dffa594 licenses: update win/apple licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-01-12 16:41:20 -08:00
Andrew Dunham
6f18fbce8d tailcfg: document zero value for KeyExpiry
Change-Id: I50889f0205ecf66c415f50f9019c190448c991fc
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2023-01-12 13:13:46 -05:00
Tom DNetto
2ac5474be1 net/flowtrack,wgengine/filter: refactor Cache to use generics
Signed-off-by: Tom DNetto <tom@tailscale.com>
2023-01-11 15:29:09 -08:00
Will Norris
3becf82dd3 types/views: add SliceEqualAnyOrder func
This is based on the tagsEqual func from corp/control/control.go, moved
here so that it can be reused in other places.

Signed-off-by: Will Norris <will@tailscale.com>
2023-01-11 15:18:40 -08:00
Andrew Dunham
1e67947cfa control/controlclient, tailcfg: add Node.Expired field, set for expired nodes
Nodes that are expired, taking into account the time delta calculated
from MapResponse.ControlTime have the newly-added Expired boolean set.
For additional defense-in-depth, also replicate what control does and
clear the Endpoints and DERP fields, and additionally set the node key
to a bogus value.

Updates #6932

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Ia2bd6b56064416feee28aef5699ca7090940662a
2023-01-11 09:45:21 -05:00
Denton Gentry
22ebb25e83 cmd/tailscale: disable HTTPS verification for QNAP auth.
QNAP's "Force HTTPS" mode redirects even localhost HTTP to
HTTPS, but uses a self-signed certificate which fails
verification. We accommodate this by disabling checking
of the cert.

Fixes https://github.com/tailscale/tailscale/issues/6903

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-01-10 21:49:28 -08:00
James Tucker
2afa1672ac ipn/ipnlocal: disallow unsigned peers from WoL
Unsigned peers should not be allowed to generate Wake-on-Lan packets,
only access Funnel.

Updates #6934
Updates #7515
Updates #6475

Signed-off-by: James Tucker <james@tailscale.com>
2023-01-10 15:54:48 -08:00
License Updater
237b1108b3 licenses: update tailscale{,d} licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-01-10 09:39:19 -08:00
Will Norris
fff617c988 go.mod: bump golang.org/x/net and dependencies
I don't think CVE-2022-41717 necessarily impacts us (maybe as part of
funnel?), but it came up in a recent security scan so worth updating
anyway.

Signed-off-by: Will Norris <will@tailscale.com>
2023-01-10 09:30:44 -08:00
License Updater
c684ca7a0c licenses: update tailscale{,d} licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-01-09 14:36:00 -08:00
Brad Fitzpatrick
1116602d4c ssh/tailssh: add OpenBSD support for Tailscale SSH
And bump go.mod for https://github.com/u-root/u-root/pull/2593

Change-Id: I36ec94c5b2b76d671cb739f1e9a1a43ca1d9d1b1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-09 12:58:15 -08:00
Brad Fitzpatrick
be67b8e75b ssh/tailssh: fix Tailscale SSH to non-root tailscaled
Fix regression from 337c77964b where
tailscaled started calling Setgroups. Prior to that, SSH to a non-root
tailscaled was working.

Instead, ignore any failure calling Setgroups if the groups are
already correct.

Fixes #6888

Change-Id: I561991ddb37eaf2620759c6bcaabd36e0fb2a22d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-06 13:19:12 -08:00
Brad Fitzpatrick
8047dfa2dc ssh/tailssh: unify some of the incubator_* GOOS files into incubator.go
In prep for fix for #6888

Change-Id: I79f780c6467a9b7ac03017b27d412d6b0d2f7e6b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-06 13:19:12 -08:00
Brad Fitzpatrick
ebbf5c57b3 README.md: update with some new links, refresh
And remove Darwin from the list, as macOS was already there.

Change-Id: I76bdcad97c926771f44a67140af21f07a8334796
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-05 13:06:45 -08:00
David Anderson
39efba528f cmd/containerboot: use TS_AUTHKEY as the parameter for auth keys
We still accept the previous TS_AUTH_KEY for backwards compatibility, but the documented option name is the spelling we use everywhere else.

Updates #6321

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-01-05 13:03:39 -08:00
Brad Fitzpatrick
69c0b7e712 ipn/ipnlocal: add c2n handler to flush logtail for support debugging
Updates tailscale/corp#8564

Change-Id: I0c619d4007069f90cffd319fba66bd034d63e84d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-05 12:06:07 -08:00
Tom DNetto
673b3d8dbd net/dns,userspace: remove unused DNS paths, normalize query limit on iOS
With a42a594bb3, iOS uses netstack and
hence there are no longer any platforms which use the legacy MagicDNS path. As such, we remove it.

We also normalize the limit for max in-flight DNS queries on iOS (it was 64, now its 256 as per other platforms).
It was 64 for the sake of being cautious about memory, but now we have 50Mb (iOS-15 and greater) instead of 15Mb
so we have the spare headroom.

Signed-off-by: Tom DNetto <tom@tailscale.com>
2023-01-05 11:56:14 -08:00
Brad Fitzpatrick
10eec37cd9 scripts: permit 2023 in license headers
Change-Id: Ia018cb8491871c8bf756c454d085780b75512962
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-05 11:41:47 -08:00
Mihai Parparita
8f2bc0708b logtail: make logs flush delay dynamic
Instead of a static FlushDelay configuration value, use a FlushDelayFn
function that we invoke every time we decide send logs. This will allow
mobile clients to be more dynamic about when to send logs.

Updates #6768

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2023-01-04 16:59:25 -08:00
Tom DNetto
0088c5ddc0 health,ipn/ipnlocal: report the node being locked out as a health issue
Signed-off-by: Tom DNetto <tom@tailscale.com>
2023-01-04 16:20:47 -08:00
Tom DNetto
907f85cd67 cmd/tailscale,tka: make KeyID return an error instead of panicking
Signed-off-by: Tom DNetto <tom@tailscale.com>
2023-01-04 09:51:31 -08:00
Tom DNetto
8724aa254f cmd/tailscale,tka: implement compat for TKA messages, minor UX tweaks
Signed-off-by: Tom DNetto <tom@tailscale.com>
2023-01-04 09:51:31 -08:00
Kristoffer Dalby
c4e262a0fc ipn/profiles: set default prefs based on Windows registry (#6803)
Co-authored-by: Maisem Ali maisem@tailscale.com
2023-01-04 18:34:31 +01:00
Brad Fitzpatrick
eafbf8886d ipn/localapi: add localapi debug endpoints for packet filter/matches
For debugging #6423. This is easier than TS_DEBUG_MAP, as this means I
can pipe things into jq, etc.

Updates #6423

Change-Id: Ib3e7496b2eb3f47d4bed42e9b8045a441424b23c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-03 15:54:51 -08:00
Brad Fitzpatrick
b2b8e62476 util/codegen: permit running in directories without copyright headers
It broke in our corp repo that lacks copyright headers.

Change-Id: Iafc433e6b6affe83b45477899455527658dc4f12
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-03 11:09:35 -08:00
David Anderson
91e64ca74f cmd/tailscale/cli: redact private key in debug netmap output by default
This makes `tailscale debug watch-ipn` safe to use for troubleshooting
user issues, in addition to local debugging during development.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-01-03 10:06:24 -08:00
License Updater
d72575eaaa licenses: update android licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-01-03 09:48:39 -08:00
James Tucker
b2c55e62c8 net/tlsdial,tstest,version: use go command from $PATH
Go now includes the GOROOT bin directory in $PATH while running tests
and generate, so it is no longer necessary to construct a path using
runtime.GOROOT().

Fixes #6689

Signed-off-by: James Tucker <james@tailscale.com>
2023-01-03 09:30:23 -08:00
Denton Gentry
467ace7d0c cmd/tailscale: use localhost for QNAP authLogin.cgi
When the user clicks on the Tailscale app in the QNAP App Center,
we do a GET from /cgi-bin/authLogin.cgi to look up their SID.

If the user clicked "secure login" on the QNAP login page to use
HTTPS, then our access to authLogin.cgi will also use HTTPS
but the certiciate is self-signed. Our GET fails with:
    Get "https://10.1.10.41/cgi-bin/authLogin.cgi?sid=abcd0123":
    x509: cannot validate certificate for 10.1.10.41 because it
    doesn't contain any IP SANs
or similar errors.

Instead, access QNAP authentication via http://localhost:8080/
as documented in
https://download.qnap.com/dev/API_QNAP_QTS_Authentication.pdf

Fixes https://github.com/tailscale/tailscale-qpkg/issues/62

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-01-03 06:09:10 -08:00
Brad Fitzpatrick
aad6830df0 util/codegen, all: use latest year, not time.Now, in generated files
Updates #6865

Change-Id: I6b86c646968ebbd4553cf37df5e5612fbf5c5f7d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-02 20:38:32 -08:00
Brad Fitzpatrick
ea70aa3d98 net/dns/resolvconffile: fix handling of multiple search domains
Fixes #6875

Change-Id: I57eb9312c9a1c81792ce2b5a0a0f254213b05df2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-01-02 20:19:16 -08:00
896 changed files with 11160 additions and 5786 deletions

View File

@@ -12,7 +12,6 @@ body:
attributes:
label: What is the issue?
description: What happened? What did you expect to happen?
placeholder: oh no
validations:
required: true
- type: textarea
@@ -61,6 +60,13 @@ body:
placeholder: e.g., 1.14.4
validations:
required: false
- type: textarea
id: other-software
attributes:
label: Other software
description: What [other software](https://github.com/tailscale/tailscale/wiki/OtherSoftwareInterop) (networking, security, etc) are you running?
validations:
required: false
- type: input
id: bug-report
attributes:

View File

@@ -1,31 +0,0 @@
name: CIFuzz
on: [pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'tailscale'
dry-run: false
language: go
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'tailscale'
fuzz-seconds: 300
dry-run: false
language: go
- name: Upload Crash
uses: actions/upload-artifact@v3
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts

View File

@@ -47,7 +47,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -58,7 +58,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -72,4 +72,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@@ -1,55 +0,0 @@
name: Android-Cross
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
id: go
- name: Android smoke build
# Super minimal Android build that doesn't even use CGO and doesn't build everything that's needed
# and is only arm64. But it's a smoke build: it's not meant to catch everything. But it'll catch
# some Android breakages early.
# TODO(bradfitz): better; see https://github.com/tailscale/tailscale/issues/4482
env:
GOOS: android
GOARCH: arm64
run: go install ./net/netns ./ipn/ipnlocal ./wgengine/magicsock/ ./wgengine/ ./wgengine/router/ ./wgengine/netstack ./util/dnsname/ ./ipn/ ./net/interfaces ./wgengine/router/ ./tailcfg/ ./types/logger/ ./net/dns ./hostinfo ./version
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,63 +0,0 @@
name: Darwin-Cross
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
id: go
- name: macOS build cmd
env:
GOOS: darwin
GOARCH: amd64
run: go build ./cmd/...
- name: macOS build tests
env:
GOOS: darwin
GOARCH: amd64
run: for d in $(go list -f '{{if .TestGoFiles}}{{.Dir}}{{end}}' ./... ); do (echo $d; cd $d && go test -c ); done
- name: iOS build most
env:
GOOS: ios
GOARCH: arm64
run: go install ./ipn/... ./wgengine/ ./types/... ./control/controlclient
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,57 +0,0 @@
name: FreeBSD-Cross
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
id: go
- name: FreeBSD build cmd
env:
GOOS: freebsd
GOARCH: amd64
run: go build ./cmd/...
- name: FreeBSD build tests
env:
GOOS: freebsd
GOARCH: amd64
run: for d in $(go list -f '{{if .TestGoFiles}}{{.Dir}}{{end}}' ./... ); do (echo $d; cd $d && go test -c ); done
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,57 +0,0 @@
name: Loongnix-Cross
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
id: go
- name: Loongnix build cmd
env:
GOOS: linux
GOARCH: loong64
run: go build ./cmd/...
- name: Loongnix build tests
env:
GOOS: linux
GOARCH: loong64
run: for d in $(go list -f '{{if .TestGoFiles}}{{.Dir}}{{end}}' ./... ); do (echo $d; cd $d && go test -c ); done
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,57 +0,0 @@
name: OpenBSD-Cross
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
id: go
- name: OpenBSD build cmd
env:
GOOS: openbsd
GOARCH: amd64
run: go build ./cmd/...
- name: OpenBSD build tests
env:
GOOS: openbsd
GOARCH: amd64
run: for d in $(go list -f '{{if .TestGoFiles}}{{.Dir}}{{end}}' ./... ); do (echo $d; cd $d && go test -c ); done
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,58 +0,0 @@
name: Wasm-Cross
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
id: go
- name: Wasm client build
env:
GOOS: js
GOARCH: wasm
run: go build ./cmd/tsconnect/wasm ./cmd/tailscale/cli
- name: tsconnect static build
# Use our custom Go toolchain, we set build tags (to control binary size)
# that depend on it.
run: |
./tool/go run ./cmd/tsconnect --fast-compression build
./tool/go run ./cmd/tsconnect --fast-compression build-pkg
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,57 +0,0 @@
name: Windows-Cross
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
id: go
- name: Windows build cmd
env:
GOOS: windows
GOARCH: amd64
run: go build ./cmd/...
- name: Windows build tests
env:
GOOS: windows
GOARCH: amd64
run: for d in $(go list -f '{{if .TestGoFiles}}{{.Dir}}{{end}}' ./... ); do (echo $d; cd $d && go test -c ); done
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,33 +0,0 @@
name: depaware
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
- name: depaware
run: go run github.com/tailscale/depaware --check
tailscale.com/cmd/tailscaled
tailscale.com/cmd/tailscale
tailscale.com/cmd/derper

View File

@@ -1,42 +0,0 @@
name: go generate
on:
push:
branches:
- main
- "release-branch/*"
pull_request:
branches:
- "*"
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
- name: check 'go generate' is clean
run: |
if [[ "${{github.ref}}" == release-branch/* ]]
then
pkgs=$(go list ./... | grep -v dnsfallback)
else
pkgs=$(go list ./... | grep -v dnsfallback)
fi
go generate $pkgs
echo
echo
git diff --name-only --exit-code || (echo "The files above need updating. Please run 'go generate'."; exit 1)

View File

@@ -1,35 +0,0 @@
name: go mod tidy
on:
push:
branches:
- main
pull_request:
branches:
- "*"
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
- name: check 'go mod tidy' is clean
run: |
go mod tidy
echo
echo
git diff --name-only --exit-code || (echo "Please run 'go mod tidy'."; exit 1)

View File

@@ -1,45 +0,0 @@
name: license
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
- name: Run license checker
run: ./scripts/check_license_headers.sh .
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,67 +0,0 @@
name: Linux race
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
id: go
- name: Basic build
run: go build ./cmd/...
- name: Run tests and benchmarks with -race flag on linux
run: go test -race -bench=. -benchtime=1x ./...
- name: Check that no tracked files in the repo have been modified
run: git diff --no-ext-diff --name-only --exit-code || (echo "Build/test modified the files above."; exit 1)
- name: Check that no files have been added to the repo
run: |
# Note: The "error: pathspec..." you see below is normal!
# In the success case in which there are no new untracked files,
# git ls-files complains about the pathspec not matching anything.
# That's OK. It's not worth the effort to suppress. Please ignore it.
if git ls-files --others --exclude-standard --directory --no-empty-directory --error-unmatch -- ':/*'
then
echo "Build/test created untracked files in the repo (file names above)."
exit 1
fi
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,77 +0,0 @@
name: Linux
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-22.04
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
id: go
- name: Basic build
run: go build ./cmd/...
- name: Build variants
run: |
go install --tags=ts_include_cli ./cmd/tailscaled
go install --tags=ts_omit_aws ./cmd/tailscaled
- name: Get QEMU
run: |
sudo apt-get -y update
sudo apt-get -y install qemu-user
- name: Run tests on linux
run: go test -bench=. -benchtime=1x ./...
- name: Check that no tracked files in the repo have been modified
run: git diff --no-ext-diff --name-only --exit-code || (echo "Build/test modified the files above."; exit 1)
- name: Check that no files have been added to the repo
run: |
# Note: The "error: pathspec..." you see below is normal!
# In the success case in which there are no new untracked files,
# git ls-files complains about the pathspec not matching anything.
# That's OK. It's not worth the effort to suppress. Please ignore it.
if git ls-files --others --exclude-standard --directory --no-empty-directory --error-unmatch -- ':/*'
then
echo "Build/test created untracked files in the repo (file names above)."
exit 1
fi
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,67 +0,0 @@
name: Linux 32-bit
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
id: go
- name: Basic build
run: GOARCH=386 go build ./cmd/...
- name: Run tests on linux
run: GOARCH=386 go test -bench=. -benchtime=1x ./...
- name: Check that no tracked files in the repo have been modified
run: git diff --no-ext-diff --name-only --exit-code || (echo "Build/test modified the files above."; exit 1)
- name: Check that no files have been added to the repo
run: |
# Note: The "error: pathspec..." you see below is normal!
# In the success case in which there are no new untracked files,
# git ls-files complains about the pathspec not matching anything.
# That's OK. It's not worth the effort to suppress. Please ignore it.
if git ls-files --others --exclude-standard --directory --no-empty-directory --error-unmatch -- ':/*'
then
echo "Build/test created untracked files in the repo (file names above)."
exit 1
fi
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,113 +0,0 @@
name: static-analysis
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
gofmt:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
- name: Run gofmt (goimports)
run: go run golang.org/x/tools/cmd/goimports -d --format-only .
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'
vet:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Check out code
uses: actions/checkout@v3
- name: Run go vet
run: go vet ./...
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'
staticcheck:
runs-on: ubuntu-latest
strategy:
matrix:
goos: [linux, windows, darwin]
goarch: [amd64]
include:
- goos: windows
goarch: 386
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Check out code
uses: actions/checkout@v3
- name: Install staticcheck
run: "GOBIN=~/.local/bin go install honnef.co/go/tools/cmd/staticcheck"
- name: Print staticcheck version
run: "staticcheck -version"
- name: "Run staticcheck (${{ matrix.goos }}/${{ matrix.goarch }})"
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: "staticcheck -- $(go list ./... | grep -v tempfork)"
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

388
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,388 @@
# This is our main "CI tests" workflow. It runs everything that should run on
# both PRs and merged commits, and for the latter reports failures to slack.
name: CI
env:
# Our fuzz job, powered by OSS-Fuzz, fails periodically because we upgrade to
# new Go versions very eagerly. OSS-Fuzz is a little more conservative, and
# ends up being unable to compile our code.
#
# When this happens, we want to disable the fuzz target until OSS-Fuzz catches
# up. However, we also don't want to forget to turn it back on when OSS-Fuzz
# can once again build our code.
#
# This variable toggles the fuzz job between two modes:
# - false: we expect fuzzing to be happy, and should report failure if it's not.
# - true: we expect fuzzing is broken, and should report failure if it start working.
TS_FUZZ_CURRENTLY_BROKEN: true
on:
push:
branches:
- "main"
- "release-branch/*"
pull_request:
branches:
- "*"
concurrency:
# For PRs, later CI runs preempt previous ones. e.g. a force push on a PR
# cancels running CI jobs and starts all new ones.
#
# For non-PR pushes, concurrency.group needs to be unique for every distinct
# CI run we want to have happen. Use run_id, which in practice means all
# non-PR CI runs will be allowed to run without preempting each other.
group: ${{ github.workflow }}-$${{ github.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
test:
strategy:
fail-fast: false # don't abort the entire matrix if one element fails
matrix:
include:
- goarch: amd64
- goarch: amd64
variant: race
- goarch: "386" # thanks yaml
runs-on: ubuntu-22.04
steps:
- name: checkout
uses: actions/checkout@v3
- name: build all
run: ./tool/go build ./...
env:
GOARCH: ${{ matrix.goarch }}
- name: build variant CLIs
run: |
./build_dist.sh --extra-small ./cmd/tailscaled
./build_dist.sh --box ./cmd/tailscaled
./build_dist.sh --extra-small --box ./cmd/tailscaled
rm -f tailscaled
env:
GOARCH: ${{ matrix.goarch }}
- name: get qemu # for tstest/archtest
if: matrix.goarch == 'amd64' && matrix.variant == ''
run: |
sudo apt-get -y update
sudo apt-get -y install qemu-user
- name: build test wrapper
run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper
- name: test all
if: matrix.variant != 'race'
run: ./tool/go test -exec=/tmp/testwrapper -bench=. -benchtime=1x ./...
env:
GOARCH: ${{ matrix.goarch }}
- name: test all (race)
if: matrix.variant == 'race'
run: ./tool/go test -race -exec=/tmp/testwrapper -bench=. -benchtime=1x ./...
env:
GOARCH: ${{ matrix.goarch }}
- name: check that no tracked files changed
run: git diff --no-ext-diff --name-only --exit-code || (echo "Build/test modified the files above."; exit 1)
- name: check that no new files were added
run: |
# Note: The "error: pathspec..." you see below is normal!
# In the success case in which there are no new untracked files,
# git ls-files complains about the pathspec not matching anything.
# That's OK. It's not worth the effort to suppress. Please ignore it.
if git ls-files --others --exclude-standard --directory --no-empty-directory --error-unmatch -- ':/*'
then
echo "Build/test created untracked files in the repo (file names above)."
exit 1
fi
windows:
runs-on: windows-2022
steps:
- name: checkout
uses: actions/checkout@v3
- name: Restore Cache
uses: actions/cache@v3
with:
# Note: unlike the other setups, this is only grabbing the mod download
# cache, rather than the whole mod directory, as the download cache
# contains zips that can be unpacked in parallel faster than they can be
# fetched and extracted by tar
path: |
~/go/pkg/mod/cache
~\AppData\Local\go-build
# The -2- here should be incremented when the scheme of data to be
# cached changes (e.g. path above changes).
# TODO(raggi): add a go version here.
key: ${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
- name: test
# Don't use -bench=. -benchtime=1x.
# Somewhere in the layers (powershell?)
# the equals signs cause great confusion.
run: ./tool/go test -bench . -benchtime 1x ./...
vm:
runs-on: ["self-hosted", "linux", "vm"]
# VM tests run with some privileges, don't let them run on 3p PRs.
if: github.repository == 'tailscale/tailscale'
steps:
- name: checkout
uses: actions/checkout@v3
- name: Run VM tests
run: ./tool/go test ./tstest/integration/vms -v -no-s3 -run-vm-tests -run=TestRunUbuntu2004
env:
HOME: "/tmp"
TMPDIR: "/tmp"
XDB_CACHE_HOME: "/var/lib/ghrunner/cache"
cross: # cross-compile checks, build only.
strategy:
fail-fast: false # don't abort the entire matrix if one element fails
matrix:
include:
# Note: linux/amd64 is not in this matrix, because that goos/goarch is
# tested more exhaustively in the 'test' job above.
- goos: linux
goarch: arm64
- goos: linux
goarch: "386" # thanks yaml
- goos: linux
goarch: loong64
# macOS
- goos: darwin
goarch: amd64
- goos: darwin
goarch: arm64
# Windows
- goos: windows
goarch: amd64
- goos: windows
goarch: arm64
# BSDs
- goos: freebsd
goarch: amd64
- goos: openbsd
goarch: amd64
runs-on: ubuntu-22.04
steps:
- name: checkout
uses: actions/checkout@v3
- name: build all
run: ./tool/go build ./cmd/...
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: "0"
- name: build tests
run: ./tool/go test -exec=true ./...
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: "0"
ios: # similar to cross above, but iOS can't build most of the repo. So, just
#make it build a few smoke packages.
runs-on: ubuntu-22.04
steps:
- name: checkout
uses: actions/checkout@v3
- name: build some
run: ./tool/go build ./ipn/... ./wgengine/ ./types/... ./control/controlclient
env:
GOOS: ios
GOARCH: arm64
android:
# similar to cross above, but android fails to build a few pieces of the
# repo. We should fix those pieces, they're small, but as a stepping stone,
# only test the subset of android that our past smoke test checked.
runs-on: ubuntu-22.04
steps:
- name: checkout
uses: actions/checkout@v3
# Super minimal Android build that doesn't even use CGO and doesn't build everything that's needed
# and is only arm64. But it's a smoke build: it's not meant to catch everything. But it'll catch
# some Android breakages early.
# TODO(bradfitz): better; see https://github.com/tailscale/tailscale/issues/4482
- name: build some
run: ./tool/go install ./net/netns ./ipn/ipnlocal ./wgengine/magicsock/ ./wgengine/ ./wgengine/router/ ./wgengine/netstack ./util/dnsname/ ./ipn/ ./net/interfaces ./wgengine/router/ ./tailcfg/ ./types/logger/ ./net/dns ./hostinfo ./version
env:
GOOS: android
GOARCH: arm64
wasm: # builds tsconnect, which is the only wasm build we support
runs-on: ubuntu-22.04
steps:
- name: checkout
uses: actions/checkout@v3
- name: build tsconnect client
run: ./tool/go build ./cmd/tsconnect/wasm ./cmd/tailscale/cli
env:
GOOS: js
GOARCH: wasm
- name: build tsconnect server
# Note, no GOOS/GOARCH in env on this build step, we're running a build
# tool that handles the build itself.
run: |
./tool/go run ./cmd/tsconnect --fast-compression build
./tool/go run ./cmd/tsconnect --fast-compression build-pkg
fuzz:
# This target periodically breaks (see TS_FUZZ_CURRENTLY_BROKEN at the top
# of the file), so it's more complex than usual: the 'build fuzzers' step
# might fail, and depending on the value of 'TS_FUZZ_CURRENTLY_BROKEN', that
# might or might not be fine. The steps after the build figure out whether
# the success/failure is expected, and appropriately pass/fail the job
# overall accordingly.
#
# Practically, this means that all steps after 'build fuzzers' must have an
# explicit 'if' condition, because the default condition for steps is
# 'success()', meaning "only run this if no previous steps failed".
if: github.event_name == 'pull_request'
runs-on: ubuntu-22.04
steps:
- name: build fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
# continue-on-error makes steps.build.conclusion be 'success' even if
# steps.build.outcome is 'failure'. This means this step does not
# contribute to the job's overall pass/fail evaluation.
continue-on-error: true
with:
oss-fuzz-project-name: 'tailscale'
dry-run: false
language: go
- name: report unexpectedly broken fuzz build
if: steps.build.outcome == 'failure' && env.TS_FUZZ_CURRENTLY_BROKEN != 'true'
run: |
echo "fuzzer build failed, see above for why"
echo "if the failure is due to OSS-Fuzz not being on the latest Go yet,"
echo "set TS_FUZZ_CURRENTLY_BROKEN=true in .github/workflows/test.yml"
echo "to temporarily disable fuzzing until OSS-Fuzz works again."
exit 1
- name: report unexpectedly working fuzz build
if: steps.build.outcome == 'success' && env.TS_FUZZ_CURRENTLY_BROKEN == 'true'
run: |
echo "fuzzer build succeeded, but we expect it to be broken"
echo "please set TS_FUZZ_CURRENTLY_BROKEN=false in .github/workflows/test.yml"
echo "to reenable fuzz testing"
exit 1
- name: run fuzzers
id: run
# Run the fuzzers whenever they're able to build, even if we're going to
# report a failure because TS_FUZZ_CURRENTLY_BROKEN is set to the wrong
# value.
if: steps.build.outcome == 'success'
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'tailscale'
fuzz-seconds: 300
dry-run: false
language: go
- name: upload crash
uses: actions/upload-artifact@v3
if: steps.run.outcome != 'success' && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
depaware:
runs-on: ubuntu-22.04
steps:
- name: checkout
uses: actions/checkout@v3
- name: check depaware
run: |
export PATH=$(./tool/go env GOROOT)/bin:$PATH
find . -name 'depaware.txt' | xargs -n1 dirname | xargs ./tool/go run github.com/tailscale/depaware --check
go_generate:
runs-on: ubuntu-22.04
steps:
- name: checkout
uses: actions/checkout@v3
- name: check that 'go generate' is clean
run: |
pkgs=$(./tool/go list ./... | grep -v dnsfallback)
./tool/go generate $pkgs
echo
echo
git diff --name-only --exit-code || (echo "The files above need updating. Please run 'go generate'."; exit 1)
go_mod_tidy:
runs-on: ubuntu-22.04
steps:
- name: checkout
uses: actions/checkout@v3
- name: check that 'go mod tidy' is clean
run: |
./tool/go mod tidy
echo
echo
git diff --name-only --exit-code || (echo "Please run 'go mod tidy'."; exit 1)
licenses:
runs-on: ubuntu-22.04
steps:
- name: checkout
uses: actions/checkout@v3
- name: check licenses
run: ./scripts/check_license_headers.sh .
staticcheck:
runs-on: ubuntu-22.04
strategy:
fail-fast: false # don't abort the entire matrix if one element fails
matrix:
goos: ["linux", "windows", "darwin"]
goarch: ["amd64"]
include:
- goos: "windows"
goarch: "386"
steps:
- name: checkout
uses: actions/checkout@v3
- name: install staticcheck
run: GOBIN=~/.local/bin ./tool/go install honnef.co/go/tools/cmd/staticcheck
- name: run staticcheck
run: |
export GOROOT=$(./tool/go env GOROOT)
export PATH=$GOROOT/bin:$PATH
staticcheck -- $(./tool/go list ./... | grep -v tempfork)
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
notify_slack:
# Only notify slack for merged commits, not PR failures.
if: failure() && github.event_name == 'push'
# Any of these jobs failing causes a slack notification.
needs:
- android
- test
- windows
- vm
- cross
- ios
- wasm
- fuzz
- depaware
- go_generate
- go_mod_tidy
- licenses
- staticcheck
runs-on: ubuntu-22.04
steps:
- name: notify
uses: ruby/action-slack@v3.0.0
with:
payload: |
{
"attachments": [{
"title": "Failure: ${{ github.workflow }}",
"title_link": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks",
"text": "${{ github.repository }}@${{ github.ref_name }}: <https://github.com/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}>",
"fields": [{ "value": ${{ toJson(github.event.head_commit.message) }}, "short": false }],
"footer": "${{ github.event.head_commit.committer.name }} at ${{ github.event.head_commit.timestamp }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

49
.github/workflows/update-flake.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: update-flake
on:
# run action when a change lands in the main branch which updates go.mod. Also
# allow manual triggering.
push:
branches:
- main
paths:
- go.mod
- .github/workflows/update-flakes.yml
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
tailscale:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Run update-flakes
run: ./update-flake.sh
- name: Get access token
uses: tibdex/github-app-token@f717b5ecd4534d3c4df4ce9b5c1c2214f0f7cd06 # v1.6.0
id: generate-token
with:
app_id: ${{ secrets.LICENSING_APP_ID }}
installation_id: ${{ secrets.LICENSING_APP_INSTALLATION_ID }}
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
- name: Send pull request
uses: peter-evans/create-pull-request@ad43dccb4d726ca8514126628bec209b8354b6dd #v4.1.4
with:
token: ${{ steps.generate-token.outputs.token }}
author: Flakes Updater <noreply@tailscale.com>
committer: Flakes Updater <noreply@tailscale.com>
branch: flakes
commit-message: "go.mod.sri: update SRI hash for go.mod changes"
title: "go.mod.sri: update SRI hash for go.mod changes"
body: Triggered by ${{ github.repository }}@${{ github.sha }}
signoff: true
delete-branch: true
reviewers: danderson

View File

@@ -1,51 +0,0 @@
name: VM
on:
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
ubuntu2004-LTS-cloud-base:
runs-on: [ self-hosted, linux, vm ]
if: "(github.repository == 'tailscale/tailscale') && !contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Set GOPATH
run: echo "GOPATH=$HOME/go" >> $GITHUB_ENV
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
- name: Run VM tests
run: go test ./tstest/integration/vms -v -no-s3 -run-vm-tests -run=TestRunUbuntu2004
env:
HOME: "/tmp"
TMPDIR: "/tmp"
XDG_CACHE_HOME: "/var/lib/ghrunner/cache"
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,67 +0,0 @@
name: Windows
on:
push:
branches:
- main
pull_request:
branches:
- '*'
- 'release-branch/*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
test:
runs-on: windows-latest
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
- name: Restore Cache
uses: actions/cache@v3
with:
# Note: unlike some other setups, this is only grabbing the mod download
# cache, rather than the whole mod directory, as the download cache
# contains zips that can be unpacked in parallel faster than they can be
# fetched and extracted by tar
path: |
~/go/pkg/mod/cache
~\AppData\Local\go-build
# The -2- here should be incremented when the scheme of data to be
# cached changes (e.g. path above changes).
# TODO(raggi): add a go version here.
key: ${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
- name: Test
# Don't use -bench=. -benchtime=1x.
# Somewhere in the layers (powershell?)
# the equals signs cause great confusion.
run: go test -bench . -benchtime 1x ./...
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,6 +1,5 @@
# Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Copyright (c) Tailscale Inc & AUTHORS
# SPDX-License-Identifier: BSD-3-Clause
############################################################################
#
@@ -32,7 +31,7 @@
# $ docker exec tailscaled tailscale status
FROM golang:1.19-alpine AS build-env
FROM golang:1.20-alpine AS build-env
WORKDIR /go/src/tailscale
@@ -63,9 +62,9 @@ ENV VERSION_GIT_HASH=$VERSION_GIT_HASH
ARG TARGETARCH
RUN GOARCH=$TARGETARCH go install -ldflags="\
-X tailscale.com/version.Long=$VERSION_LONG \
-X tailscale.com/version.Short=$VERSION_SHORT \
-X tailscale.com/version.GitCommit=$VERSION_GIT_HASH" \
-X tailscale.com/version.longStamp=$VERSION_LONG \
-X tailscale.com/version.shortStamp=$VERSION_SHORT \
-X tailscale.com/version.gitCommitStamp=$VERSION_GIT_HASH" \
-v ./cmd/tailscale ./cmd/tailscaled ./cmd/containerboot
FROM alpine:3.16

View File

@@ -1,6 +1,5 @@
# Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Copyright (c) Tailscale Inc & AUTHORS
# SPDX-License-Identifier: BSD-3-Clause
FROM alpine:3.16
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables

View File

@@ -1,7 +1,6 @@
BSD 3-Clause License
Copyright (c) 2020 Tailscale & AUTHORS.
All rights reserved.
Copyright (c) 2020 Tailscale Inc & AUTHORS.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -12,13 +12,17 @@ tidy:
./tool/go mod tidy
updatedeps:
./tool/go run github.com/tailscale/depaware --update \
# depaware (via x/tools/go/packages) shells back to "go", so make sure the "go"
# it finds in its $$PATH is the right one.
PATH="$$(./tool/go env GOROOT)/bin:$$PATH" ./tool/go run github.com/tailscale/depaware --update \
tailscale.com/cmd/tailscaled \
tailscale.com/cmd/tailscale \
tailscale.com/cmd/derper
depaware:
./tool/go run github.com/tailscale/depaware --check \
# depaware (via x/tools/go/packages) shells back to "go", so make sure the "go"
# it finds in its $$PATH is the right one.
PATH="$$(./tool/go env GOROOT)/bin:$$PATH" ./tool/go run github.com/tailscale/depaware --check \
tailscale.com/cmd/tailscaled \
tailscale.com/cmd/tailscale \
tailscale.com/cmd/derper

View File

@@ -6,27 +6,41 @@ Private WireGuard® networks made easy
## Overview
This repository contains all the open source Tailscale client code and
the `tailscaled` daemon and `tailscale` CLI tool. The `tailscaled`
daemon runs on Linux, Windows and [macOS](https://tailscale.com/kb/1065/macos-variants/), and to varying degrees on FreeBSD, OpenBSD, and Darwin. (The Tailscale iOS and Android apps use this repo's code, but this repo doesn't contain the mobile GUI code.)
This repository contains the majority of Tailscale's open source code.
Notably, it includes the `tailscaled` daemon and
the `tailscale` CLI tool. The `tailscaled` daemon runs on Linux, Windows,
[macOS](https://tailscale.com/kb/1065/macos-variants/), and to varying degrees
on FreeBSD and OpenBSD. The Tailscale iOS and Android apps use this repo's
code, but this repo doesn't contain the mobile GUI code.
The Android app is at https://github.com/tailscale/tailscale-android
Other [Tailscale repos](https://github.com/orgs/tailscale/repositories) of note:
The Synology package is at https://github.com/tailscale/tailscale-synology
* the Android app is at https://github.com/tailscale/tailscale-android
* the Synology package is at https://github.com/tailscale/tailscale-synology
* the QNAP package is at https://github.com/tailscale/tailscale-qpkg
* the Chocolatey packaging is at https://github.com/tailscale/tailscale-chocolatey
For background on which parts of Tailscale are open source and why,
see [https://tailscale.com/opensource/](https://tailscale.com/opensource/).
## Using
We serve packages for a variety of distros at
https://pkgs.tailscale.com .
We serve packages for a variety of distros and platforms at
[https://pkgs.tailscale.com](https://pkgs.tailscale.com/).
## Other clients
The [macOS, iOS, and Windows clients](https://tailscale.com/download)
use the code in this repository but additionally include small GUI
wrappers that are not open source.
wrappers. The GUI wrappers on non-open source platforms are themselves
not open source.
## Building
We always require the latest Go release, currently Go 1.20. (While we build
releases with our [Go fork](https://github.com/tailscale/go/), its use is not
required.)
```
go install tailscale.com/cmd/tailscale{,d}
```
@@ -43,8 +57,6 @@ If your distro has conventions that preclude the use of
`build_dist.sh`, please do the equivalent of what it does in your
distro's way, so that bug reports contain useful version information.
We require the latest Go release, currently Go 1.19.
## Bugs
Please file any issues about this code or the hosted service on

View File

@@ -1 +1 @@
1.35.0
1.37.0

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2019 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package atomicfile contains code related to writing to filesystems
// atomically.

View File

@@ -46,7 +46,7 @@ EOF
fi
tags=""
ldflags="-X tailscale.com/version.Long=${LONG} -X tailscale.com/version.Short=${SHORT} -X tailscale.com/version.GitCommit=${GIT_HASH}"
ldflags="-X tailscale.com/version.longStamp=${LONG} -X tailscale.com/version.shortStamp=${SHORT}"
# build_dist.sh arguments must precede go build arguments.
while [ "$#" -gt 1 ]; do
@@ -54,7 +54,7 @@ while [ "$#" -gt 1 ]; do
--extra-small)
shift
ldflags="$ldflags -w -s"
tags="${tags:+$tags,}ts_omit_aws,ts_omit_bird,ts_omit_tap"
tags="${tags:+$tags,}ts_omit_aws,ts_omit_bird,ts_omit_tap,ts_omit_kube"
;;
--box)
shift

View File

@@ -23,28 +23,29 @@ set -eu
export PATH=$PWD/tool:$PATH
eval $(./build_dist.sh shellvars)
DEFAULT_TAGS="v${VERSION_SHORT},v${VERSION_MINOR}"
DEFAULT_REPOS="tailscale/tailscale,ghcr.io/tailscale/tailscale"
DEFAULT_BASE="ghcr.io/tailscale/alpine-base:3.16"
DEFAULT_TARGET="client"
DEFAULT_TAGS="v${VERSION_SHORT},v${VERSION_MINOR}"
DEFAULT_BASE="tailscale/alpine-base:3.16"
PUSH="${PUSH:-false}"
REPOS="${REPOS:-${DEFAULT_REPOS}}"
TARGET="${TARGET:-${DEFAULT_TARGET}}"
TAGS="${TAGS:-${DEFAULT_TAGS}}"
BASE="${BASE:-${DEFAULT_BASE}}"
TARGET="${TARGET:-${DEFAULT_TARGET}}"
case "$TARGET" in
client)
DEFAULT_REPOS="tailscale/tailscale"
REPOS="${REPOS:-${DEFAULT_REPOS}}"
go run github.com/tailscale/mkctr \
--gopaths="\
tailscale.com/cmd/tailscale:/usr/local/bin/tailscale, \
tailscale.com/cmd/tailscaled:/usr/local/bin/tailscaled, \
tailscale.com/cmd/containerboot:/usr/local/bin/containerboot" \
--ldflags="\
-X tailscale.com/version.Long=${VERSION_LONG} \
-X tailscale.com/version.Short=${VERSION_SHORT} \
-X tailscale.com/version.GitCommit=${VERSION_GIT_HASH}" \
-X tailscale.com/version.longStamp=${VERSION_LONG} \
-X tailscale.com/version.shortStamp=${VERSION_SHORT} \
-X tailscale.com/version.gitCommitStamp=${VERSION_GIT_HASH}" \
--base="${BASE}" \
--tags="${TAGS}" \
--repos="${REPOS}" \
@@ -52,12 +53,14 @@ case "$TARGET" in
/usr/local/bin/containerboot
;;
operator)
DEFAULT_REPOS="tailscale/k8s-operator"
REPOS="${REPOS:-${DEFAULT_REPOS}}"
go run github.com/tailscale/mkctr \
--gopaths="tailscale.com/cmd/k8s-operator:/usr/local/bin/operator" \
--ldflags="\
-X tailscale.com/version.Long=${VERSION_LONG} \
-X tailscale.com/version.Short=${VERSION_SHORT} \
-X tailscale.com/version.GitCommit=${VERSION_GIT_HASH}" \
-X tailscale.com/version.longStamp=${VERSION_LONG} \
-X tailscale.com/version.shortStamp=${VERSION_SHORT} \
-X tailscale.com/version.gitCommitStamp=${VERSION_GIT_HASH}" \
--base="${BASE}" \
--tags="${TAGS}" \
--repos="${REPOS}" \

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package chirp implements a client to communicate with the BIRD Internet
// Routing Daemon.

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package chirp
import (

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build go1.19

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package apitype contains types for the Tailscale LocalAPI and control plane API.
package apitype
@@ -33,3 +32,9 @@ type WaitingFile struct {
Name string
Size int64
}
// SetPushDeviceTokenRequest is the body POSTed to the LocalAPI endpoint /set-device-token.
type SetPushDeviceTokenRequest struct {
// PushDeviceToken is the iOS/macOS APNs device token (and any future Android equivalent).
PushDeviceToken string
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package apitype

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build go1.19

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build go1.19

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// The servetls program shows how to run an HTTPS server
// using a Tailscale cert via LetsEncrypt.

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package tailscale
@@ -55,14 +54,14 @@ func (c *Client) Keys(ctx context.Context) ([]string, error) {
return nil, handleErrorResponse(b, resp)
}
var keys []struct {
ID string `json:"id"`
var keys struct {
Keys []*Key `json:"keys"`
}
if err := json.Unmarshal(b, &keys); err != nil {
return nil, err
}
ret := make([]string, 0, len(keys))
for _, k := range keys {
ret := make([]string, 0, len(keys.Keys))
for _, k := range keys.Keys {
ret = append(ret, k.ID)
}
return ret, nil

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build go1.19
@@ -114,6 +113,7 @@ func (lc *LocalClient) defaultDialer(ctx context.Context, network, addr string)
//
// DoLocalRequest may mutate the request to add Authorization headers.
func (lc *LocalClient) DoLocalRequest(req *http.Request) (*http.Response, error) {
req.Header.Set("Tailscale-Cap", strconv.Itoa(int(tailcfg.CurrentCapabilityVersion)))
lc.tsClientOnce.Do(func() {
lc.tsClient = &http.Client{
Transport: &http.Transport{
@@ -257,6 +257,23 @@ func (lc *LocalClient) DaemonMetrics(ctx context.Context) ([]byte, error) {
return lc.get200(ctx, "/localapi/v0/metrics")
}
// TailDaemonLogs returns a stream the Tailscale daemon's logs as they arrive.
// Close the context to stop the stream.
func (lc *LocalClient) TailDaemonLogs(ctx context.Context) (io.Reader, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+apitype.LocalAPIHost+"/localapi/v0/logtap", nil)
if err != nil {
return nil, err
}
res, err := lc.doLocalRequestNiceError(req)
if err != nil {
return nil, err
}
if res.StatusCode != 200 {
return nil, errors.New(res.Status)
}
return res.Body, nil
}
// Pprof returns a pprof profile of the Tailscale daemon.
func (lc *LocalClient) Pprof(ctx context.Context, pprofType string, sec int) ([]byte, error) {
var secArg string
@@ -1002,6 +1019,36 @@ func (lc *LocalClient) DebugDERPRegion(ctx context.Context, regionIDOrCode strin
return decodeJSON[*ipnstate.DebugDERPRegionReport](body)
}
// DebugSetExpireIn marks the current node key to expire in d.
//
// This is meant primarily for debug and testing.
func (lc *LocalClient) DebugSetExpireIn(ctx context.Context, d time.Duration) error {
v := url.Values{"expiry": {fmt.Sprint(time.Now().Add(d).Unix())}}
_, err := lc.send(ctx, "POST", "/localapi/v0/set-expiry-sooner?"+v.Encode(), 200, nil)
return err
}
// StreamDebugCapture streams a pcap-formatted packet capture.
//
// The provided context does not determine the lifetime of the
// returned io.ReadCloser.
func (lc *LocalClient) StreamDebugCapture(ctx context.Context) (io.ReadCloser, error) {
req, err := http.NewRequestWithContext(ctx, "POST", "http://"+apitype.LocalAPIHost+"/localapi/v0/debug-capture", nil)
if err != nil {
return nil, err
}
res, err := lc.doLocalRequestNiceError(req)
if err != nil {
res.Body.Close()
return nil, err
}
if res.StatusCode != 200 {
res.Body.Close()
return nil, errors.New(res.Status)
}
return res.Body, nil
}
// WatchIPNBus subscribes to the IPN notification bus. It returns a watcher
// once the bus is connected successfully.
//

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build go1.19

View File

@@ -1,11 +1,10 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !go1.19
//go:build !go1.20
package tailscale
func init() {
you_need_Go_1_19_to_compile_Tailscale()
you_need_Go_1_20_to_compile_Tailscale()
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build go1.19

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build go1.19
@@ -11,6 +10,8 @@ import (
"fmt"
"net/http"
"net/url"
"tailscale.com/util/httpm"
)
// TailnetDeleteRequest handles sending a DELETE request for a tailnet to control.
@@ -22,7 +23,7 @@ func (c *Client) TailnetDeleteRequest(ctx context.Context, tailnetID string) (er
}()
path := fmt.Sprintf("%s/api/v2/tailnet/%s", c.baseURL(), url.PathEscape(string(tailnetID)))
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, path, nil)
req, err := http.NewRequestWithContext(ctx, httpm.DELETE, path, nil)
if err != nil {
return err
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build go1.19

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Program addlicense adds a license header to a file.
// It is intended for use with 'go generate',
@@ -15,26 +14,24 @@ import (
)
var (
year = flag.Int("year", 0, "copyright year")
file = flag.String("file", "", "file to modify")
)
func usage() {
fmt.Fprintf(os.Stderr, `
usage: addlicense -year YEAR -file FILE <subcommand args...>
usage: addlicense -file FILE <subcommand args...>
`[1:])
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, `
addlicense adds a Tailscale license to the beginning of file,
using year as the copyright year.
addlicense adds a Tailscale license to the beginning of file.
It is intended for use with 'go generate', so it also runs a subcommand,
which presumably creates the file.
Sample usage:
addlicense -year 2021 -file pull_strings.go stringer -type=pull
addlicense -file pull_strings.go stringer -type=pull
`[1:])
os.Exit(2)
}
@@ -54,7 +51,7 @@ func main() {
check(err)
f, err := os.OpenFile(*file, os.O_TRUNC|os.O_WRONLY, 0644)
check(err)
_, err = fmt.Fprintf(f, license, *year)
_, err = fmt.Fprint(f, license)
check(err)
_, err = f.Write(b)
check(err)
@@ -70,8 +67,7 @@ func check(err error) {
}
var license = `
// Copyright (c) %d Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
`[1:]

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Cloner is a tool to automate the creation of a Clone method.
//

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux
@@ -12,7 +11,8 @@
// As with most container things, configuration is passed through environment
// variables. All configuration is optional.
//
// - TS_AUTH_KEY: the authkey to use for login.
// - TS_AUTHKEY: the authkey to use for login.
// - TS_HOSTNAME: the hostname to request for the node.
// - TS_ROUTES: subnet routes to advertise.
// - TS_DEST_IP: proxy all incoming Tailscale traffic to the given
// destination.
@@ -42,7 +42,7 @@
// TS_KUBE_SECRET="" and TS_STATE_DIR=/path/to/storage/dir. The state dir should
// be persistent storage.
//
// Additionally, if TS_AUTH_KEY is not set and the TS_KUBE_SECRET contains an
// Additionally, if TS_AUTHKEY is not set and the TS_KUBE_SECRET contains an
// "authkey" field, that key is used as the tailscale authkey.
package main
@@ -73,7 +73,8 @@ func main() {
tailscale.I_Acknowledge_This_API_Is_Unstable = true
cfg := &settings{
AuthKey: defaultEnv("TS_AUTH_KEY", ""),
AuthKey: defaultEnvs([]string{"TS_AUTHKEY", "TS_AUTH_KEY"}, ""),
Hostname: defaultEnv("TS_HOSTNAME", ""),
Routes: defaultEnv("TS_ROUTES", ""),
ProxyTo: defaultEnv("TS_DEST_IP", ""),
DaemonExtraArgs: defaultEnv("TS_TAILSCALED_EXTRA_ARGS", ""),
@@ -355,7 +356,11 @@ func tailscaledArgs(cfg *settings) []string {
args := []string{"--socket=" + cfg.Socket}
switch {
case cfg.InKubernetes && cfg.KubeSecret != "":
args = append(args, "--state=kube:"+cfg.KubeSecret, "--statedir=/tmp")
args = append(args, "--state=kube:"+cfg.KubeSecret)
if cfg.StateDir == "" {
cfg.StateDir = "/tmp"
}
fallthrough
case cfg.StateDir != "":
args = append(args, "--statedir="+cfg.StateDir)
default:
@@ -394,6 +399,9 @@ func tailscaleUp(ctx context.Context, cfg *settings) error {
if cfg.Routes != "" {
args = append(args, "--advertise-routes="+cfg.Routes)
}
if cfg.Hostname != "" {
args = append(args, "--hostname="+cfg.Hostname)
}
if cfg.ExtraArgs != "" {
args = append(args, strings.Fields(cfg.ExtraArgs)...)
}
@@ -522,6 +530,7 @@ func installIPTablesRule(ctx context.Context, dstStr string, tsIPs []netip.Prefi
// settings is all the configuration for containerboot.
type settings struct {
AuthKey string
Hostname string
Routes string
ProxyTo string
DaemonExtraArgs string
@@ -548,6 +557,15 @@ func defaultEnv(name, defVal string) string {
return defVal
}
func defaultEnvs(names []string, defVal string) string {
for _, name := range names {
if v, ok := os.LookupEnv(name); ok {
return v
}
}
return defVal
}
// defaultBool returns the boolean value of the given envvar name, or
// defVal if unset or not a bool.
func defaultBool(name string, defVal bool) bool {

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux
@@ -146,6 +145,24 @@ func TestContainerBoot(t *testing.T) {
{
// Userspace mode, ephemeral storage, authkey provided on every run.
Name: "authkey",
Env: map[string]string{
"TS_AUTHKEY": "tskey-key",
},
Phases: []phase{
{
WantCmds: []string{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking",
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key",
},
},
{
Notify: runningNotify,
},
},
},
{
// Userspace mode, ephemeral storage, authkey provided on every run.
Name: "authkey-old-flag",
Env: map[string]string{
"TS_AUTH_KEY": "tskey-key",
},
@@ -164,7 +181,7 @@ func TestContainerBoot(t *testing.T) {
{
Name: "authkey_disk_state",
Env: map[string]string{
"TS_AUTH_KEY": "tskey-key",
"TS_AUTHKEY": "tskey-key",
"TS_STATE_DIR": filepath.Join(d, "tmp"),
},
Phases: []phase{
@@ -182,8 +199,8 @@ func TestContainerBoot(t *testing.T) {
{
Name: "routes",
Env: map[string]string{
"TS_AUTH_KEY": "tskey-key",
"TS_ROUTES": "1.2.3.0/24,10.20.30.0/24",
"TS_AUTHKEY": "tskey-key",
"TS_ROUTES": "1.2.3.0/24,10.20.30.0/24",
},
Phases: []phase{
{
@@ -204,7 +221,7 @@ func TestContainerBoot(t *testing.T) {
{
Name: "routes_kernel_ipv4",
Env: map[string]string{
"TS_AUTH_KEY": "tskey-key",
"TS_AUTHKEY": "tskey-key",
"TS_ROUTES": "1.2.3.0/24,10.20.30.0/24",
"TS_USERSPACE": "false",
},
@@ -227,7 +244,7 @@ func TestContainerBoot(t *testing.T) {
{
Name: "routes_kernel_ipv6",
Env: map[string]string{
"TS_AUTH_KEY": "tskey-key",
"TS_AUTHKEY": "tskey-key",
"TS_ROUTES": "::/64,1::/64",
"TS_USERSPACE": "false",
},
@@ -250,7 +267,7 @@ func TestContainerBoot(t *testing.T) {
{
Name: "routes_kernel_all_families",
Env: map[string]string{
"TS_AUTH_KEY": "tskey-key",
"TS_AUTHKEY": "tskey-key",
"TS_ROUTES": "::/64,1.2.3.0/24",
"TS_USERSPACE": "false",
},
@@ -273,7 +290,7 @@ func TestContainerBoot(t *testing.T) {
{
Name: "proxy",
Env: map[string]string{
"TS_AUTH_KEY": "tskey-key",
"TS_AUTHKEY": "tskey-key",
"TS_DEST_IP": "1.2.3.4",
"TS_USERSPACE": "false",
},
@@ -295,7 +312,7 @@ func TestContainerBoot(t *testing.T) {
{
Name: "authkey_once",
Env: map[string]string{
"TS_AUTH_KEY": "tskey-key",
"TS_AUTHKEY": "tskey-key",
"TS_AUTH_ONCE": "true",
},
Phases: []phase{
@@ -354,7 +371,7 @@ func TestContainerBoot(t *testing.T) {
// Explicitly set to an empty value, to override the default of "tailscale".
"TS_KUBE_SECRET": "",
"TS_STATE_DIR": filepath.Join(d, "tmp"),
"TS_AUTH_KEY": "tskey-key",
"TS_AUTHKEY": "tskey-key",
},
KubeSecret: map[string]string{},
Phases: []phase{
@@ -376,7 +393,7 @@ func TestContainerBoot(t *testing.T) {
Env: map[string]string{
"KUBERNETES_SERVICE_HOST": kube.Host,
"KUBERNETES_SERVICE_PORT_HTTPS": kube.Port,
"TS_AUTH_KEY": "tskey-key",
"TS_AUTHKEY": "tskey-key",
},
KubeSecret: map[string]string{},
KubeDenyPatch: true,
@@ -532,6 +549,22 @@ func TestContainerBoot(t *testing.T) {
},
},
},
{
Name: "hostname",
Env: map[string]string{
"TS_HOSTNAME": "my-server",
},
Phases: []phase{
{
WantCmds: []string{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking",
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --hostname=my-server",
},
}, {
Notify: runningNotify,
},
},
},
}
for _, test := range tests {

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package main

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package main

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package main

View File

@@ -11,7 +11,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
github.com/fxamacker/cbor/v2 from tailscale.com/tka
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
github.com/hdevalence/ed25519consensus from tailscale.com/tka
LW github.com/josharian/native from github.com/mdlayher/netlink+
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/klauspost/compress/flate from nhooyr.io/websocket
@@ -34,6 +34,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/derp/derphttp from tailscale.com/cmd/derper
tailscale.com/disco from tailscale.com/derp
tailscale.com/envknob from tailscale.com/derp+
tailscale.com/health from tailscale.com/net/tlsdial
tailscale.com/hostinfo from tailscale.com/net/interfaces+
tailscale.com/ipn from tailscale.com/client/tailscale
tailscale.com/ipn/ipnstate from tailscale.com/client/tailscale+
@@ -64,12 +65,13 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/types/empty from tailscale.com/ipn
tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
tailscale.com/types/key from tailscale.com/cmd/derper+
tailscale.com/types/lazy from tailscale.com/version+
tailscale.com/types/logger from tailscale.com/cmd/derper+
tailscale.com/types/netmap from tailscale.com/ipn
tailscale.com/types/opt from tailscale.com/client/tailscale+
tailscale.com/types/persist from tailscale.com/ipn
tailscale.com/types/preftype from tailscale.com/ipn
tailscale.com/types/ptr from tailscale.com/hostinfo
tailscale.com/types/ptr from tailscale.com/hostinfo+
tailscale.com/types/structs from tailscale.com/ipn+
tailscale.com/types/tkatype from tailscale.com/types/key+
tailscale.com/types/views from tailscale.com/ipn/ipnstate+
@@ -78,10 +80,13 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
tailscale.com/util/dnsname from tailscale.com/hostinfo+
tailscale.com/util/httpm from tailscale.com/client/tailscale
tailscale.com/util/lineread from tailscale.com/hostinfo+
tailscale.com/util/mak from tailscale.com/syncs
tailscale.com/util/mak from tailscale.com/syncs+
tailscale.com/util/multierr from tailscale.com/health
tailscale.com/util/set from tailscale.com/health
tailscale.com/util/singleflight from tailscale.com/net/dnscache
tailscale.com/util/strs from tailscale.com/hostinfo+
tailscale.com/util/vizerror from tailscale.com/tsweb
W 💣 tailscale.com/util/winutil from tailscale.com/hostinfo+
tailscale.com/version from tailscale.com/derp+
tailscale.com/version/distro from tailscale.com/hostinfo+
@@ -95,7 +100,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
golang.org/x/crypto/chacha20poly1305 from crypto/tls
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
golang.org/x/crypto/curve25519 from crypto/tls+
golang.org/x/crypto/curve25519 from golang.org/x/crypto/nacl/box+
golang.org/x/crypto/hkdf from crypto/tls
golang.org/x/crypto/nacl/box from tailscale.com/types/key
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
@@ -133,6 +138,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
crypto/cipher from crypto/aes+
crypto/des from crypto/tls+
crypto/dsa from crypto/x509
crypto/ecdh from crypto/ecdsa+
crypto/ecdsa from crypto/tls+
crypto/ed25519 from crypto/tls+
crypto/elliptic from crypto/ecdsa+

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// The derper binary is a simple DERP server.
package main // import "tailscale.com/cmd/derper"

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package main

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package main
@@ -17,7 +16,6 @@ import (
"tailscale.com/derp/derphttp"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/util/strs"
)
func startMesh(s *derp.Server) error {
@@ -51,7 +49,7 @@ func startMeshWithHost(s *derp.Server, host string) error {
}
var d net.Dialer
var r net.Resolver
if base, ok := strs.CutSuffix(host, ".tailscale.com"); ok && port == "443" {
if base, ok := strings.CutSuffix(host, ".tailscale.com"); ok && port == "443" {
subCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
vpcHost := base + "-vpc.tailscale.com"

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package main

View File

@@ -1,74 +1,46 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// The derpprobe binary probes derpers.
package main // import "tailscale.com/cmd/derper/derpprobe"
package main
import (
"bytes"
"context"
crand "crypto/rand"
"crypto/x509"
"encoding/json"
"errors"
"expvar"
"flag"
"fmt"
"html"
"io"
"log"
"net"
"net/http"
"os"
"sort"
"strings"
"sync"
"time"
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
"tailscale.com/net/stun"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/prober"
"tailscale.com/tsweb"
)
var (
derpMapURL = flag.String("derp-map", "https://login.tailscale.com/derpmap/default", "URL to DERP map (https:// or file://)")
listen = flag.String("listen", ":8030", "HTTP listen address")
probeOnce = flag.Bool("once", false, "probe once and print results, then exit; ignores the listen flag")
)
// certReissueAfter is the time after which we expect all certs to be
// reissued, at minimum.
//
// This is currently set to the date of the LetsEncrypt ALPN revocation event of Jan 2022:
// https://community.letsencrypt.org/t/questions-about-renewing-before-tls-alpn-01-revocations/170449
//
// If there's another revocation event, bump this again.
var certReissueAfter = time.Unix(1643226768, 0)
var (
mu sync.Mutex
state = map[nodePair]pairStatus{}
lastDERPMap *tailcfg.DERPMap
lastDERPMapAt time.Time
certs = map[string]*x509.Certificate{}
interval = flag.Duration("interval", 15*time.Second, "probe interval")
)
func main() {
flag.Parse()
// proactively load the DERP map. Nothing terrible happens if this fails, so we ignore
// the error. The Slack bot will print a notification that the DERP map was empty.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, _ = getDERPMap(ctx)
p := prober.New().WithSpread(true).WithOnce(*probeOnce)
dp, err := prober.DERP(p, *derpMapURL, *interval, *interval, *interval)
if err != nil {
log.Fatal(err)
}
p.Run("derpmap-probe", *interval, nil, dp.ProbeMap)
if *probeOnce {
log.Printf("Starting probe (may take up to 1m)")
probe()
log.Printf("Probe results:")
st := getOverallStatus()
log.Printf("Waiting for all probes (may take up to 1m)")
p.Wait()
st := getOverallStatus(p)
for _, s := range st.good {
log.Printf("good: %s", s)
}
@@ -78,15 +50,11 @@ func main() {
return
}
go probeLoop()
go slackLoop()
log.Fatal(http.ListenAndServe(*listen, http.HandlerFunc(serve)))
}
func setCert(name string, cert *x509.Certificate) {
mu.Lock()
defer mu.Unlock()
certs[name] = cert
mux := http.NewServeMux()
tsweb.Debugger(mux)
expvar.Publish("derpprobe", p.Expvar())
mux.HandleFunc("/", http.HandlerFunc(serveFunc(p)))
log.Fatal(http.ListenAndServe(*listen, mux))
}
type overallStatus struct {
@@ -101,471 +69,43 @@ func (st *overallStatus) addGoodf(format string, a ...any) {
st.good = append(st.good, fmt.Sprintf(format, a...))
}
func getOverallStatus() (o overallStatus) {
mu.Lock()
defer mu.Unlock()
if lastDERPMap == nil {
o.addBadf("no DERP map")
return
}
now := time.Now()
if age := now.Sub(lastDERPMapAt); age > time.Minute {
o.addBadf("DERPMap hasn't been successfully refreshed in %v", age.Round(time.Second))
}
addPairMeta := func(pair nodePair) {
st, ok := state[pair]
age := now.Sub(st.at).Round(time.Second)
switch {
case !ok:
o.addBadf("no state for %v", pair)
case st.err != nil:
o.addBadf("%v: %v", pair, st.err)
case age > 90*time.Second:
o.addBadf("%v: update is %v old", pair, age)
default:
o.addGoodf("%v: %v, %v ago", pair, st.latency.Round(time.Millisecond), age)
}
}
for _, reg := range sortedRegions(lastDERPMap) {
for _, from := range reg.Nodes {
addPairMeta(nodePair{"UDP", from.Name})
for _, to := range reg.Nodes {
addPairMeta(nodePair{from.Name, to.Name})
}
}
}
var subjs []string
for k := range certs {
subjs = append(subjs, k)
}
sort.Strings(subjs)
soon := time.Now().Add(14 * 24 * time.Hour) // in 2 weeks; autocert does 30 days by default
for _, s := range subjs {
cert := certs[s]
if cert.NotBefore.Before(certReissueAfter) {
o.addBadf("cert %q needs reissuing; NotBefore=%v", s, cert.NotBefore.Format(time.RFC3339))
func getOverallStatus(p *prober.Prober) (o overallStatus) {
for p, i := range p.ProbeInfo() {
if i.End.IsZero() {
// Do not show probes that have not finished yet.
continue
}
if cert.NotAfter.Before(soon) {
o.addBadf("cert %q expiring soon (%v); wasn't auto-refreshed", s, cert.NotAfter.Format(time.RFC3339))
continue
if i.Result {
o.addGoodf("%s: %s", p, i.Latency)
} else {
o.addBadf("%s: %s", p, i.Error)
}
o.addGoodf("cert %q good %v - %v", s, cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339))
}
sort.Strings(o.bad)
sort.Strings(o.good)
return
}
func serve(w http.ResponseWriter, r *http.Request) {
st := getOverallStatus()
summary := "All good"
if (float64(len(st.bad)) / float64(len(st.bad)+len(st.good))) > 0.25 {
// This will generate an alert and page a human.
// It also ends up in Slack, but as part of the alert handling pipeline not
// because we generated a Slack notification from here.
w.WriteHeader(500)
summary = fmt.Sprintf("%d problems", len(st.bad))
}
io.WriteString(w, "<html><head><style>.bad { font-weight: bold; color: #700; }</style></head>\n")
fmt.Fprintf(w, "<body><h1>derp probe</h1>\n%s:<ul>", summary)
for _, s := range st.bad {
fmt.Fprintf(w, "<li class=bad>%s</li>\n", html.EscapeString(s))
}
for _, s := range st.good {
fmt.Fprintf(w, "<li>%s</li>\n", html.EscapeString(s))
}
io.WriteString(w, "</ul></body></html>\n")
}
func notifySlack(text string) error {
type SlackRequestBody struct {
Text string `json:"text"`
}
slackBody, err := json.Marshal(SlackRequestBody{Text: text})
if err != nil {
return err
}
webhookUrl := os.Getenv("SLACK_WEBHOOK")
if webhookUrl == "" {
return errors.New("No SLACK_WEBHOOK configured")
}
req, err := http.NewRequest("POST", webhookUrl, bytes.NewReader(slackBody))
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return errors.New(resp.Status)
}
body, _ := io.ReadAll(resp.Body)
if string(body) != "ok" {
return errors.New("Non-ok response returned from Slack")
}
return nil
}
// We only page a human if it looks like there is a significant outage across multiple regions.
// To Slack, we report all failures great and small.
func slackLoop() {
inBadState := false
for {
time.Sleep(time.Second * 30)
st := getOverallStatus()
if len(st.bad) > 0 && !inBadState {
err := notifySlack(strings.Join(st.bad, "\n"))
if err == nil {
inBadState = true
} else {
log.Printf("%d problems, notify Slack failed: %v", len(st.bad), err)
}
func serveFunc(p *prober.Prober) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
st := getOverallStatus(p)
summary := "All good"
if (float64(len(st.bad)) / float64(len(st.bad)+len(st.good))) > 0.25 {
// Returning a 500 allows monitoring this server externally and configuring
// an alert on HTTP response code.
w.WriteHeader(500)
summary = fmt.Sprintf("%d problems", len(st.bad))
}
if len(st.bad) == 0 && inBadState {
err := notifySlack("All DERPs recovered.")
if err == nil {
inBadState = false
}
io.WriteString(w, "<html><head><style>.bad { font-weight: bold; color: #700; }</style></head>\n")
fmt.Fprintf(w, "<body><h1>derp probe</h1>\n%s:<ul>", summary)
for _, s := range st.bad {
fmt.Fprintf(w, "<li class=bad>%s</li>\n", html.EscapeString(s))
}
for _, s := range st.good {
fmt.Fprintf(w, "<li>%s</li>\n", html.EscapeString(s))
}
io.WriteString(w, "</ul></body></html>\n")
}
}
func sortedRegions(dm *tailcfg.DERPMap) []*tailcfg.DERPRegion {
ret := make([]*tailcfg.DERPRegion, 0, len(dm.Regions))
for _, r := range dm.Regions {
ret = append(ret, r)
}
sort.Slice(ret, func(i, j int) bool { return ret[i].RegionID < ret[j].RegionID })
return ret
}
type nodePair struct {
from string // DERPNode.Name, or "UDP" for a STUN query to 'to'
to string // DERPNode.Name
}
func (p nodePair) String() string { return fmt.Sprintf("(%s→%s)", p.from, p.to) }
type pairStatus struct {
err error
latency time.Duration
at time.Time
}
func setDERPMap(dm *tailcfg.DERPMap) {
mu.Lock()
defer mu.Unlock()
lastDERPMap = dm
lastDERPMapAt = time.Now()
}
func setState(p nodePair, latency time.Duration, err error) {
mu.Lock()
defer mu.Unlock()
st := pairStatus{
err: err,
latency: latency,
at: time.Now(),
}
state[p] = st
if err != nil {
log.Printf("%+v error: %v", p, err)
} else {
log.Printf("%+v: %v", p, latency.Round(time.Millisecond))
}
}
func probeLoop() {
ticker := time.NewTicker(15 * time.Second)
for {
err := probe()
if err != nil {
log.Printf("probe: %v", err)
}
<-ticker.C
}
}
func probe() error {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
dm, err := getDERPMap(ctx)
if err != nil {
return err
}
var wg sync.WaitGroup
wg.Add(len(dm.Regions))
for _, reg := range dm.Regions {
reg := reg
go func() {
defer wg.Done()
for _, from := range reg.Nodes {
latency, err := probeUDP(ctx, dm, from)
setState(nodePair{"UDP", from.Name}, latency, err)
for _, to := range reg.Nodes {
latency, err := probeNodePair(ctx, dm, from, to)
setState(nodePair{from.Name, to.Name}, latency, err)
}
}
}()
}
wg.Wait()
return ctx.Err()
}
func probeUDP(ctx context.Context, dm *tailcfg.DERPMap, n *tailcfg.DERPNode) (latency time.Duration, err error) {
pc, err := net.ListenPacket("udp", ":0")
if err != nil {
return 0, err
}
defer pc.Close()
uc := pc.(*net.UDPConn)
tx := stun.NewTxID()
req := stun.Request(tx)
for _, ipStr := range []string{n.IPv4, n.IPv6} {
if ipStr == "" {
continue
}
port := n.STUNPort
if port == -1 {
continue
}
if port == 0 {
port = 3478
}
for {
ip := net.ParseIP(ipStr)
_, err := uc.WriteToUDP(req, &net.UDPAddr{IP: ip, Port: port})
if err != nil {
return 0, err
}
buf := make([]byte, 1500)
uc.SetReadDeadline(time.Now().Add(2 * time.Second))
t0 := time.Now()
n, _, err := uc.ReadFromUDP(buf)
d := time.Since(t0)
if err != nil {
if ctx.Err() != nil {
return 0, fmt.Errorf("timeout reading from %v: %v", ip, err)
}
if d < time.Second {
return 0, fmt.Errorf("error reading from %v: %v", ip, err)
}
time.Sleep(100 * time.Millisecond)
continue
}
txBack, _, err := stun.ParseResponse(buf[:n])
if err != nil {
return 0, fmt.Errorf("parsing STUN response from %v: %v", ip, err)
}
if txBack != tx {
return 0, fmt.Errorf("read wrong tx back from %v", ip)
}
if latency == 0 || d < latency {
latency = d
}
break
}
}
return latency, nil
}
func probeNodePair(ctx context.Context, dm *tailcfg.DERPMap, from, to *tailcfg.DERPNode) (latency time.Duration, err error) {
// The passed in context is a minute for the whole region. The
// idea is that each node pair in the region will be done
// serially and regularly in the future, reusing connections
// (at least in the happy path). For now they don't reuse
// connections and probe at most once every 15 seconds. We
// bound the duration of a single node pair within a region
// so one bad one can't starve others.
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
fromc, err := newConn(ctx, dm, from)
if err != nil {
return 0, err
}
defer fromc.Close()
toc, err := newConn(ctx, dm, to)
if err != nil {
return 0, err
}
defer toc.Close()
// Wait a bit for from's node to hear about to existing on the
// other node in the region, in the case where the two nodes
// are different.
if from.Name != to.Name {
time.Sleep(100 * time.Millisecond) // pretty arbitrary
}
// Make a random packet
pkt := make([]byte, 8)
crand.Read(pkt)
t0 := time.Now()
// Send the random packet.
sendc := make(chan error, 1)
go func() {
sendc <- fromc.Send(toc.SelfPublicKey(), pkt)
}()
select {
case <-ctx.Done():
return 0, fmt.Errorf("timeout sending via %q: %w", from.Name, ctx.Err())
case err := <-sendc:
if err != nil {
return 0, fmt.Errorf("error sending via %q: %w", from.Name, err)
}
}
// Receive the random packet.
recvc := make(chan any, 1) // either derp.ReceivedPacket or error
go func() {
for {
m, err := toc.Recv()
if err != nil {
recvc <- err
return
}
switch v := m.(type) {
case derp.ReceivedPacket:
recvc <- v
default:
log.Printf("%v: ignoring Recv frame type %T", to.Name, v)
// Loop.
}
}
}()
select {
case <-ctx.Done():
return 0, fmt.Errorf("timeout receiving from %q: %w", to.Name, ctx.Err())
case v := <-recvc:
if err, ok := v.(error); ok {
return 0, fmt.Errorf("error receiving from %q: %w", to.Name, err)
}
p := v.(derp.ReceivedPacket)
if p.Source != fromc.SelfPublicKey() {
return 0, fmt.Errorf("got data packet from unexpected source, %v", p.Source)
}
if !bytes.Equal(p.Data, pkt) {
return 0, fmt.Errorf("unexpected data packet %q", p.Data)
}
}
return time.Since(t0), nil
}
func newConn(ctx context.Context, dm *tailcfg.DERPMap, n *tailcfg.DERPNode) (*derphttp.Client, error) {
priv := key.NewNode()
dc := derphttp.NewRegionClient(priv, log.Printf, func() *tailcfg.DERPRegion {
rid := n.RegionID
return &tailcfg.DERPRegion{
RegionID: rid,
RegionCode: fmt.Sprintf("%s-%s", dm.Regions[rid].RegionCode, n.Name),
RegionName: dm.Regions[rid].RegionName,
Nodes: []*tailcfg.DERPNode{n},
}
})
dc.IsProber = true
err := dc.Connect(ctx)
if err != nil {
return nil, err
}
cs, ok := dc.TLSConnectionState()
if !ok {
dc.Close()
return nil, errors.New("no TLS state")
}
if len(cs.PeerCertificates) == 0 {
dc.Close()
return nil, errors.New("no peer certificates")
}
if cs.ServerName != n.HostName {
dc.Close()
return nil, fmt.Errorf("TLS server name %q != derp hostname %q", cs.ServerName, n.HostName)
}
setCert(cs.ServerName, cs.PeerCertificates[0])
errc := make(chan error, 1)
go func() {
m, err := dc.Recv()
if err != nil {
errc <- err
return
}
switch m.(type) {
case derp.ServerInfoMessage:
errc <- nil
default:
errc <- fmt.Errorf("unexpected first message type %T", errc)
}
}()
select {
case err := <-errc:
if err != nil {
go dc.Close()
return nil, err
}
case <-ctx.Done():
go dc.Close()
return nil, fmt.Errorf("timeout waiting for ServerInfoMessage: %w", ctx.Err())
}
return dc, nil
}
var httpOrFileClient = &http.Client{Transport: httpOrFileTransport()}
func httpOrFileTransport() http.RoundTripper {
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
return tr
}
func getDERPMap(ctx context.Context) (*tailcfg.DERPMap, error) {
req, err := http.NewRequestWithContext(ctx, "GET", *derpMapURL, nil)
if err != nil {
return nil, err
}
res, err := httpOrFileClient.Do(req)
if err != nil {
mu.Lock()
defer mu.Unlock()
if lastDERPMap != nil && time.Since(lastDERPMapAt) < 10*time.Minute {
// Assume that control is restarting and use
// the same one for a bit.
return lastDERPMap, nil
}
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
return nil, fmt.Errorf("fetching %s: %s", *derpMapURL, res.Status)
}
dm := new(tailcfg.DERPMap)
if err := json.NewDecoder(res.Body).Decode(dm); err != nil {
return nil, fmt.Errorf("decoding %s JSON: %v", *derpMapURL, err)
}
setDERPMap(dm)
return dm, nil
}

1
cmd/get-authkey/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
get-authkey

76
cmd/get-authkey/main.go Normal file
View File

@@ -0,0 +1,76 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// get-authkey allocates an authkey using an OAuth API client
// https://tailscale.com/kb/1215/oauth-clients/ and prints it
// to stdout for scripts to capture and use.
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"strings"
"golang.org/x/oauth2/clientcredentials"
"tailscale.com/client/tailscale"
)
func main() {
// Required to use our client API. We're fine with the instability since the
// client lives in the same repo as this code.
tailscale.I_Acknowledge_This_API_Is_Unstable = true
reusable := flag.Bool("reusable", false, "allocate a reusable authkey")
ephemeral := flag.Bool("ephemeral", false, "allocate an ephemeral authkey")
preauth := flag.Bool("preauth", true, "set the authkey as pre-authorized")
tags := flag.String("tags", "", "comma-separated list of tags to apply to the authkey")
flag.Parse()
clientId := os.Getenv("TS_API_CLIENT_ID")
clientSecret := os.Getenv("TS_API_CLIENT_SECRET")
if clientId == "" || clientSecret == "" {
log.Fatal("TS_API_CLIENT_ID and TS_API_CLIENT_SECRET must be set")
}
if *tags == "" {
log.Fatal("at least one tag must be specified")
}
baseUrl := os.Getenv("TS_BASE_URL")
if baseUrl == "" {
baseUrl = "https://api.tailscale.com"
}
credentials := clientcredentials.Config{
ClientID: clientId,
ClientSecret: clientSecret,
TokenURL: baseUrl + "/api/v2/oauth/token",
Scopes: []string{"device"},
}
ctx := context.Background()
tsClient := tailscale.NewClient("-", nil)
tsClient.HTTPClient = credentials.Client(ctx)
tsClient.BaseURL = baseUrl
caps := tailscale.KeyCapabilities{
Devices: tailscale.KeyDeviceCapabilities{
Create: tailscale.KeyDeviceCreateCapabilities{
Reusable: *reusable,
Ephemeral: *ephemeral,
Preauthorized: *preauth,
Tags: strings.Split(*tags, ","),
},
},
}
authkey, _, err := tsClient.CreateKey(ctx, caps)
if err != nil {
log.Fatal(err.Error())
}
fmt.Println(authkey)
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package main

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Command gitops-pusher allows users to use a GitOps flow for managing Tailscale ACLs.
//
@@ -23,6 +22,7 @@ import (
"github.com/peterbourgon/ff/v3/ffcli"
"github.com/tailscale/hujson"
"tailscale.com/util/httpm"
)
var (
@@ -235,7 +235,7 @@ func applyNewACL(ctx context.Context, tailnet, apiKey, policyFname, oldEtag stri
}
defer fin.Close()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("https://%s/api/v2/tailnet/%s/acl", *apiServer, tailnet), fin)
req, err := http.NewRequestWithContext(ctx, httpm.POST, fmt.Sprintf("https://%s/api/v2/tailnet/%s/acl", *apiServer, tailnet), fin)
if err != nil {
return err
}
@@ -275,7 +275,7 @@ func testNewACLs(ctx context.Context, tailnet, apiKey, policyFname string) error
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("https://%s/api/v2/tailnet/%s/acl/validate", *apiServer, tailnet), bytes.NewBuffer(data))
req, err := http.NewRequestWithContext(ctx, httpm.POST, fmt.Sprintf("https://%s/api/v2/tailnet/%s/acl/validate", *apiServer, tailnet), bytes.NewBuffer(data))
if err != nil {
return err
}
@@ -347,7 +347,7 @@ type ACLTestErrorDetail struct {
}
func getACLETag(ctx context.Context, tailnet, apiKey string) (string, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://%s/api/v2/tailnet/%s/acl", *apiServer, tailnet), nil)
req, err := http.NewRequestWithContext(ctx, httpm.GET, fmt.Sprintf("https://%s/api/v2/tailnet/%s/acl", *apiServer, tailnet), nil)
if err != nil {
return "", err
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// The hello binary runs hello.ts.net.
package main // import "tailscale.com/cmd/hello"

View File

@@ -0,0 +1,24 @@
# Copyright (c) Tailscale Inc & AUTHORS
# SPDX-License-Identifier: BSD-3-Clause
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: tailscale-auth-proxy
rules:
- apiGroups: [""]
resources: ["users"]
verbs: ["impersonate"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tailscale-auth-proxy
subjects:
- kind: ServiceAccount
name: operator
namespace: tailscale
roleRef:
kind: ClusterRole
name: tailscale-auth-proxy
apiGroup: rbac.authorization.k8s.io

View File

@@ -1,6 +1,5 @@
# Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Copyright (c) Tailscale Inc & AUTHORS
# SPDX-License-Identifier: BSD-3-Clause
apiVersion: v1
kind: Namespace
@@ -125,7 +124,7 @@ spec:
secretName: operator-oauth
containers:
- name: operator
image: tailscale/k8s-operator:latest
image: tailscale/k8s-operator:unstable
resources:
requests:
cpu: 500m
@@ -146,9 +145,11 @@ spec:
- name: CLIENT_SECRET_FILE
value: /oauth/client_secret
- name: PROXY_IMAGE
value: tailscale/tailscale:latest
value: tailscale/tailscale:unstable
- name: PROXY_TAGS
value: tag:k8s
- name: AUTH_PROXY
value: "false"
volumeMounts:
- name: oauth
mountPath: /oauth

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// tailscale-operator provides a way to expose services running in a Kubernetes
// cluster to your Tailnet.
@@ -26,6 +25,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -43,6 +43,7 @@ import (
"tailscale.com/ipn/store/kubestore"
"tailscale.com/tsnet"
"tailscale.com/types/logger"
"tailscale.com/util/dnsname"
)
func main() {
@@ -51,15 +52,16 @@ func main() {
tailscale.I_Acknowledge_This_API_Is_Unstable = true
var (
hostname = defaultEnv("OPERATOR_HOSTNAME", "tailscale-operator")
kubeSecret = defaultEnv("OPERATOR_SECRET", "")
operatorTags = defaultEnv("OPERATOR_INITIAL_TAGS", "tag:k8s-operator")
tsNamespace = defaultEnv("OPERATOR_NAMESPACE", "")
tslogging = defaultEnv("OPERATOR_LOGGING", "info")
clientIDPath = defaultEnv("CLIENT_ID_FILE", "")
clientSecretPath = defaultEnv("CLIENT_SECRET_FILE", "")
image = defaultEnv("PROXY_IMAGE", "tailscale/tailscale:latest")
tags = defaultEnv("PROXY_TAGS", "tag:k8s")
hostname = defaultEnv("OPERATOR_HOSTNAME", "tailscale-operator")
kubeSecret = defaultEnv("OPERATOR_SECRET", "")
operatorTags = defaultEnv("OPERATOR_INITIAL_TAGS", "tag:k8s-operator")
tsNamespace = defaultEnv("OPERATOR_NAMESPACE", "")
tslogging = defaultEnv("OPERATOR_LOGGING", "info")
clientIDPath = defaultEnv("CLIENT_ID_FILE", "")
clientSecretPath = defaultEnv("CLIENT_SECRET_FILE", "")
image = defaultEnv("PROXY_IMAGE", "tailscale/tailscale:latest")
tags = defaultEnv("PROXY_TAGS", "tag:k8s")
shouldRunAuthProxy = defaultEnv("AUTH_PROXY", "false")
)
var opts []kzap.Opts
@@ -164,14 +166,6 @@ waitOnline:
time.Sleep(time.Second)
}
sr := &ServiceReconciler{
tsClient: tsClient,
defaultTags: strings.Split(tags, ","),
operatorNamespace: tsNamespace,
proxyImage: image,
logger: zlog.Named("service-reconciler"),
}
// For secrets and statefulsets, we only get permission to touch the objects
// in the controller's own namespace. This cannot be expressed by
// .Watches(...) below, instead you have to add a per-type field selector to
@@ -181,7 +175,8 @@ waitOnline:
nsFilter := cache.ObjectSelector{
Field: fields.SelectorFromSet(fields.Set{"metadata.namespace": tsNamespace}),
}
mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{
restConfig := config.GetConfigOrDie()
mgr, err := manager.New(restConfig, manager.Options{
NewCache: cache.BuilderWithOptions(cache.Options{
SelectorsByObject: map[client.Object]cache.ObjectSelector{
&corev1.Secret{}: nsFilter,
@@ -193,6 +188,15 @@ waitOnline:
startlog.Fatalf("could not create manager: %v", err)
}
sr := &ServiceReconciler{
Client: mgr.GetClient(),
tsClient: tsClient,
defaultTags: strings.Split(tags, ","),
operatorNamespace: tsNamespace,
proxyImage: image,
logger: zlog.Named("service-reconciler"),
}
reconcileFilter := handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request {
ls := o.GetLabels()
if ls[LabelManaged] != "true" {
@@ -221,6 +225,17 @@ waitOnline:
}
startlog.Infof("Startup complete, operator running")
if shouldRunAuthProxy == "true" {
rc, err := rest.TransportFor(restConfig)
if err != nil {
startlog.Fatalf("could not get rest transport: %v", err)
}
authProxyListener, err := s.Listen("tcp", ":443")
if err != nil {
startlog.Fatalf("could not listen on :443: %v", err)
}
go runAuthProxy(lc, authProxyListener, rc, zlog.Named("auth-proxy").Infof)
}
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
startlog.Fatalf("could not start manager: %v", err)
}
@@ -234,8 +249,9 @@ const (
FinalizerName = "tailscale.com/finalizer"
AnnotationExpose = "tailscale.com/expose"
AnnotationTags = "tailscale.com/tags"
AnnotationExpose = "tailscale.com/expose"
AnnotationTags = "tailscale.com/tags"
AnnotationHostname = "tailscale.com/hostname"
)
// ServiceReconciler is a simple ControllerManagedBy example implementation.
@@ -369,6 +385,11 @@ func (a *ServiceReconciler) maybeCleanup(ctx context.Context, logger *zap.Sugare
// This function adds a finalizer to svc, ensuring that we can handle orderly
// deprovisioning later.
func (a *ServiceReconciler) maybeProvision(ctx context.Context, logger *zap.SugaredLogger, svc *corev1.Service) error {
hostname, err := nameForService(svc)
if err != nil {
return err
}
if !slices.Contains(svc.Finalizers, FinalizerName) {
// This log line is printed exactly once during initial provisioning,
// because once the finalizer is in place this block gets skipped. So,
@@ -395,7 +416,7 @@ func (a *ServiceReconciler) maybeProvision(ctx context.Context, logger *zap.Suga
if err != nil {
return fmt.Errorf("failed to create or get API key secret: %w", err)
}
_, err = a.reconcileSTS(ctx, logger, svc, hsvc, secretName)
_, err = a.reconcileSTS(ctx, logger, svc, hsvc, secretName, hostname)
if err != nil {
return fmt.Errorf("failed to reconcile statefulset: %w", err)
}
@@ -557,7 +578,7 @@ func (a *ServiceReconciler) newAuthKey(ctx context.Context, tags []string) (stri
//go:embed manifests/proxy.yaml
var proxyYaml []byte
func (a *ServiceReconciler) reconcileSTS(ctx context.Context, logger *zap.SugaredLogger, parentSvc, headlessSvc *corev1.Service, authKeySecret string) (*appsv1.StatefulSet, error) {
func (a *ServiceReconciler) reconcileSTS(ctx context.Context, logger *zap.SugaredLogger, parentSvc, headlessSvc *corev1.Service, authKeySecret, hostname string) (*appsv1.StatefulSet, error) {
var ss appsv1.StatefulSet
if err := yaml.Unmarshal(proxyYaml, &ss); err != nil {
return nil, fmt.Errorf("failed to unmarshal proxy spec: %w", err)
@@ -572,6 +593,10 @@ func (a *ServiceReconciler) reconcileSTS(ctx context.Context, logger *zap.Sugare
corev1.EnvVar{
Name: "TS_KUBE_SECRET",
Value: authKeySecret,
},
corev1.EnvVar{
Name: "TS_HOSTNAME",
Value: hostname,
})
ss.ObjectMeta = metav1.ObjectMeta{
Name: headlessSvc.Name,
@@ -591,11 +616,6 @@ func (a *ServiceReconciler) reconcileSTS(ctx context.Context, logger *zap.Sugare
return createOrUpdate(ctx, a.Client, a.operatorNamespace, &ss, func(s *appsv1.StatefulSet) { s.Spec = ss.Spec })
}
func (a *ServiceReconciler) InjectClient(c client.Client) error {
a.Client = c
return nil
}
// ptrObject is a type constraint for pointer types that implement
// client.Object.
type ptrObject[T any] interface {
@@ -683,3 +703,13 @@ func defaultEnv(envName, defVal string) string {
}
return v
}
func nameForService(svc *corev1.Service) (string, error) {
if h, ok := svc.Annotations[AnnotationHostname]; ok {
if err := dnsname.ValidLabel(h); err != nil {
return "", fmt.Errorf("invalid Tailscale hostname %q: %w", h, err)
}
return h, nil
}
return svc.Namespace + "-" + svc.Name, nil
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package main
@@ -66,7 +65,7 @@ func TestLoadBalancerClass(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
// Normally the Tailscale proxy pod would come up here and write its info
// into the secret. Simulate that, then verify reconcile again and verify
@@ -187,7 +186,7 @@ func TestAnnotations(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
want := &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
@@ -284,7 +283,7 @@ func TestAnnotationIntoLB(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
// Normally the Tailscale proxy pod would come up here and write its info
// into the secret. Simulate that, since it would have normally happened at
@@ -328,7 +327,7 @@ func TestAnnotationIntoLB(t *testing.T) {
expectReconciled(t, sr, "default", "test")
// None of the proxy machinery should have changed...
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
// ... but the service should have a LoadBalancer status.
want = &corev1.Service{
@@ -400,7 +399,7 @@ func TestLBIntoAnnotation(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
// Normally the Tailscale proxy pod would come up here and write its info
// into the secret. Simulate that, then verify reconcile again and verify
@@ -457,7 +456,7 @@ func TestLBIntoAnnotation(t *testing.T) {
expectReconciled(t, sr, "default", "test")
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
want = &corev1.Service{
TypeMeta: metav1.TypeMeta{
@@ -481,6 +480,108 @@ func TestLBIntoAnnotation(t *testing.T) {
expectEqual(t, fc, want)
}
func TestCustomHostname(t *testing.T) {
fc := fake.NewFakeClient()
ft := &fakeTSClient{}
zl, err := zap.NewDevelopment()
if err != nil {
t.Fatal(err)
}
sr := &ServiceReconciler{
Client: fc,
tsClient: ft,
defaultTags: []string{"tag:k8s"},
operatorNamespace: "operator-ns",
proxyImage: "tailscale/tailscale",
logger: zl.Sugar(),
}
// Create a service that we should manage, and check that the initial round
// of objects looks right.
mustCreate(t, fc, &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
// The apiserver is supposed to set the UID, but the fake client
// doesn't. So, set it explicitly because other code later depends
// on it being set.
UID: types.UID("1234-UID"),
Annotations: map[string]string{
"tailscale.com/expose": "true",
"tailscale.com/hostname": "reindeer-flotilla",
},
},
Spec: corev1.ServiceSpec{
ClusterIP: "10.20.30.40",
Type: corev1.ServiceTypeClusterIP,
},
})
expectReconciled(t, sr, "default", "test")
fullName, shortName := findGenName(t, fc, "default", "test")
expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "reindeer-flotilla"))
want := &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
Finalizers: []string{"tailscale.com/finalizer"},
UID: types.UID("1234-UID"),
Annotations: map[string]string{
"tailscale.com/expose": "true",
"tailscale.com/hostname": "reindeer-flotilla",
},
},
Spec: corev1.ServiceSpec{
ClusterIP: "10.20.30.40",
Type: corev1.ServiceTypeClusterIP,
},
}
expectEqual(t, fc, want)
// Turn the service back into a ClusterIP service, which should make the
// operator clean up.
mustUpdate(t, fc, "default", "test", func(s *corev1.Service) {
delete(s.ObjectMeta.Annotations, "tailscale.com/expose")
})
// synchronous StatefulSet deletion triggers a requeue. But, the StatefulSet
// didn't create any child resources since this is all faked, so the
// deletion goes through immediately.
expectReconciled(t, sr, "default", "test")
expectMissing[appsv1.StatefulSet](t, fc, "operator-ns", shortName)
// Second time around, the rest of cleanup happens.
expectReconciled(t, sr, "default", "test")
expectMissing[appsv1.StatefulSet](t, fc, "operator-ns", shortName)
expectMissing[corev1.Service](t, fc, "operator-ns", shortName)
expectMissing[corev1.Secret](t, fc, "operator-ns", fullName)
want = &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
UID: types.UID("1234-UID"),
Annotations: map[string]string{
"tailscale.com/hostname": "reindeer-flotilla",
},
},
Spec: corev1.ServiceSpec{
ClusterIP: "10.20.30.40",
Type: corev1.ServiceTypeClusterIP,
},
}
expectEqual(t, fc, want)
}
func expectedSecret(name string) *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{
@@ -529,7 +630,7 @@ func expectedHeadlessService(name string) *corev1.Service {
}
}
func expectedSTS(stsName, secretName string) *appsv1.StatefulSet {
func expectedSTS(stsName, secretName, hostname string) *appsv1.StatefulSet {
return &appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{
Kind: "StatefulSet",
@@ -578,6 +679,7 @@ func expectedSTS(stsName, secretName string) *appsv1.StatefulSet {
{Name: "TS_AUTH_ONCE", Value: "true"},
{Name: "TS_DEST_IP", Value: "10.20.30.40"},
{Name: "TS_KUBE_SECRET", Value: secretName},
{Name: "TS_HOSTNAME", Value: hostname},
},
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{

80
cmd/k8s-operator/proxy.go Normal file
View File

@@ -0,0 +1,80 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"context"
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
"tailscale.com/client/tailscale"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/types/logger"
)
type whoIsKey struct{}
// authProxy is an http.Handler that authenticates requests using the Tailscale
// LocalAPI and then proxies them to the Kubernetes API.
type authProxy struct {
logf logger.Logf
lc *tailscale.LocalClient
rp *httputil.ReverseProxy
}
func (h *authProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
who, err := h.lc.WhoIs(r.Context(), r.RemoteAddr)
if err != nil {
h.logf("failed to authenticate caller: %v", err)
http.Error(w, "failed to authenticate caller", http.StatusInternalServerError)
return
}
r = r.WithContext(context.WithValue(r.Context(), whoIsKey{}, who))
h.rp.ServeHTTP(w, r)
}
func runAuthProxy(lc *tailscale.LocalClient, ls net.Listener, rt http.RoundTripper, logf logger.Logf) {
u, err := url.Parse(fmt.Sprintf("https://%s:%s", os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT_HTTPS")))
if err != nil {
log.Fatalf("runAuthProxy: failed to parse URL %v", err)
}
ap := &authProxy{
logf: logf,
lc: lc,
rp: &httputil.ReverseProxy{
Director: func(r *http.Request) {
// Replace the request with the user's identity.
who := r.Context().Value(whoIsKey{}).(*apitype.WhoIsResponse)
r.Header.Set("Impersonate-User", who.UserProfile.LoginName)
// Remove all authentication headers.
r.Header.Del("Authorization")
r.Header.Del("Impersonate-Group")
r.Header.Del("Impersonate-Uid")
for k := range r.Header {
if strings.HasPrefix(k, "Impersonate-Extra-") {
r.Header.Del(k)
}
}
// Replace the URL with the Kubernetes APIServer.
r.URL.Scheme = u.Scheme
r.URL.Host = u.Host
},
Transport: rt,
},
}
if err := http.Serve(tls.NewListener(ls, &tls.Config{
GetCertificate: lc.GetCertificate,
}), ap); err != nil {
log.Fatalf("runAuthProxy: failed to serve %v", err)
}
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// The mkmanifest command is a simple helper utility to create a '.syso' file
// that contains a Windows manifest file.

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// mkpkg builds the Tailscale rpm and deb packages.
package main
@@ -58,6 +57,7 @@ func main() {
postrm := flag.String("postrm", "", "debian postrm script path")
replaces := flag.String("replaces", "", "package which this package replaces, if any")
depends := flag.String("depends", "", "comma-separated list of packages this package depends on")
recommends := flag.String("recommends", "", "comma-separated list of packages this package recommends")
flag.Parse()
filesMap, err := parseFiles(*files)
@@ -93,6 +93,9 @@ func main() {
if len(*depends) != 0 {
info.Overridables.Depends = strings.Split(*depends, ",")
}
if len(*recommends) != 0 {
info.Overridables.Recommends = strings.Split(*recommends, ",")
}
if *replaces != "" {
info.Overridables.Replaces = []string{*replaces}
info.Overridables.Conflicts = []string{*replaces}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// nardump is like nix-store --dump, but in Go, writing a NAR
// file (tar-like, but focused on being reproducible) to stdout

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// netlogfmt parses a stream of JSON log messages from stdin and
// formats the network traffic logs produced by "tailscale.com/wgengine/netlog"

View File

@@ -129,7 +129,7 @@ the `Expected-Tailnet` header to your auth request:
```nginx
location /auth {
# ...
proxy_set_header Expected-Tailnet "tailscale.com";
proxy_set_header Expected-Tailnet "tailnet012345.ts.net";
}
```
@@ -146,6 +146,8 @@ generic "forbidden" error page:
</html>
```
You can get the tailnet name from [the admin panel](https://login.tailscale.com/admin/dns).
## Building
Install `cmd/mkpkg`:

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// The pgproxy server is a proxy for the Postgres wire protocol.
package main

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// The printdep command is a build system tool for printing out information
// about dependencies.
@@ -19,7 +18,6 @@ import (
var (
goToolchain = flag.Bool("go", false, "print the supported Go toolchain git hash (a github.com/tailscale/go commit)")
goToolchainURL = flag.Bool("go-url", false, "print the URL to the tarball of the Tailscale Go toolchain")
goToolchainSRI = flag.Bool("go-sri", false, "print the SRI hash of the Tailscale Go toolchain")
alpine = flag.Bool("alpine", false, "print the tag of alpine docker image")
)
@@ -33,23 +31,11 @@ func main() {
fmt.Println(strings.TrimSpace(ts.GoToolchainRev))
}
if *goToolchainURL {
var suffix string
switch runtime.GOARCH {
case "amd64":
// None
case "arm64":
suffix = "-" + runtime.GOARCH
default:
log.Fatalf("unsupported GOARCH %q", runtime.GOARCH)
}
switch runtime.GOOS {
case "linux", "darwin":
default:
log.Fatalf("unsupported GOOS %q", runtime.GOOS)
}
fmt.Printf("https://github.com/tailscale/go/releases/download/build-%s/%s%s.tar.gz\n", strings.TrimSpace(ts.GoToolchainRev), runtime.GOOS, suffix)
}
if *goToolchainSRI {
fmt.Println(strings.TrimSpace(ts.GoToolchainSRI))
fmt.Printf("https://github.com/tailscale/go/releases/download/build-%s/%s-%s.tar.gz\n", strings.TrimSpace(ts.GoToolchainRev), runtime.GOOS, runtime.GOARCH)
}
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// proxy-to-grafana is a reverse proxy which identifies users based on their
// originating Tailscale identity and maps them to corresponding Grafana

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Program speedtest provides the speedtest command. The reason to keep it separate from
// the normal tailscale cli is because it is not yet ready to go in the tailscale binary.

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// ssh-auth-none-demo is a demo SSH server that's meant to run on the
// public internet (at 188.166.70.128 port 2222) and

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Command stunc makes a STUN request to a STUN server and prints the result.
package main

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// The sync-containers command synchronizes container image tags from one
// registry to another.
@@ -24,6 +23,7 @@ import (
"strings"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/authn/github"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
@@ -47,8 +47,9 @@ func main() {
log.Fatalf("--dst is required")
}
keychain := authn.NewMultiKeychain(authn.DefaultKeychain, github.Keychain)
opts := []remote.Option{
remote.WithAuthFromKeychain(authn.DefaultKeychain),
remote.WithAuthFromKeychain(keychain),
remote.WithContext(context.Background()),
}
@@ -84,6 +85,15 @@ func main() {
log.Printf("%d tags to remove: %s\n", len(remove), strings.Join(remove, ", "))
log.Printf("Not removing any tags for safety.\n")
}
var wellKnown = [...]string{"latest", "stable"}
for _, tag := range wellKnown {
if needsUpdate(*src, *dst, tag) {
if err := copyTag(*src, *dst, tag, opts...); err != nil {
log.Printf("Updating tag %q: progress error: %v", tag, err)
}
}
}
}
func copyTag(srcStr, dstStr, tag string, opts ...remote.Option) error {
@@ -177,3 +187,26 @@ func diffTags(src, dst []string) (add, remove []string) {
sort.Strings(remove)
return add, remove
}
func needsUpdate(srcStr, dstStr, tag string) bool {
src, err := name.ParseReference(fmt.Sprintf("%s:%s", srcStr, tag))
if err != nil {
return false
}
dst, err := name.ParseReference(fmt.Sprintf("%s:%s", dstStr, tag))
if err != nil {
return false
}
srcDesc, err := remote.Get(src)
if err != nil {
return false
}
dstDesc, err := remote.Get(dst)
if err != nil {
return true
}
return srcDesc.Digest != dstDesc.Digest
}

View File

@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package cli
import (
"unsafe"
"golang.org/x/sys/windows"
)
func init() {
verifyAuthenticode = verifyAuthenticodeWindows
}
func verifyAuthenticodeWindows(path string) error {
path16, err := windows.UTF16PtrFromString(path)
if err != nil {
return err
}
data := &windows.WinTrustData{
Size: uint32(unsafe.Sizeof(windows.WinTrustData{})),
UIChoice: windows.WTD_UI_NONE,
RevocationChecks: windows.WTD_REVOKE_WHOLECHAIN, // Full revocation checking, as this is called with network connectivity.
UnionChoice: windows.WTD_CHOICE_FILE,
StateAction: windows.WTD_STATEACTION_VERIFY,
FileOrCatalogOrBlobOrSgnrOrCert: unsafe.Pointer(&windows.WinTrustFileInfo{
Size: uint32(unsafe.Sizeof(windows.WinTrustFileInfo{})),
FilePath: path16,
}),
}
err = windows.WinVerifyTrustEx(windows.InvalidHWND, &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, data)
data.StateAction = windows.WTD_STATEACTION_CLOSE
windows.WinVerifyTrustEx(windows.InvalidHWND, &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, data)
return err
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cli

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cli

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package cli contains the cmd/tailscale CLI code in a package that can be included
// in other wrapper binaries such as the Mac and Windows clients.
@@ -15,7 +14,6 @@ import (
"log"
"os"
"runtime"
"strconv"
"strings"
"sync"
"text/tabwriter"
@@ -48,52 +46,6 @@ func outln(a ...any) {
fmt.Fprintln(Stdout, a...)
}
// ActLikeCLI reports whether a GUI application should act like the
// CLI based on os.Args, GOOS, the context the process is running in
// (pty, parent PID), etc.
func ActLikeCLI() bool {
// This function is only used on macOS.
if runtime.GOOS != "darwin" {
return false
}
// Escape hatch to let people force running the macOS
// GUI Tailscale binary as the CLI.
if v, _ := strconv.ParseBool(os.Getenv("TAILSCALE_BE_CLI")); v {
return true
}
// If our parent is launchd, we're definitely not
// being run as a CLI.
if os.Getppid() == 1 {
return false
}
// Xcode adds the -NSDocumentRevisionsDebugMode flag on execution.
// If present, we are almost certainly being run as a GUI.
for _, arg := range os.Args {
if arg == "-NSDocumentRevisionsDebugMode" {
return false
}
}
// Looking at the environment of the GUI Tailscale app (ps eww
// $PID), empirically none of these environment variables are
// present. But all or some of these should be present with
// Terminal.all and bash or zsh.
for _, e := range []string{
"SHLVL",
"TERM",
"TERM_PROGRAM",
"PS1",
} {
if os.Getenv(e) != "" {
return true
}
}
return false
}
func newFlagSet(name string) *flag.FlagSet {
onError := flag.ExitOnError
if runtime.GOOS == "js" {
@@ -196,6 +148,10 @@ change in the future.
rootCmd.Subcommands = append(rootCmd.Subcommands, debugCmd)
case slices.Contains(args, "serve"):
rootCmd.Subcommands = append(rootCmd.Subcommands, serveCmd)
case slices.Contains(args, "update"):
rootCmd.Subcommands = append(rootCmd.Subcommands, updateCmd)
case slices.Contains(args, "configure"):
rootCmd.Subcommands = append(rootCmd.Subcommands, configureCmd)
}
if runtime.GOOS == "linux" && distro.Get() == distro.Synology {
rootCmd.Subcommands = append(rootCmd.Subcommands, configureHostCmd)

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cli

View File

@@ -0,0 +1,152 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_kube
package cli
import (
"context"
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/peterbourgon/ff/v3/ffcli"
"golang.org/x/exp/slices"
"k8s.io/client-go/util/homedir"
"sigs.k8s.io/yaml"
)
func init() {
configureCmd.Subcommands = append(configureCmd.Subcommands, configureKubeconfigCmd)
}
var configureKubeconfigCmd = &ffcli.Command{
Name: "kubeconfig",
ShortHelp: "Configure kubeconfig to use Tailscale",
ShortUsage: "kubeconfig <hostname-or-fqdn>",
LongHelp: strings.TrimSpace(`
Run this command to configure your kubeconfig to use Tailscale for authentication to a Kubernetes cluster.
The hostname argument should be set to the Tailscale hostname of the peer running as an auth proxy in the cluster.
`),
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("kubeconfig")
return fs
})(),
Exec: runConfigureKubeconfig,
}
func runConfigureKubeconfig(ctx context.Context, args []string) error {
if len(args) != 1 {
return errors.New("unknown arguments")
}
hostOrFQDN := args[0]
st, err := localClient.Status(ctx)
if err != nil {
return err
}
if st.BackendState != "Running" {
return errors.New("Tailscale is not running")
}
targetFQDN, ok := nodeDNSNameFromArg(st, hostOrFQDN)
if !ok {
return fmt.Errorf("no peer found with hostname %q", hostOrFQDN)
}
targetFQDN = strings.TrimSuffix(targetFQDN, ".")
confPath := filepath.Join(homedir.HomeDir(), ".kube", "config")
if err := setKubeconfigForPeer(targetFQDN, confPath); err != nil {
return err
}
printf("kubeconfig configured for %q\n", hostOrFQDN)
return nil
}
// appendOrSetNamed finds a map with a "name" key matching name in dst, and
// replaces it with val. If no such map is found, val is appended to dst.
func appendOrSetNamed(dst []any, name string, val map[string]any) []any {
if got := slices.IndexFunc(dst, func(m any) bool {
if m, ok := m.(map[string]any); ok {
return m["name"] == name
}
return false
}); got != -1 {
dst[got] = val
} else {
dst = append(dst, val)
}
return dst
}
var errInvalidKubeconfig = errors.New("invalid kubeconfig")
func updateKubeconfig(cfgYaml []byte, fqdn string) ([]byte, error) {
var cfg map[string]any
if len(cfgYaml) > 0 {
if err := yaml.Unmarshal(cfgYaml, &cfg); err != nil {
return nil, errInvalidKubeconfig
}
}
if cfg == nil {
cfg = map[string]any{
"apiVersion": "v1",
"kind": "Config",
}
} else if cfg["apiVersion"] != "v1" || cfg["kind"] != "Config" {
return nil, errInvalidKubeconfig
}
var clusters []any
if cm, ok := cfg["clusters"]; ok {
clusters = cm.([]any)
}
cfg["clusters"] = appendOrSetNamed(clusters, fqdn, map[string]any{
"name": fqdn,
"cluster": map[string]string{
"server": "https://" + fqdn,
},
})
var users []any
if um, ok := cfg["users"]; ok {
users = um.([]any)
}
cfg["users"] = appendOrSetNamed(users, "tailscale-auth", map[string]any{
// We just need one of these, and can reuse it for all clusters.
"name": "tailscale-auth",
"user": map[string]string{
// We do not use the token, but if we do not set anything here
// kubectl will prompt for a username and password.
"token": "unused",
},
})
var contexts []any
if cm, ok := cfg["contexts"]; ok {
contexts = cm.([]any)
}
cfg["contexts"] = appendOrSetNamed(contexts, fqdn, map[string]any{
"name": fqdn,
"context": map[string]string{
"cluster": fqdn,
"user": "tailscale-auth",
},
})
cfg["current-context"] = fqdn
return yaml.Marshal(cfg)
}
func setKubeconfigForPeer(fqdn, filePath string) error {
b, err := os.ReadFile(filePath)
if err != nil && !os.IsNotExist(err) {
return err
}
b, err = updateKubeconfig(b, fqdn)
if err != nil {
return err
}
return os.WriteFile(filePath, b, 0600)
}

View File

@@ -0,0 +1,196 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_kube
package cli
import (
"bytes"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestKubeconfig(t *testing.T) {
const fqdn = "foo.tail-scale.ts.net"
tests := []struct {
name string
in string
want string
wantErr error
}{
{
name: "invalid-yaml",
in: `apiVersion: v1
kind: ,asdf`,
wantErr: errInvalidKubeconfig,
},
{
name: "invalid-cfg",
in: `apiVersion: v1
kind: Pod`,
wantErr: errInvalidKubeconfig,
},
{
name: "empty",
in: "",
want: `apiVersion: v1
clusters:
- cluster:
server: https://foo.tail-scale.ts.net
name: foo.tail-scale.ts.net
contexts:
- context:
cluster: foo.tail-scale.ts.net
user: tailscale-auth
name: foo.tail-scale.ts.net
current-context: foo.tail-scale.ts.net
kind: Config
users:
- name: tailscale-auth
user:
token: unused`,
},
{
name: "already-configured",
in: `apiVersion: v1
clusters:
- cluster:
server: https://foo.tail-scale.ts.net
name: foo.tail-scale.ts.net
contexts:
- context:
cluster: foo.tail-scale.ts.net
user: tailscale-auth
name: foo.tail-scale.ts.net
kind: Config
current-context: foo.tail-scale.ts.net
users:
- name: tailscale-auth
user:
token: unused`,
want: `apiVersion: v1
clusters:
- cluster:
server: https://foo.tail-scale.ts.net
name: foo.tail-scale.ts.net
contexts:
- context:
cluster: foo.tail-scale.ts.net
user: tailscale-auth
name: foo.tail-scale.ts.net
current-context: foo.tail-scale.ts.net
kind: Config
users:
- name: tailscale-auth
user:
token: unused`,
},
{
name: "other-cluster",
in: `apiVersion: v1
clusters:
- cluster:
server: https://192.168.1.1:8443
name: some-cluster
contexts:
- context:
cluster: some-cluster
user: some-auth
name: some-cluster
kind: Config
current-context: some-cluster
users:
- name: some-auth
user:
token: asdfasdf`,
want: `apiVersion: v1
clusters:
- cluster:
server: https://192.168.1.1:8443
name: some-cluster
- cluster:
server: https://foo.tail-scale.ts.net
name: foo.tail-scale.ts.net
contexts:
- context:
cluster: some-cluster
user: some-auth
name: some-cluster
- context:
cluster: foo.tail-scale.ts.net
user: tailscale-auth
name: foo.tail-scale.ts.net
current-context: foo.tail-scale.ts.net
kind: Config
users:
- name: some-auth
user:
token: asdfasdf
- name: tailscale-auth
user:
token: unused`,
},
{
name: "already-using-tailscale",
in: `apiVersion: v1
clusters:
- cluster:
server: https://bar.tail-scale.ts.net
name: bar.tail-scale.ts.net
contexts:
- context:
cluster: bar.tail-scale.ts.net
user: tailscale-auth
name: bar.tail-scale.ts.net
kind: Config
current-context: bar.tail-scale.ts.net
users:
- name: tailscale-auth
user:
token: unused`,
want: `apiVersion: v1
clusters:
- cluster:
server: https://bar.tail-scale.ts.net
name: bar.tail-scale.ts.net
- cluster:
server: https://foo.tail-scale.ts.net
name: foo.tail-scale.ts.net
contexts:
- context:
cluster: bar.tail-scale.ts.net
user: tailscale-auth
name: bar.tail-scale.ts.net
- context:
cluster: foo.tail-scale.ts.net
user: tailscale-auth
name: foo.tail-scale.ts.net
current-context: foo.tail-scale.ts.net
kind: Config
users:
- name: tailscale-auth
user:
token: unused`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := updateKubeconfig([]byte(tt.in), fqdn)
if err != nil {
if err != tt.wantErr {
t.Fatalf("updateKubeconfig() error = %v, wantErr %v", err, tt.wantErr)
}
return
} else if tt.wantErr != nil {
t.Fatalf("updateKubeconfig() error = %v, wantErr %v", err, tt.wantErr)
}
got = bytes.TrimSpace(got)
want := []byte(strings.TrimSpace(tt.want))
if d := cmp.Diff(want, got); d != "" {
t.Errorf("Kubeconfig() mismatch (-want +got):\n%s", d)
}
})
}
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cli
@@ -19,9 +18,23 @@ import (
"tailscale.com/version/distro"
)
// configureHostCmd is the "tailscale configure-host" command which was once
// used to configure Synology devices, but is now a compatibility alias to
// "tailscale configure synology".
var configureHostCmd = &ffcli.Command{
Name: "configure-host",
Exec: runConfigureHost,
Exec: runConfigureSynology,
ShortHelp: synologyConfigureCmd.ShortHelp,
LongHelp: synologyConfigureCmd.LongHelp,
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("configure-host")
return fs
})(),
}
var synologyConfigureCmd = &ffcli.Command{
Name: "synology",
Exec: runConfigureSynology,
ShortHelp: "Configure Synology to enable more Tailscale features",
LongHelp: strings.TrimSpace(`
The 'configure-host' command is intended to run at boot as root
@@ -31,14 +44,12 @@ permission to use it.
See: https://tailscale.com/kb/1152/synology-outbound/
`),
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("configure-host")
fs := newFlagSet("synology")
return fs
})(),
}
var configureHostArgs struct{}
func runConfigureHost(ctx context.Context, args []string) error {
func runConfigureSynology(ctx context.Context, args []string) error {
if len(args) > 0 {
return errors.New("unknown arguments")
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cli
import (
"context"
"flag"
"runtime"
"strings"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/version/distro"
)
var configureCmd = &ffcli.Command{
Name: "configure",
ShortHelp: "Configure the host to enable more Tailscale features",
LongHelp: strings.TrimSpace(`
The 'configure' command is intended to provide a way to configure different
services on the host to enable more Tailscale features.
`),
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("configure")
return fs
})(),
Subcommands: configureSubcommands(),
Exec: func(ctx context.Context, args []string) error {
return flag.ErrHelp
},
}
func configureSubcommands() (out []*ffcli.Command) {
if runtime.GOOS == "linux" && distro.Get() == distro.Synology {
out = append(out, synologyConfigureCmd)
}
return out
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cli
@@ -21,6 +20,7 @@ import (
"net/netip"
"net/url"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
@@ -40,7 +40,7 @@ import (
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/util/must"
"tailscale.com/util/strs"
"tailscale.com/wgengine/capture"
)
var debugCmd = &ffcli.Command{
@@ -76,6 +76,17 @@ var debugCmd = &ffcli.Command{
Exec: runDaemonGoroutines,
ShortHelp: "print tailscaled's goroutines",
},
{
Name: "daemon-logs",
Exec: runDaemonLogs,
ShortHelp: "watch tailscaled's server logs",
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("daemon-logs")
fs.IntVar(&daemonLogsArgs.verbose, "verbose", 0, "verbosity level")
fs.BoolVar(&daemonLogsArgs.time, "time", false, "include client time")
return fs
})(),
},
{
Name: "metrics",
Exec: runDaemonMetrics,
@@ -134,6 +145,7 @@ var debugCmd = &ffcli.Command{
fs := newFlagSet("watch-ipn")
fs.BoolVar(&watchIPNArgs.netmap, "netmap", true, "include netmap in messages")
fs.BoolVar(&watchIPNArgs.initial, "initial", false, "include initial status")
fs.BoolVar(&watchIPNArgs.showPrivateKey, "show-private-key", false, "include node private key in printed netmap")
return fs
})(),
},
@@ -154,6 +166,16 @@ var debugCmd = &ffcli.Command{
return fs
})(),
},
{
Name: "set-expire",
Exec: runSetExpire,
ShortHelp: "manipulate node key expiry for testing",
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("set-expire")
fs.DurationVar(&setExpireArgs.in, "in", 0, "if non-zero, set node key to expire this duration from now")
return fs
})(),
},
{
Name: "dev-store-set",
Exec: runDevStoreSet,
@@ -169,6 +191,16 @@ var debugCmd = &ffcli.Command{
Exec: runDebugDERP,
ShortHelp: "test a DERP configuration",
},
{
Name: "capture",
Exec: runCapture,
ShortHelp: "streams pcaps for debugging",
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("capture")
fs.StringVar(&captureArgs.outFile, "o", "", "path to stream the pcap (or - for stdout), leave empty to start wireshark")
return fs
})(),
},
},
}
@@ -238,7 +270,7 @@ func runDebug(ctx context.Context, args []string) error {
e.Encode(wfs)
return nil
}
if name, ok := strs.CutPrefix(debugArgs.file, "delete:"); ok {
if name, ok := strings.CutPrefix(debugArgs.file, "delete:"); ok {
return localClient.DeleteWaitingFile(ctx, name)
}
rc, size, err := localClient.GetWaitingFile(ctx, debugArgs.file)
@@ -319,8 +351,9 @@ func runPrefs(ctx context.Context, args []string) error {
}
var watchIPNArgs struct {
netmap bool
initial bool
netmap bool
initial bool
showPrivateKey bool
}
func runWatchIPN(ctx context.Context, args []string) error {
@@ -328,6 +361,9 @@ func runWatchIPN(ctx context.Context, args []string) error {
if watchIPNArgs.initial {
mask = ipn.NotifyInitialState | ipn.NotifyInitialPrefs | ipn.NotifyInitialNetMap
}
if !watchIPNArgs.showPrivateKey {
mask |= ipn.NotifyNoPrivateKeys
}
watcher, err := localClient.WatchIPNBus(ctx, mask)
if err != nil {
return err
@@ -414,6 +450,39 @@ func runDaemonGoroutines(ctx context.Context, args []string) error {
return nil
}
var daemonLogsArgs struct {
verbose int
time bool
}
func runDaemonLogs(ctx context.Context, args []string) error {
logs, err := localClient.TailDaemonLogs(ctx)
if err != nil {
return err
}
d := json.NewDecoder(logs)
for {
var line struct {
Text string `json:"text"`
Verbose int `json:"v"`
Time string `json:"client_time"`
}
err := d.Decode(&line)
if err != nil {
return err
}
line.Text = strings.TrimSpace(line.Text)
if line.Text == "" || line.Verbose > daemonLogsArgs.verbose {
continue
}
if daemonLogsArgs.time {
fmt.Printf("%s %s\n", line.Time, line.Text)
} else {
fmt.Println(line.Text)
}
}
}
var metricsArgs struct {
watch bool
}
@@ -665,3 +734,58 @@ func runDebugDERP(ctx context.Context, args []string) error {
fmt.Printf("%s\n", must.Get(json.MarshalIndent(st, "", " ")))
return nil
}
var setExpireArgs struct {
in time.Duration
}
func runSetExpire(ctx context.Context, args []string) error {
if len(args) != 0 || setExpireArgs.in == 0 {
return errors.New("usage --in=<duration>")
}
return localClient.DebugSetExpireIn(ctx, setExpireArgs.in)
}
var captureArgs struct {
outFile string
}
func runCapture(ctx context.Context, args []string) error {
stream, err := localClient.StreamDebugCapture(ctx)
if err != nil {
return err
}
defer stream.Close()
switch captureArgs.outFile {
case "-":
fmt.Fprintln(os.Stderr, "Press Ctrl-C to stop the capture.")
_, err = io.Copy(os.Stdout, stream)
return err
case "":
lua, err := os.CreateTemp("", "ts-dissector")
if err != nil {
return err
}
defer os.Remove(lua.Name())
lua.Write([]byte(capture.DissectorLua))
if err := lua.Close(); err != nil {
return err
}
wireshark := exec.CommandContext(ctx, "wireshark", "-X", "lua_script:"+lua.Name(), "-k", "-i", "-")
wireshark.Stdin = stream
wireshark.Stdout = os.Stdout
wireshark.Stderr = os.Stderr
return wireshark.Run()
}
f, err := os.OpenFile(captureArgs.outFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer f.Close()
fmt.Fprintln(os.Stderr, "Press Ctrl-C to stop the capture.")
_, err = io.Copy(f, stream)
return err
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux || windows || darwin
@@ -8,11 +7,13 @@ package cli
import (
"fmt"
"os/exec"
"path/filepath"
"runtime"
"strings"
ps "github.com/mitchellh/go-ps"
"tailscale.com/version/distro"
)
// fixTailscaledConnectError is called when the local tailscaled has
@@ -47,9 +48,27 @@ func fixTailscaledConnectError(origErr error) error {
case "darwin":
return fmt.Errorf("failed to connect to local Tailscale service; is Tailscale running?")
case "linux":
return fmt.Errorf("failed to connect to local tailscaled; it doesn't appear to be running (sudo systemctl start tailscaled ?)")
var hint string
if isSystemdSystem() {
hint = " (sudo systemctl start tailscaled ?)"
}
return fmt.Errorf("failed to connect to local tailscaled; it doesn't appear to be running%s", hint)
}
return fmt.Errorf("failed to connect to local tailscaled process; it doesn't appear to be running")
}
return fmt.Errorf("failed to connect to local tailscaled (which appears to be running as %v, pid %v). Got error: %w", foundProc.Executable(), foundProc.Pid(), origErr)
}
// isSystemdSystem reports whether the current machine uses systemd
// and in particular whether the systemctl command is available.
func isSystemdSystem() bool {
if runtime.GOOS != "linux" {
return false
}
switch distro.Get() {
case distro.QNAP, distro.Gokrazy, distro.Synology:
return false
}
_, err := exec.LookPath("systemctl")
return err == nil
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !linux && !windows && !darwin

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cli

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cli
@@ -32,7 +31,6 @@ import (
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/util/quarantine"
"tailscale.com/util/strs"
"tailscale.com/version"
)
@@ -91,7 +89,7 @@ func runCp(ctx context.Context, args []string) error {
return errors.New("usage: tailscale file cp <files...> <target>:")
}
files, target := args[:len(args)-1], args[len(args)-1]
target, ok := strs.CutSuffix(target, ":")
target, ok := strings.CutSuffix(target, ":")
if !ok {
return fmt.Errorf("final argument to 'tailscale file cp' must end in colon")
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cli

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cli

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cli

Some files were not shown because too many files have changed in this diff Show More