mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-23 09:21:41 +00:00
cmd/dist,release/dist: sign release tarballs with an ECDSA key (#8759)
Pass an optional PEM-encoded ECDSA key to `cmd/dist` to sign all built tarballs. The signature is stored next to the tarball with a `.sig` extension. Tested this with an `openssl`-generated key pair and verified the resulting signature. Updates #8760 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
parent
ed46442cb1
commit
eef15b4ffc
5
cmd/dist/dist.go
vendored
5
cmd/dist/dist.go
vendored
@ -6,6 +6,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
@ -19,10 +20,10 @@ import (
|
|||||||
|
|
||||||
var synologyPackageCenter bool
|
var synologyPackageCenter bool
|
||||||
|
|
||||||
func getTargets() ([]dist.Target, error) {
|
func getTargets(tgzSigner crypto.Signer) ([]dist.Target, error) {
|
||||||
var ret []dist.Target
|
var ret []dist.Target
|
||||||
|
|
||||||
ret = append(ret, unixpkgs.Targets()...)
|
ret = append(ret, unixpkgs.Targets(tgzSigner)...)
|
||||||
// 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
|
||||||
|
37
release/dist/cli/cli.go
vendored
37
release/dist/cli/cli.go
vendored
@ -6,6 +6,9 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -23,7 +26,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() ([]dist.Target, error)) *ffcli.Command {
|
func CLI(getTargets func(tgzSigner crypto.Signer) ([]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]",
|
||||||
@ -33,7 +36,7 @@ func CLI(getTargets func() ([]dist.Target, error)) *ffcli.Command {
|
|||||||
{
|
{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Exec: func(ctx context.Context, args []string) error {
|
Exec: func(ctx context.Context, args []string) error {
|
||||||
targets, err := getTargets()
|
targets, err := getTargets(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -49,7 +52,11 @@ func CLI(getTargets func() ([]dist.Target, error)) *ffcli.Command {
|
|||||||
{
|
{
|
||||||
Name: "build",
|
Name: "build",
|
||||||
Exec: func(ctx context.Context, args []string) error {
|
Exec: func(ctx context.Context, args []string) error {
|
||||||
targets, err := getTargets()
|
tgzSigner, err := parseSigningKey(buildArgs.tgzSigningKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
targets, err := getTargets(tgzSigner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -61,6 +68,7 @@ func CLI(getTargets func() ([]dist.Target, error)) *ffcli.Command {
|
|||||||
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")
|
||||||
return fs
|
return fs
|
||||||
})(),
|
})(),
|
||||||
LongHelp: strings.TrimSpace(`
|
LongHelp: strings.TrimSpace(`
|
||||||
@ -88,8 +96,9 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBuild(ctx context.Context, filters []string, targets []dist.Target) error {
|
func runBuild(ctx context.Context, filters []string, targets []dist.Target) error {
|
||||||
@ -142,3 +151,21 @@ func runBuild(ctx context.Context, filters []string, targets []dist.Target) erro
|
|||||||
fmt.Println("Done! Took", time.Since(st))
|
fmt.Println("Done! Took", time.Since(st))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseSigningKey(path string) (crypto.Signer, error) {
|
||||||
|
if path == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
raw, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b, rest := pem.Decode(raw)
|
||||||
|
if b == nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode PEM data in %q", path)
|
||||||
|
}
|
||||||
|
if len(rest) > 0 {
|
||||||
|
return nil, fmt.Errorf("trailing data in %q, please check that the key file was not corrupted", path)
|
||||||
|
}
|
||||||
|
return x509.ParseECPrivateKey(b.Bytes)
|
||||||
|
}
|
||||||
|
26
release/dist/unixpkgs/pkgs.go
vendored
26
release/dist/unixpkgs/pkgs.go
vendored
@ -7,6 +7,9 @@ package unixpkgs
|
|||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha512"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -22,6 +25,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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tgzTarget) arch() string {
|
func (t *tgzTarget) arch() string {
|
||||||
@ -65,7 +69,11 @@ func (t *tgzTarget) Build(b *dist.Build) ([]string, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
gw := gzip.NewWriter(f)
|
// 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)
|
||||||
defer gw.Close()
|
defer gw.Close()
|
||||||
tw := tar.NewWriter(gw)
|
tw := tar.NewWriter(gw)
|
||||||
defer tw.Close()
|
defer tw.Close()
|
||||||
@ -146,7 +154,21 @@ func (t *tgzTarget) Build(b *dist.Build) ([]string, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return []string{filename}, nil
|
files := []string{filename}
|
||||||
|
|
||||||
|
if t.signer != nil {
|
||||||
|
sig, err := t.signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA512)
|
||||||
|
if 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type debTarget struct {
|
type debTarget struct {
|
||||||
|
5
release/dist/unixpkgs/targets.go
vendored
5
release/dist/unixpkgs/targets.go
vendored
@ -4,6 +4,7 @@
|
|||||||
package unixpkgs
|
package unixpkgs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@ -14,7 +15,7 @@ import (
|
|||||||
_ "github.com/goreleaser/nfpm/rpm"
|
_ "github.com/goreleaser/nfpm/rpm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Targets() []dist.Target {
|
func Targets(signer crypto.Signer) []dist.Target {
|
||||||
var ret []dist.Target
|
var ret []dist.Target
|
||||||
for goosgoarch := range tarballs {
|
for goosgoarch := range tarballs {
|
||||||
goos, goarch := splitGoosGoarch(goosgoarch)
|
goos, goarch := splitGoosGoarch(goosgoarch)
|
||||||
@ -23,6 +24,7 @@ func Targets() []dist.Target {
|
|||||||
"GOOS": goos,
|
"GOOS": goos,
|
||||||
"GOARCH": goarch,
|
"GOARCH": goarch,
|
||||||
},
|
},
|
||||||
|
signer: signer,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for goosgoarch := range debs {
|
for goosgoarch := range debs {
|
||||||
@ -53,6 +55,7 @@ func Targets() []dist.Target {
|
|||||||
"GOARCH": "386",
|
"GOARCH": "386",
|
||||||
"GO386": "softfloat",
|
"GO386": "softfloat",
|
||||||
},
|
},
|
||||||
|
signer: signer,
|
||||||
})
|
})
|
||||||
|
|
||||||
sort.Slice(ret, func(i, j int) bool {
|
sort.Slice(ret, func(i, j int) bool {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user