Compare commits

...

22 Commits

Author SHA1 Message Date
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
65 changed files with 669 additions and 454 deletions

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.19. (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

@@ -80,7 +80,7 @@ func main() {
w("}")
}
cloneOutput := pkg.Name + "_clone.go"
if err := codegen.WritePackageFile("tailscale.com/cmd/cloner", pkg, cloneOutput, it, buf); err != nil {
if err := codegen.WritePackageFile("tailscale.com/cmd/cloner", pkg, cloneOutput, codegen.CopyrightYear("."), it, buf); err != nil {
log.Fatal(err)
}
}

View File

@@ -12,7 +12,7 @@
// 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_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,7 @@ 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"}, ""),
Routes: defaultEnv("TS_ROUTES", ""),
ProxyTo: defaultEnv("TS_DEST_IP", ""),
DaemonExtraArgs: defaultEnv("TS_TAILSCALED_EXTRA_ARGS", ""),
@@ -548,6 +548,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

@@ -146,6 +146,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 +182,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 +200,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 +222,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 +245,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 +268,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 +291,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 +313,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 +372,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 +394,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,

View File

@@ -134,6 +134,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
})(),
},
@@ -319,8 +320,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 +330,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

View File

@@ -5,6 +5,7 @@
package cli
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
@@ -99,6 +100,22 @@ func runNetworkLockInit(ctx context.Context, args []string) error {
return err
}
// Common mistake: Not specifying the current node's key as one of the trusted keys.
foundSelfKey := false
for _, k := range keys {
keyID, err := k.ID()
if err != nil {
return err
}
if bytes.Equal(keyID, st.PublicKey.KeyID()) {
foundSelfKey = true
break
}
}
if !foundSelfKey {
return errors.New("the tailnet lock key of the current node must be one of the trusted keys during initialization")
}
fmt.Println("You are initializing tailnet lock with the following trusted signing keys:")
for _, k := range keys {
fmt.Printf(" - tlpub:%x (%s key)\n", k.Public, k.Kind.String())
@@ -196,7 +213,7 @@ func runNetworkLockStatus(ctx context.Context, args []string) error {
line.WriteString(fmt.Sprint(k.Votes))
line.WriteString("\t")
if k.Key == st.PublicKey {
line.WriteString("(us)")
line.WriteString("(self)")
}
fmt.Println(line.String())
}
@@ -444,8 +461,13 @@ func nlDescribeUpdate(update ipnstate.NetworkLockUpdate, color bool) (string, er
var stanza strings.Builder
printKey := func(key *tka.Key, prefix string) {
fmt.Fprintf(&stanza, "%sType: %s\n", prefix, key.Kind.String())
fmt.Fprintf(&stanza, "%sKeyID: %x\n", prefix, key.ID())
fmt.Fprintf(&stanza, "%sVotes: %d\n", prefix, key.Votes)
if keyID, err := key.ID(); err == nil {
fmt.Fprintf(&stanza, "%sKeyID: %x\n", prefix, keyID)
} else {
// Older versions of the client shouldn't explode when they encounter an
// unknown key type.
fmt.Fprintf(&stanza, "%sKeyID: <Error: %v>\n", prefix, err)
}
if key.Meta != nil {
fmt.Fprintf(&stanza, "%sMetadata: %+v\n", prefix, key.Meta)
}

View File

@@ -223,8 +223,8 @@ func qnapAuthnQtoken(r *http.Request, user, token string) (string, *qnapAuthResp
"user": []string{user},
}
u := url.URL{
Scheme: r.URL.Scheme,
Host: r.URL.Host,
Scheme: "http",
Host: "127.0.0.1:8080",
Path: "/cgi-bin/authLogin.cgi",
RawQuery: query.Encode(),
}
@@ -237,8 +237,8 @@ func qnapAuthnSid(r *http.Request, user, sid string) (string, *qnapAuthResponse,
"sid": []string{sid},
}
u := url.URL{
Scheme: r.URL.Scheme,
Host: r.URL.Host,
Scheme: "http",
Host: "127.0.0.1:8080",
Path: "/cgi-bin/authLogin.cgi",
RawQuery: query.Encode(),
}

View File

@@ -247,7 +247,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/net/tsaddr from tailscale.com/ipn+
tailscale.com/net/tsdial from tailscale.com/control/controlclient+
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
tailscale.com/net/tstun from tailscale.com/net/dns+
tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
tailscale.com/net/wsconn from tailscale.com/control/controlhttp+
tailscale.com/paths from tailscale.com/ipn/ipnlocal+
💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux || darwin || freebsd
//go:build linux || darwin || freebsd || openbsd
package main

View File

@@ -526,6 +526,9 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logid string) (_ *ip
return nil, fmt.Errorf("ipnlocal.NewLocalBackend: %w", err)
}
lb.SetVarRoot(opts.VarRoot)
if logPol != nil {
lb.SetLogFlusher(logPol.Logtail.StartFlush)
}
if root := lb.TailscaleVarRoot(); root != "" {
dnsfallback.SetCachePath(filepath.Join(root, "derpmap.cached.json"))
}

View File

@@ -384,7 +384,7 @@ func main() {
genView(buf, it, typ, pkg.Types)
}
out := pkg.Name + "_view.go"
if err := codegen.WritePackageFile("tailscale/cmd/viewer", pkg, out, it, buf); err != nil {
if err := codegen.WritePackageFile("tailscale/cmd/viewer", pkg, out, codegen.CopyrightYear("."), it, buf); err != nil {
log.Fatal(err)
}
if runCloner {

View File

@@ -16,6 +16,7 @@ import (
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/types/opt"
"tailscale.com/types/views"
"tailscale.com/wgengine/filter"
)
@@ -40,6 +41,7 @@ type mapSession struct {
lastDNSConfig *tailcfg.DNSConfig
lastDERPMap *tailcfg.DERPMap
lastUserProfile map[tailcfg.UserID]tailcfg.UserProfile
lastPacketFilterRules views.Slice[tailcfg.FilterRule]
lastParsedPacketFilter []filter.Match
lastSSHPolicy *tailcfg.SSHPolicy
collectServices bool
@@ -96,6 +98,7 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
if pf := resp.PacketFilter; pf != nil {
var err error
ms.lastPacketFilterRules = views.SliceOf(pf)
ms.lastParsedPacketFilter, err = filter.MatchesFromFilterRules(pf)
if err != nil {
ms.logf("parsePacketFilter: %v", err)
@@ -147,21 +150,22 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
}
nm := &netmap.NetworkMap{
NodeKey: ms.privateNodeKey.Public(),
PrivateKey: ms.privateNodeKey,
MachineKey: ms.machinePubKey,
Peers: resp.Peers,
UserProfiles: make(map[tailcfg.UserID]tailcfg.UserProfile),
Domain: ms.lastDomain,
DomainAuditLogID: ms.lastDomainAuditLogID,
DNS: *ms.lastDNSConfig,
PacketFilter: ms.lastParsedPacketFilter,
SSHPolicy: ms.lastSSHPolicy,
CollectServices: ms.collectServices,
DERPMap: ms.lastDERPMap,
Debug: debug,
ControlHealth: ms.lastHealth,
TKAEnabled: ms.lastTKAInfo != nil && !ms.lastTKAInfo.Disabled,
NodeKey: ms.privateNodeKey.Public(),
PrivateKey: ms.privateNodeKey,
MachineKey: ms.machinePubKey,
Peers: resp.Peers,
UserProfiles: make(map[tailcfg.UserID]tailcfg.UserProfile),
Domain: ms.lastDomain,
DomainAuditLogID: ms.lastDomainAuditLogID,
DNS: *ms.lastDNSConfig,
PacketFilter: ms.lastParsedPacketFilter,
PacketFilterRules: ms.lastPacketFilterRules,
SSHPolicy: ms.lastSSHPolicy,
CollectServices: ms.collectServices,
DERPMap: ms.lastDERPMap,
Debug: debug,
ControlHealth: ms.lastHealth,
TKAEnabled: ms.lastTKAInfo != nil && !ms.lastTKAInfo.Disabled,
}
ms.netMapBuilding = nm

5
go.mod
View File

@@ -64,7 +64,7 @@ require (
github.com/tc-hib/winres v0.1.6
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
github.com/u-root/u-root v0.9.1-0.20221111022710-6e9699743f5d
github.com/u-root/u-root v0.9.1-0.20230109201855-948a78c969ad
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
go.uber.org/zap v1.21.0
go4.org/mem v0.0.0-20210711025021-927187094b94
@@ -104,7 +104,7 @@ require (
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
github.com/OpenPeeDeeP/depguard v1.0.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
@@ -134,6 +134,7 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/charithe/durationcheck v0.0.9 // indirect
github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af // indirect
github.com/cloudflare/circl v1.1.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
github.com/daixiang0/gci v0.2.9 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect

13
go.sum
View File

@@ -90,8 +90,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us=
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 h1:XcF0cTDJeiuZ5NU8w7WUDge0HRwwNRmxj/GGk6KSA6g=
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I=
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
@@ -195,6 +195,7 @@ github.com/breml/bidichk v0.2.1 h1:SRNtZuLdfkxtocj+xyHXKC1Uv3jVi6EPYx+NHSTNQvE=
github.com/breml/bidichk v0.2.1/go.mod h1:zbfeitpevDUGI7V91Uzzuwrn4Vls8MoBMrwtt78jmso=
github.com/butuzov/ireturn v0.1.1 h1:QvrO2QF2+/Cx1WA/vETCIYBKtRjc30vesdoPUNo1EbY=
github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -212,6 +213,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao=
github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -1194,8 +1197,9 @@ github.com/tommy-muehle/go-mnd/v2 v2.4.0 h1:1t0f8Uiaq+fqKteUR4N9Umr6E99R+lDnLnq7
github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
github.com/u-root/u-root v0.9.1-0.20221111022710-6e9699743f5d h1:sT5Q2xFrqgm/3yrCkVLkVuEFpG07UXz9ALqxxN1SmZc=
github.com/u-root/u-root v0.9.1-0.20221111022710-6e9699743f5d/go.mod h1:jMbuI3nENTNzHW9mYwQ57b8/DSuJTq+joYY18x/WGxE=
github.com/u-root/gobusybox/src v0.0.0-20221229083637-46b2883a7f90 h1:zTk5683I9K62wtZ6eUa6vu6IWwVHXPnoKK5n2unAwv0=
github.com/u-root/u-root v0.9.1-0.20230109201855-948a78c969ad h1:0lEUXaz1vhlAtoMpu18vhb16s5rGRpNCl2trxc2/Qbg=
github.com/u-root/u-root v0.9.1-0.20230109201855-948a78c969ad/go.mod h1:DBkDtiZyONk9hzVEdB/PWI9B4TxDkElWlVTHseglrZY=
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f h1:dpx1PHxYqAnXzbryJrWP1NQLzEjwcVgFLhkknuFQ7ww=
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
@@ -1564,6 +1568,7 @@ golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211002104244-808efd93c36d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@@ -70,6 +70,9 @@ const (
// SysDNSManager is the name of the net/dns manager subsystem.
SysDNSManager = Subsystem("dns-manager")
// SysTKA is the name of the tailnet key authority subsystem.
SysTKA = Subsystem("tailnet-lock")
)
// NewWarnable returns a new warnable item that the caller can mark
@@ -194,6 +197,12 @@ func SetDNSManagerHealth(err error) { setErr(SysDNSManager, err) }
// DNSOSHealth returns the net/dns.OSConfigurator error state.
func DNSOSHealth() error { return get(SysDNSOS) }
// SetTKAHealth sets the health of the tailnet key authority.
func SetTKAHealth(err error) { setErr(SysTKA, err) }
// TKAHealth returns the tailnet key authority error state.
func TKAHealth() error { return get(SysTKA) }
// SetLocalLogConfigHealth sets the error state of this client's local log configuration.
func SetLocalLogConfigHealth(err error) {
mu.Lock()

View File

@@ -65,6 +65,8 @@ const (
NotifyInitialState // if set, the first Notify message (sent immediately) will contain the current State + BrowseToURL
NotifyInitialPrefs // if set, the first Notify message (sent immediately) will contain the current Prefs
NotifyInitialNetMap // if set, the first Notify message (sent immediately) will contain the current NetMap
NotifyNoPrivateKeys // if set, private keys that would normally be sent in updates are zeroed out
)
// Notify is a communication from a backend (e.g. tailscaled) to a frontend

View File

@@ -26,6 +26,16 @@ func (b *LocalBackend) handleC2N(w http.ResponseWriter, r *http.Request) {
// Test handler.
body, _ := io.ReadAll(r.Body)
w.Write(body)
case "/logtail/flush":
if r.Method != "POST" {
http.Error(w, "bad method", http.StatusMethodNotAllowed)
return
}
if b.TryFlushLogs() {
w.WriteHeader(http.StatusNoContent)
} else {
http.Error(w, "no log flusher wired up", http.StatusInternalServerError)
}
case "/debug/goroutines":
w.Header().Set("Content-Type", "text/plain")
w.Write(goroutines.ScrubbedGoroutineDump())

View File

@@ -140,6 +140,7 @@ type LocalBackend struct {
gotPortPollRes chan struct{} // closed upon first readPoller result
newDecompressor func() (controlclient.Decompressor, error)
varRoot string // or empty if SetVarRoot never called
logFlushFunc func() // or nil if SetLogFlusher wasn't called
sshAtomicBool atomic.Bool
shutdownCalled bool // if Shutdown has been called
@@ -1742,6 +1743,24 @@ func (b *LocalBackend) readPoller() {
func (b *LocalBackend) WatchNotifications(ctx context.Context, mask ipn.NotifyWatchOpt, fn func(roNotify *ipn.Notify) (keepGoing bool)) {
ch := make(chan *ipn.Notify, 128)
origFn := fn
if mask&ipn.NotifyNoPrivateKeys != 0 {
fn = func(n *ipn.Notify) bool {
if n.NetMap == nil || n.NetMap.PrivateKey.IsZero() {
return origFn(n)
}
// The netmap in n is shared across all watchers, so to mutate it for a
// single watcher we have to clone the notify and the netmap. We can
// make shallow clones, at least.
nm2 := *n.NetMap
n2 := *n
n2.NetMap = &nm2
n2.NetMap.PrivateKey = key.NodePrivate{}
return origFn(&n2)
}
}
var ini *ipn.Notify
b.mu.Lock()
@@ -2395,7 +2414,7 @@ func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error {
if !envknob.UseWIPCode() {
return errors.New("The Tailscale SSH server is disabled on macOS tailscaled by default. To try, set env TAILSCALE_USE_WIP_CODE=1")
}
case "freebsd":
case "freebsd", "openbsd":
default:
return errors.New("The Tailscale SSH server is not supported on " + runtime.GOOS)
}
@@ -2997,6 +3016,25 @@ func (b *LocalBackend) SetVarRoot(dir string) {
b.varRoot = dir
}
// SetLogFlusher sets a func to be called to flush log uploads.
//
// It should only be called before the LocalBackend is used.
func (b *LocalBackend) SetLogFlusher(flushFunc func()) {
b.logFlushFunc = flushFunc
}
// TryFlushLogs calls the log flush function. It returns false if a log flush
// function was never initialized with SetLogFlusher.
//
// TryFlushLogs should not block.
func (b *LocalBackend) TryFlushLogs() bool {
if b.logFlushFunc == nil {
return false
}
b.logFlushFunc()
return true
}
// TailscaleVarRoot returns the root directory of Tailscale's writable
// storage area. (e.g. "/var/lib/tailscale")
//

View File

@@ -20,6 +20,7 @@ import (
"time"
"tailscale.com/envknob"
"tailscale.com/health"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/tsaddr"
@@ -60,9 +61,11 @@ func (b *LocalBackend) permitTKAInitLocked() bool {
func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
// TODO(tom): Remove this guard for 1.35 and later.
if b.tka == nil && !b.permitTKAInitLocked() {
health.SetTKAHealth(nil)
return
}
if b.tka == nil {
health.SetTKAHealth(nil)
return // TKA not enabled.
}
@@ -111,6 +114,13 @@ func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
} else {
b.tka.filtered = nil
}
// Check that we ourselves are not locked out, report a health issue if so.
if nm.SelfNode != nil && b.tka.authority.NodeKeyAuthorized(nm.SelfNode.Key, nm.SelfNode.KeySignature) != nil {
health.SetTKAHealth(errors.New("this node is locked out; it will not have connectivity until it is signed. For more info, see https://tailscale.com/s/locked-out"))
} else {
health.SetTKAHealth(nil)
}
}
// tkaSyncIfNeeded examines TKA info reported from the control plane,
@@ -177,6 +187,7 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsVie
b.logf("Disablement failed, leaving TKA enabled. Error: %v", err)
} else {
isEnabled = false
health.SetTKAHealth(nil)
}
} else {
return fmt.Errorf("[bug] unreachable invariant of wantEnabled /w isEnabled")
@@ -666,7 +677,11 @@ func (b *LocalBackend) NetworkLockModify(addKeys, removeKeys []tka.Key) (err err
}
}
for _, removeKey := range removeKeys {
if err := updater.RemoveKey(removeKey.ID()); err != nil {
keyID, err := removeKey.ID()
if err != nil {
return err
}
if err := updater.RemoveKey(keyID); err != nil {
return err
}
}

View File

@@ -321,7 +321,7 @@ func TestTKASync(t *testing.T) {
name: "control has an update",
controlAUMs: func(t *testing.T, a *tka.Authority, storage tka.Chonk, signer tka.Signer) []tka.AUM {
b := a.NewUpdater(signer)
if err := b.RemoveKey(someKey.ID()); err != nil {
if err := b.RemoveKey(someKey.MustID()); err != nil {
t.Fatal(err)
}
aums, err := b.Finalize(storage)
@@ -336,7 +336,7 @@ func TestTKASync(t *testing.T) {
name: "node has an update",
nodeAUMs: func(t *testing.T, a *tka.Authority, storage tka.Chonk, signer tka.Signer) []tka.AUM {
b := a.NewUpdater(signer)
if err := b.RemoveKey(someKey.ID()); err != nil {
if err := b.RemoveKey(someKey.MustID()); err != nil {
t.Fatal(err)
}
aums, err := b.Finalize(storage)
@@ -351,7 +351,7 @@ func TestTKASync(t *testing.T) {
name: "node and control diverge",
controlAUMs: func(t *testing.T, a *tka.Authority, storage tka.Chonk, signer tka.Signer) []tka.AUM {
b := a.NewUpdater(signer)
if err := b.SetKeyMeta(someKey.ID(), map[string]string{"ye": "swiggity"}); err != nil {
if err := b.SetKeyMeta(someKey.MustID(), map[string]string{"ye": "swiggity"}); err != nil {
t.Fatal(err)
}
aums, err := b.Finalize(storage)
@@ -362,7 +362,7 @@ func TestTKASync(t *testing.T) {
},
nodeAUMs: func(t *testing.T, a *tka.Authority, storage tka.Chonk, signer tka.Signer) []tka.AUM {
b := a.NewUpdater(signer)
if err := b.SetKeyMeta(someKey.ID(), map[string]string{"ye": "swooty"}); err != nil {
if err := b.SetKeyMeta(someKey.MustID(), map[string]string{"ye": "swooty"}); err != nil {
t.Fatal(err)
}
aums, err := b.Finalize(storage)

View File

@@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"math/rand"
"net/netip"
"runtime"
"time"
@@ -19,6 +20,7 @@ import (
"tailscale.com/types/logger"
"tailscale.com/util/clientmetric"
"tailscale.com/util/strs"
"tailscale.com/util/winutil"
"tailscale.com/version"
)
@@ -322,7 +324,7 @@ func (pm *profileManager) setAsUserSelectedProfileLocked() error {
func (pm *profileManager) loadSavedPrefs(key ipn.StateKey) (ipn.PrefsView, error) {
bs, err := pm.store.ReadState(key)
if err == ipn.ErrStateNotExist || len(bs) == 0 {
return emptyPrefs, nil
return defaultPrefs, nil
}
if err != nil {
return ipn.PrefsView{}, err
@@ -394,15 +396,29 @@ func (pm *profileManager) writeKnownProfiles() error {
func (pm *profileManager) NewProfile() {
metricNewProfile.Add(1)
pm.prefs = emptyPrefs
pm.prefs = defaultPrefs
pm.isNewProfile = true
pm.currentProfile = &ipn.LoginProfile{}
}
// emptyPrefs is the default prefs for a new profile.
var emptyPrefs = func() ipn.PrefsView {
// defaultPrefs is the default prefs for a new profile.
var defaultPrefs = func() ipn.PrefsView {
prefs := ipn.NewPrefs()
prefs.WantRunning = false
prefs.ControlURL = winutil.GetPolicyString("LoginURL", "")
if exitNode := winutil.GetPolicyString("ExitNodeIP", ""); exitNode != "" {
if ip, err := netip.ParseAddr(exitNode); err == nil {
prefs.ExitNodeIP = ip
}
}
// Allow Incoming (used by the UI) is the negation of ShieldsUp (used by the
// backend), so this has to convert between the two conventions.
prefs.ShieldsUp = winutil.GetPolicyString("AllowIncomingConnections", "") == "never"
prefs.ForceDaemon = winutil.GetPolicyString("UnattendedMode", "") == "always"
return prefs.View()
}()

View File

@@ -53,7 +53,7 @@ func TestProfileCurrentUserSwitch(t *testing.T) {
} else if pm.currentProfile.ID != "" {
t.Fatalf("currentProfile.ID = %q, want empty", pm.currentProfile.ID)
}
if !pm.CurrentPrefs().Equals(emptyPrefs) {
if !pm.CurrentPrefs().Equals(defaultPrefs) {
t.Fatalf("CurrentPrefs() = %v, want emptyPrefs", pm.CurrentPrefs().Pretty())
}
@@ -67,7 +67,7 @@ func TestProfileCurrentUserSwitch(t *testing.T) {
} else if pm.currentProfile.ID != "" {
t.Fatalf("currentProfile.ID = %q, want empty", pm.currentProfile.ID)
}
if !pm.CurrentPrefs().Equals(emptyPrefs) {
if !pm.CurrentPrefs().Equals(defaultPrefs) {
t.Fatalf("CurrentPrefs() = %v, want emptyPrefs", pm.CurrentPrefs().Pretty())
}
}
@@ -159,7 +159,7 @@ func TestProfileManagement(t *testing.T) {
}
wantCurProfile := ""
wantProfiles := map[string]ipn.PrefsView{
"": emptyPrefs,
"": defaultPrefs,
}
checkProfiles := func(t *testing.T) {
t.Helper()
@@ -237,7 +237,7 @@ func TestProfileManagement(t *testing.T) {
t.Logf("Create new profile")
pm.NewProfile()
wantCurProfile = ""
wantProfiles[""] = emptyPrefs
wantProfiles[""] = defaultPrefs
checkProfiles(t)
{
@@ -276,7 +276,7 @@ func TestProfileManagement(t *testing.T) {
t.Logf("Create new profile - 2")
pm.NewProfile()
wantCurProfile = ""
wantProfiles[""] = emptyPrefs
wantProfiles[""] = defaultPrefs
checkProfiles(t)
t.Logf("Login with the existing profile")
@@ -310,7 +310,7 @@ func TestProfileManagementWindows(t *testing.T) {
}
wantCurProfile := ""
wantProfiles := map[string]ipn.PrefsView{
"": emptyPrefs,
"": defaultPrefs,
}
checkProfiles := func(t *testing.T) {
t.Helper()
@@ -363,7 +363,7 @@ func TestProfileManagementWindows(t *testing.T) {
t.Logf("Create new profile")
pm.NewProfile()
wantCurProfile = ""
wantProfiles[""] = emptyPrefs
wantProfiles[""] = defaultPrefs
checkProfiles(t)
t.Logf("Save as test profile")
@@ -380,7 +380,7 @@ func TestProfileManagementWindows(t *testing.T) {
t.Fatal(err)
}
wantCurProfile = ""
wantProfiles[""] = emptyPrefs
wantProfiles[""] = defaultPrefs
checkProfiles(t)
{

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux || (darwin && !ios) || freebsd
//go:build linux || (darwin && !ios) || freebsd || openbsd
package ipnlocal

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ios || (!linux && !darwin && !freebsd)
//go:build ios || (!linux && !darwin && !freebsd && !openbsd)
package ipnlocal

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// 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.

View File

@@ -61,39 +61,41 @@ var handler = map[string]localAPIHandler{
// The other /localapi/v0/NAME handlers are exact matches and contain only NAME
// without a trailing slash:
"bugreport": (*Handler).serveBugReport,
"check-ip-forwarding": (*Handler).serveCheckIPForwarding,
"check-prefs": (*Handler).serveCheckPrefs,
"component-debug-logging": (*Handler).serveComponentDebugLogging,
"debug": (*Handler).serveDebug,
"debug-derp-region": (*Handler).serveDebugDERPRegion,
"derpmap": (*Handler).serveDERPMap,
"dev-set-state-store": (*Handler).serveDevSetStateStore,
"dial": (*Handler).serveDial,
"file-targets": (*Handler).serveFileTargets,
"goroutines": (*Handler).serveGoroutines,
"id-token": (*Handler).serveIDToken,
"login-interactive": (*Handler).serveLoginInteractive,
"logout": (*Handler).serveLogout,
"metrics": (*Handler).serveMetrics,
"ping": (*Handler).servePing,
"prefs": (*Handler).servePrefs,
"pprof": (*Handler).servePprof,
"serve-config": (*Handler).serveServeConfig,
"set-dns": (*Handler).serveSetDNS,
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
"start": (*Handler).serveStart,
"status": (*Handler).serveStatus,
"tka/init": (*Handler).serveTKAInit,
"tka/log": (*Handler).serveTKALog,
"tka/modify": (*Handler).serveTKAModify,
"tka/sign": (*Handler).serveTKASign,
"tka/status": (*Handler).serveTKAStatus,
"tka/disable": (*Handler).serveTKADisable,
"tka/force-local-disable": (*Handler).serveTKALocalDisable,
"upload-client-metrics": (*Handler).serveUploadClientMetrics,
"watch-ipn-bus": (*Handler).serveWatchIPNBus,
"whois": (*Handler).serveWhoIs,
"bugreport": (*Handler).serveBugReport,
"check-ip-forwarding": (*Handler).serveCheckIPForwarding,
"check-prefs": (*Handler).serveCheckPrefs,
"component-debug-logging": (*Handler).serveComponentDebugLogging,
"debug": (*Handler).serveDebug,
"debug-derp-region": (*Handler).serveDebugDERPRegion,
"debug-packet-filter-matches": (*Handler).serveDebugPacketFilterMatches,
"debug-packet-filter-rules": (*Handler).serveDebugPacketFilterRules,
"derpmap": (*Handler).serveDERPMap,
"dev-set-state-store": (*Handler).serveDevSetStateStore,
"dial": (*Handler).serveDial,
"file-targets": (*Handler).serveFileTargets,
"goroutines": (*Handler).serveGoroutines,
"id-token": (*Handler).serveIDToken,
"login-interactive": (*Handler).serveLoginInteractive,
"logout": (*Handler).serveLogout,
"metrics": (*Handler).serveMetrics,
"ping": (*Handler).servePing,
"prefs": (*Handler).servePrefs,
"pprof": (*Handler).servePprof,
"serve-config": (*Handler).serveServeConfig,
"set-dns": (*Handler).serveSetDNS,
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
"start": (*Handler).serveStart,
"status": (*Handler).serveStatus,
"tka/init": (*Handler).serveTKAInit,
"tka/log": (*Handler).serveTKALog,
"tka/modify": (*Handler).serveTKAModify,
"tka/sign": (*Handler).serveTKASign,
"tka/status": (*Handler).serveTKAStatus,
"tka/disable": (*Handler).serveTKADisable,
"tka/force-local-disable": (*Handler).serveTKALocalDisable,
"upload-client-metrics": (*Handler).serveUploadClientMetrics,
"watch-ipn-bus": (*Handler).serveWatchIPNBus,
"whois": (*Handler).serveWhoIs,
}
func randHex(n int) string {
@@ -290,6 +292,7 @@ func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
http.Error(w, "only POST allowed", http.StatusMethodNotAllowed)
return
}
defer h.b.TryFlushLogs() // kick off upload after bugreport's done logging
logMarker := func() string {
return fmt.Sprintf("BUG-%v-%v-%v", h.backendLogID, time.Now().UTC().Format("20060102150405Z"), randHex(8))
@@ -506,6 +509,40 @@ func (h *Handler) serveDevSetStateStore(w http.ResponseWriter, r *http.Request)
io.WriteString(w, "done\n")
}
func (h *Handler) serveDebugPacketFilterRules(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite {
http.Error(w, "debug access denied", http.StatusForbidden)
return
}
nm := h.b.NetMap()
if nm == nil {
http.Error(w, "no netmap", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
enc.SetIndent("", "\t")
enc.Encode(nm.PacketFilterRules)
}
func (h *Handler) serveDebugPacketFilterMatches(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite {
http.Error(w, "debug access denied", http.StatusForbidden)
return
}
nm := h.b.NetMap()
if nm == nil {
http.Error(w, "no netmap", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
enc.SetIndent("", "\t")
enc.Encode(nm.PacketFilter)
}
func (h *Handler) serveComponentDebugLogging(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite {
http.Error(w, "debug access denied", http.StatusForbidden)

View File

@@ -57,12 +57,12 @@ Client][]. See also the dependencies in the [Tailscale CLI][].
- [golang.org/x/exp/shiny](https://pkg.go.dev/golang.org/x/exp/shiny) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/334a2380:shiny/LICENSE))
- [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/062f8c9f:LICENSE))
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.2.0:LICENSE))
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/886fb937:LICENSE))
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.2.0:LICENSE))
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.1.0:LICENSE))
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/2204b661:LICENSE))
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.2.0:LICENSE))
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.4.0:LICENSE))
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/579cf78f:LICENSE))
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/846276b3dbc5/LICENSE))
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/703fd9b7fbc0/LICENSE))
- [inet.af/netaddr](https://pkg.go.dev/inet.af/netaddr) ([BSD-3-Clause](https://github.com/inetaf/netaddr/blob/097006376321/LICENSE))
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))

View File

@@ -69,7 +69,7 @@ Some packages may only be included on certain architectures or operating systems
- [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/4fa124729667/LICENSE))
- [github.com/tcnksm/go-httpstat](https://pkg.go.dev/github.com/tcnksm/go-httpstat) ([MIT](https://github.com/tcnksm/go-httpstat/blob/v0.2.0/LICENSE))
- [github.com/toqueteos/webbrowser](https://pkg.go.dev/github.com/toqueteos/webbrowser) ([MIT](https://github.com/toqueteos/webbrowser/blob/v1.2.0/LICENSE.md))
- [github.com/u-root/u-root/pkg/termios](https://pkg.go.dev/github.com/u-root/u-root/pkg/termios) ([BSD-3-Clause](https://github.com/u-root/u-root/blob/6e9699743f5d/LICENSE))
- [github.com/u-root/u-root/pkg/termios](https://pkg.go.dev/github.com/u-root/u-root/pkg/termios) ([BSD-3-Clause](https://github.com/u-root/u-root/blob/948a78c969ad/LICENSE))
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/c3537552635f/LICENSE))
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/650dca95af54/LICENSE))
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/50045581ed74/LICENSE))

View File

@@ -66,13 +66,12 @@ type Config struct {
// that's safe to embed in a JSON string literal without further escaping.
MetricsDelta func() string
// FlushDelay is how long to wait to accumulate logs before
// uploading them.
// FlushDelayFn, if non-nil is a func that returns how long to wait to
// accumulate logs before uploading them. 0 or negative means to upload
// immediately.
//
// If zero, a default value is used. (currently 2 seconds)
//
// Negative means to upload immediately.
FlushDelay time.Duration
// If nil, a default value is used. (currently 2 seconds)
FlushDelayFn func() time.Duration
// IncludeProcID, if true, results in an ephemeral process identifier being
// included in logs. The ID is random and not guaranteed to be globally
@@ -118,13 +117,13 @@ func NewLogger(cfg Config, logf tslogger.Logf) *Logger {
}
}
if s := envknob.String("TS_DEBUG_LOGTAIL_FLUSHDELAY"); s != "" {
var err error
cfg.FlushDelay, err = time.ParseDuration(s)
if err != nil {
if delay, err := time.ParseDuration(s); err == nil {
cfg.FlushDelayFn = func() time.Duration { return delay }
} else {
log.Fatalf("invalid TS_DEBUG_LOGTAIL_FLUSHDELAY: %v", err)
}
} else if cfg.FlushDelay == 0 && !envknob.Bool("IN_TS_TEST") {
cfg.FlushDelay = defaultFlushDelay
} else if cfg.FlushDelayFn == nil && envknob.Bool("IN_TS_TEST") {
cfg.FlushDelayFn = func() time.Duration { return 0 }
}
stdLogf := func(f string, a ...any) {
@@ -145,7 +144,7 @@ func NewLogger(cfg Config, logf tslogger.Logf) *Logger {
skipClientTime: cfg.SkipClientTime,
drainWake: make(chan struct{}, 1),
sentinel: make(chan int32, 16),
flushDelay: cfg.FlushDelay,
flushDelayFn: cfg.FlushDelayFn,
timeNow: cfg.TimeNow,
bo: backoff.NewBackoff("logtail", stdLogf, 30*time.Second),
metricsDelta: cfg.MetricsDelta,
@@ -179,8 +178,8 @@ type Logger struct {
skipClientTime bool
linkMonitor *monitor.Mon
buffer Buffer
drainWake chan struct{} // signal to speed up drain
flushDelay time.Duration // negative or zero to upload agressively, or >0 to batch at this delay
drainWake chan struct{} // signal to speed up drain
flushDelayFn func() time.Duration // negative or zero return value to upload aggressively, or >0 to batch at this delay
flushPending atomic.Bool
sentinel chan int32
timeNow func() time.Time
@@ -462,12 +461,24 @@ func (l *Logger) upload(ctx context.Context, body []byte, origlen int) (uploaded
return true, nil
}
// Flush uploads all logs to the server.
// It blocks until complete or there is an unrecoverable error.
// Flush uploads all logs to the server. It blocks until complete or there is an
// unrecoverable error.
//
// TODO(bradfitz): this apparently just returns nil, as of tailscale/corp@9c2ec35.
// Finish cleaning this up.
func (l *Logger) Flush() error {
return nil
}
// StartFlush starts a log upload, if anything is pending.
//
// If l is nil, StartFlush is a no-op.
func (l *Logger) StartFlush() {
if l != nil {
l.tryDrainWake()
}
}
// logtailDisabled is whether logtail uploads to logcatcher are disabled.
var logtailDisabled atomic.Bool
@@ -500,12 +511,16 @@ func (l *Logger) sendLocked(jsonBlob []byte) (int, error) {
n, err := l.buffer.Write(jsonBlob)
if l.flushDelay > 0 {
flushDelay := defaultFlushDelay
if l.flushDelayFn != nil {
flushDelay = l.flushDelayFn()
}
if flushDelay > 0 {
if l.flushPending.CompareAndSwap(false, true) {
if l.flushTimer == nil {
l.flushTimer = time.AfterFunc(l.flushDelay, l.tryDrainWake)
l.flushTimer = time.AfterFunc(flushDelay, l.tryDrainWake)
} else {
l.flushTimer.Reset(l.flushDelay)
l.flushTimer.Reset(flushDelay)
}
}
} else {

View File

@@ -19,41 +19,21 @@ import (
"golang.org/x/exp/slices"
"tailscale.com/health"
"tailscale.com/net/dns/resolver"
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tsdial"
"tailscale.com/net/tstun"
"tailscale.com/types/dnstype"
"tailscale.com/types/ipproto"
"tailscale.com/types/logger"
"tailscale.com/util/clientmetric"
"tailscale.com/util/dnsname"
"tailscale.com/wgengine/monitor"
)
var (
magicDNSIP = tsaddr.TailscaleServiceIP()
magicDNSIPv6 = tsaddr.TailscaleServiceIPv6()
)
var (
errFullQueue = errors.New("request queue full")
)
// maxActiveQueries returns the maximal number of DNS requests that be
// can running.
// If EnqueueRequest is called when this many requests are already pending,
// the request will be dropped to avoid blocking the caller.
func maxActiveQueries() int32 {
if runtime.GOOS == "ios" {
// For memory paranoia reasons on iOS, match the
// historical Tailscale 1.x..1.8 behavior for now
// (just before the 1.10 release).
return 64
}
// But for other platforms, allow more burstiness:
return 256
}
// maxActiveQueries returns the maximal number of DNS requests that can
// be running.
const maxActiveQueries = 256
// We use file-ignore below instead of ignore because on some platforms,
// the lint exception is necessary and on others it is not,
@@ -75,13 +55,6 @@ type response struct {
type Manager struct {
logf logger.Logf
// When netstack is not used, Manager implements magic DNS.
// In this case, responses tracks completed DNS requests
// which need a response, and NextPacket() synthesizes a
// fake IP+UDP header to finish assembling the response.
//
// TODO(tom): Rip out once all platforms use netstack.
responses chan response
activeQueriesAtomic int32
ctx context.Context // good until Down
@@ -98,10 +71,9 @@ func NewManager(logf logger.Logf, oscfg OSConfigurator, linkMon *monitor.Mon, di
}
logf = logger.WithPrefix(logf, "dns: ")
m := &Manager{
logf: logf,
resolver: resolver.New(logf, linkMon, linkSel, dialer),
os: oscfg,
responses: make(chan response),
logf: logf,
resolver: resolver.New(logf, linkMon, linkSel, dialer),
os: oscfg,
}
m.ctx, m.ctxCancel = context.WithCancel(context.Background())
m.logf("using %T", m.os)
@@ -316,89 +288,6 @@ func toIPsOnly(resolvers []*dnstype.Resolver) (ret []netip.Addr) {
return ret
}
// EnqueuePacket is the legacy path for handling magic DNS traffic, and is
// called with a DNS request payload.
//
// TODO(tom): Rip out once all platforms use netstack.
func (m *Manager) EnqueuePacket(bs []byte, proto ipproto.Proto, from, to netip.AddrPort) error {
if to.Port() != 53 || proto != ipproto.UDP {
return nil
}
if n := atomic.AddInt32(&m.activeQueriesAtomic, 1); n > maxActiveQueries() {
atomic.AddInt32(&m.activeQueriesAtomic, -1)
metricDNSQueryErrorQueue.Add(1)
return errFullQueue
}
go func() {
resp, err := m.resolver.Query(m.ctx, bs, from)
if err != nil {
atomic.AddInt32(&m.activeQueriesAtomic, -1)
m.logf("dns query: %v", err)
return
}
select {
case <-m.ctx.Done():
return
case m.responses <- response{resp, from}:
}
}()
return nil
}
// NextPacket is the legacy path for obtaining DNS results in response to
// magic DNS queries. It blocks until a response is available.
//
// TODO(tom): Rip out once all platforms use netstack.
func (m *Manager) NextPacket() ([]byte, error) {
var resp response
select {
case <-m.ctx.Done():
return nil, net.ErrClosed
case resp = <-m.responses:
// continue
}
// Unused space is needed further down the stack. To avoid extra
// allocations/copying later on, we allocate such space here.
const offset = tstun.PacketStartOffset
var buf []byte
switch {
case resp.to.Addr().Is4():
h := packet.UDP4Header{
IP4Header: packet.IP4Header{
Src: magicDNSIP,
Dst: resp.to.Addr(),
},
SrcPort: 53,
DstPort: resp.to.Port(),
}
hlen := h.Len()
buf = make([]byte, offset+hlen+len(resp.pkt))
copy(buf[offset+hlen:], resp.pkt)
h.Marshal(buf[offset:])
case resp.to.Addr().Is6():
h := packet.UDP6Header{
IP6Header: packet.IP6Header{
Src: magicDNSIPv6,
Dst: resp.to.Addr(),
},
SrcPort: 53,
DstPort: resp.to.Port(),
}
hlen := h.Len()
buf = make([]byte, offset+hlen+len(resp.pkt))
copy(buf[offset+hlen:], resp.pkt)
h.Marshal(buf[offset:])
}
atomic.AddInt32(&m.activeQueriesAtomic, -1)
return buf, nil
}
// Query executes a DNS query received from the given address. The query is
// provided in bs as a wire-encoded DNS query without any transport header.
// This method is called for requests arriving over UDP and TCP.
@@ -410,7 +299,7 @@ func (m *Manager) Query(ctx context.Context, bs []byte, from netip.AddrPort) ([]
// continue
}
if n := atomic.AddInt32(&m.activeQueriesAtomic, 1); n > maxActiveQueries() {
if n := atomic.AddInt32(&m.activeQueriesAtomic, 1); n > maxActiveQueries {
atomic.AddInt32(&m.activeQueriesAtomic, -1)
metricDNSQueryErrorQueue.Add(1)
return nil, errFullQueue

View File

@@ -83,17 +83,26 @@ func Parse(r io.Reader) (*Config, error) {
}
if s, ok := strs.CutPrefix(line, "search"); ok {
domain := strings.TrimSpace(s)
if len(domain) == len(s) {
domains := strings.TrimSpace(s)
if len(domains) == len(s) {
// No leading space?!
return nil, fmt.Errorf("missing space after \"domain\" in %q", line)
return nil, fmt.Errorf("missing space after \"search\" in %q", line)
}
fqdn, err := dnsname.ToFQDN(domain)
if err != nil {
return nil, fmt.Errorf("parsing search domains %q: %w", line, err)
for len(domains) > 0 {
domain := domains
i := strings.IndexAny(domain, " \t")
if i != -1 {
domain = domain[:i]
domains = strings.TrimSpace(domains[i+1:])
} else {
domains = ""
}
fqdn, err := dnsname.ToFQDN(domain)
if err != nil {
return nil, fmt.Errorf("parsing search domain %q in %q: %w", domain, line, err)
}
config.SearchDomains = append(config.SearchDomains, fqdn)
}
config.SearchDomains = append(config.SearchDomains, fqdn)
continue
}
}
return config, nil

View File

@@ -57,6 +57,31 @@ func TestParse(t *testing.T) {
},
{in: `searchtailsacle.com`, wantErr: true},
{in: `search`, wantErr: true},
// Issue 6875: there can be multiple search domains, and even if they're
// over 253 bytes long total.
{
in: "search search-01.example search-02.example search-03.example search-04.example search-05.example search-06.example search-07.example search-08.example search-09.example search-10.example search-11.example search-12.example search-13.example search-14.example search-15.example\n",
want: &Config{
SearchDomains: []dnsname.FQDN{
"search-01.example.",
"search-02.example.",
"search-03.example.",
"search-04.example.",
"search-05.example.",
"search-06.example.",
"search-07.example.",
"search-08.example.",
"search-09.example.",
"search-10.example.",
"search-11.example.",
"search-12.example.",
"search-13.example.",
"search-14.example.",
"search-15.example.",
},
},
},
}
for _, tt := range tests {

View File

@@ -43,7 +43,7 @@ func TestFallbackRootWorks(t *testing.T) {
crtFile := filepath.Join(d, "tlsdial.test.crt")
keyFile := filepath.Join(d, "tlsdial.test.key")
caFile := filepath.Join(d, "rootCA.pem")
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"),
cmd := exec.Command("go",
"run", "filippo.io/mkcert",
"--cert-file="+crtFile,
"--key-file="+keyFile,

View File

@@ -10,7 +10,7 @@
check_file() {
got=$1
for year in `seq 2019 2022`; do
for year in `seq 2019 2023`; do
want=$(cat <<EOF
// Copyright (c) $year Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style

View File

@@ -8,7 +8,7 @@
// and groups to the specified `--uid`, `--gid` and `--groups`, and
// then launches the requested `--cmd`.
//go:build linux || (darwin && !ios) || freebsd
//go:build linux || (darwin && !ios) || freebsd || openbsd
package tailssh
@@ -24,6 +24,7 @@ import (
"os/user"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"syscall"
@@ -33,6 +34,7 @@ import (
"github.com/u-root/u-root/pkg/termios"
"go4.org/mem"
gossh "golang.org/x/crypto/ssh"
"golang.org/x/exp/slices"
"golang.org/x/sys/unix"
"tailscale.com/cmd/tailscaled/childproc"
"tailscale.com/envknob"
@@ -693,3 +695,59 @@ func acceptEnvPair(kv string) bool {
}
return k == "TERM" || k == "LANG" || strings.HasPrefix(k, "LC_")
}
func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
func (ia *incubatorArgs) loginArgs() []string {
switch runtime.GOOS {
case "linux":
if distro.Get() == distro.Arch && !fileExists("/etc/pam.d/remote") {
// See https://github.com/tailscale/tailscale/issues/4924
//
// Arch uses a different login binary that makes the -h flag set the PAM
// service to "remote". So if they don't have that configured, don't
// pass -h.
return []string{ia.loginCmdPath, "-f", ia.localUser, "-p"}
}
return []string{ia.loginCmdPath, "-f", ia.localUser, "-h", ia.remoteIP, "-p"}
case "darwin", "freebsd", "openbsd":
return []string{ia.loginCmdPath, "-fp", "-h", ia.remoteIP, ia.localUser}
}
panic("unimplemented")
}
func setGroups(groupIDs []int) error {
if runtime.GOOS == "darwin" && len(groupIDs) > 16 {
// darwin returns "invalid argument" if more than 16 groups are passed to syscall.Setgroups
// some info can be found here:
// https://opensource.apple.com/source/samba/samba-187.8/patches/support-darwin-initgroups-syscall.auto.html
// this fix isn't great, as anyone reading this has probably just wasted hours figuring out why
// some permissions thing isn't working, due to some arbitrary group ordering, but it at least allows
// this to work for more things than it previously did.
groupIDs = groupIDs[:16]
}
err := syscall.Setgroups(groupIDs)
if err != nil && os.Geteuid() != 0 && groupsMatchCurrent(groupIDs) {
// If we're not root, ignore a Setgroups failure if all groups are the same.
return nil
}
return err
}
func groupsMatchCurrent(groupIDs []int) bool {
existing, err := syscall.Getgroups()
if err != nil {
return false
}
if len(existing) != len(groupIDs) {
return false
}
groupIDs = slices.Clone(groupIDs)
sort.Ints(groupIDs)
sort.Ints(existing)
return slices.Equal(groupIDs, existing)
}

View File

@@ -1,21 +0,0 @@
// 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.
package tailssh
import "syscall"
func (ia *incubatorArgs) loginArgs() []string {
return []string{ia.loginCmdPath, "-fp", "-h", ia.remoteIP, ia.localUser}
}
func setGroups(groupIDs []int) error {
// darwin returns "invalid argument" if more than 16 groups are passed to syscall.Setgroups
// some info can be found here:
// https://opensource.apple.com/source/samba/samba-187.8/patches/support-darwin-initgroups-syscall.auto.html
// this fix isn't great, as anyone reading this has probably just wasted hours figuring out why
// some permissions thing isn't working, due to some arbitrary group ordering, but it at least allows
// this to work for more things than it previously did.
return syscall.Setgroups(groupIDs[:16])
}

View File

@@ -1,15 +0,0 @@
// 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.
package tailssh
import "syscall"
func (ia *incubatorArgs) loginArgs() []string {
return []string{ia.loginCmdPath, "-fp", "-h", ia.remoteIP, ia.localUser}
}
func setGroups(groupIDs []int) error {
return syscall.Setgroups(groupIDs)
}

View File

@@ -16,7 +16,6 @@ import (
"github.com/godbus/dbus/v5"
"tailscale.com/types/logger"
"tailscale.com/version/distro"
)
func init() {
@@ -173,24 +172,3 @@ func maybeStartLoginSessionLinux(logf logger.Logf, ia incubatorArgs) (func() err
}
return nil, nil
}
func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
func (ia *incubatorArgs) loginArgs() []string {
if distro.Get() == distro.Arch && !fileExists("/etc/pam.d/remote") {
// See https://github.com/tailscale/tailscale/issues/4924
//
// Arch uses a different login binary that makes the -h flag set the PAM
// service to "remote". So if they don't have that configured, don't
// pass -h.
return []string{ia.loginCmdPath, "-f", ia.localUser, "-p"}
}
return []string{ia.loginCmdPath, "-f", ia.localUser, "-h", ia.remoteIP, "-p"}
}
func setGroups(groupIDs []int) error {
return syscall.Setgroups(groupIDs)
}

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux || (darwin && !ios) || freebsd
//go:build linux || (darwin && !ios) || freebsd || openbsd
// Package tailssh is an SSH server integrated into Tailscale.
package tailssh

View File

@@ -88,7 +88,8 @@ type CapabilityVersion int
// - 49: 2022-11-03: Client understands EarlyNoise
// - 50: 2022-11-14: Client understands CapabilityIngress
// - 51: 2022-11-30: Client understands CapabilityTailnetLockAlpha
const CurrentCapabilityVersion CapabilityVersion = 51
// - 52: 2023-01-05: client can handle c2n POST /logtail/flush
const CurrentCapabilityVersion CapabilityVersion = 52
type StableID string

View File

@@ -150,7 +150,7 @@ func (a *AUM) StaticValidate() error {
return errors.New("absent parent must be represented by a nil slice")
}
for i, sig := range a.Signatures {
if len(sig.KeyID) == 0 || len(sig.Signature) != ed25519.SignatureSize {
if len(sig.KeyID) != 32 || len(sig.Signature) != ed25519.SignatureSize {
return fmt.Errorf("signature %d has missing keyID or malformed signature", i)
}
}
@@ -196,8 +196,13 @@ func (a *AUM) StaticValidate() error {
case AUMNoOp:
default:
// TODO(tom): Ignore unknown AUMs for GA.
return fmt.Errorf("unknown AUM kind: %v", a.MessageKind)
// An AUM with an unknown message kind was received! That means
// that a future version of tailscaled added some feature we don't
// understand.
//
// The future-compatibility contract for AUM message types is that
// they must only add new features, not change the semantics of existing
// mechanisms or features. As such, old clients can safely ignore them.
}
return nil
@@ -276,14 +281,20 @@ func (a *AUM) Parent() (h AUMHash, ok bool) {
return h, false
}
func (a *AUM) sign25519(priv ed25519.PrivateKey) {
func (a *AUM) sign25519(priv ed25519.PrivateKey) error {
key := Key{Kind: Key25519, Public: priv.Public().(ed25519.PublicKey)}
sigHash := a.SigHash()
keyID, err := key.ID()
if err != nil {
return err
}
a.Signatures = append(a.Signatures, tkatype.Signature{
KeyID: key.ID(),
KeyID: keyID,
Signature: ed25519.Sign(priv, sigHash[:]),
})
return nil
}
// Weight computes the 'signature weight' of the AUM

View File

@@ -189,7 +189,7 @@ func TestAUMWeight(t *testing.T) {
{
"Unary key",
AUM{
Signatures: []tkatype.Signature{{KeyID: key.ID()}},
Signatures: []tkatype.Signature{{KeyID: key.MustID()}},
},
State{
Keys: []Key{key},
@@ -199,7 +199,7 @@ func TestAUMWeight(t *testing.T) {
{
"Multiple keys",
AUM{
Signatures: []tkatype.Signature{{KeyID: key.ID()}, {KeyID: key2.ID()}},
Signatures: []tkatype.Signature{{KeyID: key.MustID()}, {KeyID: key2.MustID()}},
},
State{
Keys: []Key{key, key2},
@@ -209,7 +209,7 @@ func TestAUMWeight(t *testing.T) {
{
"Double use",
AUM{
Signatures: []tkatype.Signature{{KeyID: key.ID()}, {KeyID: key.ID()}},
Signatures: []tkatype.Signature{{KeyID: key.MustID()}, {KeyID: key.MustID()}},
},
State{
Keys: []Key{key},

View File

@@ -60,7 +60,12 @@ func (b *UpdateBuilder) mkUpdate(update AUM) error {
// AddKey adds a new key to the authority.
func (b *UpdateBuilder) AddKey(key Key) error {
if _, err := b.state.GetKey(key.ID()); err == nil {
keyID, err := key.ID()
if err != nil {
return err
}
if _, err := b.state.GetKey(keyID); err == nil {
return fmt.Errorf("cannot add key %v: already exists", key)
}
return b.mkUpdate(AUM{MessageKind: AUMAddKey, Key: &key})

View File

@@ -19,7 +19,7 @@ func (s signer25519) SignAUM(sigHash tkatype.AUMSigHash) ([]tkatype.Signature, e
key := Key{Kind: Key25519, Public: priv.Public().(ed25519.PublicKey)}
return []tkatype.Signature{{
KeyID: key.ID(),
KeyID: key.MustID(),
Signature: ed25519.Sign(priv, sigHash[:]),
}}, nil
}
@@ -54,7 +54,7 @@ func TestAuthorityBuilderAddKey(t *testing.T) {
if err := a.Inform(storage, updates); err != nil {
t.Fatalf("could not apply generated updates: %v", err)
}
if _, err := a.state.GetKey(key2.ID()); err != nil {
if _, err := a.state.GetKey(key2.MustID()); err != nil {
t.Errorf("could not read new key: %v", err)
}
}
@@ -75,7 +75,7 @@ func TestAuthorityBuilderRemoveKey(t *testing.T) {
}
b := a.NewUpdater(signer25519(priv))
if err := b.RemoveKey(key2.ID()); err != nil {
if err := b.RemoveKey(key2.MustID()); err != nil {
t.Fatalf("RemoveKey(%v) failed: %v", key2, err)
}
updates, err := b.Finalize(storage)
@@ -88,7 +88,7 @@ func TestAuthorityBuilderRemoveKey(t *testing.T) {
if err := a.Inform(storage, updates); err != nil {
t.Fatalf("could not apply generated updates: %v", err)
}
if _, err := a.state.GetKey(key2.ID()); err != ErrNoSuchKey {
if _, err := a.state.GetKey(key2.MustID()); err != ErrNoSuchKey {
t.Errorf("GetKey(key2).err = %v, want %v", err, ErrNoSuchKey)
}
}
@@ -107,8 +107,8 @@ func TestAuthorityBuilderSetKeyVote(t *testing.T) {
}
b := a.NewUpdater(signer25519(priv))
if err := b.SetKeyVote(key.ID(), 5); err != nil {
t.Fatalf("SetKeyVote(%v) failed: %v", key.ID(), err)
if err := b.SetKeyVote(key.MustID(), 5); err != nil {
t.Fatalf("SetKeyVote(%v) failed: %v", key.MustID(), err)
}
updates, err := b.Finalize(storage)
if err != nil {
@@ -120,7 +120,7 @@ func TestAuthorityBuilderSetKeyVote(t *testing.T) {
if err := a.Inform(storage, updates); err != nil {
t.Fatalf("could not apply generated updates: %v", err)
}
k, err := a.state.GetKey(key.ID())
k, err := a.state.GetKey(key.MustID())
if err != nil {
t.Fatal(err)
}
@@ -143,7 +143,7 @@ func TestAuthorityBuilderSetKeyMeta(t *testing.T) {
}
b := a.NewUpdater(signer25519(priv))
if err := b.SetKeyMeta(key.ID(), map[string]string{"b": "c"}); err != nil {
if err := b.SetKeyMeta(key.MustID(), map[string]string{"b": "c"}); err != nil {
t.Fatalf("SetKeyMeta(%v) failed: %v", key, err)
}
updates, err := b.Finalize(storage)
@@ -156,7 +156,7 @@ func TestAuthorityBuilderSetKeyMeta(t *testing.T) {
if err := a.Inform(storage, updates); err != nil {
t.Fatalf("could not apply generated updates: %v", err)
}
k, err := a.state.GetKey(key.ID())
k, err := a.state.GetKey(key.MustID())
if err != nil {
t.Fatal(err)
}
@@ -185,10 +185,10 @@ func TestAuthorityBuilderMultiple(t *testing.T) {
if err := b.AddKey(key2); err != nil {
t.Fatalf("AddKey(%v) failed: %v", key2, err)
}
if err := b.SetKeyVote(key2.ID(), 42); err != nil {
if err := b.SetKeyVote(key2.MustID(), 42); err != nil {
t.Fatalf("SetKeyVote(%v) failed: %v", key2, err)
}
if err := b.RemoveKey(key.ID()); err != nil {
if err := b.RemoveKey(key.MustID()); err != nil {
t.Fatalf("RemoveKey(%v) failed: %v", key, err)
}
updates, err := b.Finalize(storage)
@@ -201,14 +201,14 @@ func TestAuthorityBuilderMultiple(t *testing.T) {
if err := a.Inform(storage, updates); err != nil {
t.Fatalf("could not apply generated updates: %v", err)
}
k, err := a.state.GetKey(key2.ID())
k, err := a.state.GetKey(key2.MustID())
if err != nil {
t.Fatal(err)
}
if got, want := k.Votes, uint(42); got != want {
t.Errorf("key.Votes = %d, want %d", got, want)
}
if _, err := a.state.GetKey(key.ID()); err != ErrNoSuchKey {
if _, err := a.state.GetKey(key.MustID()); err != ErrNoSuchKey {
t.Errorf("GetKey(key).err = %v, want %v", err, ErrNoSuchKey)
}
}
@@ -243,7 +243,7 @@ func TestAuthorityBuilderCheckpointsAfterXUpdates(t *testing.T) {
if err := a.Inform(storage, updates); err != nil {
t.Fatalf("could not apply generated updates: %v", err)
}
if _, err := a.state.GetKey(key2.ID()); err != nil {
if _, err := a.state.GetKey(key2.MustID()); err != nil {
t.Fatal(err)
}

View File

@@ -267,7 +267,7 @@ func (c *testChain) makeAUM(v *testchainNode) AUM {
sigHash := aum.SigHash()
for _, key := range c.SignAllKeys {
aum.Signatures = append(aum.Signatures, tkatype.Signature{
KeyID: c.Key[key].ID(),
KeyID: c.Key[key].MustID(),
Signature: ed25519.Sign(c.KeyPrivs[key], sigHash[:]),
})
}
@@ -276,7 +276,7 @@ func (c *testChain) makeAUM(v *testchainNode) AUM {
// sign it using that key.
if key := v.SignedWith; key != "" {
aum.Signatures = append(aum.Signatures, tkatype.Signature{
KeyID: c.Key[key].ID(),
KeyID: c.Key[key].MustID(),
Signature: ed25519.Sign(c.KeyPrivs[key], sigHash[:]),
})
}

View File

@@ -74,14 +74,25 @@ func (k Key) Clone() Key {
return out
}
func (k Key) ID() tkatype.KeyID {
// MustID returns the KeyID of the key, panicking if an error is
// encountered. This must only be used for tests.
func (k Key) MustID() tkatype.KeyID {
id, err := k.ID()
if err != nil {
panic(err)
}
return id
}
// ID returns the KeyID of the key.
func (k Key) ID() (tkatype.KeyID, error) {
switch k.Kind {
// Because 25519 public keys are so short, we just use the 32-byte
// public as their 'key ID'.
case Key25519:
return tkatype.KeyID(k.Public)
return tkatype.KeyID(k.Public), nil
default:
panic("unsupported key kind")
return nil, fmt.Errorf("unknown key kind: %v", k.Kind)
}
}

View File

@@ -49,7 +49,7 @@ func TestVerify25519(t *testing.T) {
sigHash := aum.SigHash()
aum.Signatures = []tkatype.Signature{
{
KeyID: key.ID(),
KeyID: key.MustID(),
Signature: ed25519.Sign(priv, sigHash[:]),
},
}
@@ -92,7 +92,7 @@ func TestNLPrivate(t *testing.T) {
// We manually compute the keyID, so make sure its consistent with
// tka.Key.ID().
if !bytes.Equal(k.ID(), p.KeyID()) {
t.Errorf("private.KeyID() & tka KeyID differ: %x != %x", k.ID(), p.KeyID())
if !bytes.Equal(k.MustID(), p.KeyID()) {
t.Errorf("private.KeyID() & tka KeyID differ: %x != %x", k.MustID(), p.KeyID())
}
}

View File

@@ -273,7 +273,7 @@ func TestForkingPropagation(t *testing.T) {
F1.template = removeKey1`,
optSignAllUsing("key2"),
optKey("key2", key, priv),
optTemplate("removeKey1", AUM{MessageKind: AUMRemoveKey, KeyID: s.defaultKey.ID()})),
optTemplate("removeKey1", AUM{MessageKind: AUMRemoveKey, KeyID: s.defaultKey.MustID()})),
})
s.testSyncsBetween(control, n2)
s.checkHaveConsensus(control, n2)
@@ -282,10 +282,10 @@ func TestForkingPropagation(t *testing.T) {
s.testSyncsBetween(control, n1)
s.checkHaveConsensus(n1, n2)
if _, err := n1.A.state.GetKey(s.defaultKey.ID()); err != ErrNoSuchKey {
if _, err := n1.A.state.GetKey(s.defaultKey.MustID()); err != ErrNoSuchKey {
t.Error("default key was still present")
}
if _, err := n1.A.state.GetKey(key.ID()); err != nil {
if _, err := n1.A.state.GetKey(key.MustID()); err != nil {
t.Errorf("key2 was not trusted: %v", err)
}
}
@@ -305,7 +305,9 @@ func TestInvalidAUMPropagationRejected(t *testing.T) {
l3 := n1.AUMs["L3"]
l3H := l3.Hash()
l4 := AUM{MessageKind: AUMAddKey, PrevAUMHash: l3H[:]}
l4.sign25519(s.defaultPriv)
if err := l4.sign25519(s.defaultPriv); err != nil {
t.Fatal(err)
}
l4H := l4.Hash()
n1.storage.CommitVerifiedAUMs([]AUM{l4})
n1.A.state.LastAUMHash = &l4H
@@ -371,7 +373,9 @@ func TestBadSigAUMPropagationRejected(t *testing.T) {
l3 := n1.AUMs["L3"]
l3H := l3.Hash()
l4 := AUM{MessageKind: AUMNoOp, PrevAUMHash: l3H[:]}
l4.sign25519(s.defaultPriv)
if err := l4.sign25519(s.defaultPriv); err != nil {
t.Fatal(err)
}
l4.Signatures[0].Signature[3] = 42
l4H := l4.Hash()
n1.storage.CommitVerifiedAUMs([]AUM{l4})

View File

@@ -22,7 +22,7 @@ func TestSigDirect(t *testing.T) {
sig := NodeKeySignature{
SigKind: SigDirect,
KeyID: k.ID(),
KeyID: k.MustID(),
Pubkey: nodeKeyPub,
}
sigHash := sig.SigHash()
@@ -65,7 +65,7 @@ func TestSigNested(t *testing.T) {
// the network-lock key.
nestedSig := NodeKeySignature{
SigKind: SigDirect,
KeyID: k.ID(),
KeyID: k.MustID(),
Pubkey: oldPub,
WrappingPubkey: rPub,
}
@@ -132,7 +132,7 @@ func TestSigNested_DeepNesting(t *testing.T) {
// the network-lock key.
nestedSig := NodeKeySignature{
SigKind: SigDirect,
KeyID: k.ID(),
KeyID: k.MustID(),
Pubkey: oldPub,
WrappingPubkey: rPub,
}
@@ -204,7 +204,7 @@ func TestSigCredential(t *testing.T) {
// public key.
nestedSig := NodeKeySignature{
SigKind: SigCredential,
KeyID: k.ID(),
KeyID: k.MustID(),
WrappingPubkey: cPub,
}
sigHash := nestedSig.SigHash()
@@ -280,11 +280,11 @@ func TestSigSerializeUnserialize(t *testing.T) {
key := Key{Kind: Key25519, Public: pub, Votes: 2}
sig := NodeKeySignature{
SigKind: SigDirect,
KeyID: key.ID(),
KeyID: key.MustID(),
Pubkey: nodeKeyPub,
Nested: &NodeKeySignature{
SigKind: SigDirect,
KeyID: key.ID(),
KeyID: key.MustID(),
Pubkey: nodeKeyPub,
},
}

View File

@@ -29,9 +29,6 @@ type State struct {
// DisablementSecrets are KDF-derived values which can be used
// to turn off the TKA in the event of a consensus-breaking bug.
//
// TODO(tom): This is an alpha feature, remove this mechanism once
// we have confidence in our implementation.
DisablementSecrets [][]byte `cbor:"2,keyasint"`
// Keys are the public keys currently trusted by the TKA.
@@ -48,7 +45,12 @@ type State struct {
// GetKey returns the trusted key with the specified KeyID.
func (s State) GetKey(key tkatype.KeyID) (Key, error) {
for _, k := range s.Keys {
if bytes.Equal(k.ID(), key) {
keyID, err := k.ID()
if err != nil {
return Key{}, err
}
if bytes.Equal(keyID, key) {
return k, nil
}
}
@@ -172,7 +174,11 @@ func (s State) applyVerifiedAUM(update AUM) (State, error) {
if update.Key == nil {
return State{}, errors.New("no key to add provided")
}
if _, err := s.GetKey(update.Key.ID()); err == nil {
keyID, err := update.Key.ID()
if err != nil {
return State{}, err
}
if _, err := s.GetKey(keyID); err == nil {
return State{}, errors.New("key already exists")
}
out := s.cloneForUpdate(&update)
@@ -195,7 +201,11 @@ func (s State) applyVerifiedAUM(update AUM) (State, error) {
}
out := s.cloneForUpdate(&update)
for i := range out.Keys {
if bytes.Equal(out.Keys[i].ID(), update.KeyID) {
keyID, err := out.Keys[i].ID()
if err != nil {
return State{}, err
}
if bytes.Equal(keyID, update.KeyID) {
out.Keys[i] = k
}
}
@@ -204,7 +214,11 @@ func (s State) applyVerifiedAUM(update AUM) (State, error) {
case AUMRemoveKey:
idx := -1
for i := range s.Keys {
if bytes.Equal(update.KeyID, s.Keys[i].ID()) {
keyID, err := s.Keys[i].ID()
if err != nil {
return State{}, err
}
if bytes.Equal(update.KeyID, keyID) {
idx = i
break
}
@@ -217,9 +231,15 @@ func (s State) applyVerifiedAUM(update AUM) (State, error) {
return out, nil
default:
// TODO(tom): Instead of erroring, update lastHash and
// continue (to preserve future compatibility).
return State{}, fmt.Errorf("unhandled message: %v", update.MessageKind)
// An AUM with an unknown message kind was received! That means
// that a future version of tailscaled added some feature we don't
// understand.
//
// The future-compatibility contract for AUM message types is that
// they must only add new features, not change the semantics of existing
// mechanisms or features. As such, old clients can safely ignore them.
out := s.cloneForUpdate(&update)
return out, nil
}
}
@@ -274,7 +294,17 @@ func (s *State) staticValidateCheckpoint() error {
if i == j {
continue
}
if bytes.Equal(k.ID(), k2.ID()) {
id1, err := k.ID()
if err != nil {
return fmt.Errorf("key[%d]: %w", i, err)
}
id2, err := k2.ID()
if err != nil {
return fmt.Errorf("key[%d]: %w", j, err)
}
if bytes.Equal(id1, id2) {
return fmt.Errorf("key[%d]: duplicates key[%d]", i, j)
}
}

View File

@@ -124,7 +124,7 @@ func TestForkResolutionMessageType(t *testing.T) {
L3.hashSeed = 18
`,
optTemplate("addKey", AUM{MessageKind: AUMAddKey, Key: &key}),
optTemplate("removeKey", AUM{MessageKind: AUMRemoveKey, KeyID: key.ID()}))
optTemplate("removeKey", AUM{MessageKind: AUMRemoveKey, KeyID: key.MustID()}))
l1H := c.AUMHashes["L1"]
l2H := c.AUMHashes["L2"]
@@ -165,7 +165,7 @@ func TestComputeStateAt(t *testing.T) {
if err != nil {
t.Fatalf("computeStateAt(G1) failed: %v", err)
}
if _, err := state.GetKey(key.ID()); err != ErrNoSuchKey {
if _, err := state.GetKey(key.MustID()); err != ErrNoSuchKey {
t.Errorf("expected key to be missing: err = %v", err)
}
if *state.LastAUMHash != c.AUMHashes["G1"] {
@@ -182,7 +182,7 @@ func TestComputeStateAt(t *testing.T) {
if *state.LastAUMHash != wantHash {
t.Errorf("LastAUMHash = %x, want %x", *state.LastAUMHash, wantHash)
}
if _, err := state.GetKey(key.ID()); err != nil {
if _, err := state.GetKey(key.MustID()); err != nil {
t.Errorf("expected key to be present at state: err = %v", err)
}
}
@@ -234,7 +234,7 @@ func TestOpenAuthority(t *testing.T) {
i2, i2H := fakeAUM(t, 2, &i1H)
i3, i3H := fakeAUM(t, 5, &i2H)
l2, l2H := fakeAUM(t, AUM{MessageKind: AUMNoOp, KeyID: []byte{7}, Signatures: []tkatype.Signature{{KeyID: key.ID()}}}, &i3H)
l2, l2H := fakeAUM(t, AUM{MessageKind: AUMNoOp, KeyID: []byte{7}, Signatures: []tkatype.Signature{{KeyID: key.MustID()}}}, &i3H)
l3, l3H := fakeAUM(t, 4, &i3H)
g2, g2H := fakeAUM(t, 8, nil)
@@ -266,7 +266,7 @@ func TestOpenAuthority(t *testing.T) {
t.Fatalf("New() failed: %v", err)
}
// Should include the key added in G1
if _, err := a.state.GetKey(key.ID()); err != nil {
if _, err := a.state.GetKey(key.MustID()); err != nil {
t.Errorf("missing G1 key: %v", err)
}
// The head of the chain should be L2.
@@ -338,10 +338,10 @@ func TestCreateBootstrapAuthority(t *testing.T) {
}
// Both authorities should trust the key laid down in the genesis state.
if !a1.KeyTrusted(key.ID()) {
if !a1.KeyTrusted(key.MustID()) {
t.Error("a1 did not trust genesis key")
}
if !a2.KeyTrusted(key.ID()) {
if !a2.KeyTrusted(key.MustID()) {
t.Error("a2 did not trust genesis key")
}
}

View File

@@ -11,8 +11,6 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
@@ -53,7 +51,7 @@ func TestInQemu(t *testing.T) {
}
t.Logf("using %v", look)
}
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"),
cmd := exec.Command("go",
"test",
"--exec="+execVia,
"-v",

View File

@@ -35,6 +35,7 @@ import (
_ "tailscale.com/paths"
_ "tailscale.com/safesocket"
_ "tailscale.com/smallzstd"
_ "tailscale.com/ssh/tailssh"
_ "tailscale.com/syncs"
_ "tailscale.com/tailcfg"
_ "tailscale.com/tsweb"

View File

@@ -13,13 +13,11 @@ import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
)
func TestDeps(t *testing.T) {
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), "list", "-json", ".")
cmd := exec.Command("go", "list", "-json", ".")
cmd.Env = append(os.Environ(), "GOOS=ios", "GOARCH=arm64")
out, err := cmd.Output()
if err != nil {

View File

@@ -13,13 +13,11 @@ import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
)
func TestDeps(t *testing.T) {
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), "list", "-json", ".")
cmd := exec.Command("go", "list", "-json", ".")
cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm")
out, err := cmd.Output()
if err != nil {

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// 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.

View File

@@ -16,6 +16,7 @@ import (
"tailscale.com/tailcfg"
"tailscale.com/tka"
"tailscale.com/types/key"
"tailscale.com/types/views"
"tailscale.com/wgengine/filter"
)
@@ -38,9 +39,10 @@ type NetworkMap struct {
Peers []*tailcfg.Node // sorted by Node.ID
DNS tailcfg.DNSConfig
// TODO(maisem) : replace with View.
Hostinfo tailcfg.Hostinfo
PacketFilter []filter.Match
SSHPolicy *tailcfg.SSHPolicy // or nil, if not enabled/allowed
Hostinfo tailcfg.Hostinfo
PacketFilter []filter.Match
PacketFilterRules views.Slice[tailcfg.FilterRule]
SSHPolicy *tailcfg.SSHPolicy // or nil, if not enabled/allowed
// CollectServices reports whether this node's Tailnet has
// requested that info about services be included in HostInfo.

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// 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.

View File

@@ -6,6 +6,7 @@
package codegen
import (
"bufio"
"bytes"
"fmt"
"go/ast"
@@ -13,13 +14,16 @@ import (
"go/types"
"io"
"os"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/imports"
"tailscale.com/util/mak"
"tailscale.com/util/must"
)
// LoadTypes returns all named types in pkgName, keyed by their type name.
@@ -54,11 +58,13 @@ func HasNoClone(structTag string) bool {
return false
}
const header = `// Copyright (c) %d Tailscale Inc & AUTHORS All rights reserved.
const copyrightHeader = `// 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.
// Code generated by %v; DO NOT EDIT.
`
const genAndPackageHeader = `// Code generated by %v; DO NOT EDIT.
package %s
`
@@ -104,15 +110,18 @@ func (it *ImportTracker) Write(w io.Writer) {
fmt.Fprintf(w, ")\n\n")
}
func writeHeader(w io.Writer, tool, pkg string) {
fmt.Fprintf(w, header, time.Now().Year(), tool, pkg)
func writeHeader(w io.Writer, tool, pkg string, copyrightYear int) {
if copyrightYear != 0 {
fmt.Fprintf(w, copyrightHeader, copyrightYear)
}
fmt.Fprintf(w, genAndPackageHeader, tool, pkg)
}
// WritePackageFile adds a file with the provided imports and contents to package.
// The tool param is used to identify the tool that generated package file.
func WritePackageFile(tool string, pkg *packages.Package, path string, it *ImportTracker, contents *bytes.Buffer) error {
func WritePackageFile(tool string, pkg *packages.Package, path string, copyrightYear int, it *ImportTracker, contents *bytes.Buffer) error {
buf := new(bytes.Buffer)
writeHeader(buf, tool, pkg.Name)
writeHeader(buf, tool, pkg.Name, copyrightYear)
it.Write(buf)
if _, err := buf.Write(contents.Bytes()); err != nil {
return err
@@ -263,3 +272,51 @@ func IsViewType(typ types.Type) bool {
}
return t.Field(0).Name() == "ж"
}
// CopyrightYear reports the greatest copyright year in non-generated *.go files
// in the current directory, for use in the copyright line of generated code.
//
// It panics on I/O error, as it's assumed this is only being used by "go
// generate" or GitHub actions.
//
// TODO(bradfitz,dgentry): determine what heuristic to use for all this: latest
// year, earliest, none? don't list years at all? IANAL. Get advice of others.
// For now we just want to unbreak the tree. See Issue 6865.
func CopyrightYear(dir string) (year int) {
files, err := os.ReadDir(dir)
if err != nil {
panic(err)
}
rxYear := regexp.MustCompile(`^// Copyright \(c\) (20\d{2}) `)
rxGenerated := regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`)
Files:
for _, f := range files {
name := f.Name()
if !f.Type().IsRegular() ||
strings.HasPrefix(name, ".") || // includes emacs noise
!strings.HasSuffix(name, ".go") ||
strings.HasSuffix(name, "_clone.go") ||
strings.HasSuffix(name, "_view.go") ||
strings.HasSuffix(name, "_test.go") {
continue
}
src, err := os.ReadFile(filepath.Join(dir, name))
if err != nil {
panic(err)
}
bs := bufio.NewScanner(bytes.NewReader(src))
for bs.Scan() {
line := bs.Bytes()
if m := rxYear.FindSubmatch(line); m != nil {
if y := must.Get(strconv.Atoi(string(m[1]))); y > year {
year = y
}
continue
}
if rxGenerated.Match(line) {
continue Files
}
}
}
return year
}

View File

@@ -23,8 +23,7 @@ var (
func TestFindModuleInfo(t *testing.T) {
dir := t.TempDir()
name := filepath.Join(dir, "tailscaled-version-test")
goTool := filepath.Join(runtime.GOROOT(), "bin", "go"+exe())
out, err := exec.Command(goTool, "build", "-o", name, "tailscale.com/cmd/tailscaled").CombinedOutput()
out, err := exec.Command("go", "build", "-o", name, "tailscale.com/cmd/tailscaled").CombinedOutput()
if err != nil {
t.Fatalf("failed to build tailscaled: %v\n%s", err, out)
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// 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.

View File

@@ -11,7 +11,6 @@ import (
"errors"
"fmt"
"io"
"net"
"net/netip"
"reflect"
"runtime"
@@ -56,13 +55,6 @@ import (
"tailscale.com/wgengine/wglog"
)
const magicDNSPort = 53
var (
magicDNSIP = tsaddr.TailscaleServiceIP()
magicDNSIPv6 = tsaddr.TailscaleServiceIPv6()
)
// Lazy wireguard-go configuration parameters.
const (
// lazyPeerIdleThreshold is the idle duration after
@@ -462,8 +454,6 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
e.logf("Starting link monitor...")
e.linkMon.Start()
go e.pollResolver()
e.logf("Engine created.")
return e, nil
}
@@ -491,19 +481,6 @@ func echoRespondToAll(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
// tailscaled directly. Other packets are allowed to proceed into the
// main ACL filter.
func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
// Handle traffic to the service IP.
// TODO(tom): Netstack handles this when it is installed. Rip all
// this out once netstack is used on all platforms.
switch p.Dst.Addr() {
case magicDNSIP, magicDNSIPv6:
err := e.dns.EnqueuePacket(append([]byte(nil), p.Payload()...), p.IPProto, p.Src, p.Dst)
if err != nil {
e.logf("dns: enqueue: %v", err)
}
metricMagicDNSPacketIn.Add(1)
return filter.Drop
}
if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
isLocalAddr, ok := e.isLocalAddr.LoadOk()
if !ok {
@@ -523,27 +500,6 @@ func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper)
return filter.Accept
}
// pollResolver reads packets from the DNS resolver and injects them inbound.
//
// TODO(tom): Remove this fallback path (via NextPacket()) once all
// platforms use netstack.
func (e *userspaceEngine) pollResolver() {
for {
bs, err := e.dns.NextPacket()
if errors.Is(err, net.ErrClosed) {
return
}
if err != nil {
e.logf("dns: error: %v", err)
continue
}
// The leading empty space required by the semantics of
// InjectInboundDirect is allocated in NextPacket().
e.tundev.InjectInboundDirect(bs, tstun.PacketStartOffset)
}
}
var debugTrimWireguard = envknob.RegisterOptBool("TS_DEBUG_TRIM_WIREGUARD")
// forceFullWireguardConfig reports whether we should give wireguard our full

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// 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.