Skip to content

Retain blueprint as /root/blueprint.toml in built images#2274

Draft
ochosi wants to merge 2 commits intoosbuild:mainfrom
ochosi:retain-blueprint
Draft

Retain blueprint as /root/blueprint.toml in built images#2274
ochosi wants to merge 2 commits intoosbuild:mainfrom
ochosi:retain-blueprint

Conversation

@ochosi
Copy link
Copy Markdown
Contributor

@ochosi ochosi commented Mar 20, 2026

Why

This change follows the established patter of Anaconda which retains a kickstart in /root and a similar pattern in bootc disk images that retains Containerfiles in /root.
Today, users who receive a built image have no easy way to know what blueprint produced it or to rebuild it. The RHSM facts we already embed provide some provenance metadata (API type, blueprint ID), but not the full blueprint itself.

This PR writes the complete blueprint to /root/blueprint.toml in every built image. This serves two purposes:

  1. Provenance -- anyone inspecting a running system can see exactly what customizations were requested. This is useful for auditing, debugging, and support.
  2. Reproducibility -- a user can extract the blueprint and feed it back into the build tooling to produce a similar image. This is not SLSA3-grade provenance (the blueprint is not signed, and the exact package versions are not pinned in it), but it covers the common case of "I have this image, how do I make another one like it?"

What

  • A new BlueprintTOML []byte field on OSCustomizations carries the TOML-encoded blueprint through the manifest pipeline.
  • osCustomizations() encodes the blueprint using toml.Marshal() (the Blueprint struct already has toml:"..." tags, and BurntSushi/toml is already a dependency).
  • OS.serialize() writes it to /root/blueprint.toml via the existing inline-data copy stage mechanism (same pattern as modularity failsafe files).
  • User passwords (Customizations.User[].Password) are redacted before marshaling -- the blueprint is deep-copied and passwords are nilled out so they never appear in the image, whether plaintext or hashed.

Design decision

Since this is a proposal, I opted for a minimal implementation. If we decide we want to go ahead and do this, but the architecture is wrong, I'm happy to rework this PR.

Testing

  • Built a test image manually with image-builder-cli and verified /root/blueprint.toml exists with expected content
  • Unit test verifying the serialized pipeline contains a copy stage targeting /root/blueprint.toml
  • Unit tests for TOML encoding and password redaction in osCustomizations()
  • Regenerate manifest checksums (all manifests will change due to the new stage)

ochosi added 2 commits March 21, 2026 00:30
This change follows Anaconda's established pattern of retaining
kickstarts in /root and a pattern in bootc disk image creation that
retains Containerfiles in /root.
For end-users, this is intended as an easy and accessible way of
rebuilding an image.

Encode the blueprint used to build an image as TOML and write it to
/root/blueprint.toml inside the image filesystem as build provenance.
This uses the existing inline-data copy stage mechanism, the same
pattern used for modularity failsafe files. The blueprint is always
retained, for all distros.

The TOML-encoded blueprint flows through a dedicated BlueprintTOML
field on OSCustomizations (not mixed with user-provided Files).
Deep-copy the blueprint and nil out all User[].Password fields before
TOML-marshaling into BlueprintTOML. This prevents plaintext or hashed
passwords from being written to /root/blueprint.toml in the image.

The original blueprint passed to the rest of the pipeline is not
modified; DeepCopy() performs a JSON round-trip to produce an
independent copy.
@ochosi ochosi force-pushed the retain-blueprint branch from 280fb57 to 681d7a8 Compare March 20, 2026 23:31
Copy link
Copy Markdown
Member

@supakeen supakeen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this purely from a functional standpoint, not the implementation:

This is nice to have though there are some concerns.

One is that we'd need to have this functionality gated behind an ImageOption (BuildOption) so we can turn it off for any images produced in build systems that use customizations (e.g. RHUI, LVMified images for clouds, etc). Initially off by default for the cli as we will want to plumb through/make the option available in koji-image-builder and pungi for those systems while the service can toggle it on.

The other is secrets, any file, first-boot, or many of the other customizations can potentially contain secrets. While those secrets would usually be able to be found by root anyhow it'd need quite some thought.

@ochosi
Copy link
Copy Markdown
Contributor Author

ochosi commented Mar 21, 2026

That all makes sense! Fwiw I had considered many of those angles already, but mostly wanted to get us to decide if this is something we want to do.

I totally agree with gating this (but maybe "on by default"?) and more things may have to be redacted.

First boot customizations etc are tricky because we cannot know what to redact. Might be easiest to just not retain those at all. Ofc that somewhat diminishes the value of the blueprint retention.

@supakeen
Copy link
Copy Markdown
Member

On by default should be the end goal but we'd need to start with off by default until we have deployed and configured versions of koji-image-builder and pungi that turn it off. After that it can be turned on by default in the library if we want to.

This is to make sure that blueprints don't start showing up in Fedora, CentOS, and RHEL composes and marketplace images where blueprints are also used in the pungi configs.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants