Repository navigation
b1_05_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.
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:
- Understand the address
quay.io/my-org/backend:v1.2.0? - Find the exact unique version of that image (even if the
v1.2.0tag changes)? - Fetch the image's configuration file (which contains details like the base image)?
- 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.
Let's break down the key skills of this "librarian":
-
Understanding Addresses (Parsing Image References):
- Software artifacts in a registry have addresses, like
quay.io/my-org/backend:v1.2.0orghcr.io/enterprise-contract/ec-cli:latest@sha256:abcdef123.... These are called Image References. -
ec-clineeds to parse these strings to understand the different parts:-
quay.ioorghcr.io: The registry (the library). -
my-org/backendorenterprise-contract/ec-cli: The repository (a specific shelf or section). -
:v1.2.0or: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).
-
- Software artifacts in a registry have addresses, like
-
Finding the Exact Version (Resolving Digests):
- Tags like
:v1.2.0are convenient for humans but can be ambiguous. The maintainer might update the image and keep the same tag. - For reliable validation,
ec-clineeds the digest (thesha256:...part). If you only provide a tag (e.g.,quay.io/my-org/backend:v1.2.0),ec-cliwill ask the registry: "What is the current digest for the tagv1.2.0in themy-org/backendrepository?" This process is called resolving the digest. It ensuresec-clichecks the exact artifact you intended, even if the tag changes later.
- Tags like
-
Fetching Specific Items (Artifact Retrieval):
- Once
ec-clihas 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
cosignstore signatures and attestations alongside the image in the registry.ec-cliusescosign'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-clican fetch individual layers (parts of the image) and extract specific files if needed.
- Once
This component works behind the scenes whenever you run commands like ec validate image.
- You provide an image reference (e.g., in the Application Snapshot).
-
ec-cliparses the reference. - It resolves the digest if necessary.
- 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).
- 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.
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
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.
Let's peek at some relevant (simplified) code concepts:
-
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:
NewImageReferenceuses libraries to break down the input URL string into its components (registry, repository, tag, digest). TheresolveDigestmethod (called byParseAndResolveifimageRef.Digestis empty) contacts the registry to get the current digest associated with the tag. -
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).
-
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. -
The OCI Client:
File:
internal/utils/oci/client.goThis file defines the
Clientinterface and thedefaultClientimplementation. It acts as a central wrapper around libraries likego-containerregistryandcosignto provide a consistent way forec-clito:- Fetch image metadata (
Head,Image,Index). - Resolve digests (
ResolveDigest). - Fetch layers/blobs (
Layer). - Trigger signature/attestation verification (delegating to
cosignlibraries, which handle the registry interactions for finding those specific artifact types).
- Fetch image metadata (
-
Rego Integration:
File:
internal/rego/oci/oci.goThis code allows Rego policies to directly call functions like
ec.oci.image_manifest(ref)orec.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.
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