Skip to content

Document optional strict bootc signature policy (and provide patches for options) for custom images #215

Description

@Danathar

Note

I thought I'd submit this after running into an issue and banging my head trying to figure out why my strict signature policy was not working during bootc upgrade after switching to my custom image. The cosign v2 vs v3 behavior drove me crazy for a while. So, with some AI assistance to make it easier to read and verify reference links, here is something that might help others. I'd be happy to write additional documentation or patches if people agree it would be helpful. This is probably too advanced for a basic user, but if you are going to use a custom repo as the source of truth for a production box, I think strict signature policy, similar in spirit to how Universal Blue protects its own images, is worthwhile. Troubleshooting signature issues is NOT FUN! ;) Honestly, we could skip all this too and just recommend people use BlueBuild if they want the strict stuff without the additional bits added here (they make a good product), but...here it is.

Summary

The current image-template signing flow is good for producing signed custom images, but it does not appear to document the additional steps needed when a user wants their installed bootc system to strictly enforce signatures for their own custom image repository on future upgrades.

The template already handles CI signing well. The missing advanced documentation is about installed-system enforcement: baking repo-specific trust policy into the custom image so future bootc upgrade operations reject unsigned or improperly signed updates.

That distinction matters because an image can be signed and manually verifiable without the installed system requiring signatures later.

Why this matters

The template currently signs published images with cosign, and the README tells users to generate a keypair, commit cosign.pub, store cosign.key as SIGNING_SECRET, and switch with:

sudo bootc switch ghcr.io/<username>/<image_name>

That is a much smoother default path, and it is probably the right beginner experience.

However, users who expect stricter production-style behavior may assume that because CI signs the image, their installed machine will require those signatures during future bootc upgrade operations. That requires more than CI signing. The image also needs to install the public key and write container signature policy/discovery config inside the OS.

Without those extra steps, the image is signed, but the installed system has no policy that requires future updates to be signed by that custom repo key.

Suggested documentation addition

Add an advanced section such as:

Optional: enforce signatures for your custom image during bootc upgrade

It could explain that strict enforcement requires:

  1. Copying the repo's cosign.pub into the image, for example:

    /etc/pki/containers/<image_name>.pub
    
  2. Writing a repository-specific /etc/containers/policy.json rule using sigstoreSigned, for example:

    {
      "transports": {
        "docker": {
          "ghcr.io/<username>/<image_name>": [
            {
              "type": "sigstoreSigned",
              "keyPath": "/etc/pki/containers/<image_name>.pub",
              "signedIdentity": {"type": "matchRepository"}
            }
          ]
        }
      }
    }
  3. Writing a matching /etc/containers/registries.d/ file so containers/image can discover cosign signatures:

    docker:
      ghcr.io/<username>/<image_name>:
        use-sigstore-attachments: true
  4. Recommending the explicit policy-enforcing form for the first switch into the custom image:

    sudo bootc switch --enforce-container-sigpolicy ghcr.io/<username>/<image_name>:latest
    sudo systemctl reboot
  5. Documenting that after booting into the custom image family, future updates should normally be:

    sudo bootc upgrade

Cosign compatibility gotcha (what I ran into after upgrading cosign to v3)

If the optional strict-policy path uses use-sigstore-attachments: true, docs should also mention that the signer needs to produce signatures in the format that bootc/containers-image policy can discover. Otherwise, a future cosign v3-based signing flow may produce signatures that default cosign verify can find, while the bootc/containers-image policy path reports:

A signature was required, but no signature exists

That can happen when the signature is stored in a newer bundle/referrer format but the installed policy expects legacy sigstore attachments through use-sigstore-attachments.

For strict bootc policy compatibility with that discovery path, docs should tell users how to verify the same signature format that bootc policy will discover.

For example, with cosign v3 this may require signing with flags like:

cosign sign \
  --yes \
  --new-bundle-format=false \
  --use-signing-config=false \
  --registry-referrers-mode=legacy \
  --key env://COSIGN_PRIVATE_KEY \
  ghcr.io/<username>/<image_name>@sha256:<digest>

And verifying with:

cosign verify \
  --key cosign.pub \
  --new-bundle-format=false \
  ghcr.io/<username>/<image_name>:latest

The current template appears to install cosign v2.6.1, so this may not affect the template today. But it is worth documenting before the template or users move to cosign v3, because default cosign verify passing is not always the same thing as bootc policy being able to discover the signature.

Possible implementation options

This could be added in one of a few ways:

  1. Documentation-only advanced section.
  2. Optional helper script in build_files/ that writes policy files when users opt in.
  3. Optional Containerfile snippet users can uncomment.
  4. A note pointing users to BlueBuild's signing module if that is the preferred supported path for strict policy generation.

Why not make this the default?

I do not think this necessarily has to be the default for all image-template users. Strict enforcement has real operational sharp edges:

  1. key rotation must be planned carefully,
  2. users need to use the policy-enforcing first switch command,
  3. repo renames affect the trust policy scope,
  4. local unsigned builds are not accepted by strict policy,
  5. signature storage format matters.

But documenting the distinction would help users who want to run custom images as a production system and expect the installed machine to reject future unsigned updates.

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions