Skip to content
Permalink
Browse files
feat: use digest to sign docker images/manifests (#3556)
this drives it home by using the actual images/manifest digests to sign
with cosign by default.

the default signing command is changing in this PR, but since `digest`
should be always there (if not, the pipeline will fail way earlier), it
should be fine.

refs #3496
refs #3540

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
  • Loading branch information
caarlos0 committed Nov 15, 2022
1 parent 2a46d62 commit b55b9976c7871e29bcdf70628be749b96b32296d
Show file tree
Hide file tree
Showing 6 changed files with 21 additions and 9 deletions.
@@ -137,6 +137,7 @@ const (
ExtraBinaries = "Binaries"
ExtraRefresh = "Refresh"
ExtraReplaces = "Replaces"
ExtraDigest = "Digest"
)

// Extras represents the extra fields in an artifact.
@@ -20,7 +20,7 @@ import (

const (
dockerConfigExtra = "DockerConfig"
dockerDigestExtra = "Digest"
dockerDigestExtra = artifact.ExtraDigest

useBuildx = "buildx"
useDocker = "docker"
@@ -154,6 +154,7 @@ func signone(ctx *context.Context, cfg config.Sign, art *artifact.Artifact) ([]*
env["artifactName"] = art.Name // shouldn't be used
env["artifact"] = art.Path
env["artifactID"] = art.ID()
env["digest"] = artifact.ExtraOr(*art, artifact.ExtraDigest, "")

tmplEnv, err := templateEnvS(ctx, cfg.Env)
if err != nil {
@@ -28,7 +28,7 @@ func (DockerPipe) Default(ctx *context.Context) error {
cfg.Cmd = "cosign"
}
if len(cfg.Args) == 0 {
cfg.Args = []string{"sign", "--key=cosign.key", "$artifact"}
cfg.Args = []string{"sign", "--key=cosign.key", "${artifact}@${digest}"}
}
if cfg.Artifacts == "" {
cfg.Artifacts = "none"
@@ -28,7 +28,7 @@ func TestDockerSignDefault(t *testing.T) {
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].Args, []string{"sign", "--key=cosign.key", "${artifact}@${digest}"})
require.Equal(t, ctx.Config.DockerSigns[0].Artifacts, "none")
}

@@ -54,12 +54,15 @@ func TestDockerSignArtifacts(t *testing.T) {
testlib.CheckPath(t, "cosign")
key := "cosign.key"
cmd := "sh"
args := []string{"-c", "echo ${artifact} > ${signature} && cosign sign --key=" + key + " --upload=false ${artifact} > ${signature}"}
args := []string{"-c", "echo ${artifact}@${digest} > ${signature} && cosign sign --key=" + key + " --upload=false ${artifact}@${digest} > ${signature}"}
password := "password"

img1 := "ghcr.io/caarlos0/goreleaser-docker-manifest-actions-example:1.2.1-amd64"
img1Digest := "sha256:d7bf8be1b156cc0cd9d2e33765a69bc968d4ef6b2dea9b207d63129b9709862a"
img2 := "ghcr.io/caarlos0/goreleaser-docker-manifest-actions-example:1.2.1-arm64v8"
img2Digest := "sha256:551801b7f42f8c33bfabb06e25804c2aca14776d2b7df33e07de54e887910b72"
man1 := "ghcr.io/caarlos0/goreleaser-docker-manifest-actions-example:1.2.1"
man1Digest := "sha256:b5db21408555f1ef5d68008a0a03a7caba3f29b62c64f1404e139b005a20bf03"

for name, cfg := range map[string]struct {
Signs []config.Sign
@@ -88,7 +91,7 @@ func TestDockerSignArtifacts(t *testing.T) {
Stdin: &password,
Cmd: "cosign",
Certificate: `{{ replace (replace (replace .Env.artifact "/" "-") ":" "-") "." "" }}.pem`,
Args: []string{"sign", "--output-certificate=${certificate}", "--key=" + key, "--upload=false", "${artifact}"},
Args: []string{"sign", "--output-certificate=${certificate}", "--key=" + key, "--upload=false", "${artifact}@${digest}"},
},
},
},
@@ -165,23 +168,26 @@ func TestDockerSignArtifacts(t *testing.T) {
Path: img1,
Type: artifact.DockerImage,
Extra: map[string]interface{}{
artifact.ExtraID: "img1",
artifact.ExtraID: "img1",
artifact.ExtraDigest: img1Digest,
},
})
ctx.Artifacts.Add(&artifact.Artifact{
Name: img2,
Path: img2,
Type: artifact.DockerImage,
Extra: map[string]interface{}{
artifact.ExtraID: "img2",
artifact.ExtraID: "img2",
artifact.ExtraDigest: img2Digest,
},
})
ctx.Artifacts.Add(&artifact.Artifact{
Name: man1,
Path: man1,
Type: artifact.DockerManifest,
Extra: map[string]interface{}{
artifact.ExtraID: "man1",
artifact.ExtraID: "man1",
artifact.ExtraDigest: man1Digest,
},
})

@@ -28,7 +28,7 @@ docker_signs:
# Command line templateable arguments for the command
#
# defaults to `["sign", "--key=cosign.key", "${artifact}"]`
# defaults to `["sign", "--key=cosign.key", "${artifact}@${digest}"]`
args: ["sign", "--key=cosign.key", "--upload=false", "${artifact}"]
@@ -77,12 +77,16 @@ docker_signs:
These environment variables might be available in the fields that are templateable:

- `${artifact}`: the path to the artifact that will be signed [^1]
- `${digest}`: the digest of the image/manifest that will be signed [^2]
- `${artifactID}`: the ID of the artifact that will be signed
- `${certificate}`: the certificate file name, if provided

[^1]: notice that this might contain `/` characters, which depending on how
you use it might evaluate to actual paths within the file system. Use with
care.
[^2]: those are extracted automatically when running Docker push from within
GoReleaser. Using the digest helps making sure you're signing the right image
and avoid concurrency issues.


## Common usage example

0 comments on commit b55b997

Please sign in to comment.