1
0
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:
Carlos Alexandro Becker 2021-11-11 22:56:03 -03:00 committed by GitHub
parent 52cf951c30
commit 312e52a760
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 314 additions and 77 deletions

View File

@ -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:

View File

@ -359,6 +359,7 @@ func TestTypeToString(t *testing.T) {
DockerManifest,
Checksum,
Signature,
Certificate,
UploadableSourceArchive,
BrewTap,
GoFishRig,

View File

@ -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...)

View File

@ -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()},
},

View File

@ -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

View File

@ -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{}},
),
},
{

View File

@ -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",
})

View File

@ -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 {

View File

@ -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)

View File

@ -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),
)

View File

@ -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) {

View File

@ -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 {

View File

@ -129,6 +129,7 @@ func TestDockerSignArtifacts(t *testing.T) {
},
},
},
// TODO: keyless test?
} {
t.Run(name, func(t *testing.T) {
ctx := context.New(config.Project{})

View File

@ -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{}

View File

@ -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.

View File

@ -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

View File

@ -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"}))
}

View File

@ -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`

View File

@ -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