Skip to content

b1_05_image___oci_artifact_handling_

Simon Baird edited this page Apr 23, 2025 · 1 revision

Chapter 5: Image & OCI Artifact Handling

In the previous chapter, we learned how Sigstore Verification helps ec-cli trust the authenticity and integrity of software artifacts like container images and their attestations. It's like having a way to verify the official seal on a document.

But where are these documents (images, attestations, signatures) stored, and how does ec-cli find and retrieve the specific ones it needs? If Sigstore verification is the notary public, we still need a skilled librarian who knows how to navigate the library shelves to find the right documents to notarize.

This is the job of Image & OCI Artifact Handling.

What Problem Does it Solve? Finding Things in the Registry

Container images, signatures, attestations, and other related bits of information (collectively called "artifacts") are usually stored in a Container Registry (also called an OCI Registry – OCI stands for Open Container Initiative, a set of standards). Think of registries like Quay.io, Docker Hub, or GitHub Container Registry as massive digital libraries or warehouses for software components.

Use Case: Imagine your policy requires checking the base image used in quay.io/my-org/backend:v1.2.0 and verifying an attestation that proves it passed security scans. How does ec-cli:

  1. Understand the address quay.io/my-org/backend:v1.2.0?
  2. Find the exact unique version of that image (even if the v1.2.0 tag changes)?
  3. Fetch the image's configuration file (which contains details like the base image)?
  4. Locate the associated security scan attestation and its signature?

The Image & OCI Artifact Handling abstraction is responsible for all these interactions with the registry. It's the component that knows how to "speak OCI" to fetch the necessary information.

Key Concepts: The Registry Librarian's Skills

Let's break down the key skills of this "librarian":

  1. Understanding Addresses (Parsing Image References):

    • Software artifacts in a registry have addresses, like quay.io/my-org/backend:v1.2.0 or ghcr.io/enterprise-contract/ec-cli:latest@sha256:abcdef123.... These are called Image References.
    • ec-cli needs to parse these strings to understand the different parts:
      • quay.io or ghcr.io: The registry (the library).
      • my-org/backend or enterprise-contract/ec-cli: The repository (a specific shelf or section).
      • :v1.2.0 or :latest: The tag (a label, like "latest edition", which can move to different items over time).
      • @sha256:abcdef123...: The digest (a unique, unchanging fingerprint or ID, like an ISBN for a specific printing of a book).
  2. Finding the Exact Version (Resolving Digests):

    • Tags like :v1.2.0 are convenient for humans but can be ambiguous. The maintainer might update the image and keep the same tag.
    • For reliable validation, ec-cli needs the digest (the sha256:... part). If you only provide a tag (e.g., quay.io/my-org/backend:v1.2.0), ec-cli will ask the registry: "What is the current digest for the tag v1.2.0 in the my-org/backend repository?" This process is called resolving the digest. It ensures ec-cli checks the exact artifact you intended, even if the tag changes later.
  3. Fetching Specific Items (Artifact Retrieval):

    • Once ec-cli has the exact address (usually including the digest), it can ask the registry for specific pieces of information:
      • Image Configuration: A file (usually JSON) embedded within the image that contains metadata like the image's architecture, environment variables, entry point, and importantly, its layers and potentially its base image.
      • Attestations & Signatures: As we saw in Chapter 4: Sigstore Verification, tools like cosign store signatures and attestations alongside the image in the registry. ec-cli uses cosign's libraries, which interact with the registry to find these items based on the image digest.
      • Manifests: A file describing the contents of an image (like its layers and config file location).
      • (Advanced) Files within an Image: Some policies might need to inspect specific files inside the container image (e.g., a license file or a specific configuration file). ec-cli can fetch individual layers (parts of the image) and extract specific files if needed.

How ec-cli Uses OCI Handling

This component works behind the scenes whenever you run commands like ec validate image.

  1. You provide an image reference (e.g., in the Application Snapshot).
  2. ec-cli parses the reference.
  3. It resolves the digest if necessary.
  4. It interacts with the registry to fetch the image config, attestations, signatures, and any other artifacts required by the policy rules or the validation process itself (like Sigstore Verification).
  5. The fetched information is then used as input for the Validation Engine (Evaluator).

You don't usually interact with this component directly, but understanding its role helps diagnose issues related to registry access or missing artifacts.

Under the Hood: Talking to the Registry

Let's see a simplified flow of how ec-cli might handle an image reference during validation:

sequenceDiagram
    participant User
    participant ec_cli as ec validate image
    participant OCI_Handler as OCI Artifact Handler
    participant Registry as Container Registry
    participant Evaluator as Validation Engine

    User->>ec_cli: Runs `ec validate image --images '{"components":[{"containerImage":"quay.io/my-org/backend:v1.2.0"}]}' ...`
    ec_cli->>OCI_Handler: Process "quay.io/my-org/backend:v1.2.0"
    Note over OCI_Handler: Parses ref: finds registry, repo, tag. Needs digest!
    OCI_Handler->>Registry: What's the digest for quay.io/my-org/backend:v1.2.0?
    Registry-->>OCI_Handler: Digest is sha256:abc...
    Note over OCI_Handler: Now has full address: quay.io/my-org/backend@sha256:abc...
    OCI_Handler->>Registry: Get image config for sha256:abc...
    Registry-->>OCI_Handler: Image config JSON
    OCI_Handler->>Registry: Find attestations/signatures for sha256:abc... (using cosign libs)
    Registry-->>OCI_Handler: Attestations/Signatures found
    OCI_Handler-->>ec_cli: Returns resolved ref, config, attestations, etc.
    Note over ec_cli: Data fetched successfully.
    ec_cli->>Evaluator: Evaluate image using fetched data and policy rules
    Evaluator-->>ec_cli: Evaluation results
    ec_cli-->>User: Shows report
Loading

This diagram shows the OCI Handler acting as the intermediary, translating ec-cli's needs into requests the Registry understands, handling parsing, digest resolution, and fetching.

Code Snippets

Let's peek at some relevant (simplified) code concepts:

  1. Parsing and Resolving References:

    File: internal/image/process.go

    // Simplified concept of parsing a URL string
    func NewImageReference(url string, opts ...name.Option) (*ImageReference, error) {
        // Use the 'go-containerregistry/pkg/name' library to parse the URL
        ref, err := name.ParseReference(url, opts...)
        if err != nil {
            return nil, err // Handle parsing errors
        }
        imageRef := &ImageReference{ref: ref}
    
        // Try parsing specifically as a tag to capture the tag if present
        tagRef, err := name.NewTag(strings.Split(url, "@")[0], opts...)
        if err == nil {
            imageRef.Tag = tagRef.TagStr()
            imageRef.Repository = tagRef.Context().Name()
        }
    
        // Try parsing specifically as a digest to capture the digest if present
        digestRef, err := name.NewDigest(url, opts...)
        if err == nil {
            imageRef.Digest = digestRef.DigestStr()
            imageRef.Repository = digestRef.Context().Name()
        }
        // ... store repository name ...
        return imageRef, nil
    }
    
    // Simplified concept of resolving a digest if only a tag was given
    func (i ImageReference) resolveDigest(ctx context.Context, opts ...name.Option) (*ImageReference, error) {
        // Use library function to query the registry for the HEAD descriptor
        // (metadata including the digest) for the tag reference 'i.ref'
        descriptor, err := remote.Head(i.ref, remote.WithAuthFromKeychain(authn.DefaultKeychain)) // Simplified options
        if err != nil {
            return nil, err // Handle network/auth errors
        }
        digest := descriptor.Digest.String() // Get the digest from the response
        if digest == "" {
            return nil, fmt.Errorf("digest for image %q is empty", i.ref.String())
        }
    
        // Create a new reference string with the digest
        resolvedUrl := fmt.Sprintf("%s@%s", i.ref.Context().Name(), digest)
        // Parse the new string to get a complete ImageReference
        return NewImageReference(resolvedUrl, opts...)
    }

    Explanation: NewImageReference uses libraries to break down the input URL string into its components (registry, repository, tag, digest). The resolveDigest method (called by ParseAndResolve if imageRef.Digest is empty) contacts the registry to get the current digest associated with the tag.

  2. Fetching Image Configuration:

    File: internal/fetchers/oci/config/config.go

    // Simplified concept of fetching the image config file
    func FetchImageConfig(ctx context.Context, ref name.Reference) (json.RawMessage, error) {
        // Get an OCI client (handles registry communication)
        client := oci.NewClient(ctx)
    
        // Ask the client for the image object using the reference
        image, err := client.Image(ref) // 'ref' should ideally include the digest
        if err != nil {
            return nil, err // Handle network/auth errors
        }
    
        // Ask the image object for its configuration file
        configFile, err := image.ConfigFile()
        if err != nil {
            return nil, err // Handle errors reading config
        }
    
        // Convert the config data to JSON format
        configJSON, err := json.Marshal(configFile.Config)
        if err != nil {
            return nil, err // Handle JSON conversion errors
        }
    
        return configJSON, nil
    }

    Explanation: This function takes an image reference (preferably with a digest), uses an OCI client to get the image object from the registry, retrieves the configuration file from that object, and returns it as raw JSON data. This JSON data is then used as input for the Validation Engine (Evaluator).

  3. Fetching Files from Inside an Image:

    File: internal/fetchers/oci/files/files.go

    // Simplified concept of extracting specific files
    func ImageFiles(ctx context.Context, ref name.Reference, extractors []Extractor) (map[string]json.RawMessage, error) {
        // Get OCI client and image object (similar to FetchImageConfig)
        img, err := oci.NewClient(ctx).Image(ref)
        if err != nil { /* ... */ }
    
        // 'extractors' define which files/directories to look for (e.g., based on policy needs)
        matchers := /* ... create matcher functions based on extractors ... */
        if len(matchers) == 0 { return nil, nil } // Nothing to extract
    
        // Get a reader for the image layers (like a stream of tar archives)
        content := mutate.Extract(img) // Library function to access layers
        defer content.Close()
        archive := tar.NewReader(content)
    
        files := map[string]json.RawMessage{} // Store extracted file contents
        for { // Loop through files in the layers
            header, err := archive.Next() // Get next file header
            if err == io.EOF { break } // End of archive
            if err != nil { /* ... */ }
    
            // Check if this file matches any of our required paths/patterns
            if needsExtraction(header, matchers) {
                data, err := io.ReadAll(archive) // Read the file content
                if err != nil { /* ... */ }
                // Try converting to JSON (assuming policy needs structured data)
                jsonData, err := yaml.YAMLToJSON(data)
                if err == nil {
                    files[header.Name] = jsonData // Store if successful
                }
            }
        }
        return files, nil
    }

    Explanation: This function is more complex. It gets access to the image's layers (which are like tarballs). It iterates through the files within those layers. If a file matches criteria defined by the policy (via extractors), it reads the file content and tries to parse it as YAML or JSON, storing the result. This allows policies to check data inside the container.

  4. The OCI Client:

    File: internal/utils/oci/client.go

    This file defines the Client interface and the defaultClient implementation. It acts as a central wrapper around libraries like go-containerregistry and cosign to provide a consistent way for ec-cli to:

    • Fetch image metadata (Head, Image, Index).
    • Resolve digests (ResolveDigest).
    • Fetch layers/blobs (Layer).
    • Trigger signature/attestation verification (delegating to cosign libraries, which handle the registry interactions for finding those specific artifact types).
  5. Rego Integration:

    File: internal/rego/oci/oci.go

    This code allows Rego policies to directly call functions like ec.oci.image_manifest(ref) or ec.oci.image_files(ref, paths). This means a policy rule itself can trigger fetching data from the registry during evaluation, providing flexibility for advanced checks. These functions use the same underlying OCI client logic.

Conclusion

You've now learned about the "librarian" role within ec-cli – the Image & OCI Artifact Handling component. It's responsible for understanding image references, finding the exact artifact versions by resolving digests, and fetching necessary items like configurations, signatures, attestations, and even files from OCI container registries. This ensures that the Sigstore Verification and Validation Engine (Evaluator) have the correct and necessary information to perform their checks.

Sometimes, the information needed isn't directly attached to the image itself but is related through build pipelines. How does ec-cli handle tracking information related to Tekton pipelines?

Let's explore that in the next chapter: Tekton Bundle Tracking.


Generated by AI Codebase Knowledge Builder

Clone this wiki locally