cmd/dist,release/dist: add distsign signing hooks (#9070)

Add `dist.Signer` hook which can arbitrarily sign linux/synology
artifacts. Plumb it through in `cmd/dist` and remove existing tarball
signing key. Distsign signing will happen on a remote machine, not using
a local key.

Updates #755
Updates #8760

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
pull/9077/head
Andrew Lytvynov 1 year ago committed by GitHub
parent dc8287ab3b
commit b42c4e2da1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

6
cmd/dist/dist.go vendored

@ -19,10 +19,10 @@ import (
var synologyPackageCenter bool var synologyPackageCenter bool
func getTargets(signers unixpkgs.Signers) ([]dist.Target, error) { func getTargets() ([]dist.Target, error) {
var ret []dist.Target var ret []dist.Target
ret = append(ret, unixpkgs.Targets(signers)...) ret = append(ret, unixpkgs.Targets(unixpkgs.Signers{})...)
// Synology packages can be built either for sideloading, or for // Synology packages can be built either for sideloading, or for
// distribution by Synology in their package center. When // distribution by Synology in their package center. When
// distributed through the package center, apps can request // distributed through the package center, apps can request
@ -33,7 +33,7 @@ func getTargets(signers unixpkgs.Signers) ([]dist.Target, error) {
// Since only we can provide packages to Synology for // Since only we can provide packages to Synology for
// distribution, we default to building the "sideload" variant of // distribution, we default to building the "sideload" variant of
// packages that we distribute on pkgs.tailscale.com. // packages that we distribute on pkgs.tailscale.com.
ret = append(ret, synology.Targets(synologyPackageCenter)...) ret = append(ret, synology.Targets(synologyPackageCenter, nil)...)
return ret, nil return ret, nil
} }

@ -20,7 +20,6 @@ import (
"github.com/peterbourgon/ff/v3/ffcli" "github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/clientupdate/distsign" "tailscale.com/clientupdate/distsign"
"tailscale.com/release/dist" "tailscale.com/release/dist"
"tailscale.com/release/dist/unixpkgs"
) )
// CLI returns a CLI root command to build release packages. // CLI returns a CLI root command to build release packages.
@ -28,7 +27,7 @@ import (
// getTargets is a function that gets run in the Exec function of commands that // getTargets is a function that gets run in the Exec function of commands that
// need to know the target list. Its execution is deferred in this way to allow // need to know the target list. Its execution is deferred in this way to allow
// customization of command FlagSets with flags that influence the target list. // customization of command FlagSets with flags that influence the target list.
func CLI(getTargets func(unixpkgs.Signers) ([]dist.Target, error)) *ffcli.Command { func CLI(getTargets func() ([]dist.Target, error)) *ffcli.Command {
return &ffcli.Command{ return &ffcli.Command{
Name: "dist", Name: "dist",
ShortUsage: "dist [flags] <command> [command flags]", ShortUsage: "dist [flags] <command> [command flags]",
@ -38,7 +37,7 @@ func CLI(getTargets func(unixpkgs.Signers) ([]dist.Target, error)) *ffcli.Comman
{ {
Name: "list", Name: "list",
Exec: func(ctx context.Context, args []string) error { Exec: func(ctx context.Context, args []string) error {
targets, err := getTargets(unixpkgs.Signers{}) targets, err := getTargets()
if err != nil { if err != nil {
return err return err
} }
@ -54,11 +53,7 @@ func CLI(getTargets func(unixpkgs.Signers) ([]dist.Target, error)) *ffcli.Comman
{ {
Name: "build", Name: "build",
Exec: func(ctx context.Context, args []string) error { Exec: func(ctx context.Context, args []string) error {
tgzSigner, err := parseSigningKey(buildArgs.tgzSigningKey) targets, err := getTargets()
if err != nil {
return err
}
targets, err := getTargets(unixpkgs.Signers{Tarball: tgzSigner})
if err != nil { if err != nil {
return err return err
} }
@ -70,7 +65,6 @@ func CLI(getTargets func(unixpkgs.Signers) ([]dist.Target, error)) *ffcli.Comman
fs := flag.NewFlagSet("build", flag.ExitOnError) fs := flag.NewFlagSet("build", flag.ExitOnError)
fs.StringVar(&buildArgs.manifest, "manifest", "", "manifest file to write") fs.StringVar(&buildArgs.manifest, "manifest", "", "manifest file to write")
fs.BoolVar(&buildArgs.verbose, "verbose", false, "verbose logging") fs.BoolVar(&buildArgs.verbose, "verbose", false, "verbose logging")
fs.StringVar(&buildArgs.tgzSigningKey, "tgz-signing-key", "", "path to private signing key for release tarballs")
fs.StringVar(&buildArgs.webClientRoot, "web-client-root", "", "path to root of web client source to build") fs.StringVar(&buildArgs.webClientRoot, "web-client-root", "", "path to root of web client source to build")
return fs return fs
})(), })(),
@ -147,7 +141,6 @@ func runList(ctx context.Context, filters []string, targets []dist.Target) error
var buildArgs struct { var buildArgs struct {
manifest string manifest string
verbose bool verbose bool
tgzSigningKey string
webClientRoot string webClientRoot string
} }

@ -8,6 +8,7 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"io"
"log" "log"
"os" "os"
"os/exec" "os/exec"
@ -29,6 +30,27 @@ type Target interface {
Build(build *Build) ([]string, error) Build(build *Build) ([]string, error)
} }
// Signer is pluggable signer for a Target.
type Signer func(io.Reader) ([]byte, error)
// SignFile signs the file at filePath with s and writes the signature to
// sigPath.
func (s Signer) SignFile(filePath, sigPath string) error {
f, err := os.Open(filePath)
if err != nil {
return err
}
defer f.Close()
sig, err := s(f)
if err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
return os.WriteFile(sigPath, sig, 0644)
}
// A Build is a build context for Targets. // A Build is a build context for Targets.
type Build struct { type Build struct {
// Repo is a path to the root Go module for the build. // Repo is a path to the root Go module for the build.

@ -25,6 +25,7 @@ type target struct {
dsmMajorVersion int dsmMajorVersion int
goenv map[string]string goenv map[string]string
packageCenter bool packageCenter bool
signer dist.Signer
} }
func (t *target) String() string { func (t *target) String() string {
@ -37,15 +38,10 @@ func (t *target) Build(b *dist.Build) ([]string, error) {
return nil, err return nil, err
} }
out, err := t.buildSPK(b, inner) return t.buildSPK(b, inner)
if err != nil {
return nil, err
}
return []string{out}, nil
} }
func (t *target) buildSPK(b *dist.Build, inner *innerPkg) (string, error) { func (t *target) buildSPK(b *dist.Build, inner *innerPkg) ([]string, error) {
filename := fmt.Sprintf("tailscale-%s-%s-%d-dsm%d.spk", t.filenameArch, b.Version.Short, b.Version.Synology[t.dsmMajorVersion], t.dsmMajorVersion) filename := fmt.Sprintf("tailscale-%s-%s-%d-dsm%d.spk", t.filenameArch, b.Version.Short, b.Version.Synology[t.dsmMajorVersion], t.dsmMajorVersion)
out := filepath.Join(b.Out, filename) out := filepath.Join(b.Out, filename)
log.Printf("Building %s", filename) log.Printf("Building %s", filename)
@ -57,7 +53,7 @@ func (t *target) buildSPK(b *dist.Build, inner *innerPkg) (string, error) {
f, err := os.Create(out) f, err := os.Create(out)
if err != nil { if err != nil {
return "", err return nil, err
} }
defer f.Close() defer f.Close()
tw := tar.NewWriter(f) tw := tar.NewWriter(f)
@ -78,17 +74,27 @@ func (t *target) buildSPK(b *dist.Build, inner *innerPkg) (string, error) {
static("scripts/preupgrade", "scripts/preupgrade", 0644), static("scripts/preupgrade", "scripts/preupgrade", 0644),
) )
if err != nil { if err != nil {
return "", err return nil, err
} }
if err := tw.Close(); err != nil { if err := tw.Close(); err != nil {
return "", err return nil, err
} }
if err := f.Close(); err != nil { if err := f.Close(); err != nil {
return "", err return nil, err
}
files := []string{out}
if t.signer != nil {
outSig := out + ".sig"
if err := t.signer.SignFile(out, outSig); err != nil {
return nil, err
}
files = append(files, outSig)
} }
return out, nil return files, nil
} }
func (t *target) mkInfo(b *dist.Build, uncompressedSz int64) []byte { func (t *target) mkInfo(b *dist.Build, uncompressedSz int64) []byte {

@ -26,7 +26,7 @@ var v7Models = []string{
"monaco", "monaco",
} }
func Targets(forPackageCenter bool) []dist.Target { func Targets(forPackageCenter bool, signer dist.Signer) []dist.Target {
var ret []dist.Target var ret []dist.Target
for _, dsmVersion := range []int{6, 7} { for _, dsmVersion := range []int{6, 7} {
ret = append(ret, ret = append(ret,
@ -38,6 +38,7 @@ func Targets(forPackageCenter bool) []dist.Target {
"GOARCH": "amd64", "GOARCH": "amd64",
}, },
packageCenter: forPackageCenter, packageCenter: forPackageCenter,
signer: signer,
}, },
&target{ &target{
filenameArch: "i686", filenameArch: "i686",
@ -47,6 +48,7 @@ func Targets(forPackageCenter bool) []dist.Target {
"GOARCH": "386", "GOARCH": "386",
}, },
packageCenter: forPackageCenter, packageCenter: forPackageCenter,
signer: signer,
}, },
&target{ &target{
filenameArch: "armv8", filenameArch: "armv8",
@ -56,6 +58,7 @@ func Targets(forPackageCenter bool) []dist.Target {
"GOARCH": "arm64", "GOARCH": "arm64",
}, },
packageCenter: forPackageCenter, packageCenter: forPackageCenter,
signer: signer,
}) })
// On older ARMv5 and ARMv7 platforms, synology used a whole // On older ARMv5 and ARMv7 platforms, synology used a whole
@ -71,6 +74,7 @@ func Targets(forPackageCenter bool) []dist.Target {
"GOARM": "5", "GOARM": "5",
}, },
packageCenter: forPackageCenter, packageCenter: forPackageCenter,
signer: signer,
}) })
} }
for _, v7Arch := range v7Models { for _, v7Arch := range v7Models {
@ -83,6 +87,7 @@ func Targets(forPackageCenter bool) []dist.Target {
"GOARM": "7", "GOARM": "7",
}, },
packageCenter: forPackageCenter, packageCenter: forPackageCenter,
signer: signer,
}) })
} }
} }

@ -7,9 +7,6 @@ package unixpkgs
import ( import (
"archive/tar" "archive/tar"
"compress/gzip" "compress/gzip"
"crypto"
"crypto/rand"
"crypto/sha512"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -26,7 +23,7 @@ import (
type tgzTarget struct { type tgzTarget struct {
filenameArch string // arch to use in filename instead of deriving from goEnv["GOARCH"] filenameArch string // arch to use in filename instead of deriving from goEnv["GOARCH"]
goEnv map[string]string goEnv map[string]string
signer crypto.Signer signer dist.Signer
} }
func (t *tgzTarget) arch() string { func (t *tgzTarget) arch() string {
@ -73,11 +70,7 @@ func (t *tgzTarget) Build(b *dist.Build) ([]string, error) {
return nil, err return nil, err
} }
defer f.Close() defer f.Close()
// Hash the final output we're writing to the file, after tar and gzip gw := gzip.NewWriter(f)
// writers did their thing.
h := sha512.New()
hw := io.MultiWriter(f, h)
gw := gzip.NewWriter(hw)
defer gw.Close() defer gw.Close()
tw := tar.NewWriter(gw) tw := tar.NewWriter(gw)
defer tw.Close() defer tw.Close()
@ -161,15 +154,11 @@ func (t *tgzTarget) Build(b *dist.Build) ([]string, error) {
files := []string{filename} files := []string{filename}
if t.signer != nil { if t.signer != nil {
sig, err := t.signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA512) outSig := out + ".sig"
if err != nil { if err := t.signer.SignFile(out, outSig); err != nil {
return nil, err
}
sigFilename := out + ".sig"
if err := os.WriteFile(sigFilename, sig, 0644); err != nil {
return nil, err return nil, err
} }
files = append(files, filename+".sig") files = append(files, filepath.Base(outSig))
} }
return files, nil return files, nil
@ -291,7 +280,7 @@ func (t *debTarget) Build(b *dist.Build) ([]string, error) {
type rpmTarget struct { type rpmTarget struct {
goEnv map[string]string goEnv map[string]string
signFn func(io.Reader) ([]byte, error) signer dist.Signer
} }
func (t *rpmTarget) os() string { func (t *rpmTarget) os() string {
@ -387,7 +376,7 @@ func (t *rpmTarget) Build(b *dist.Build) ([]string, error) {
Group: "Network", Group: "Network",
Signature: nfpm.RPMSignature{ Signature: nfpm.RPMSignature{
PackageSignature: nfpm.PackageSignature{ PackageSignature: nfpm.PackageSignature{
SignFn: t.signFn, SignFn: t.signer,
}, },
}, },
}, },

@ -4,9 +4,7 @@
package unixpkgs package unixpkgs
import ( import (
"crypto"
"fmt" "fmt"
"io"
"sort" "sort"
"strings" "strings"
@ -17,8 +15,8 @@ import (
) )
type Signers struct { type Signers struct {
Tarball crypto.Signer Tarball dist.Signer
RPM func(io.Reader) ([]byte, error) RPM dist.Signer
} }
func Targets(signers Signers) []dist.Target { func Targets(signers Signers) []dist.Target {
@ -49,7 +47,7 @@ func Targets(signers Signers) []dist.Target {
"GOOS": goos, "GOOS": goos,
"GOARCH": goarch, "GOARCH": goarch,
}, },
signFn: signers.RPM, signer: signers.RPM,
}) })
} }

Loading…
Cancel
Save