Skip to content

b1_04_sigstore_verification_

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

Chapter 4: Sigstore Verification

In the previous chapter, we saw how the Validation Engine (Evaluator) acts like a judge, applying rules from our policy to the evidence (data about our container images). But a crucial question remains: how can the judge trust the evidence?

Imagine someone gives the judge a document claiming a container image passed security scans. How does the judge know this document is real and hasn't been forged or altered? This is where Sigstore Verification comes in.

What Problem Does it Solve? Trusting Your Software Artifacts

In the world of software, especially with container images flying around, it's vital to ensure they haven't been tampered with and that any claims made about them (like "this image was built by our official CI/CD pipeline" or "this image has no critical vulnerabilities") are authentic.

Use Case: You want to run ec-cli to check if your quay.io/my-org/backend:v1.2.0 image complies with your company's policy. The policy requires a security scan attestation signed by your official build system. How does ec-cli confirm that the attestation it finds really came from the build system and hasn't been faked?

Sigstore Verification provides the mechanism to check the digital "seals" (signatures) on container images and their associated attestations. It's like a digital notary public, verifying the authenticity and integrity of these software credentials.

Key Concepts: The Sigstore Toolbox

ec-cli leverages the tools and concepts from the Sigstore project to perform these checks. Think of Sigstore as a toolbox for signing and verifying software:

  1. Digital Signatures: Imagine a unique, unforgeable wax seal on a letter. A digital signature works similarly. It's created using a secret private key and can be verified by anyone using the corresponding public key. If the signature is valid, it proves:

    • Authenticity: Who signed the artifact (the owner of the private key).
    • Integrity: The artifact hasn't been changed since it was signed.
  2. cosign: This is the primary command-line tool (and underlying library used by ec-cli) for signing and verifying container images and other artifacts (like attestations). It handles the mechanics of attaching signatures and checking them.

  3. Fulcio (for Keyless Signing): Traditionally, managing cryptographic keys is complex. Sigstore offers a "keyless" approach. Fulcio is like a temporary certificate office. It can issue short-lived signing certificates based on an identity you already have, like your GitHub account or Google Cloud service account (using OpenID Connect - OIDC). This means you don't need to manage long-term keys yourself for signing.

  4. Rekor (Transparency Log): This is like a public, tamper-proof ledger or bulletin board. When something is signed (especially using the keyless method), a record of that signing event can be published to Rekor. When verifying, cosign (and thus ec-cli) can check Rekor to see if the signature was publicly recorded. This adds another layer of security, making it harder for attackers to hide malicious actions.

How ec-cli Uses Sigstore Verification

ec-cli doesn't perform the cryptographic operations directly. Instead, it uses the libraries provided by cosign to handle the verification heavy lifting. The instructions for how to verify are given in the Policy Definition & Sources.

Remember the policy configuration file from Chapter 2? It contains clues for ec-cli on how to verify signatures:

# my-policy-config.yaml (Simplified)
---
# Option 1: Using a specific Public Key
publicKey: |
  -----BEGIN PUBLIC KEY-----
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
  -----END PUBLIC KEY-----
rekorUrl: "https://rekor.sigstore.dev" # Check the public Rekor log
---
# Option 2: Using Keyless Verification (Identity)
identity:
  subject: "build-robot@my-company.com" # Expected signer identity
  issuer: "https://accounts.google.com" # Where the identity comes from
rekorUrl: "https://rekor.sigstore.dev" # Check the public Rekor log
---
# Option 3: Keyless but ignoring Rekor check
identity:
  subjectRegexp: ".*@my-company-ci.iam.gserviceaccount.com" # Regex allowed
  issuer: "https://token.actions.githubusercontent.com"
# If ignoreRekor is true, Rekor check is skipped
ignoreRekor: true
---

Explanation:

  • publicKey: If provided, ec-cli will use cosign to check if signatures were made by the corresponding private key.
  • identity: If publicKey is not provided, ec-cli expects keyless signatures. It checks if the short-lived certificate used for signing matches the expected subject (e.g., email, service account) and issuer (e.g., Google, GitHub Actions). You can use exact matches (subject, issuer) or regular expressions (subjectRegexp, issuerRegexp).
  • rekorUrl: Tells ec-cli which Rekor instance to query to see if the signature event was logged.
  • ignoreRekor: If set to true, ec-cli will skip the Rekor check.

The Flow:

When ec validate image runs:

  1. It identifies an image from the Application Snapshot.
  2. It finds associated signatures and attestations in the OCI registry (more on this in the next chapter).
  3. It reads the verification rules (publicKey or identity, rekorUrl, ignoreRekor) from the Policy Definition & Sources.
  4. It calls the cosign libraries to perform Sigstore verification.
  5. If verification passes, the signatures/attestations are considered trustworthy and can be used as input for the Validation Engine (Evaluator).
  6. If verification fails, ec-cli flags this as an error. The associated attestations might be ignored or cause the overall validation to fail.

Under the Hood: Verifying the Seals

Let's trace how ec-cli might verify an attestation's signature using the keyless method.

sequenceDiagram
    participant User
    participant ec_cli as ec validate image
    participant Policy as Policy Config
    participant OCIRegistry as OCI Registry
    participant CosignLib as Cosign Library
    participant RekorClient as Rekor Client

    User->>ec_cli: Runs `ec validate image --images ... --policy policy.yaml`
    ec_cli->>Policy: Load policy.yaml (reads identity, rekorUrl)
    Policy-->>ec_cli: Identity: subject='ci@org', issuer='github', rekorUrl='...'
    ec_cli->>OCIRegistry: Find attestations for image 'img@sha256:abc'
    OCIRegistry-->>ec_cli: Found Attestation A (with signature S)
    ec_cli->>CosignLib: Verify Attestation A / Signature S with identity & rekorUrl
    Note over CosignLib: Checks signature math is correct
    Note over CosignLib: Checks certificate matches identity (subject/issuer)
    CosignLib->>RekorClient: Is signature S recorded in Rekor? (if !ignoreRekor)
    RekorClient-->>CosignLib: Yes, found entry
    CosignLib-->>ec_cli: Verification SUCCESSFUL
    Note over ec_cli: Attestation A is trusted, pass to Evaluator
Loading

Code Connection:

  1. Loading Policy Settings: ec-cli parses the policy configuration to determine the verification method.

    File: internal/policy/policy.go (Simplified checkOpts function logic)

    // Simplified logic for creating cosign verification options
    func checkOpts(ctx context.Context, p *policy) (*cosign.CheckOpts, error) {
        opts := cosign.CheckOpts{}
        var err error
    
        if p.PublicKey != "" {
            // If publicKey is set, prepare for key-based verification
            log.Debug("Using public key verification")
            opts.SigVerifier, err = signatureVerifier(ctx, p) // Gets the verifier from the key
            // ... handle error ...
        } else {
            // Otherwise, prepare for keyless verification
            log.Debug("Using keyless verification")
            opts.Identities = []cosign.Identity{p.Identity()} // Use subject/issuer from policy
            // Fetch root certificates needed for keyless
            opts.RootCerts, _ = fulcio.GetRoots()
            opts.IntermediateCerts, _ = fulcio.GetIntermediates()
            // ... handle errors ...
        }
    
        opts.IgnoreTlog = p.ignoreRekor // Check if Rekor should be ignored
    
        if !opts.IgnoreTlog && p.RekorUrl != "" {
            // If checking Rekor, create a client
            opts.RekorClient, err = rekor.NewClient(p.RekorUrl)
            // ... handle error ...
            opts.RekorPubKeys, _ = cosign.GetRekorPubs(ctx) // Get Rekor's public keys
            // ... handle error ...
        }
    
        return &opts, nil
    }

    Explanation: This function reads the policy object (which holds data parsed from the policy YAML) and prepares a cosign.CheckOpts structure. This structure tells the cosign library exactly how to verify (use this key, or check this identity, check/ignore Rekor, use this Rekor URL).

  2. Triggering Verification: The main validation logic uses the prepared CheckOpts.

    File: internal/image/validate.go (Simplified ValidateImage function)

    func ValidateImage(ctx context.Context, comp app.SnapshotComponent, /*...*/ p policy.Policy, /*...*/) (*output.Output, error) {
        // ... setup, create application snapshot image 'a' ...
    
        // *** Trigger Signature Verification ***
        // 'a.ValidateImageSignature' and 'a.ValidateAttestationSignature'
        // will internally use the CheckOpts derived from the policy 'p'
        // to call cosign library functions.
        out.SetImageSignatureCheckFromError(a.ValidateImageSignature(ctx))
        out.SetAttestationSignatureCheckFromError(a.ValidateAttestationSignature(ctx))
    
        // If AttestationSignatureCheck failed, often return early
        if !out.AttestationSignatureCheck.Passed {
            return out, nil
        }
    
        // ... continue with syntax checks and evaluation ...
    
        return out, nil
    }

    Explanation: The ValidateImage function calls methods like ValidateAttestationSignature on the image object a. These methods use the cosign.CheckOpts (configured based on the policy) to invoke the actual verification routines from the cosign library.

  3. Using Verification Results: The results (success/failure, verified signatures/attestations) are stored in the output.Output object. Attestations that pass verification are then used by the Validation Engine (Evaluator).

  4. (Advanced) Rego Integration: Policies written in Rego can also directly invoke Sigstore verification using built-in functions.

    File: internal/rego/sigstore/sigstore.go (Conceptual call)

    # Example Rego rule snippet
    import rego.v1
    
    # Get verification options from the policy config (simplified)
    verification_options := {
        "certificate_identity": input.policy.identity.subject,
        "certificate_oidc_issuer": input.policy.identity.issuer,
        "rekor_url": input.policy.rekorUrl,
        "ignore_rekor": input.policy.ignoreRekor or false,
    }
    
    # Call the built-in function to verify attestations for the image
    verify_result := ec.sigstore.verify_attestation(input.imageRef, verification_options)
    
    # Deny if verification failed or no valid attestations were found
    deny contains msg if {
      not verify_result.success
      msg := sprintf("Sigstore attestation verification failed for %s: %s", [input.imageRef, concat(", ", verify_result.errors)])
    }

    Explanation: Rego rules can use functions like ec.sigstore.verify_attestation, passing the image reference and verification options derived from the policy. This allows complex policy logic to directly incorporate signature checks. The Go code (internal/rego/sigstore/sigstore.go) implements these Rego functions by calling the same underlying cosign libraries.

Conclusion

You've learned about Sigstore Verification – ec-cli's mechanism for ensuring trust in container images and attestations. It acts like a digital notary, using tools like cosign, Fulcio, and Rekor to check digital signatures. We saw how the Policy Definition & Sources control whether to use public keys or keyless identity verification, and whether to consult the Rekor transparency log. This verification step is crucial for providing trustworthy evidence to the Validation Engine (Evaluator).

Now that we understand how ec-cli verifies the authenticity of artifacts, let's look closer at how it actually finds and handles these artifacts (images, signatures, attestations) in container registries.

Next up: Image & OCI Artifact Handling.


Generated by AI Codebase Knowledge Builder

Clone this wiki locally