Skip to content

Complete OCI 1.1 Referrers API Support Across All Cosign Commands #4335

@arewm

Description

@arewm

Description

We currently push attestations to container registries using tags. I want to enable our builds to start pushing all attestations using the referrer's API but there are some tooling gaps in being able to download and verify this metadata.

Commands WITH OCI 1.1 Support:

  • cosign tree - --registry-referrers-mode=oci-1-1 + --experimental-oci11
  • cosign sign - --registry-referrers-mode=oci-1-1
  • cosign attach sbom - --registry-referrers-mode=oci-1-1
  • cosign verify - --experimental-oci11

Commands MISSING OCI 1.1 Support:

  • cosign download attestation
  • cosign download signature
  • cosign download sbom
  • cosign verify-attestation
  • cosign attach signature
  • cosign attach attestation
  • cosign attest (creates and attaches new attestations)

I think that we should have universal support for the OCI 1.1 referrer's API. The inconsistency here is confusing. When proposing how to make Chains use the referrer's API, I didn't realized that I ended up trying to use the new bundle format instead of just changing the storage mechanism.

All endpoints should ideally have options such that it would be possible to achieve all of the following:

# Download commands get OCI 1.1 support
cosign download attestation --experimental-oci11 example.com/app:v1.0

# Verify attestation gets OCI 1.1 support
cosign verify-attestation --experimental-oci11 example.com/app:v1.0

# Attach commands get OCI 1.1 support  
cosign attach signature --experimental-oci11 --signature=sig.txt example.com/app:v1.0

# Create commands get OCI 1.1 support
cosign attest --experimental-oci11 --predicate=predicate.json --type=slsaprovenance example.com/app:v1.0
cosign sign --experimental-oci11 --key=cosign.key example.com/app:v1.0

I see two potential approaches that address different priorities. Is there a preference between these approaches? I feel like I likely don't understand the full complexity of the latter approach, but I also feel like it is the better approach as I expect it would/could be more consistent with future bundle support as well. The first approach would definitely be simpler to implement.

Option A: Extend the current pattern

The current implementations just handle interfacing with the referrer's API directly within the functions. For example, verify and tree use ociremote.Referrers() while attach sbom calls mutate.Subject().

If we were to leverage this pattern, there would likely be duplicated code across multiple subcommands. This would work, is likely going to be faster, and would have fewer potential side effects.

Option B: Create a unified abstraction for managing artifact operations

In looking at the code, it seems like we have many locations where three different types of operations are performed: find, attach, and create. If we extract these all into a centralized artifact management abstraction then we could potentially get a benefit of code reuse across them (i.e. in a new pkg/oci/artifacts/ package).

This approach could theoretically also simplify cosign's use as a dependency.

Its primary interface would be something like this:

// Unified interface for all artifact types
type ArtifactManager interface {
    FindArtifacts(ctx context.Context, subject name.Digest, artifactType string, filters ...Filter) ([]Artifact, error)
    AttachArtifact(ctx context.Context, subject name.Digest, artifact Artifact, opts AttachOptions) error  
    CreateArtifact(ctx context.Context, subject name.Digest, content []byte, artifactType string, signingOpts SigningOptions, attachOpts AttachOptions) error
}

This would let the CLI implementations follow patterns to defer common work to the manager

// CLI command implementations using the unified manager:
func DownloadAttestationCmd(...) error {
    manager, _ := artifacts.NewArtifactManager(storageMode, format, regOpts)
    filters := []Filter{PredicateTypeFilter(predicateType)}
    artifacts, err := manager.FindArtifacts(ctx, digest, "att", filters...)
    // output results
}

func AttachSBOMCmd(...) error {
    manager, _ := artifacts.NewArtifactManager(storageMode, format, regOpts)
    artifact := Artifact{Type: "sbom", Content: sbomData, MediaType: mediaType}
    return manager.AttachArtifact(ctx, digest, artifact, opts)
}

func AttestCmd(...) error {
    manager, _ := artifacts.NewArtifactManager(storageMode, format, regOpts)
    return manager.CreateArtifact(ctx, digest, predicate, "att", signingOpts, attachOpts)
}

I think that this approach should be able to be reused for bundles which might also simplify planning related to v3.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions