1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-08 03:31:59 +02:00

feat: add artifact sign pipeline

This patch adds a generic artifact signing pipeline.

Fixes #166
This commit is contained in:
Frank Schroeder 2017-12-13 17:14:01 +01:00
parent 3ea8bc097f
commit 7b95e1e342
No known key found for this signature in database
GPG Key ID: 4D65C6EAEC87DECD
8 changed files with 197 additions and 0 deletions

View File

@ -144,6 +144,14 @@ type FPM struct {
XXX map[string]interface{} `yaml:",inline"`
}
// Sign config
type Sign struct {
Cmd string `yaml:"cmd,omitempty"`
Args []string `yaml:"args,omitempty"`
Signature string `yaml:"signature,omitempty"`
Artifacts string `yaml:"artifacts,omitempty"`
}
// SnapcraftAppMetadata for the binaries that will be in the snap package
type SnapcraftAppMetadata struct {
Plugs []string
@ -238,6 +246,7 @@ type Project struct {
Artifactories []Artifactory `yaml:",omitempty"`
Changelog Changelog `yaml:",omitempty"`
Dist string `yaml:",omitempty"`
Sign Sign `yaml:",omitempty"`
// this is a hack ¯\_(ツ)_/¯
SingleBuild Build `yaml:"build,omitempty"`

View File

@ -37,6 +37,7 @@ type Context struct {
Git GitInfo
Binaries map[string]map[string][]Binary
Artifacts []string
Checksums []string
Dockers []string
ReleaseNotes string
Version string
@ -50,6 +51,7 @@ type Context struct {
var (
artifactsLock sync.Mutex
checksumsLock sync.Mutex
dockersLock sync.Mutex
binariesLock sync.Mutex
)
@ -63,6 +65,15 @@ func (ctx *Context) AddArtifact(file string) {
log.WithField("artifact", file).Info("new release artifact")
}
// AddChecksum adds a checksum file.
func (ctx *Context) AddChecksum(file string) {
checksumsLock.Lock()
defer checksumsLock.Unlock()
file = strings.TrimPrefix(file, ctx.Config.Dist+string(filepath.Separator))
ctx.Checksums = append(ctx.Checksums, file)
log.WithField("checksum", file).Info("new checksum file")
}
// AddDocker adds a docker image to the docker images list
func (ctx *Context) AddDocker(image string) {
dockersLock.Lock()

51
docs/075-sign.md Normal file
View File

@ -0,0 +1,51 @@
---
title: Signing
---
GoReleaser can sign some or all of the generated artifacts. Signing ensures
that the artifacts have been generated by yourself and your users can verify
that by comparing the generated signature with your public signing key.
Signing works in combination with checksum files and it is generally sufficient
to sign the checksum files only.
The default is configured to create a detached signature for the checksum files
with [GunPG](https://www.gnupg.org/) and your default key. To enable signing
just add
```yaml
# goreleaser.yml
sign:
artifacts: checksum
```
To customize the signing pipeline you can use the following options:
```yml
# .goreleaser.yml
sign:
# name of the signature file.
# '${in}' is the path to the artifact that should be signed.
#
# signature: "${artifact}.sig"
# path to the signature command
#
# cmd: gpg
# command line arguments for the command
#
# to sign with a specific key use
# args: ["-u", "<key id, fingerprint, email, ...>", "--output", "${signature}", "--detach-sign", "${artifact}"]
#
# args: ["--output", "${signature}", "--detach-sign", "${artifact}"]
# which artifacts to sign
#
# checksum: only checksum file(s)
# all: all artifacts
# none: no signing
#
# artifacts: none
```

View File

@ -25,6 +25,7 @@ import (
"github.com/goreleaser/goreleaser/pipeline/fpm"
"github.com/goreleaser/goreleaser/pipeline/git"
"github.com/goreleaser/goreleaser/pipeline/release"
"github.com/goreleaser/goreleaser/pipeline/sign"
"github.com/goreleaser/goreleaser/pipeline/snapcraft"
yaml "gopkg.in/yaml.v2"
)
@ -50,6 +51,7 @@ var pipes = []pipeline.Piper{
fpm.Pipe{}, // archive via fpm (deb, rpm, etc)
snapcraft.Pipe{}, // archive via snapcraft (snap)
checksums.Pipe{}, // checksums of the files
sign.Pipe{}, // sign artifacts
docker.Pipe{}, // create and push docker images
artifactory.Pipe{}, // push to artifactory
release.Pipe{}, // release to github

View File

@ -39,6 +39,7 @@ func (Pipe) Run(ctx *context.Context) (err error) {
log.WithError(err).Errorf("failed to close %s", file.Name())
}
ctx.AddArtifact(file.Name())
ctx.AddChecksum(file.Name())
}()
var g errgroup.Group
for _, artifact := range ctx.Artifacts {

View File

@ -14,6 +14,7 @@ import (
"github.com/goreleaser/goreleaser/pipeline/docker"
"github.com/goreleaser/goreleaser/pipeline/fpm"
"github.com/goreleaser/goreleaser/pipeline/release"
"github.com/goreleaser/goreleaser/pipeline/sign"
"github.com/goreleaser/goreleaser/pipeline/snapshot"
)
@ -31,6 +32,7 @@ var defaulters = []pipeline.Defaulter{
build.Pipe{},
fpm.Pipe{},
checksums.Pipe{},
sign.Pipe{},
docker.Pipe{},
artifactory.Pipe{},
brew.Pipe{},

103
pipeline/sign/sign.go Normal file
View File

@ -0,0 +1,103 @@
package sign
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/goreleaser/goreleaser/context"
"github.com/goreleaser/goreleaser/pipeline"
)
type Pipe struct{}
func (Pipe) String() string {
return "signing artifacts"
}
func (Pipe) Default(ctx *context.Context) error {
cfg := &ctx.Config.Sign
if cfg.Cmd == "" {
cfg.Cmd = "gpg"
}
if cfg.Signature == "" {
cfg.Signature = "${artifact}.sig"
}
if len(cfg.Args) == 0 {
cfg.Args = []string{"--output", "$signature", "--detach-sig", "$artifact"}
}
if cfg.Artifacts == "" {
cfg.Artifacts = "none"
}
return nil
}
func (Pipe) Run(ctx *context.Context) error {
switch ctx.Config.Sign.Artifacts {
case "checksum":
return sign(ctx, ctx.Checksums)
case "all":
return sign(ctx, ctx.Artifacts)
case "none":
return pipeline.Skip("artifact signing disabled")
default:
return fmt.Errorf("invalid list of artifacts to sign: %s", ctx.Config.Sign.Artifacts)
}
}
func sign(ctx *context.Context, artifacts []string) error {
var sigs []string
for _, a := range artifacts {
sig, err := signone(ctx, a)
if err != nil {
return err
}
sigs = append(sigs, sig)
}
for _, sig := range sigs {
ctx.AddArtifact(sig)
}
return nil
}
func signone(ctx *context.Context, artifact string) (string, error) {
cfg := ctx.Config.Sign
artifact = filepath.Join(ctx.Config.Dist, artifact)
env := map[string]string{
"artifact": artifact,
}
sig := expand(cfg.Signature, env)
if sig == "" {
return "", fmt.Errorf("sign: signature file cannot be empty")
}
if sig == artifact {
return "", fmt.Errorf("sign: artifact and signature cannot be the same")
}
env["signature"] = sig
// todo(fs): check if $out already exists
var args []string
for _, a := range cfg.Args {
args = append(args, expand(a, env))
}
cmd := exec.Command(cfg.Cmd, args...)
output, err := cmd.CombinedOutput()
if len(output) > 200 {
output = output[:200]
}
if err != nil {
return "", fmt.Errorf("sign: %s failed with %q", cfg.Cmd, string(output))
}
return sig, nil
}
func expand(s string, env map[string]string) string {
return os.Expand(s, func(key string) string {
return env[key]
})
}

View File

@ -0,0 +1,18 @@
package sign
import (
"testing"
"github.com/stretchr/testify/assert"
)
const (
gpgPlainKeyID = "0279C27FC1602A0E"
gpgEncryptedKeyID = "2AB4ABE1A4A47546"
gpgPassword = "secret"
gpgHome = "./testdata/gnupg"
)
func TestDescription(t *testing.T) {
assert.NotEmpty(t, Pipe{}.String())
}