Skip to content

Commit e1b9343

Browse files
2 parents 094d82f + c365c9c commit e1b9343

File tree

229 files changed

+16556
-6697
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

229 files changed

+16556
-6697
lines changed

Dockerfile

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
# Copyright (c) Tailscale Inc & AUTHORS
22
# SPDX-License-Identifier: BSD-3-Clause
33

4-
############################################################################
4+
# Note that this Dockerfile is currently NOT used to build any of the published
5+
# Tailscale container images and may have drifted from the image build mechanism
6+
# we use.
7+
# Tailscale images are currently built using https://github.com/tailscale/mkctr,
8+
# and the build script can be found in ./build_docker.sh.
59
#
6-
# WARNING: Tailscale is not yet officially supported in container
7-
# environments, such as Docker and Kubernetes. Though it should work, we
8-
# don't regularly test it, and we know there are some feature limitations.
910
#
10-
# See current bugs tagged "containers":
11-
# https://github.com/tailscale/tailscale/labels/containers
12-
#
13-
############################################################################
14-
1511
# This Dockerfile includes all the tailscale binaries.
1612
#
1713
# To build the Dockerfile:
@@ -46,7 +42,7 @@ RUN go install \
4642
gvisor.dev/gvisor/pkg/tcpip/stack \
4743
golang.org/x/crypto/ssh \
4844
golang.org/x/crypto/acme \
49-
nhooyr.io/websocket \
45+
github.com/coder/websocket \
5046
github.com/mdlayher/netlink
5147

5248
COPY . .

VERSION.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.70.0
1+
1.72.0

build_docker.sh

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
#!/usr/bin/env sh
2-
3-
#
4-
# Runs `go build` with flags configured for docker distribution. All
5-
# it does differently from `go build` is burn git commit and version
6-
# information into the binaries inside docker, so that we can track down user
7-
# issues.
8-
#
9-
############################################################################
10-
#
11-
# WARNING: Tailscale is not yet officially supported in container
12-
# environments, such as Docker and Kubernetes. Though it should work, we
13-
# don't regularly test it, and we know there are some feature limitations.
14-
#
15-
# See current bugs tagged "containers":
16-
# https://github.com/tailscale/tailscale/labels/containers
172
#
18-
############################################################################
3+
# This script builds Tailscale container images using
4+
# github.com/tailscale/mkctr.
5+
# By default the images will be tagged with the current version and git
6+
# hash of this repository as produced by ./cmd/mkversion.
7+
# This is the image build mechanim used to build the official Tailscale
8+
# container images.
199

2010
set -eu
2111

@@ -49,7 +39,7 @@ case "$TARGET" in
4939
-X tailscale.com/version.gitCommitStamp=${VERSION_GIT_HASH}" \
5040
--base="${BASE}" \
5141
--tags="${TAGS}" \
52-
--gotags="ts_kube" \
42+
--gotags="ts_kube,ts_package_container" \
5343
--repos="${REPOS}" \
5444
--push="${PUSH}" \
5545
--target="${PLATFORM}" \

client/tailscale/acl.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,12 @@ func (c *Client) ACLHuJSON(ctx context.Context) (acl *ACLHuJSON, err error) {
161161
// ACLTestFailureSummary specifies the JSON format sent to the
162162
// JavaScript client to be rendered in the HTML.
163163
type ACLTestFailureSummary struct {
164-
User string `json:"user,omitempty"`
164+
// User is the source ("src") value of the ACL test that failed.
165+
// The name "user" is a legacy holdover from the original naming and
166+
// is kept for compatibility but it may also contain any value
167+
// that's valid in a ACL test "src" field.
168+
User string `json:"user,omitempty"`
169+
165170
Errors []string `json:"errors,omitempty"`
166171
Warnings []string `json:"warnings,omitempty"`
167172
}
@@ -281,6 +286,9 @@ type UserRuleMatch struct {
281286
Users []string `json:"users"`
282287
Ports []string `json:"ports"`
283288
LineNumber int `json:"lineNumber"`
289+
// Via is the list of targets through which Users can access Ports.
290+
// See https://tailscale.com/kb/1378/via for more information.
291+
Via []string `json:"via,omitempty"`
284292

285293
// Postures is a list of posture policies that are
286294
// associated with this match. The rules can be looked

client/tailscale/localclient.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ type LocalClient struct {
6969
// connecting to the GUI client variants.
7070
UseSocketOnly bool
7171

72+
// OmitAuth, if true, omits sending the local Tailscale daemon any
73+
// authentication token that might be required by the platform.
74+
//
75+
// As of 2024-08-12, only macOS uses an authentication token. OmitAuth is
76+
// meant for when Dial is set and the LocalAPI is being proxied to a
77+
// different operating system, such as in integration tests.
78+
OmitAuth bool
79+
7280
// tsClient does HTTP requests to the local Tailscale daemon.
7381
// It's lazily initialized on first use.
7482
tsClient *http.Client
@@ -124,8 +132,10 @@ func (lc *LocalClient) DoLocalRequest(req *http.Request) (*http.Response, error)
124132
},
125133
}
126134
})
127-
if _, token, err := safesocket.LocalTCPPortAndToken(); err == nil {
128-
req.SetBasicAuth("", token)
135+
if !lc.OmitAuth {
136+
if _, token, err := safesocket.LocalTCPPortAndToken(); err == nil {
137+
req.SetBasicAuth("", token)
138+
}
129139
}
130140
return lc.tsClient.Do(req)
131141
}
@@ -933,7 +943,20 @@ func CertPair(ctx context.Context, domain string) (certPEM, keyPEM []byte, err e
933943
//
934944
// API maturity: this is considered a stable API.
935945
func (lc *LocalClient) CertPair(ctx context.Context, domain string) (certPEM, keyPEM []byte, err error) {
936-
res, err := lc.send(ctx, "GET", "/localapi/v0/cert/"+domain+"?type=pair", 200, nil)
946+
return lc.CertPairWithValidity(ctx, domain, 0)
947+
}
948+
949+
// CertPairWithValidity returns a cert and private key for the provided DNS
950+
// domain.
951+
//
952+
// It returns a cached certificate from disk if it's still valid.
953+
// When minValidity is non-zero, the returned certificate will be valid for at
954+
// least the given duration, if permitted by the CA. If the certificate is
955+
// valid, but for less than minValidity, it will be synchronously renewed.
956+
//
957+
// API maturity: this is considered a stable API.
958+
func (lc *LocalClient) CertPairWithValidity(ctx context.Context, domain string, minValidity time.Duration) (certPEM, keyPEM []byte, err error) {
959+
res, err := lc.send(ctx, "GET", fmt.Sprintf("/localapi/v0/cert/%s?type=pair&min_validity=%s", domain, minValidity), 200, nil)
937960
if err != nil {
938961
return nil, nil, err
939962
}

client/web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.0.1",
44
"license": "BSD-3-Clause",
55
"engines": {
6-
"node": "18.16.1",
6+
"node": "18.20.4",
77
"yarn": "1.22.19"
88
},
99
"type": "module",

cmd/containerboot/main.go

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@
5252
// ${TS_CERT_DOMAIN}, it will be replaced with the value of the available FQDN.
5353
// It cannot be used in conjunction with TS_DEST_IP. The file is watched for changes,
5454
// and will be re-applied when it changes.
55+
// - TS_HEALTHCHECK_ADDR_PORT: if specified, an HTTP health endpoint will be
56+
// served at /healthz at the provided address, which should be in form [<address>]:<port>.
57+
// If not set, no health check will be run. If set to :<port>, addr will default to 0.0.0.0
58+
// The health endpoint will return 200 OK if this node has at least one tailnet IP address,
59+
// otherwise returns 503.
60+
// NB: the health criteria might change in the future.
5561
// - TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR: if specified, a path to a
5662
// directory that containers tailscaled config in file. The config file needs to be
5763
// named cap-<current-tailscaled-cap>.hujson. If this is set, TS_HOSTNAME,
@@ -95,6 +101,7 @@ import (
95101
"log"
96102
"math"
97103
"net"
104+
"net/http"
98105
"net/netip"
99106
"os"
100107
"os/exec"
@@ -158,6 +165,7 @@ func main() {
158165
AllowProxyingClusterTrafficViaIngress: defaultBool("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS", false),
159166
PodIP: defaultEnv("POD_IP", ""),
160167
EnableForwardingOptimizations: defaultBool("TS_EXPERIMENTAL_ENABLE_FORWARDING_OPTIMIZATIONS", false),
168+
HealthCheckAddrPort: defaultEnv("TS_HEALTHCHECK_ADDR_PORT", ""),
161169
}
162170

163171
if err := cfg.validate(); err != nil {
@@ -349,6 +357,9 @@ authLoop:
349357

350358
certDomain = new(atomic.Pointer[string])
351359
certDomainChanged = make(chan bool, 1)
360+
361+
h = &healthz{} // http server for the healthz endpoint
362+
healthzRunner = sync.OnceFunc(func() { runHealthz(cfg.HealthCheckAddrPort, h) })
352363
)
353364
if cfg.ServeConfigPath != "" {
354365
go watchServeConfigChanges(ctx, cfg.ServeConfigPath, certDomainChanged, certDomain, client)
@@ -565,6 +576,13 @@ runLoop:
565576
log.Fatalf("storing device IPs and FQDN in Kubernetes Secret: %v", err)
566577
}
567578
}
579+
580+
if cfg.HealthCheckAddrPort != "" {
581+
h.Lock()
582+
h.hasAddrs = len(addrs) != 0
583+
h.Unlock()
584+
healthzRunner()
585+
}
568586
}
569587
if !startupTasksDone {
570588
// For containerboot instances that act as TCP
@@ -1152,7 +1170,8 @@ type settings struct {
11521170
// PodIP is the IP of the Pod if running in Kubernetes. This is used
11531171
// when setting up rules to proxy cluster traffic to cluster ingress
11541172
// target.
1155-
PodIP string
1173+
PodIP string
1174+
HealthCheckAddrPort string
11561175
}
11571176

11581177
func (s *settings) validate() error {
@@ -1201,6 +1220,11 @@ func (s *settings) validate() error {
12011220
if s.EnableForwardingOptimizations && s.UserspaceMode {
12021221
return errors.New("TS_EXPERIMENTAL_ENABLE_FORWARDING_OPTIMIZATIONS is not supported in userspace mode")
12031222
}
1223+
if s.HealthCheckAddrPort != "" {
1224+
if _, err := netip.ParseAddrPort(s.HealthCheckAddrPort); err != nil {
1225+
return fmt.Errorf("error parsing TS_HEALTH_CHECK_ADDR_PORT value %q: %w", s.HealthCheckAddrPort, err)
1226+
}
1227+
}
12041228
return nil
12051229
}
12061230

@@ -1374,3 +1398,41 @@ func tailscaledConfigFilePath() string {
13741398
log.Printf("Using tailscaled config file %q for capability version %q", maxCompatVer, tailcfg.CurrentCapabilityVersion)
13751399
return path.Join(dir, kubeutils.TailscaledConfigFileNameForCap(maxCompatVer))
13761400
}
1401+
1402+
// healthz is a simple health check server, if enabled it returns 200 OK if
1403+
// this tailscale node currently has at least one tailnet IP address else
1404+
// returns 503.
1405+
type healthz struct {
1406+
sync.Mutex
1407+
hasAddrs bool
1408+
}
1409+
1410+
func (h *healthz) ServeHTTP(w http.ResponseWriter, r *http.Request) {
1411+
h.Lock()
1412+
defer h.Unlock()
1413+
if h.hasAddrs {
1414+
w.Write([]byte("ok"))
1415+
} else {
1416+
http.Error(w, "node currently has no tailscale IPs", http.StatusInternalServerError)
1417+
}
1418+
}
1419+
1420+
// runHealthz runs a simple HTTP health endpoint on /healthz, listening on the
1421+
// provided address. A containerized tailscale instance is considered healthy if
1422+
// it has at least one tailnet IP address.
1423+
func runHealthz(addr string, h *healthz) {
1424+
lis, err := net.Listen("tcp", addr)
1425+
if err != nil {
1426+
log.Fatalf("error listening on the provided health endpoint address %q: %v", addr, err)
1427+
}
1428+
mux := http.NewServeMux()
1429+
mux.Handle("/healthz", h)
1430+
log.Printf("Running healthcheck endpoint at %s/healthz", addr)
1431+
hs := &http.Server{Handler: mux}
1432+
1433+
go func() {
1434+
if err := hs.Serve(lis); err != nil {
1435+
log.Fatalf("failed running health endpoint: %v", err)
1436+
}
1437+
}()
1438+
}

cmd/derper/depaware.txt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
77
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
88
github.com/beorn7/perks/quantile from github.com/prometheus/client_golang/prometheus
99
💣 github.com/cespare/xxhash/v2 from github.com/prometheus/client_golang/prometheus
10+
github.com/coder/websocket from tailscale.com/cmd/derper+
11+
github.com/coder/websocket/internal/errd from github.com/coder/websocket
12+
github.com/coder/websocket/internal/util from github.com/coder/websocket
13+
github.com/coder/websocket/internal/xsync from github.com/coder/websocket
1014
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
1115
W 💣 github.com/dblohm7/wingoes from tailscale.com/util/winutil
1216
github.com/fxamacker/cbor/v2 from tailscale.com/tka
13-
github.com/go-json-experiment/json from tailscale.com/types/opt
17+
github.com/go-json-experiment/json from tailscale.com/types/opt+
1418
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+
1519
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+
1620
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json+
@@ -82,10 +86,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
8286
google.golang.org/protobuf/runtime/protoiface from google.golang.org/protobuf/internal/impl+
8387
google.golang.org/protobuf/runtime/protoimpl from github.com/prometheus/client_model/go+
8488
google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+
85-
nhooyr.io/websocket from tailscale.com/cmd/derper+
86-
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
87-
nhooyr.io/websocket/internal/util from nhooyr.io/websocket
88-
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
8989
tailscale.com from tailscale.com/version
9090
tailscale.com/atomicfile from tailscale.com/cmd/derper+
9191
tailscale.com/client/tailscale from tailscale.com/derp
@@ -146,9 +146,11 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
146146
tailscale.com/util/cloudenv from tailscale.com/hostinfo+
147147
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy
148148
tailscale.com/util/ctxkey from tailscale.com/tsweb+
149+
💣 tailscale.com/util/deephash from tailscale.com/util/syspolicy/setting
149150
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
150151
tailscale.com/util/dnsname from tailscale.com/hostinfo+
151152
tailscale.com/util/fastuuid from tailscale.com/tsweb
153+
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
152154
tailscale.com/util/httpm from tailscale.com/client/tailscale
153155
tailscale.com/util/lineread from tailscale.com/hostinfo+
154156
L tailscale.com/util/linuxfw from tailscale.com/net/netns
@@ -159,6 +161,8 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
159161
tailscale.com/util/singleflight from tailscale.com/net/dnscache
160162
tailscale.com/util/slicesx from tailscale.com/cmd/derper+
161163
tailscale.com/util/syspolicy from tailscale.com/ipn
164+
tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy/setting
165+
tailscale.com/util/syspolicy/setting from tailscale.com/util/syspolicy
162166
tailscale.com/util/vizerror from tailscale.com/tailcfg+
163167
W 💣 tailscale.com/util/winutil from tailscale.com/hostinfo+
164168
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+
@@ -180,6 +184,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
180184
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
181185
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
182186
W golang.org/x/exp/constraints from tailscale.com/util/winutil
187+
golang.org/x/exp/maps from tailscale.com/util/syspolicy/setting
183188
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
184189
golang.org/x/net/dns/dnsmessage from net+
185190
golang.org/x/net/http/httpguts from net/http

cmd/derper/derper.go

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ func main() {
237237
tsweb.AddBrowserHeaders(w)
238238
io.WriteString(w, "User-agent: *\nDisallow: /\n")
239239
}))
240-
mux.Handle("/generate_204", http.HandlerFunc(serveNoContent))
240+
mux.Handle("/generate_204", http.HandlerFunc(derphttp.ServeNoContent))
241241
debug := tsweb.Debugger(mux)
242242
debug.KV("TLS hostname", *hostname)
243243
debug.KV("Mesh key", s.HasMeshKey())
@@ -337,7 +337,7 @@ func main() {
337337
if *httpPort > -1 {
338338
go func() {
339339
port80mux := http.NewServeMux()
340-
port80mux.HandleFunc("/generate_204", serveNoContent)
340+
port80mux.HandleFunc("/generate_204", derphttp.ServeNoContent)
341341
port80mux.Handle("/", certManager.HTTPHandler(tsweb.Port80Handler{Main: mux}))
342342
port80srv := &http.Server{
343343
Addr: net.JoinHostPort(listenHost, fmt.Sprintf("%d", *httpPort)),
@@ -378,31 +378,6 @@ func main() {
378378
}
379379
}
380380

381-
const (
382-
noContentChallengeHeader = "X-Tailscale-Challenge"
383-
noContentResponseHeader = "X-Tailscale-Response"
384-
)
385-
386-
// For captive portal detection
387-
func serveNoContent(w http.ResponseWriter, r *http.Request) {
388-
if challenge := r.Header.Get(noContentChallengeHeader); challenge != "" {
389-
badChar := strings.IndexFunc(challenge, func(r rune) bool {
390-
return !isChallengeChar(r)
391-
}) != -1
392-
if len(challenge) <= 64 && !badChar {
393-
w.Header().Set(noContentResponseHeader, "response "+challenge)
394-
}
395-
}
396-
w.WriteHeader(http.StatusNoContent)
397-
}
398-
399-
func isChallengeChar(c rune) bool {
400-
// Semi-randomly chosen as a limited set of valid characters
401-
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') ||
402-
('0' <= c && c <= '9') ||
403-
c == '.' || c == '-' || c == '_'
404-
}
405-
406381
var validProdHostname = regexp.MustCompile(`^derp([^.]*)\.tailscale\.com\.?$`)
407382

408383
func prodAutocertHostPolicy(_ context.Context, host string) error {

0 commit comments

Comments
 (0)