mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-03-11 14:39:28 +02:00
feat: sign with env and output certificate (#2662)
* feat: sign with env and output certificate * fix: test * fix: prop name * test: blob upload * test: http upload * test: exec * test: sign
This commit is contained in:
parent
52cf951c30
commit
312e52a760
@ -48,6 +48,8 @@ const (
|
||||
Checksum
|
||||
// Signature is a signature file.
|
||||
Signature
|
||||
// Certificate is a signing certificate file
|
||||
Certificate
|
||||
// UploadableSourceArchive is the archive with the current commit source code.
|
||||
UploadableSourceArchive
|
||||
// BrewTap is an uploadable homebrew tap recipe file.
|
||||
@ -80,6 +82,8 @@ func (t Type) String() string {
|
||||
return "Checksum"
|
||||
case Signature:
|
||||
return "Signature"
|
||||
case Certificate:
|
||||
return "Certificate"
|
||||
case UploadableSourceArchive:
|
||||
return "Source"
|
||||
case BrewTap:
|
||||
|
@ -359,6 +359,7 @@ func TestTypeToString(t *testing.T) {
|
||||
DockerManifest,
|
||||
Checksum,
|
||||
Signature,
|
||||
Certificate,
|
||||
UploadableSourceArchive,
|
||||
BrewTap,
|
||||
GoFishRig,
|
||||
|
@ -114,7 +114,7 @@ func filterArtifacts(artifacts artifact.Artifacts, publisher config.Publisher) [
|
||||
}
|
||||
|
||||
if publisher.Signature {
|
||||
filters = append(filters, artifact.ByType(artifact.Signature))
|
||||
filters = append(filters, artifact.ByType(artifact.Signature), artifact.ByType(artifact.Certificate))
|
||||
}
|
||||
|
||||
filter := artifact.Or(filters...)
|
||||
|
@ -41,6 +41,7 @@ func TestExecute(t *testing.T) {
|
||||
{"ubinary", "ubi", artifact.UploadableBinary},
|
||||
{"checksum", "sum", artifact.Checksum},
|
||||
{"signature", "sig", artifact.Signature},
|
||||
{"signature", "pem", artifact.Certificate},
|
||||
} {
|
||||
file := filepath.Join(folder, "a."+a.ext)
|
||||
require.NoError(t, os.WriteFile(file, []byte("lorem ipsum"), 0o644))
|
||||
@ -172,6 +173,7 @@ func TestExecute(t *testing.T) {
|
||||
{ExpectedArgs: []string{"a.ubi"}, ExitCode: 0, ExpectedEnv: osEnv()},
|
||||
{ExpectedArgs: []string{"a.tar"}, ExitCode: 0, ExpectedEnv: osEnv()},
|
||||
{ExpectedArgs: []string{"a.sig"}, ExitCode: 0, ExpectedEnv: osEnv()},
|
||||
{ExpectedArgs: []string{"a.pem"}, ExitCode: 0, ExpectedEnv: osEnv()},
|
||||
{ExpectedArgs: []string{"foo/bar"}, ExitCode: 0, ExpectedEnv: osEnv()},
|
||||
{ExpectedArgs: []string{"foo/bar:amd64"}, ExitCode: 0, ExpectedEnv: osEnv()},
|
||||
},
|
||||
|
@ -149,7 +149,7 @@ func Upload(ctx *context.Context, uploads []config.Upload, kind string, check Re
|
||||
filters = append(filters, artifact.ByType(artifact.Checksum))
|
||||
}
|
||||
if upload.Signature {
|
||||
filters = append(filters, artifact.ByType(artifact.Signature))
|
||||
filters = append(filters, artifact.ByType(artifact.Signature), artifact.ByType(artifact.Certificate))
|
||||
}
|
||||
// We support two different modes
|
||||
// - "archive": Upload all artifacts
|
||||
|
@ -249,6 +249,7 @@ func TestUpload(t *testing.T) {
|
||||
{"ubi", artifact.UploadableBinary},
|
||||
{"sum", artifact.Checksum},
|
||||
{"sig", artifact.Signature},
|
||||
{"pem", artifact.Certificate},
|
||||
} {
|
||||
file := filepath.Join(folder, "a."+a.ext)
|
||||
require.NoError(t, os.WriteFile(file, []byte("lorem ipsum"), 0o644))
|
||||
@ -438,6 +439,7 @@ func TestUpload(t *testing.T) {
|
||||
check{"/blah/2.1.0/a.tar", "u3", "x", content, map[string]string{}},
|
||||
check{"/blah/2.1.0/a.sum", "u3", "x", content, map[string]string{}},
|
||||
check{"/blah/2.1.0/a.sig", "u3", "x", content, map[string]string{}},
|
||||
check{"/blah/2.1.0/a.pem", "u3", "x", content, map[string]string{}},
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -58,10 +58,14 @@ func TestMinioUpload(t *testing.T) {
|
||||
tgzpath := filepath.Join(folder, "bin.tar.gz")
|
||||
debpath := filepath.Join(folder, "bin.deb")
|
||||
checkpath := filepath.Join(folder, "check.txt")
|
||||
sigpath := filepath.Join(folder, "f.sig")
|
||||
certpath := filepath.Join(folder, "f.pem")
|
||||
require.NoError(t, os.WriteFile(checkpath, []byte("fake checksums"), 0o744))
|
||||
require.NoError(t, os.WriteFile(srcpath, []byte("fake\nsrc"), 0o744))
|
||||
require.NoError(t, os.WriteFile(tgzpath, []byte("fake\ntargz"), 0o744))
|
||||
require.NoError(t, os.WriteFile(debpath, []byte("fake\ndeb"), 0o744))
|
||||
require.NoError(t, os.WriteFile(sigpath, []byte("fake\nsig"), 0o744))
|
||||
require.NoError(t, os.WriteFile(certpath, []byte("fake\ncert"), 0o744))
|
||||
ctx := context.New(config.Project{
|
||||
Dist: folder,
|
||||
ProjectName: "testupload",
|
||||
@ -86,6 +90,22 @@ func TestMinioUpload(t *testing.T) {
|
||||
Name: "checksum.txt",
|
||||
Path: checkpath,
|
||||
})
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Type: artifact.Signature,
|
||||
Name: "checksum.txt.sig",
|
||||
Path: sigpath,
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraID: "foo",
|
||||
},
|
||||
})
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Type: artifact.Certificate,
|
||||
Name: "checksum.pem",
|
||||
Path: certpath,
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraID: "foo",
|
||||
},
|
||||
})
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Type: artifact.UploadableSourceArchive,
|
||||
Name: "source.tar.gz",
|
||||
@ -119,6 +139,8 @@ func TestMinioUpload(t *testing.T) {
|
||||
"testupload/v1.0.0/bin.deb",
|
||||
"testupload/v1.0.0/bin.tar.gz",
|
||||
"testupload/v1.0.0/checksum.txt",
|
||||
"testupload/v1.0.0/checksum.txt.sig",
|
||||
"testupload/v1.0.0/checksum.pem",
|
||||
"testupload/v1.0.0/source.tar.gz",
|
||||
"testupload/v1.0.0/file.golden",
|
||||
})
|
||||
|
@ -81,6 +81,7 @@ func doUpload(ctx *context.Context, conf config.Blob) error {
|
||||
artifact.ByType(artifact.UploadableSourceArchive),
|
||||
artifact.ByType(artifact.Checksum),
|
||||
artifact.ByType(artifact.Signature),
|
||||
artifact.ByType(artifact.Certificate),
|
||||
artifact.ByType(artifact.LinuxPackage),
|
||||
)
|
||||
if len(conf.IDs) > 0 {
|
||||
|
11
internal/pipe/env/env.go
vendored
11
internal/pipe/env/env.go
vendored
@ -7,7 +7,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/goreleaser/goreleaser/internal/tmpl"
|
||||
@ -45,16 +44,16 @@ func setDefaultTokenFiles(ctx *context.Context) {
|
||||
// Run the pipe.
|
||||
func (Pipe) Run(ctx *context.Context) error {
|
||||
templ := tmpl.New(ctx).WithEnvS(os.Environ())
|
||||
tEnv := []string{}
|
||||
for i := range ctx.Config.Env {
|
||||
env, err := templ.Apply(ctx.Config.Env[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// XXX: this has no risk of panicking because it would already have
|
||||
// panicked at `context.go`'s `splitEnv` method.
|
||||
// Need to properly handle this at some point.
|
||||
parts := strings.SplitN(env, "=", 2)
|
||||
ctx.Env[parts[0]] = parts[1]
|
||||
tEnv = append(tEnv, env)
|
||||
}
|
||||
for k, v := range context.ToEnv(tEnv) {
|
||||
ctx.Env[k] = v
|
||||
}
|
||||
|
||||
setDefaultTokenFiles(ctx)
|
||||
|
@ -153,6 +153,7 @@ func doPublish(ctx *context.Context, client client.Client) error {
|
||||
artifact.ByType(artifact.UploadableSourceArchive),
|
||||
artifact.ByType(artifact.Checksum),
|
||||
artifact.ByType(artifact.Signature),
|
||||
artifact.ByType(artifact.Certificate),
|
||||
artifact.ByType(artifact.LinuxPackage),
|
||||
)
|
||||
|
||||
|
@ -18,23 +18,24 @@ func TestPipeDescription(t *testing.T) {
|
||||
require.NotEmpty(t, Pipe{}.String())
|
||||
}
|
||||
|
||||
func createTmpFile(tb testing.TB, folder, path string) string {
|
||||
tb.Helper()
|
||||
f, err := os.Create(filepath.Join(folder, path))
|
||||
require.NoError(tb, err)
|
||||
require.NoError(tb, f.Close())
|
||||
return f.Name()
|
||||
}
|
||||
|
||||
func TestRunPipeWithoutIDsThenDoesNotFilter(t *testing.T) {
|
||||
folder := t.TempDir()
|
||||
tarfile, err := os.Create(filepath.Join(folder, "bin.tar.gz"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, tarfile.Close())
|
||||
srcfile, err := os.Create(filepath.Join(folder, "source.tar.gz"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, srcfile.Close())
|
||||
debfile, err := os.Create(filepath.Join(folder, "bin.deb"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, debfile.Close())
|
||||
filteredtarfile, err := os.Create(filepath.Join(folder, "filtered.tar.gz"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, filteredtarfile.Close())
|
||||
filtereddebfile, err := os.Create(filepath.Join(folder, "filtered.deb"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, filtereddebfile.Close())
|
||||
tarfile := createTmpFile(t, folder, "bin.tar.gz")
|
||||
srcfile := createTmpFile(t, folder, "source.tar.gz")
|
||||
debfile := createTmpFile(t, folder, "bin.deb")
|
||||
checksumfile := createTmpFile(t, folder, "checksum")
|
||||
checksumsigfile := createTmpFile(t, folder, "checksum.sig")
|
||||
checksumpemfile := createTmpFile(t, folder, "checksum.pem")
|
||||
filteredtarfile := createTmpFile(t, folder, "filtered.tar.gz")
|
||||
filtereddebfile := createTmpFile(t, folder, "filtered.deb")
|
||||
|
||||
config := config.Project{
|
||||
Dist: folder,
|
||||
@ -50,7 +51,7 @@ func TestRunPipeWithoutIDsThenDoesNotFilter(t *testing.T) {
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Type: artifact.UploadableArchive,
|
||||
Name: "bin.tar.gz",
|
||||
Path: tarfile.Name(),
|
||||
Path: tarfile,
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraID: "foo",
|
||||
},
|
||||
@ -58,7 +59,7 @@ func TestRunPipeWithoutIDsThenDoesNotFilter(t *testing.T) {
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Type: artifact.LinuxPackage,
|
||||
Name: "bin.deb",
|
||||
Path: debfile.Name(),
|
||||
Path: debfile,
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraID: "foo",
|
||||
},
|
||||
@ -66,7 +67,7 @@ func TestRunPipeWithoutIDsThenDoesNotFilter(t *testing.T) {
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Type: artifact.UploadableArchive,
|
||||
Name: "filtered.tar.gz",
|
||||
Path: filteredtarfile.Name(),
|
||||
Path: filteredtarfile,
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraID: "bar",
|
||||
},
|
||||
@ -74,7 +75,7 @@ func TestRunPipeWithoutIDsThenDoesNotFilter(t *testing.T) {
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Type: artifact.LinuxPackage,
|
||||
Name: "filtered.deb",
|
||||
Path: filtereddebfile.Name(),
|
||||
Path: filtereddebfile,
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraID: "bar",
|
||||
},
|
||||
@ -82,11 +83,36 @@ func TestRunPipeWithoutIDsThenDoesNotFilter(t *testing.T) {
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Type: artifact.UploadableSourceArchive,
|
||||
Name: "source.tar.gz",
|
||||
Path: srcfile.Name(),
|
||||
Path: srcfile,
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraFormat: "tar.gz",
|
||||
},
|
||||
})
|
||||
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Type: artifact.Checksum,
|
||||
Name: "checksum",
|
||||
Path: checksumfile,
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraID: "bar",
|
||||
},
|
||||
})
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Type: artifact.Signature,
|
||||
Name: "checksum.sig",
|
||||
Path: checksumsigfile,
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraID: "bar",
|
||||
},
|
||||
})
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Type: artifact.Certificate,
|
||||
Name: "checksum.pem",
|
||||
Path: checksumpemfile,
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraID: "bar",
|
||||
},
|
||||
})
|
||||
client := &client.Mock{}
|
||||
require.NoError(t, doPublish(ctx, client))
|
||||
require.True(t, client.CreatedRelease)
|
||||
@ -96,6 +122,9 @@ func TestRunPipeWithoutIDsThenDoesNotFilter(t *testing.T) {
|
||||
require.Contains(t, client.UploadedFileNames, "bin.tar.gz")
|
||||
require.Contains(t, client.UploadedFileNames, "filtered.deb")
|
||||
require.Contains(t, client.UploadedFileNames, "filtered.tar.gz")
|
||||
require.Contains(t, client.UploadedFileNames, "checksum")
|
||||
require.Contains(t, client.UploadedFileNames, "checksum.pem")
|
||||
require.Contains(t, client.UploadedFileNames, "checksum.sig")
|
||||
}
|
||||
|
||||
func TestRunPipeWithIDsThenFilters(t *testing.T) {
|
||||
|
@ -101,34 +101,44 @@ func (Pipe) Run(ctx *context.Context) error {
|
||||
|
||||
func sign(ctx *context.Context, cfg config.Sign, artifacts []*artifact.Artifact) error {
|
||||
for _, a := range artifacts {
|
||||
artifact, err := signone(ctx, cfg, a)
|
||||
artifacts, err := signone(ctx, cfg, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if artifact != nil {
|
||||
for _, artifact := range artifacts {
|
||||
ctx.Artifacts.Add(artifact)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*artifact.Artifact, error) {
|
||||
func signone(ctx *context.Context, cfg config.Sign, art *artifact.Artifact) ([]*artifact.Artifact, error) {
|
||||
env := ctx.Env.Copy()
|
||||
env["artifact"] = a.Path
|
||||
env["artifactID"] = a.ID()
|
||||
env["artifactName"] = art.Name
|
||||
env["artifact"] = art.Path
|
||||
env["artifactID"] = art.ID()
|
||||
for k, v := range context.ToEnv(cfg.Env) {
|
||||
env[k] = v
|
||||
}
|
||||
|
||||
name, err := tmpl.New(ctx).WithEnv(env).Apply(expand(cfg.Signature, env))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign failed: %s: invalid template: %w", a, err)
|
||||
return nil, fmt.Errorf("sign failed: %s: %w", art.Name, err)
|
||||
}
|
||||
env["signature"] = name
|
||||
|
||||
cert, err := tmpl.New(ctx).WithEnv(env).Apply(expand(cfg.Certificate, env))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign failed: %s: %w", art.Name, err)
|
||||
}
|
||||
env["certificate"] = cert
|
||||
|
||||
// nolint:prealloc
|
||||
var args []string
|
||||
for _, a := range cfg.Args {
|
||||
arg, err := tmpl.New(ctx).WithEnv(env).Apply(expand(a, env))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign failed: %s: invalid template: %w", a, err)
|
||||
return nil, fmt.Errorf("sign failed: %s: %w", art.Name, err)
|
||||
}
|
||||
args = append(args, arg)
|
||||
}
|
||||
@ -150,7 +160,7 @@ func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*arti
|
||||
stdin = f
|
||||
}
|
||||
|
||||
fields := log.Fields{"cmd": cfg.Cmd, "artifact": a.Name}
|
||||
fields := log.Fields{"cmd": cfg.Cmd, "artifact": art.Name}
|
||||
|
||||
// The GoASTScanner flags this as a security risk.
|
||||
// However, this works as intended. The nosec annotation
|
||||
@ -164,6 +174,7 @@ func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*arti
|
||||
if stdin != nil {
|
||||
cmd.Stdin = stdin
|
||||
}
|
||||
cmd.Env = append(env.Strings(), cfg.Env...)
|
||||
log.WithFields(fields).Info("signing")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("sign: %s failed: %w: %s", cfg.Cmd, err, b.String())
|
||||
@ -173,22 +184,37 @@ func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*arti
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
env["artifact"] = a.Name
|
||||
env["artifact"] = art.Name
|
||||
name, err = tmpl.New(ctx).WithEnv(env).Apply(expand(cfg.Signature, env))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign failed: %s: invalid template: %w", a, err)
|
||||
return nil, fmt.Errorf("sign failed: %s: invalid template: %w", art.Name, err)
|
||||
}
|
||||
|
||||
artifactPathBase, _ := filepath.Split(a.Path)
|
||||
artifactPathBase, _ := filepath.Split(art.Path)
|
||||
sigFilename := filepath.Base(env["signature"])
|
||||
return &artifact.Artifact{
|
||||
Type: artifact.Signature,
|
||||
Name: name,
|
||||
Path: filepath.Join(artifactPathBase, sigFilename),
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraID: cfg.ID,
|
||||
result := []*artifact.Artifact{
|
||||
{
|
||||
Type: artifact.Signature,
|
||||
Name: name,
|
||||
Path: filepath.Join(artifactPathBase, sigFilename),
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraID: cfg.ID,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if cert != "" {
|
||||
result = append(result, &artifact.Artifact{
|
||||
Type: artifact.Certificate,
|
||||
Name: cert,
|
||||
Path: filepath.Join(artifactPathBase, cert),
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraID: cfg.ID,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func expand(s string, env map[string]string) string {
|
||||
|
@ -129,6 +129,7 @@ func TestDockerSignArtifacts(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
// TODO: keyless test?
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ctx := context.New(config.Project{})
|
||||
|
@ -83,12 +83,13 @@ func TestSignArtifacts(t *testing.T) {
|
||||
stdin := passwordUser
|
||||
tmplStdin := passwordUserTmpl
|
||||
tests := []struct {
|
||||
desc string
|
||||
ctx *context.Context
|
||||
signaturePaths []string
|
||||
signatureNames []string
|
||||
expectedErrMsg string
|
||||
user string
|
||||
desc string
|
||||
ctx *context.Context
|
||||
signaturePaths []string
|
||||
signatureNames []string
|
||||
certificateNames []string
|
||||
expectedErrMsg string
|
||||
user string
|
||||
}{
|
||||
{
|
||||
desc: "sign errors",
|
||||
@ -105,9 +106,39 @@ func TestSignArtifacts(t *testing.T) {
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
desc: "invalid certificate template",
|
||||
expectedErrMsg: `sign failed: artifact1: template: tmpl:1:3: executing "tmpl" at <.blah>: map has no entry for key "blah"`,
|
||||
ctx: context.New(
|
||||
config.Project{
|
||||
Signs: []config.Sign{
|
||||
{
|
||||
Artifacts: "all",
|
||||
Cmd: "exit",
|
||||
Certificate: "{{ .blah }}",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
desc: "invalid signature template",
|
||||
expectedErrMsg: `sign failed: artifact1: template: tmpl:1:3: executing "tmpl" at <.blah>: map has no entry for key "blah"`,
|
||||
ctx: context.New(
|
||||
config.Project{
|
||||
Signs: []config.Sign{
|
||||
{
|
||||
Artifacts: "all",
|
||||
Cmd: "exit",
|
||||
Signature: "{{ .blah }}",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
desc: "invalid args template",
|
||||
expectedErrMsg: `sign failed: ${FOO}-{{ .foo }{{}}{: invalid template: template: tmpl:1: unexpected "}" in operand`,
|
||||
expectedErrMsg: `sign failed: artifact1: template: tmpl:1: unexpected "}" in operand`,
|
||||
ctx: context.New(
|
||||
config.Project{
|
||||
Signs: []config.Sign{
|
||||
@ -282,6 +313,21 @@ func TestSignArtifacts(t *testing.T) {
|
||||
signaturePaths: []string{"artifact5.tar.gz.sig"},
|
||||
signatureNames: []string{"artifact5.tar.gz.sig"},
|
||||
},
|
||||
{
|
||||
desc: "sign only source filter by id",
|
||||
ctx: context.New(
|
||||
config.Project{
|
||||
Signs: []config.Sign{
|
||||
{
|
||||
Artifacts: "source",
|
||||
IDs: []string{"should-not-be-used"},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
signaturePaths: []string{"artifact5.tar.gz.sig"},
|
||||
signatureNames: []string{"artifact5.tar.gz.sig"},
|
||||
},
|
||||
{
|
||||
desc: "sign all artifacts with env",
|
||||
ctx: context.New(
|
||||
@ -441,6 +487,39 @@ func TestSignArtifacts(t *testing.T) {
|
||||
),
|
||||
expectedErrMsg: `sign failed: cannot open file /tmp/non-existing-file: open /tmp/non-existing-file: no such file or directory`,
|
||||
},
|
||||
{
|
||||
desc: "sign creating certificate",
|
||||
ctx: context.New(
|
||||
config.Project{
|
||||
Signs: []config.Sign{
|
||||
{
|
||||
Certificate: "${artifactName}.pem",
|
||||
Artifacts: "checksum",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
signaturePaths: []string{"checksum.sig", "checksum2.sig"},
|
||||
signatureNames: []string{"checksum.sig", "checksum2.sig"},
|
||||
certificateNames: []string{"checksum.pem", "checksum2.pem"},
|
||||
},
|
||||
{
|
||||
desc: "sign all artifacts with env and certificate",
|
||||
ctx: context.New(
|
||||
config.Project{
|
||||
Signs: []config.Sign{
|
||||
{
|
||||
Env: []string{"HONK=honk"},
|
||||
Certificate: `{{ trimsuffix (trimsuffix .Env.artifactName ".tar.gz") ".deb" }}_${HONK}.pem`,
|
||||
Artifacts: "all",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
||||
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
||||
certificateNames: []string{"artifact1_honk.pem", "artifact2_honk.pem", "artifact3_1.0.0_linux_amd64_honk.pem", "checksum_honk.pem", "checksum2_honk.pem", "artifact4_1.0.0_linux_amd64_honk.pem", "artifact5_honk.pem", "package1_honk.pem"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@ -449,12 +528,12 @@ func TestSignArtifacts(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
testSign(t, test.ctx, test.signaturePaths, test.signatureNames, test.user, test.expectedErrMsg)
|
||||
testSign(t, test.ctx, test.certificateNames, test.signaturePaths, test.signatureNames, test.user, test.expectedErrMsg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testSign(tb testing.TB, ctx *context.Context, signaturePaths []string, signatureNames []string, user, expectedErrMsg string) {
|
||||
func testSign(tb testing.TB, ctx *context.Context, certificateNames, signaturePaths, signatureNames []string, user, expectedErrMsg string) {
|
||||
tb.Helper()
|
||||
tmpdir := tb.TempDir()
|
||||
|
||||
@ -548,10 +627,27 @@ func testSign(tb testing.TB, ctx *context.Context, signaturePaths []string, sign
|
||||
require.NoError(tb, Pipe{}.Run(ctx))
|
||||
|
||||
// ensure all artifacts have an ID
|
||||
for _, arti := range ctx.Artifacts.Filter(artifact.ByType(artifact.Signature)).List() {
|
||||
for _, arti := range ctx.Artifacts.Filter(
|
||||
artifact.Or(
|
||||
artifact.ByType(artifact.Signature),
|
||||
artifact.ByType(artifact.Certificate),
|
||||
),
|
||||
).List() {
|
||||
require.NotEmptyf(tb, arti.ID(), ".Extra.ID on %s", arti.Path)
|
||||
}
|
||||
|
||||
certificates := ctx.Artifacts.Filter(artifact.ByType(artifact.Certificate)).List()
|
||||
certNames := []string{}
|
||||
for _, cert := range certificates {
|
||||
certNames = append(certNames, cert.Name)
|
||||
}
|
||||
sort.Strings(certificateNames)
|
||||
sort.Strings(certNames)
|
||||
require.Equal(tb, len(certificateNames), len(certificates))
|
||||
if len(certificateNames) > 0 {
|
||||
require.Equal(tb, certificateNames, certNames)
|
||||
}
|
||||
|
||||
// verify that only the artifacts and the signatures are in the dist dir
|
||||
gotFiles := []string{}
|
||||
|
||||
|
@ -605,14 +605,16 @@ type NFPMOverridables struct {
|
||||
|
||||
// Sign config.
|
||||
type Sign struct {
|
||||
ID string `yaml:"id,omitempty"`
|
||||
Cmd string `yaml:"cmd,omitempty"`
|
||||
Args []string `yaml:"args,omitempty"`
|
||||
Signature string `yaml:"signature,omitempty"`
|
||||
Artifacts string `yaml:"artifacts,omitempty"`
|
||||
IDs []string `yaml:"ids,omitempty"`
|
||||
Stdin *string `yaml:"stdin,omitempty"`
|
||||
StdinFile string `yaml:"stdin_file,omitempty"`
|
||||
ID string `yaml:"id,omitempty"`
|
||||
Cmd string `yaml:"cmd,omitempty"`
|
||||
Args []string `yaml:"args,omitempty"`
|
||||
Signature string `yaml:"signature,omitempty"`
|
||||
Artifacts string `yaml:"artifacts,omitempty"`
|
||||
IDs []string `yaml:"ids,omitempty"`
|
||||
Stdin *string `yaml:"stdin,omitempty"`
|
||||
StdinFile string `yaml:"stdin_file,omitempty"`
|
||||
Env []string `yaml:"env,omitempty"`
|
||||
Certificate string `yaml:"certificate,omitempty"`
|
||||
}
|
||||
|
||||
// SnapcraftAppMetadata for the binaries that will be in the snap package.
|
||||
|
@ -120,18 +120,21 @@ func Wrap(ctx ctx.Context, config config.Project) *Context {
|
||||
return &Context{
|
||||
Context: ctx,
|
||||
Config: config,
|
||||
Env: splitEnv(append(os.Environ(), config.Env...)),
|
||||
Env: ToEnv(append(os.Environ(), config.Env...)),
|
||||
Parallelism: 4,
|
||||
Artifacts: artifact.New(),
|
||||
Date: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func splitEnv(env []string) map[string]string {
|
||||
// TODO: this might panic if there is no `=` sign
|
||||
r := map[string]string{}
|
||||
// ToEnv converts a list of strings to an Env (aka a map[string]string).
|
||||
func ToEnv(env []string) Env {
|
||||
r := Env{}
|
||||
for _, e := range env {
|
||||
p := strings.SplitN(e, "=", 2)
|
||||
if len(p) != 2 || p[0] == "" {
|
||||
continue
|
||||
}
|
||||
r[p[0]] = p[1]
|
||||
}
|
||||
return r
|
||||
|
@ -30,3 +30,9 @@ func TestNewWithTimeout(t *testing.T) {
|
||||
<-ctx.Done()
|
||||
require.EqualError(t, ctx.Err(), `context canceled`)
|
||||
}
|
||||
|
||||
func TestToEnv(t *testing.T) {
|
||||
require.Equal(t, Env{"FOO": "BAR"}, ToEnv([]string{"=nope", "FOO=BAR"}))
|
||||
require.Equal(t, Env{"FOO": "BAR"}, ToEnv([]string{"nope", "FOO=BAR"}))
|
||||
require.Equal(t, Env{"FOO": "BAR", "nope": ""}, ToEnv([]string{"nope=", "FOO=BAR"}))
|
||||
}
|
||||
|
@ -20,11 +20,6 @@ docker_signs:
|
||||
id: foo
|
||||
|
||||
# Name/template of the signature file.
|
||||
#
|
||||
# Available environment variables:
|
||||
# - '${artifact}': the path to the artifact that will be signed
|
||||
# - '${artifactID}': the ID of the artifact that will be signed
|
||||
#
|
||||
# Note that with cosign you don't need to use this.
|
||||
#
|
||||
# Defaults to empty.
|
||||
@ -65,8 +60,33 @@ docker_signs:
|
||||
# StdinFile file to be given to the signature command as stdin.
|
||||
# Defaults to empty
|
||||
stdin_file: ./.password
|
||||
|
||||
# Sets a certificate name that your signing command should write to.
|
||||
# You can later use `${certificate}` or `.Env.certificate` in the `args` section.
|
||||
# This is particularly useful for keyless signing (for instance, with cosign).
|
||||
# Note that this should be a name, not a path.
|
||||
#
|
||||
# Defaults to empty.
|
||||
certificate: '{{ trimsuffix .Env.artifactName ".tar.gz" }}.pem'
|
||||
|
||||
# List of environment variables that will be passed to the signing command as well as the templates.
|
||||
#
|
||||
# Defaults to empty
|
||||
env:
|
||||
- FOO=bar
|
||||
- HONK=honkhonk
|
||||
```
|
||||
|
||||
### Available variable names
|
||||
|
||||
These environment variables might be available in the fields that are templateable:
|
||||
|
||||
- `${artifactName}`: the name of the artifact
|
||||
- `${artifact}`: the path to the artifact that will be signed
|
||||
- `${artifactID}`: the ID of the artifact that will be signed
|
||||
- `${certificate}`: the certificate filename, if provided
|
||||
- `${signature}`: the signature filename, if provided
|
||||
|
||||
## Common usage example
|
||||
|
||||
Assuming you have a `cosign.key` in the repository root and a `COSIGN_PWD`
|
||||
|
@ -34,10 +34,6 @@ signs:
|
||||
|
||||
# Name/template of the signature file.
|
||||
#
|
||||
# Available environment variables:
|
||||
# - '${artifact}': the path to the artifact that will be signed
|
||||
# - '${artifactID}': the ID of the artifact that will be signed
|
||||
#
|
||||
# Defaults to `${artifact}.sig`.
|
||||
signature: "${artifact}_sig"
|
||||
|
||||
@ -86,8 +82,33 @@ signs:
|
||||
#
|
||||
# Defaults to empty
|
||||
stdin_file: ./.password
|
||||
|
||||
# Sets a certificate that your signing command should write to.
|
||||
# You can later use `${certificate}` or `.Env.certificate` in the `args` section.
|
||||
# This is particularly useful for keyless signing (for instance, with cosign).
|
||||
# Note that this should be a name, not a path.
|
||||
#
|
||||
# Defaults to empty.
|
||||
certificate: '{{ trimsuffix .Env.artifactName ".tar.gz" }}.pem'
|
||||
|
||||
# List of environment variables that will be passed to the signing command as well as the templates.
|
||||
#
|
||||
# Defaults to empty
|
||||
env:
|
||||
- FOO=bar
|
||||
- HONK=honkhonk
|
||||
```
|
||||
|
||||
### Available variable names
|
||||
|
||||
These environment variables might be available in the fields that are templateable:
|
||||
|
||||
- `${artifactName}`: the name of the artifact
|
||||
- `${artifact}`: the path to the artifact that will be signed
|
||||
- `${artifactID}`: the ID of the artifact that will be signed
|
||||
- `${certificate}`: the certificate filename, if provided
|
||||
- `${signature}`: the signature filename
|
||||
|
||||
## Signing with cosign
|
||||
|
||||
You can sign you artifacts with [cosign][] as well.
|
||||
@ -109,6 +130,7 @@ Your users can then verify the signature with:
|
||||
cosign verify-blob -key cosign.pub -signature file.tar.gz.sig file.tar.gz
|
||||
```
|
||||
|
||||
<!-- TODO: keyless signing with cosign example -->
|
||||
|
||||
## Signing executables
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user