mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-04-11 11:42:15 +02:00
feat: sign docker images with cosign (#2423)
* feat: sign docker images with cosign Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: improve sign logging Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: do not sign if skip publish is set Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: install cosign Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * docs: fix wrong docs Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>
This commit is contained in:
parent
5bdbffc96f
commit
ad57a133fb
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@ -46,6 +46,7 @@ jobs:
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- uses: sigstore/cosign-installer@main
|
||||
-
|
||||
name: Make Setup
|
||||
run: |
|
||||
|
11
cosign.key
Normal file
11
cosign.key
Normal file
@ -0,0 +1,11 @@
|
||||
-----BEGIN ENCRYPTED COSIGN PRIVATE KEY-----
|
||||
eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6
|
||||
OCwicCI6MX0sInNhbHQiOiJ4ZGhNcWtrc0ErN2F4VU1YcmlVTXFrZW9vWVhGYkhG
|
||||
b2hWcERJQnM4bEFzPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94
|
||||
Iiwibm9uY2UiOiJKYkFYVGQrZWh0bTVnZGtYN3JNYkl1SjlsT3A3azF3eCJ9LCJj
|
||||
aXBoZXJ0ZXh0IjoiNnlRZ29sL2doSFEvTnN3bWszZFN3WmhZdGNkK1lvekFGenpr
|
||||
emFNa2YxT0dxVGphdVJlRGQyeDZtOXZQek1mL2pHV0NEeHY0VGo3eFVOblI3OUhE
|
||||
UitKNmU1SUduNTBuRkoxNDlVTUk3aWhHeXVDYWJzckZtZTJhZVhaNTRDeFczNjRi
|
||||
KzVSU1g0SkMxYUNKMlhneEJjVklRbzN0U1VpeDZuRG9zaUZEMWJVUTB5UDVCZ05K
|
||||
Qnp3dnRsMHBSNVFjQXNCaEM2aHNjQ1VXOXc9PSJ9
|
||||
-----END ENCRYPTED COSIGN PRIVATE KEY-----
|
4
cosign.pub
Normal file
4
cosign.pub
Normal file
@ -0,0 +1,4 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdOGf+JZhlk/D16pf3iR14cEjtGdn
|
||||
/3N3NDg1Ic7+bm/3ccxvrH8WfkqXftxeSzQ9ICqFdr5DVsufVp7mY5pvdw==
|
||||
-----END PUBLIC KEY-----
|
@ -21,7 +21,7 @@ import (
|
||||
"github.com/goreleaser/goreleaser/pkg/context"
|
||||
)
|
||||
|
||||
// Pipe for artifact signing.
|
||||
// Pipe that signs common artifacts.
|
||||
type Pipe struct{}
|
||||
|
||||
func (Pipe) String() string {
|
||||
@ -110,7 +110,9 @@ func sign(ctx *context.Context, cfg config.Sign, artifacts []*artifact.Artifact)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Artifacts.Add(artifact)
|
||||
if artifact != nil {
|
||||
ctx.Artifacts.Add(artifact)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -118,6 +120,7 @@ func sign(ctx *context.Context, cfg config.Sign, artifacts []*artifact.Artifact)
|
||||
func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*artifact.Artifact, error) {
|
||||
env := ctx.Env.Copy()
|
||||
env["artifact"] = a.Path
|
||||
env["artifactID"] = a.ExtraOr("ID", "").(string)
|
||||
|
||||
name, err := tmpl.New(ctx).WithEnv(env).Apply(expand(cfg.Signature, env))
|
||||
if err != nil {
|
||||
@ -152,7 +155,7 @@ func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*arti
|
||||
stdin = f
|
||||
}
|
||||
|
||||
fields := log.Fields{"cmd": cfg.Cmd}
|
||||
fields := log.Fields{"cmd": cfg.Cmd, "artifact": a.Name}
|
||||
|
||||
// The GoASTScanner flags this as a security risk.
|
||||
// However, this works as intended. The nosec annotation
|
||||
@ -171,7 +174,9 @@ func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*arti
|
||||
return nil, fmt.Errorf("sign: %s failed: %w: %s", cfg.Cmd, err, b.String())
|
||||
}
|
||||
|
||||
artifactPathBase, _ := filepath.Split(a.Path)
|
||||
if cfg.Signature == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
env["artifact"] = a.Name
|
||||
name, err = tmpl.New(ctx).WithEnv(env).Apply(expand(cfg.Signature, env))
|
||||
@ -179,6 +184,7 @@ func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*arti
|
||||
return nil, fmt.Errorf("sign failed: %s: invalid template: %w", a, err)
|
||||
}
|
||||
|
||||
artifactPathBase, _ := filepath.Split(a.Path)
|
||||
sigFilename := filepath.Base(env["signature"])
|
||||
return &artifact.Artifact{
|
||||
Type: artifact.Signature,
|
||||
|
80
internal/pipe/sign/sign_docker.go
Normal file
80
internal/pipe/sign/sign_docker.go
Normal file
@ -0,0 +1,80 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goreleaser/goreleaser/internal/artifact"
|
||||
"github.com/goreleaser/goreleaser/internal/ids"
|
||||
"github.com/goreleaser/goreleaser/internal/pipe"
|
||||
"github.com/goreleaser/goreleaser/internal/semerrgroup"
|
||||
"github.com/goreleaser/goreleaser/pkg/context"
|
||||
)
|
||||
|
||||
// Pipe that signs docker images and manifests.
|
||||
type DockerPipe struct{}
|
||||
|
||||
func (DockerPipe) String() string {
|
||||
return "signing docker images"
|
||||
}
|
||||
|
||||
// Default sets the Pipes defaults.
|
||||
func (DockerPipe) Default(ctx *context.Context) error {
|
||||
ids := ids.New("docker_signs")
|
||||
for i := range ctx.Config.DockerSigns {
|
||||
cfg := &ctx.Config.DockerSigns[i]
|
||||
if cfg.Cmd == "" {
|
||||
cfg.Cmd = "cosign"
|
||||
}
|
||||
if len(cfg.Args) == 0 {
|
||||
cfg.Args = []string{"sign", "-key=cosign.key", "$artifact"}
|
||||
}
|
||||
if cfg.Artifacts == "" {
|
||||
cfg.Artifacts = "none"
|
||||
}
|
||||
if cfg.ID == "" {
|
||||
cfg.ID = "default"
|
||||
}
|
||||
ids.Inc(cfg.ID)
|
||||
}
|
||||
return ids.Validate()
|
||||
}
|
||||
|
||||
// Run executes the Pipe.
|
||||
func (DockerPipe) Run(ctx *context.Context) error {
|
||||
if ctx.SkipSign {
|
||||
return pipe.ErrSkipSignEnabled
|
||||
}
|
||||
|
||||
if ctx.SkipPublish {
|
||||
return pipe.ErrSkipSignEnabled
|
||||
}
|
||||
|
||||
g := semerrgroup.New(ctx.Parallelism)
|
||||
for i := range ctx.Config.DockerSigns {
|
||||
cfg := ctx.Config.DockerSigns[i]
|
||||
g.Go(func() error {
|
||||
var filters []artifact.Filter
|
||||
switch cfg.Artifacts {
|
||||
case "images":
|
||||
filters = append(filters, artifact.ByType(artifact.DockerImage))
|
||||
case "manifests":
|
||||
filters = append(filters, artifact.ByType(artifact.DockerManifest))
|
||||
case "all":
|
||||
filters = append(filters, artifact.Or(
|
||||
artifact.ByType(artifact.DockerImage),
|
||||
artifact.ByType(artifact.DockerManifest),
|
||||
))
|
||||
case "none":
|
||||
return pipe.ErrSkipSignEnabled
|
||||
default:
|
||||
return fmt.Errorf("invalid list of artifacts to sign: %s", cfg.Artifacts)
|
||||
}
|
||||
|
||||
if len(cfg.IDs) > 0 {
|
||||
filters = append(filters, artifact.ByIDs(cfg.IDs...))
|
||||
}
|
||||
return sign(ctx, cfg, ctx.Artifacts.Filter(artifact.And(filters...)).List())
|
||||
})
|
||||
}
|
||||
return g.Wait()
|
||||
}
|
189
internal/pipe/sign/sign_docker_test.go
Normal file
189
internal/pipe/sign/sign_docker_test.go
Normal file
@ -0,0 +1,189 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/goreleaser/goreleaser/internal/artifact"
|
||||
"github.com/goreleaser/goreleaser/pkg/config"
|
||||
"github.com/goreleaser/goreleaser/pkg/context"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDockerSignDescription(t *testing.T) {
|
||||
require.NotEmpty(t, DockerPipe{}.String())
|
||||
}
|
||||
|
||||
func TestDockerSignDefault(t *testing.T) {
|
||||
ctx := &context.Context{
|
||||
Config: config.Project{
|
||||
DockerSigns: []config.Sign{{}},
|
||||
},
|
||||
}
|
||||
err := DockerPipe{}.Default(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ctx.Config.DockerSigns[0].Cmd, "cosign")
|
||||
require.Equal(t, ctx.Config.DockerSigns[0].Signature, "")
|
||||
require.Equal(t, ctx.Config.DockerSigns[0].Args, []string{"sign", "-key=cosign.key", "$artifact"})
|
||||
require.Equal(t, ctx.Config.DockerSigns[0].Artifacts, "none")
|
||||
}
|
||||
|
||||
func TestDockerSignDisabled(t *testing.T) {
|
||||
ctx := context.New(config.Project{})
|
||||
ctx.Config.DockerSigns = []config.Sign{
|
||||
{Artifacts: "none"},
|
||||
}
|
||||
err := DockerPipe{}.Run(ctx)
|
||||
require.EqualError(t, err, "artifact signing is disabled")
|
||||
}
|
||||
|
||||
func TestDockerSignSkipped(t *testing.T) {
|
||||
ctx := context.New(config.Project{})
|
||||
ctx.SkipSign = true
|
||||
err := DockerPipe{}.Run(ctx)
|
||||
require.EqualError(t, err, "artifact signing is disabled")
|
||||
}
|
||||
|
||||
func TestDockerSignSkipPublish(t *testing.T) {
|
||||
ctx := context.New(config.Project{})
|
||||
ctx.SkipPublish = true
|
||||
err := DockerPipe{}.Run(ctx)
|
||||
require.EqualError(t, err, "artifact signing is disabled")
|
||||
}
|
||||
|
||||
func TestDockerSignInvalidArtifacts(t *testing.T) {
|
||||
ctx := context.New(config.Project{})
|
||||
ctx.Config.DockerSigns = []config.Sign{
|
||||
{Artifacts: "foo"},
|
||||
}
|
||||
err := DockerPipe{}.Run(ctx)
|
||||
require.EqualError(t, err, "invalid list of artifacts to sign: foo")
|
||||
}
|
||||
|
||||
func TestDockerSignArtifacts(t *testing.T) {
|
||||
key := "testdata/cosign/cosign.key"
|
||||
cmd := "sh"
|
||||
args := []string{"-c", "echo ${artifact} > ${signature} && cosign sign -key=" + key + " -upload=false ${artifact} > ${signature}"}
|
||||
password := "password"
|
||||
|
||||
img1 := "ghcr.io/caarlos0/goreleaser-docker-manifest-actions-example:1.2.1-amd64"
|
||||
img2 := "ghcr.io/caarlos0/goreleaser-docker-manifest-actions-example:1.2.1-arm64v8"
|
||||
man1 := "ghcr.io/caarlos0/goreleaser-docker-manifest-actions-example:1.2.1"
|
||||
|
||||
for name, cfg := range map[string]struct {
|
||||
Signs []config.Sign
|
||||
Expected []string
|
||||
}{
|
||||
"no signature file": {
|
||||
Expected: nil, // no sigs
|
||||
Signs: []config.Sign{
|
||||
{
|
||||
Artifacts: "all",
|
||||
Stdin: &password,
|
||||
Cmd: "cosign",
|
||||
Args: []string{"sign", "-key=" + key, "-upload=false", "${artifact}"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"sign all": {
|
||||
Expected: []string{
|
||||
"testdata/cosign/all_img1.sig",
|
||||
"testdata/cosign/all_img2.sig",
|
||||
"testdata/cosign/all_man1.sig",
|
||||
},
|
||||
Signs: []config.Sign{
|
||||
{
|
||||
Artifacts: "all",
|
||||
Stdin: &password,
|
||||
Signature: `testdata/cosign/all_${artifactID}.sig`,
|
||||
Cmd: cmd,
|
||||
Args: args,
|
||||
},
|
||||
},
|
||||
},
|
||||
"sign all filtering id": {
|
||||
Expected: []string{"testdata/cosign/all_filter_by_id_img2.sig"},
|
||||
Signs: []config.Sign{
|
||||
{
|
||||
Artifacts: "all",
|
||||
IDs: []string{"img2"},
|
||||
Stdin: &password,
|
||||
Signature: "testdata/cosign/all_filter_by_id_${artifactID}.sig",
|
||||
Cmd: cmd,
|
||||
Args: args,
|
||||
},
|
||||
},
|
||||
},
|
||||
"sign images only": {
|
||||
Expected: []string{
|
||||
"testdata/cosign/images_img1.sig",
|
||||
"testdata/cosign/images_img2.sig",
|
||||
},
|
||||
Signs: []config.Sign{
|
||||
{
|
||||
Artifacts: "images",
|
||||
Stdin: &password,
|
||||
Signature: "testdata/cosign/images_${artifactID}.sig",
|
||||
Cmd: cmd,
|
||||
Args: args,
|
||||
},
|
||||
},
|
||||
},
|
||||
"sign manifests only": {
|
||||
Expected: []string{"testdata/cosign/manifests_man1.sig"},
|
||||
Signs: []config.Sign{
|
||||
{
|
||||
Artifacts: "manifests",
|
||||
Stdin: &password,
|
||||
Signature: "testdata/cosign/manifests_${artifactID}.sig",
|
||||
Cmd: cmd,
|
||||
Args: args,
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ctx := context.New(config.Project{})
|
||||
ctx.Config.DockerSigns = cfg.Signs
|
||||
|
||||
t.Cleanup(func() {
|
||||
for _, f := range cfg.Expected {
|
||||
require.NoError(t, os.Remove(f))
|
||||
}
|
||||
})
|
||||
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Name: img1,
|
||||
Path: img1,
|
||||
Type: artifact.DockerImage,
|
||||
Extra: map[string]interface{}{
|
||||
"ID": "img1",
|
||||
},
|
||||
})
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Name: img2,
|
||||
Path: img2,
|
||||
Type: artifact.DockerImage,
|
||||
Extra: map[string]interface{}{
|
||||
"ID": "img2",
|
||||
},
|
||||
})
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Name: man1,
|
||||
Path: man1,
|
||||
Type: artifact.DockerManifest,
|
||||
Extra: map[string]interface{}{
|
||||
"ID": "man1",
|
||||
},
|
||||
})
|
||||
|
||||
require.NoError(t, DockerPipe{}.Default(ctx))
|
||||
require.NoError(t, DockerPipe{}.Run(ctx))
|
||||
var sigs []string
|
||||
for _, sig := range ctx.Artifacts.Filter(artifact.ByType(artifact.Signature)).List() {
|
||||
sigs = append(sigs, sig.Name)
|
||||
}
|
||||
require.Equal(t, cfg.Expected, sigs)
|
||||
})
|
||||
}
|
||||
}
|
1
internal/pipe/sign/testdata/cosign/.gitignore
vendored
Normal file
1
internal/pipe/sign/testdata/cosign/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.sig
|
11
internal/pipe/sign/testdata/cosign/cosign.key
vendored
Normal file
11
internal/pipe/sign/testdata/cosign/cosign.key
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
-----BEGIN ENCRYPTED COSIGN PRIVATE KEY-----
|
||||
eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6
|
||||
OCwicCI6MX0sInNhbHQiOiJ4ZGhNcWtrc0ErN2F4VU1YcmlVTXFrZW9vWVhGYkhG
|
||||
b2hWcERJQnM4bEFzPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94
|
||||
Iiwibm9uY2UiOiJKYkFYVGQrZWh0bTVnZGtYN3JNYkl1SjlsT3A3azF3eCJ9LCJj
|
||||
aXBoZXJ0ZXh0IjoiNnlRZ29sL2doSFEvTnN3bWszZFN3WmhZdGNkK1lvekFGenpr
|
||||
emFNa2YxT0dxVGphdVJlRGQyeDZtOXZQek1mL2pHV0NEeHY0VGo3eFVOblI3OUhE
|
||||
UitKNmU1SUduNTBuRkoxNDlVTUk3aWhHeXVDYWJzckZtZTJhZVhaNTRDeFczNjRi
|
||||
KzVSU1g0SkMxYUNKMlhneEJjVklRbzN0U1VpeDZuRG9zaUZEMWJVUTB5UDVCZ05K
|
||||
Qnp3dnRsMHBSNVFjQXNCaEM2aHNjQ1VXOXc9PSJ9
|
||||
-----END ENCRYPTED COSIGN PRIVATE KEY-----
|
4
internal/pipe/sign/testdata/cosign/cosign.pub
vendored
Normal file
4
internal/pipe/sign/testdata/cosign/cosign.pub
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdOGf+JZhlk/D16pf3iR14cEjtGdn
|
||||
/3N3NDg1Ic7+bm/3ccxvrH8WfkqXftxeSzQ9ICqFdr5DVsufVp7mY5pvdw==
|
||||
-----END PUBLIC KEY-----
|
@ -64,5 +64,6 @@ var Pipeline = append(
|
||||
sign.Pipe{}, // sign artifacts
|
||||
docker.Pipe{}, // create and push docker images
|
||||
publish.Pipe{}, // publishes artifacts
|
||||
sign.DockerPipe{}, // sign docker images and manifests
|
||||
announce.Pipe{}, // announce releases
|
||||
)
|
||||
|
@ -662,6 +662,7 @@ type Project struct {
|
||||
Changelog Changelog `yaml:",omitempty"`
|
||||
Dist string `yaml:",omitempty"`
|
||||
Signs []Sign `yaml:",omitempty"`
|
||||
DockerSigns []Sign `yaml:"docker_signs,omitempty"`
|
||||
EnvFiles EnvFiles `yaml:"env_files,omitempty"`
|
||||
Before Before `yaml:",omitempty"`
|
||||
Source Source `yaml:",omitempty"`
|
||||
|
@ -49,6 +49,7 @@ var Defaulters = []Defaulter{
|
||||
snapcraft.Pipe{},
|
||||
checksums.Pipe{},
|
||||
sign.Pipe{},
|
||||
sign.DockerPipe{},
|
||||
docker.Pipe{},
|
||||
docker.ManifestPipe{},
|
||||
artifactory.Pipe{},
|
||||
|
89
www/docs/customization/docker_sign.md
Normal file
89
www/docs/customization/docker_sign.md
Normal file
@ -0,0 +1,89 @@
|
||||
---
|
||||
title: Docker Image Signing
|
||||
---
|
||||
|
||||
Signing Docker Images and Manifests is also possible with GoReleaser.
|
||||
This pipe was designed based on the common [sign](/customization/sign/) pipe having [cosign](https://github.com/sigstore/cosign) in mind.
|
||||
|
||||
!!! info
|
||||
Note that this pipe will run only at the end of the GoReleaser execution, as cosign will change the image in the registry.
|
||||
|
||||
|
||||
To customize the signing pipeline you can use the following options:
|
||||
|
||||
```yaml
|
||||
# .goreleaser.yml
|
||||
docker_signs:
|
||||
-
|
||||
# ID of the sign config, must be unique.
|
||||
# Only relevant if you want to produce some sort of signature file.
|
||||
#
|
||||
# Defaults to "default".
|
||||
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.
|
||||
signature: "${artifact}_sig"
|
||||
|
||||
# Path to the signature command
|
||||
#
|
||||
# Defaults to `cosign`
|
||||
cmd: cosign
|
||||
|
||||
# Command line templateable arguments for the command
|
||||
#
|
||||
# defaults to `["sign", "-key=cosign.key", "${artifact}"]`
|
||||
args: ["sign", "-key=cosign.key", "-upload=false", "${artifact}"]
|
||||
|
||||
|
||||
# Which artifacts to sign
|
||||
#
|
||||
# all: all artifacts
|
||||
# none: no signing
|
||||
# images: only docker images
|
||||
# manifests: only docker manifests
|
||||
#
|
||||
# defaults to `none`
|
||||
artifacts: all
|
||||
|
||||
# IDs of the artifacts to sign.
|
||||
#
|
||||
# Defaults to empty (which implies no ID filtering).
|
||||
ids:
|
||||
- foo
|
||||
- bar
|
||||
|
||||
# Stdin data template to be given to the signature command as stdin.
|
||||
# Defaults to empty
|
||||
stdin: '{{ .Env.GPG_PASSWORD }}'
|
||||
|
||||
# StdinFile file to be given to the signature command as stdin.
|
||||
# Defaults to empty
|
||||
stdin_file: ./.password
|
||||
```
|
||||
|
||||
## Common usage example
|
||||
|
||||
Assuming you have a `cosign.key` in the repository root and a `COSIGN_PWD`
|
||||
environment variable, the simplest configuration to sign both Docker images
|
||||
and manifests would look like this:
|
||||
|
||||
```yaml
|
||||
# .goreleaser.yml
|
||||
docker_signs:
|
||||
- artifacts: all
|
||||
stdin: '{{ .Env.COSIGN_PWD }}'
|
||||
```
|
||||
|
||||
Later on you (and anyone else) can verify the image with:
|
||||
|
||||
```sh
|
||||
cosign verify -key cosign.pub your/image
|
||||
```
|
@ -8,7 +8,7 @@ signing key.
|
||||
|
||||
GoReleaser provides means to sign both executables and archives.
|
||||
|
||||
## Archives
|
||||
## Usage
|
||||
|
||||
Signing works in combination with checksum files and it is generally sufficient
|
||||
to sign the checksum files only.
|
||||
@ -30,30 +30,34 @@ To customize the signing pipeline you can use the following options:
|
||||
signs:
|
||||
-
|
||||
# ID of the sign config, must be unique.
|
||||
#
|
||||
# Defaults to "default".
|
||||
id: foo
|
||||
|
||||
# name/template of the signature file.
|
||||
# '${artifact}' is the path to the artifact that should be signed.
|
||||
# Name/template of the signature file.
|
||||
#
|
||||
# defaults to `${artifact}.sig`
|
||||
# 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"
|
||||
|
||||
# path to the signature command
|
||||
# Path to the signature command
|
||||
#
|
||||
# defaults to `gpg`
|
||||
# Defaults to `gpg`
|
||||
cmd: gpg2
|
||||
|
||||
# command line templateable arguments for the command
|
||||
# Command line templateable arguments for the command
|
||||
#
|
||||
# to sign with a specific key use
|
||||
# args: ["-u", "<key id, fingerprint, email, ...>", "--output", "${signature}", "--detach-sign", "${artifact}"]
|
||||
#
|
||||
# defaults to `["--output", "${signature}", "--detach-sign", "${artifact}"]`
|
||||
# Defaults to `["--output", "${signature}", "--detach-sign", "${artifact}"]`
|
||||
args: ["--output", "${signature}", "${artifact}", "{{ .ProjectName }}"]
|
||||
|
||||
|
||||
# which artifacts to sign
|
||||
# Which artifacts to sign
|
||||
#
|
||||
# all: all artifacts
|
||||
# none: no signing
|
||||
@ -63,52 +67,61 @@ signs:
|
||||
# archive: archives from archive pipe
|
||||
# binary: binaries if archiving format is set to binary
|
||||
#
|
||||
# defaults to `none`
|
||||
# Defaults to `none`
|
||||
artifacts: all
|
||||
|
||||
# IDs of the artifacts to sign.
|
||||
# Defaults to all.
|
||||
#
|
||||
# If `artifacts` is checksum or source, this fields has no effect.
|
||||
#
|
||||
# Defaults to empty (which implies no filtering).
|
||||
ids:
|
||||
- foo
|
||||
- bar
|
||||
|
||||
# Stdin data template to be given to the signature command as stdin.
|
||||
#
|
||||
# Defaults to empty
|
||||
stdin: '{{ .Env.GPG_PASSWORD }}'
|
||||
|
||||
# StdinFile file to be given to the signature command as stdin.
|
||||
#
|
||||
# Defaults to empty
|
||||
stdin_file: ./.password
|
||||
```
|
||||
|
||||
### Limitations
|
||||
## Signing with cosign
|
||||
|
||||
You can sign with any command that outputs a file.
|
||||
If what you want to use does not do it, you can always hack by setting the
|
||||
command to `sh -c`. For example:
|
||||
You can sign you artifacts with [cosign][] as well.
|
||||
|
||||
Assuming you have a `cosign.key` in the repository root and a `COSIGN_PWD` environment variable set, a simple usage example would look like this:
|
||||
|
||||
```yaml
|
||||
# .goreleaser.yml
|
||||
signs:
|
||||
- cmd: sh
|
||||
args:
|
||||
- '-c'
|
||||
- 'echo "${artifact} is signed and I can prove it" | tee ${signature}'
|
||||
- cmd: cosign
|
||||
stdin: '{{ .Env.COSIGN_PWD }}'
|
||||
args: ["sign-blob", "-key=cosign.key", "-output=${signature}", "${artifact}"]
|
||||
artifacts: all
|
||||
```
|
||||
|
||||
And it will work just fine. Just make sure to always use the `${signature}`
|
||||
template variable as the result file name and `${artifact}` as the origin file.
|
||||
Your users can then verify the signature with:
|
||||
|
||||
```sh
|
||||
cosign verify-blob -key cosign.pub -signature file.tar.gz.sig file.tar.gz
|
||||
```
|
||||
|
||||
|
||||
## Executables
|
||||
## Signing executables
|
||||
|
||||
Executables can be signed after build using post hooks.
|
||||
|
||||
For example you can use [gon][] to create notarized MacOS apps:
|
||||
### With gon
|
||||
|
||||
For example, you can use [gon][] to create notarized MacOS apps:
|
||||
|
||||
```yaml
|
||||
# .goreleaser.yml
|
||||
builds:
|
||||
- binary: foo
|
||||
id: foo
|
||||
@ -129,8 +142,11 @@ builds:
|
||||
post: gon gon.hcl
|
||||
```
|
||||
|
||||
**`gon.hcl`:**
|
||||
```hcl
|
||||
and:
|
||||
|
||||
```terraform
|
||||
# gon.hcl
|
||||
#
|
||||
# The path follows a pattern
|
||||
# ./dist/BUILD-ID_TARGET/BINARY-NAME
|
||||
source = ["./dist/foo-macos_darwin_amd64/foo"]
|
||||
@ -156,4 +172,55 @@ as `extra_files` in the `release` section to make sure they also get uploaded.
|
||||
|
||||
You can also check [this issue](https://github.com/goreleaser/goreleaser/issues/1227) for more details.
|
||||
|
||||
|
||||
### With cosign
|
||||
|
||||
You can also use [cosign][] to sign the binaries directly,
|
||||
but you'll need to manually add the `.sig` files to the release and/or archive:
|
||||
|
||||
```yaml
|
||||
# .goreleaser.yml
|
||||
builds:
|
||||
- hooks:
|
||||
post:
|
||||
- sh -c "echo $COSIGN_PWD | cosign sign-blob -key cosign.key {{ .Path }} > dist/{{ .ProjectName }}_{{ .Version }}_{{ .Target }}.sig"
|
||||
|
||||
# add to the release directly:
|
||||
release:
|
||||
extra_files:
|
||||
- glob: dist/*.sig
|
||||
|
||||
# or just to the archives:
|
||||
archives:
|
||||
- files:
|
||||
- dist/*.sig
|
||||
```
|
||||
|
||||
While this works, I would recommend using the signing pipe directly.
|
||||
|
||||
## Signing Docker images and manifests
|
||||
|
||||
Please refer to [Docker Images Signing](/customization/docker_sign/).
|
||||
|
||||
## Limitations
|
||||
|
||||
You can sign with any command that either outputs a file or modify the file being signed.
|
||||
|
||||
If you want to sign with something that writes to `STDOUT` instead of a file,
|
||||
you can wrap the command inside a `sh -c` execution, for instance:
|
||||
|
||||
```yaml
|
||||
# .goreleaser.yml
|
||||
signs:
|
||||
- cmd: sh
|
||||
args:
|
||||
- '-c'
|
||||
- 'echo "${artifact} is signed and I can prove it" | tee ${signature}'
|
||||
artifacts: all
|
||||
```
|
||||
|
||||
And it will work just fine. Just make sure to always use the `${signature}`
|
||||
template variable as the result file name and `${artifact}` as the origin file.
|
||||
|
||||
[gon]: https://github.com/mitchellh/gon
|
||||
[cosign]: https://github.com/sigstore/cosign
|
||||
|
@ -78,6 +78,7 @@ nav:
|
||||
- customization/dist.md
|
||||
- customization/docker.md
|
||||
- customization/docker_manifest.md
|
||||
- customization/docker_sign.md
|
||||
- customization/env.md
|
||||
- customization/fury.md
|
||||
- customization/gomod.md
|
||||
|
Loading…
x
Reference in New Issue
Block a user