From b42c4e2da19f5f0626628371b23527ae4a07708a Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Thu, 24 Aug 2023 15:36:47 -0600 Subject: [PATCH] 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 --- cmd/dist/dist.go | 6 +++--- release/dist/cli/cli.go | 13 +++---------- release/dist/dist.go | 22 ++++++++++++++++++++++ release/dist/synology/pkgs.go | 30 ++++++++++++++++++------------ release/dist/synology/targets.go | 7 ++++++- release/dist/unixpkgs/pkgs.go | 25 +++++++------------------ release/dist/unixpkgs/targets.go | 8 +++----- 7 files changed, 62 insertions(+), 49 deletions(-) diff --git a/cmd/dist/dist.go b/cmd/dist/dist.go index 1b7a73c16..31c3fc872 100644 --- a/cmd/dist/dist.go +++ b/cmd/dist/dist.go @@ -19,10 +19,10 @@ var synologyPackageCenter bool -func getTargets(signers unixpkgs.Signers) ([]dist.Target, error) { +func getTargets() ([]dist.Target, error) { 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 // distribution by Synology in their package center. When // 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 // distribution, we default to building the "sideload" variant of // packages that we distribute on pkgs.tailscale.com. - ret = append(ret, synology.Targets(synologyPackageCenter)...) + ret = append(ret, synology.Targets(synologyPackageCenter, nil)...) return ret, nil } diff --git a/release/dist/cli/cli.go b/release/dist/cli/cli.go index 42b1b6314..3ae613a34 100644 --- a/release/dist/cli/cli.go +++ b/release/dist/cli/cli.go @@ -20,7 +20,6 @@ "github.com/peterbourgon/ff/v3/ffcli" "tailscale.com/clientupdate/distsign" "tailscale.com/release/dist" - "tailscale.com/release/dist/unixpkgs" ) // CLI returns a CLI root command to build release packages. @@ -28,7 +27,7 @@ // 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 // 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{ Name: "dist", ShortUsage: "dist [flags] [command flags]", @@ -38,7 +37,7 @@ func CLI(getTargets func(unixpkgs.Signers) ([]dist.Target, error)) *ffcli.Comman { Name: "list", Exec: func(ctx context.Context, args []string) error { - targets, err := getTargets(unixpkgs.Signers{}) + targets, err := getTargets() if err != nil { return err } @@ -54,11 +53,7 @@ func CLI(getTargets func(unixpkgs.Signers) ([]dist.Target, error)) *ffcli.Comman { Name: "build", Exec: func(ctx context.Context, args []string) error { - tgzSigner, err := parseSigningKey(buildArgs.tgzSigningKey) - if err != nil { - return err - } - targets, err := getTargets(unixpkgs.Signers{Tarball: tgzSigner}) + targets, err := getTargets() if err != nil { return err } @@ -70,7 +65,6 @@ func CLI(getTargets func(unixpkgs.Signers) ([]dist.Target, error)) *ffcli.Comman fs := flag.NewFlagSet("build", flag.ExitOnError) fs.StringVar(&buildArgs.manifest, "manifest", "", "manifest file to write") 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") return fs })(), @@ -147,7 +141,6 @@ func runList(ctx context.Context, filters []string, targets []dist.Target) error var buildArgs struct { manifest string verbose bool - tgzSigningKey string webClientRoot string } diff --git a/release/dist/dist.go b/release/dist/dist.go index 41ffe05f7..95225e470 100644 --- a/release/dist/dist.go +++ b/release/dist/dist.go @@ -8,6 +8,7 @@ "bytes" "errors" "fmt" + "io" "log" "os" "os/exec" @@ -29,6 +30,27 @@ type Target interface { 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. type Build struct { // Repo is a path to the root Go module for the build. diff --git a/release/dist/synology/pkgs.go b/release/dist/synology/pkgs.go index 67deab4d1..b1c5c3886 100644 --- a/release/dist/synology/pkgs.go +++ b/release/dist/synology/pkgs.go @@ -25,6 +25,7 @@ type target struct { dsmMajorVersion int goenv map[string]string packageCenter bool + signer dist.Signer } func (t *target) String() string { @@ -37,15 +38,10 @@ func (t *target) Build(b *dist.Build) ([]string, error) { return nil, err } - out, err := t.buildSPK(b, inner) - if err != nil { - return nil, err - } - - return []string{out}, nil + return t.buildSPK(b, inner) } -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) out := filepath.Join(b.Out, 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) if err != nil { - return "", err + return nil, err } defer f.Close() 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), ) if err != nil { - return "", err + return nil, err } if err := tw.Close(); err != nil { - return "", err + return nil, err } if err := f.Close(); err != nil { - return "", err + return nil, err } - return out, nil + 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 files, nil } func (t *target) mkInfo(b *dist.Build, uncompressedSz int64) []byte { diff --git a/release/dist/synology/targets.go b/release/dist/synology/targets.go index c05f92ecd..cea372e1a 100644 --- a/release/dist/synology/targets.go +++ b/release/dist/synology/targets.go @@ -26,7 +26,7 @@ "monaco", } -func Targets(forPackageCenter bool) []dist.Target { +func Targets(forPackageCenter bool, signer dist.Signer) []dist.Target { var ret []dist.Target for _, dsmVersion := range []int{6, 7} { ret = append(ret, @@ -38,6 +38,7 @@ func Targets(forPackageCenter bool) []dist.Target { "GOARCH": "amd64", }, packageCenter: forPackageCenter, + signer: signer, }, &target{ filenameArch: "i686", @@ -47,6 +48,7 @@ func Targets(forPackageCenter bool) []dist.Target { "GOARCH": "386", }, packageCenter: forPackageCenter, + signer: signer, }, &target{ filenameArch: "armv8", @@ -56,6 +58,7 @@ func Targets(forPackageCenter bool) []dist.Target { "GOARCH": "arm64", }, packageCenter: forPackageCenter, + signer: signer, }) // On older ARMv5 and ARMv7 platforms, synology used a whole @@ -71,6 +74,7 @@ func Targets(forPackageCenter bool) []dist.Target { "GOARM": "5", }, packageCenter: forPackageCenter, + signer: signer, }) } for _, v7Arch := range v7Models { @@ -83,6 +87,7 @@ func Targets(forPackageCenter bool) []dist.Target { "GOARM": "7", }, packageCenter: forPackageCenter, + signer: signer, }) } } diff --git a/release/dist/unixpkgs/pkgs.go b/release/dist/unixpkgs/pkgs.go index 9047cd096..2efcf4a6f 100644 --- a/release/dist/unixpkgs/pkgs.go +++ b/release/dist/unixpkgs/pkgs.go @@ -7,9 +7,6 @@ import ( "archive/tar" "compress/gzip" - "crypto" - "crypto/rand" - "crypto/sha512" "errors" "fmt" "io" @@ -26,7 +23,7 @@ type tgzTarget struct { filenameArch string // arch to use in filename instead of deriving from goEnv["GOARCH"] goEnv map[string]string - signer crypto.Signer + signer dist.Signer } func (t *tgzTarget) arch() string { @@ -73,11 +70,7 @@ func (t *tgzTarget) Build(b *dist.Build) ([]string, error) { return nil, err } defer f.Close() - // Hash the final output we're writing to the file, after tar and gzip - // writers did their thing. - h := sha512.New() - hw := io.MultiWriter(f, h) - gw := gzip.NewWriter(hw) + gw := gzip.NewWriter(f) defer gw.Close() tw := tar.NewWriter(gw) defer tw.Close() @@ -161,15 +154,11 @@ func (t *tgzTarget) Build(b *dist.Build) ([]string, error) { files := []string{filename} if t.signer != nil { - sig, err := t.signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA512) - if err != nil { + outSig := out + ".sig" + 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 - } - files = append(files, filename+".sig") + files = append(files, filepath.Base(outSig)) } return files, nil @@ -291,7 +280,7 @@ func (t *debTarget) Build(b *dist.Build) ([]string, error) { type rpmTarget struct { goEnv map[string]string - signFn func(io.Reader) ([]byte, error) + signer dist.Signer } func (t *rpmTarget) os() string { @@ -387,7 +376,7 @@ func (t *rpmTarget) Build(b *dist.Build) ([]string, error) { Group: "Network", Signature: nfpm.RPMSignature{ PackageSignature: nfpm.PackageSignature{ - SignFn: t.signFn, + SignFn: t.signer, }, }, }, diff --git a/release/dist/unixpkgs/targets.go b/release/dist/unixpkgs/targets.go index 9a90bab4d..42bab6d3b 100644 --- a/release/dist/unixpkgs/targets.go +++ b/release/dist/unixpkgs/targets.go @@ -4,9 +4,7 @@ package unixpkgs import ( - "crypto" "fmt" - "io" "sort" "strings" @@ -17,8 +15,8 @@ ) type Signers struct { - Tarball crypto.Signer - RPM func(io.Reader) ([]byte, error) + Tarball dist.Signer + RPM dist.Signer } func Targets(signers Signers) []dist.Target { @@ -49,7 +47,7 @@ func Targets(signers Signers) []dist.Target { "GOOS": goos, "GOARCH": goarch, }, - signFn: signers.RPM, + signer: signers.RPM, }) }