goreleaser: Built docker image/manifest digest is not exposed to image signing step, leading to potential race condition vulnerability

Is your feature request related to a problem? Please describe.

Currently, the Docker image digest built by dockers: ... is not exposed to docker_signs: ... in the environment variables. If the image tag in the registry is modified after pushing and before signing, signer tools that re-pull an image to sign it (including cosign) will potentially sign an image different from the one that was produced by goreleaser.

Describe the solution you’d like

Docker exposes the --metadata-file flag to docker build yields a file that looks like this in the goreleaserdocker temp dir:

{
  "containerimage.digest": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}

If we expose this built digest to the docker_signs tools via an environment variable artifactDigest, users would have the ability to pass the digest to the signing tool and avoid any chance of a race condition. In my opinion, this should be the default behavior.

docker_signs:
  - artifacts: all
    cmd: cosign
    args: ["sign", "${artifact}@${artifactDigest}"]

Describe alternatives you’ve considered

Assuming you’re using a tool like cosign that takes an image as an argument, you can fetch the digest from the local Docker daemon and pass that to the signer tool like so:

docker_signs:
  - artifacts: all
    cmd: sh
    args:
      - -c
      - cosign sign ${artifact}@$(docker inspect ${artifact} -f '{{"{{ (index .RepoDigests 0) }}"}}' | cut -d"@" -f2)

I’m not 100% sure this is actually correct though – docker inspect returns a list of digests for a given tag, which suggests there may be conditions in which multiple digests are returned for a single tag in the Docker daemon’s image store. I couldn’t find any such case in a quick code search of Docker, however.

This is quite a hack and it should be easier to avoid the race condition.

Search

  • I did search for other open and closed issues before opening this.

Code of Conduct

  • I agree to follow this project’s Code of Conduct

Additional context

  • If cases where users are already passing the --metadata-file to the docker build command via build_flag_templates, it may not be possible to reliably extract the digest from the docker build.
  • https://github.com/sigstore/cosign/issues/2047

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 1
  • Comments: 17 (10 by maintainers)

Commits related to this issue

Most upvoted comments

Actually, the digest should be fetched from the output of docker push. I demonstrate it in this blog post, which I wrote as a follow-up to my report in SigStore’s Slack channel that led to https://github.com/sigstore/cosign/issues/2047. I checked goreleaser’s code briefly, so I hope I got things right: It seems that the fix takes changing the following function to also fetch stdout and parse it:

func (i dockerImager) Push(ctx *context.Context, image string, flags []string) error {
	if err := runCommand(ctx, ".", "docker", "push", image); err != nil {
		return fmt.Errorf("failed to push %s: %w", image, err)
	}
	return nil
}

This would also force fixing the imager interface accordingly.