WIP: rebase fork onto upstream/main (v1.103.0) #15
@@ -45,7 +45,8 @@ type GokrazyUpdateArgs struct {
|
||||
URL string
|
||||
|
||||
// AllowUnsigned permits installing a GAF without signature verification.
|
||||
// This is intended for tests until signed GAF verification is implemented.
|
||||
// It is intended for tests that serve a GAF from a fileserver that does
|
||||
// not publish distsign.pub.
|
||||
AllowUnsigned bool
|
||||
|
||||
// Logf is optional; nil discards log messages.
|
||||
@@ -216,6 +217,17 @@ func (up *Updater) getUpdateFunction() (fn updateFunction, canAutoUpdate bool) {
|
||||
// release cadence with Synology Package Center and use their
|
||||
// auto-update mechanism.
|
||||
return up.updateSynology, false
|
||||
case distro.Gokrazy:
|
||||
// Only the official Tailscale appliance image (built with the
|
||||
// ts_appliance build tag, which causes hostinfo to report
|
||||
// Package="tsapp") is auto-updatable. A user running a custom
|
||||
// Gokrazy build that happens to include tailscaled must not be
|
||||
// updated with our stock GAFs. TS_FORCE_ALLOW_TSAPP_UPDATE is an
|
||||
// escape hatch for callers who know what they're doing.
|
||||
if hi.Package != "tsapp" && !envknob.Bool("TS_FORCE_ALLOW_TSAPP_UPDATE") {
|
||||
return nil, false
|
||||
}
|
||||
return up.updateGokrazy, true
|
||||
case distro.Debian: // includes Ubuntu
|
||||
return up.updateDebLike, true
|
||||
case distro.Arch:
|
||||
@@ -883,6 +895,56 @@ func (up *Updater) updateFreeBSD() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateGokrazy fetches the latest signed GAF for this gokrazy device variant
|
||||
// (vm-amd64, vm-arm64, or pi-arm64) from up.PkgsAddr and applies it via the
|
||||
// local gokrazy init update API.
|
||||
func (up *Updater) updateGokrazy() error {
|
||||
if !GokrazyUpdateFromURL.IsSet() {
|
||||
return errors.New("gokrazy update support is not linked into this binary")
|
||||
}
|
||||
variant, err := gokrazyDeviceVariant()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
latest, err := latestPackages(up.Track)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gafName, ok := latest.GAFs[variant]
|
||||
if !ok {
|
||||
return fmt.Errorf("no GAF for device %q on %q track", variant, up.Track)
|
||||
}
|
||||
if latest.GAFsVersion == "" {
|
||||
return fmt.Errorf("no GAF version on %q track", up.Track)
|
||||
}
|
||||
if !up.confirm(latest.GAFsVersion) {
|
||||
return nil
|
||||
}
|
||||
gafURL := fmt.Sprintf("%s/%s/%s", strings.TrimRight(up.PkgsAddr, "/"), up.Track, gafName)
|
||||
up.Logf("Updating to %s (%s)", latest.GAFsVersion, gafURL)
|
||||
return GokrazyUpdateFromURL.Get()(context.Background(), GokrazyUpdateArgs{
|
||||
URL: gafURL,
|
||||
Logf: up.Logf,
|
||||
})
|
||||
}
|
||||
|
||||
// gokrazyDeviceVariant returns the GAFs JSON key for the current gokrazy
|
||||
// device, e.g. "vm-amd64", "vm-arm64", or "pi-arm64". On arm64, it reads the
|
||||
// device-tree model to tell a Raspberry Pi apart from a VM.
|
||||
func gokrazyDeviceVariant() (string, error) {
|
||||
switch runtime.GOARCH {
|
||||
case "amd64":
|
||||
return "vm-amd64", nil
|
||||
case "arm64":
|
||||
b, _ := os.ReadFile("/sys/firmware/devicetree/base/model")
|
||||
if strings.HasPrefix(strings.Trim(string(b), "\x00\r\n\t "), "Raspberry Pi") {
|
||||
return "pi-arm64", nil
|
||||
}
|
||||
return "vm-arm64", nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported gokrazy GOARCH %q", runtime.GOARCH)
|
||||
}
|
||||
|
||||
func (up *Updater) updateLinuxBinary() error {
|
||||
// Root is needed to overwrite binaries and restart systemd unit.
|
||||
if err := requireRoot(); err != nil {
|
||||
@@ -1255,8 +1317,11 @@ func LatestTailscaleVersion(track string) (string, error) {
|
||||
ver = latest.MacZipsVersion
|
||||
case "linux":
|
||||
ver = latest.TarballsVersion
|
||||
if distro.Get() == distro.Synology {
|
||||
switch distro.Get() {
|
||||
case distro.Synology:
|
||||
ver = latest.SPKsVersion
|
||||
case distro.Gokrazy:
|
||||
ver = latest.GAFsVersion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1274,6 +1339,8 @@ type trackPackages struct {
|
||||
ExesVersion string
|
||||
MSIs map[string]string
|
||||
MSIsVersion string
|
||||
GAFs map[string]string
|
||||
GAFsVersion string
|
||||
MacZips map[string]string
|
||||
MacZipsVersion string
|
||||
SPKs map[string]map[string]string
|
||||
|
||||
@@ -13,9 +13,11 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/clientupdate/distsign"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
@@ -40,38 +42,23 @@ func gokrazyUpdateFromURL(ctx context.Context, args GokrazyUpdateArgs) error {
|
||||
if logf == nil {
|
||||
logf = logger.Discard
|
||||
}
|
||||
if !args.AllowUnsigned {
|
||||
return fmt.Errorf("signed GAF verification is not implemented yet; see https://github.com/tailscale/tailscale/issues/20002")
|
||||
}
|
||||
|
||||
tmp, err := os.CreateTemp("", "tailscale-gokrazy-*.gaf")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpName := tmp.Name()
|
||||
tmp.Close()
|
||||
defer os.Remove(tmpName)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", args.URL, nil)
|
||||
if err != nil {
|
||||
tmp.Close()
|
||||
return err
|
||||
}
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
tmp.Close()
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
tmp.Close()
|
||||
return fmt.Errorf("download GAF: %s", res.Status)
|
||||
}
|
||||
if _, err := io.Copy(tmp, res.Body); err != nil {
|
||||
tmp.Close()
|
||||
return err
|
||||
}
|
||||
if err := tmp.Close(); err != nil {
|
||||
return err
|
||||
if args.AllowUnsigned {
|
||||
if err := downloadGAFUnverified(ctx, args.URL, tmpName); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := downloadGAFVerified(ctx, logf, args.URL, tmpName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
zr, err := zip.OpenReader(tmpName)
|
||||
@@ -105,6 +92,59 @@ func gokrazyUpdateFromURL(ctx context.Context, args GokrazyUpdateArgs) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// downloadGAFUnverified saves the GAF at srcURL to dstPath without verifying a
|
||||
// signature. It is used only when args.AllowUnsigned is set, for tests that
|
||||
// serve the GAF from a fileserver that does not publish distsign.pub.
|
||||
func downloadGAFUnverified(ctx context.Context, srcURL, dstPath string) error {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", srcURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("download GAF: %s", res.Status)
|
||||
}
|
||||
f, err := os.Create(dstPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(f, res.Body); err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
// downloadGAFVerified saves the GAF at srcURL to dstPath, verifying the
|
||||
// detached ed25519 signature at "<srcURL>.sig" against the root signing keys
|
||||
// embedded in this binary via the distsign package.
|
||||
//
|
||||
// The signing-key bundle distsign.pub and its signature distsign.pub.sig are
|
||||
// fetched from the root of the server hosting srcURL.
|
||||
func downloadGAFVerified(ctx context.Context, logf logger.Logf, srcURL, dstPath string) error {
|
||||
u, err := url.Parse(srcURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing GAF URL %q: %w", srcURL, err)
|
||||
}
|
||||
if u.Scheme == "" || u.Host == "" {
|
||||
return fmt.Errorf("GAF URL %q is missing scheme or host", srcURL)
|
||||
}
|
||||
base := &url.URL{Scheme: u.Scheme, User: u.User, Host: u.Host}
|
||||
path := strings.TrimPrefix(u.Path, "/")
|
||||
if path == "" {
|
||||
return fmt.Errorf("GAF URL %q has no path component", srcURL)
|
||||
}
|
||||
c, err := distsign.NewClient(logf, base.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Download(ctx, path, dstPath)
|
||||
}
|
||||
|
||||
func gokrazyHTTPClient() *http.Client {
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
|
||||
@@ -126,7 +126,7 @@ func gokrazyUpdateArgsFromMagicArg(args []string) (*clientupdate.GokrazyUpdateAr
|
||||
fs.SetOutput(io.Discard)
|
||||
// This flag path is exercised end-to-end by TestGokrazyUpdatesItselfToSameImage.
|
||||
fs.StringVar(&updateURL, gokrazyUpdateFromURLMagicArg[2:], "", "URL of the Gokrazy archive format file to install")
|
||||
fs.BoolVar(&unsigned, "unsigned", false, "allow an unsigned GAF; for tests only")
|
||||
fs.BoolVar(&unsigned, "unsigned", false, "skip GAF signature verification; for tests only")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -136,9 +136,6 @@ func gokrazyUpdateArgsFromMagicArg(args []string) (*clientupdate.GokrazyUpdateAr
|
||||
if updateURL == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if !unsigned {
|
||||
return nil, errors.New("signed GAF verification is not implemented yet; see https://github.com/tailscale/tailscale/issues/20002; pass --unsigned for test updates")
|
||||
}
|
||||
if !clientupdate.GokrazyUpdateFromURL.IsSet() {
|
||||
return nil, errors.New("gokrazy update support is not linked into this binary")
|
||||
}
|
||||
|
||||
@@ -24,7 +24,15 @@
|
||||
"tailscale.com/cmd/tailscale": {
|
||||
"ExtraFilePaths": {
|
||||
"/usr": "usr-dir"
|
||||
}
|
||||
},
|
||||
"GoBuildTags": [
|
||||
"ts_appliance"
|
||||
]
|
||||
},
|
||||
"tailscale.com/cmd/tailscaled": {
|
||||
"GoBuildTags": [
|
||||
"ts_appliance"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Environment": [
|
||||
|
||||
@@ -24,7 +24,15 @@
|
||||
"tailscale.com/cmd/tailscale": {
|
||||
"ExtraFilePaths": {
|
||||
"/usr": "usr-dir"
|
||||
}
|
||||
},
|
||||
"GoBuildTags": [
|
||||
"ts_appliance"
|
||||
]
|
||||
},
|
||||
"tailscale.com/cmd/tailscaled": {
|
||||
"GoBuildTags": [
|
||||
"ts_appliance"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Environment": [
|
||||
|
||||
@@ -24,7 +24,15 @@
|
||||
"tailscale.com/cmd/tailscale": {
|
||||
"ExtraFilePaths": {
|
||||
"/usr": "usr-dir"
|
||||
}
|
||||
},
|
||||
"GoBuildTags": [
|
||||
"ts_appliance"
|
||||
]
|
||||
},
|
||||
"tailscale.com/cmd/tailscaled": {
|
||||
"GoBuildTags": [
|
||||
"ts_appliance"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Environment": [
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && ts_appliance
|
||||
|
||||
package hostinfo
|
||||
|
||||
func init() {
|
||||
linuxBuildTagPackageType = "tsapp"
|
||||
}
|
||||
Reference in New Issue
Block a user