sign

package
v0.15.14 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 16, 2026 License: MIT Imports: 38 Imported by: 0

Documentation

Overview

Package sign checks that an asset carries the signatures its entry says it does.

Recording a signature is not the same as there being one. The entry says how the asset can be verified; this runs the verification, with aqua's own verifiers, so that what aqua-registry-g2 promises is what aqua will be able to do at install time. A release that is signed for the generator and not for the installer would be the worst of both.

Index

Constants

This section is empty.

Variables

View Source
var ErrUnverified = errors.New("the asset can't be verified the way its entry says it can")

ErrUnverified says a signature the entry carries couldn't be shown to hold, either because it doesn't or because the check couldn't be made.

Functions

func Render

func Render(asset *generate.Asset, version string)

Render fills in the templates an entry's signing configuration still holds.

The definition writes them, and the file being generated describes one version of one environment, so nothing in it should have to be worked out again later. It is done last because the pattern a signer was guessed by, and the name it was replaced with, are both written with the version in them.

func Structure added in v0.12.0

func Structure(asset *generate.Asset, version string)

Structure moves what cosign's command line says into the fields that say it, on an entry whose templates have been rendered.

The files cosign is given -- the signature, the certificate, the key, the bundle -- go into the downloadable files aqua already has for them, and who the certificate must name goes into fields of its own. aqua turns both back into the same flags when it verifies, so the command is the same; what changes is that a reader can see what is verified without parsing a command line. A flag no field names stays in opts.

A file is a release asset of the entry's own repository at this version when its URL says so, which is how it is downloaded with the same client as the asset; anything else is a URL.

func TemplateVersion

func TemplateVersion(s, version string) string

TemplateVersion puts the version back where a value holds it.

A workflow signs under the ref it ran for, so its name holds the version, and a definition pinning it as it stands would only ever match the one release. A service account signs under a name that doesn't move and is pinned as it is.

It is the definition that wants this. What a run generates describes one version and holds no templates at all.

func Unstructure added in v0.12.1

func Unstructure(c *aquaregistry.Cosign, asset *generate.Asset, version string) *aquaregistry.Cosign

Unstructure is a cosign configuration written back the way Structure found it: every file cosign is given but the bundle, and who the certificate must name, as flags in opts.

It is what a definition records. A definition is the template every version is generated from and has always held cosign as a command line; Structure is how one version's entry says it.

Types

type Identity

type Identity struct {
	// SAN is the subject alternative name of the signing certificate: a workflow
	// URI for something built by GitHub Actions, an email address for a service
	// account.
	SAN string
	// Issuer is the OIDC issuer that vouched for the SAN. Without it the SAN is
	// only a string anyone could have put in a certificate of their own.
	Issuer string
	// Repository and Ref are the repository the signing run belonged to and the ref
	// it ran for, as Fulcio recorded them. They are empty for a signature made
	// somewhere other than GitHub Actions.
	//
	// They matter because a workflow is shared. suzuki-shunsuke/go-release-workflow
	// signs the releases of dozens of repositories, so a certificate naming it is a
	// certificate any of them could have obtained: asking for the workflow alone
	// accepts a signature made for somebody else's release.
	Repository string
	Ref        string
}

Identity is who signed an artifact, as cosign is asked to check it.

It is read off the signature rather than guessed at. aqua gr assumes the signer is a workflow in the package's own repository, which is right often enough to be misleading: sigstore/cosign signs with a service account, and a definition carrying that assumption fails for everyone who installs it.

func (*Identity) Opts

func (i *Identity) Opts() []string

Opts renders the identity as the cosign flags that check for it.

type UnverifiedError added in v0.15.13

type UnverifiedError struct {
	// Kind is the kind of signing, as the check names it: "cosign", "slsa_provenance".
	Kind string
	Err  error
}

UnverifiedError is an asset a check of one kind of signing didn't hold for.

func (*UnverifiedError) Error added in v0.15.13

func (e *UnverifiedError) Error() string

func (*UnverifiedError) Unwrap added in v0.15.13

func (e *UnverifiedError) Unwrap() []error

Unwrap makes the error ErrUnverified, and the reason the check gave.

type Verifier

type Verifier struct {
	// contains filtered or unexported fields
}

Verifier runs the signature verifications an entry asks for.

func New

func New(ctx context.Context, logger *slog.Logger, httpClient *http.Client) (*Verifier, error)

New creates a Verifier.

The verification tools are installed the first time a check needs one, into aqua's own root directory — the same copies aqua would use, and the same place AQUA_ROOT_DIR points at. aqua's verifiers don't install anything themselves: they work out where the tool is and run it, and putting it there is the install path's job, which this stands in for.

func (*Verifier) Check

func (v *Verifier) Check(ctx context.Context, logger *slog.Logger, pkgName, version string, asset *generate.Asset, path string) error

Check verifies every signature the asset's entry carries against the file at path, and reports whether they all held.

A signature that can't be verified stops the version rather than being dropped from the entry. The two readings of a failure -- the release stopped being signed, and the check couldn't be made right now -- are not distinguishable here, and only one of them is safe to act on: recording a signed package as unsigned is a loss that nothing later notices, while not publishing a version is undone by the next run.

It happened the other way first. cosign was rate limited while the kubernetes packages were generated, five retries inside two seconds all met the same limit, and the entries came out saying those releases carry no signature at all.

The cost is a version that stalls: an upstream that really did stop signing, or a signer that can't be read off the certificate, produces nothing until somebody changes the definition. That is the direction to fail in, and it is why the signer is read off the signature first, which settles the case this used to exist for -- a guessed identity that was never right.

The environment the asset is for is what the signature names are rendered with, not the machine this runs on: the entry for darwin/arm64 is verified with darwin/arm64's signature wherever the run happens.

func (*Verifier) DropUnattested added in v0.12.2

func (v *Verifier) DropUnattested()

DropUnattested makes an attestation GitHub doesn't hold come off the entry, rather than fail it. It is for generating.

A definition says a package is attested for every version unless an override says otherwise, and the versions before a release started being attested don't override it. Failing the entry there stopped everything checked after the signatures -- the archive's files, the libc its executables need -- and the attestation came off later anyway, when GitHub was asked whether it holds one: FiloSottile/age v1.2.1 lost linked_libc that way.

The registry's own check of a generated file doesn't do this. A file saying it is attested when it isn't is what that check is for.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL