Skip to content

Harden image integrity, guest preparation, and Proxmox template replacement#5

Open
iandk wants to merge 24 commits into
mainfrom
codex/audit-remediation
Open

Harden image integrity, guest preparation, and Proxmox template replacement#5
iandk wants to merge 24 commits into
mainfrom
codex/audit-remediation

Conversation

@iandk

@iandk iandk commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Why

Cloudbuilder turns upstream cloud images into reusable Proxmox templates and
therefore sits on a destructive and security-sensitive path: it downloads and
customizes operating-system images, replaces existing templates, and affects
every VM cloned from those templates.

The previous implementation relied on mutable upstream downloads, had limited
validation of customized or imported images, and replaced templates without a
complete rollback transaction. Several guest assumptions had also drifted
across current RHEL, Debian, Fedora, openSUSE, Ubuntu, and Alpine releases.

This PR makes reliability and fail-closed behavior the default. In particular,
successful updates preserve the existing VMID, and every managed template is
required to consume its Proxmox Cloud-Init drive.

What changed

Image provenance and downloads

  • Pin every supported upstream image with SHA-256 or vendor SHA-512 metadata.
  • Verify downloads before decompression or customization and record the digest
    of the final customized artifact.
  • Reject mutated cached artifacts, malformed manifests, path traversal,
    unexpected fields, backing-file images, and digest mismatches before publish.
  • Add bounded retries and a global deadline for transient download failures.
  • Refresh the catalog to supported releases and remove Fedora 42, Ubuntu 25.04,
    and Ubuntu 25.10 from active management. Existing Proxmox templates for those
    releases are deliberately not deleted.

Proxmox replacement safety

  • Replace templates through a validated candidate plus full-clone rollback
    backup instead of an unprotected destroy/import sequence.
  • Preserve the original VMID across successful updates and restore the backup
    to that VMID when cutover fails.
  • Fail closed when template inventory, storage contents, base disks, or linked
    clone state cannot be established safely.
  • Reserve staging VMIDs explicitly, disable implicit Cloud-Init package upgrades
    with ciupgrade=0, and require a Cloud-Init drive on imported templates.
  • Reject forced manifest imports whose requested VMID conflicts with the live
    same-name template before local publication, download, clone lookup, or
    Proxmox mutation.
  • Serialize host-side operations with a global lock.

Guest correctness and security

  • Make Cloud-Init installation and boot lifecycle an explicit template
    contract for legacy systemd, split systemd, and OpenRC guests.
  • Validate Cloud-Init unit files, persistent links, and canonical link targets
    read-only before publishing normal builds or manifest imports.
  • Handle Fedora/Debian split Cloud-Init units and openSUSE's one-byte split-unit
    placeholders without relying on systemctl capability probes inside the
    virt-customize chroot.
  • Preserve vendor Cloud-Init modules and SSH configuration while enforcing
    PasswordAuthentication no and PermitRootLogin no without retaining a
    temporary host key.
  • Preserve per-clone machine identity and SSH host-key regeneration.
  • Keep network-online waits bounded without making a missing DHCP lease block
    first boot indefinitely.
  • Update Rocky before installing additional packages and use responsive Fedora
    and Rocky mirrors.
  • Guard RHEL BLS/fstab kernel arguments against libguestfs appliance leakage,
    relabel RHEL images with the guest's own SELinux policy, and verify critical
    PID 1/QGA labels before publication.
  • Support openSUSE's vendor SSH layout and clean Alpine Cloud-Init configuration
    without truncating following YAML.

Maintenance and documentation

  • Add GitHub Actions validation for Python syntax, templates.json, the unit
    suite, and the network wait script.
  • Add focused tests for download integrity, manifest safety, metadata atomicity,
    linked-clone checks, transactional rollback, VMID preservation, Cloud-Init
    lifecycle variants, SSH policy, SELinux/BLS behavior, and distro catalog rules.
  • Document lifecycle guarantees, safe replacement behavior, Cloud-Init
    requirements, supported testing paths, and operational recovery behavior.
  • Remove the manually maintained changelog; Git commits and pull requests remain
    the intentional project history.
  • Make update checks non-mutating by default while retaining explicit forced
    self-update behavior.

Validation performed

  • 64/64 Python unit tests passed locally and on the Proxmox host.
  • All 11 managed templates were full-cloned and booted as disposable canaries.
  • QGA availability, requested hostname, Cloud-Init user/key injection, machine
    identity, SSH policy, disk growth, guest-agent/service state, and bounded
    network waits were checked across the distro matrix.
  • Stopped-disk inspection passed for all 11 canaries, including empty source
    machine identity, unique clone machine IDs/SSH host keys, clean Cloud-Init
    status, exact injected data, and approximately +1 GiB root filesystem growth.
  • Alma 9/10, Fedora 43, Rocky 10, and CentOS Stream 10 were rebuilt and imported
    transactionally with their original VMIDs preserved.
  • Fedora 43 was rebuilt once more after the final split-unit guard changes and
    passed a fresh post-cutover live clone test from VMID 9008.
  • Every final local qcow2 passed full digest comparison with metadata,
    qemu-img check, and no-backing-file validation.
  • Read-only Cloud-Init lifecycle validation passed all 11 final images; the
    actual guest guard ran successfully on Alma, Fedora, Debian, openSUSE, and
    Alpine overlays.
  • Final Proxmox inspection confirmed the exact managed name/VMID mapping,
    stopped template state, ciupgrade=0, Cloud-Init drive, SCSI disk, firewall,
    no locks, no staging/canary VMs or volumes, and no modification of the three
    unmanaged EOL templates.
  • cloudbuilder.py --status left metadata unchanged.

The BM1 test bridge intentionally had no DHCP service. This validated the
bounded no-lease behavior and absence of first-boot hangs; it did not validate
successful DHCP lease acquisition from that bridge.

Compatibility and rollout notes

  • Existing templates keep their VMIDs. Replacement now requires enough storage
    for a full rollback clone and a candidate during cutover.
  • Replacement refuses to proceed when linked-clone safety cannot be proven.
  • Cloud-Init is now mandatory for every managed template and imported artifact.
  • Previously customized local images without a recorded final digest require a
    rebuild before they can be trusted as cache/recovery inputs.
  • Operations remain serialized per Cloudbuilder host. Safe parallel builds need
    cluster-wide locking and VMID reservation and are intentionally outside this
    PR.
  • GitHub Release signing/attestation is also left for follow-up; this PR's trust
    model is pinned upstream digests plus verified final-artifact metadata.

Reviewer focus

  • Transaction boundaries and rollback cleanup in proxmox.py.
  • Fail-closed ordering before destructive actions in cloudbuilder.py.
  • Download, manifest, and read-only guest validation in template.py.
  • Distro component composition and pinned catalog data in templates.json.
  • Whether the documented storage overhead and refusal modes are operationally
    clear enough for existing deployments.

Checklist

  • No change to the intentional Git/PR-based release-history model.
  • Existing VMIDs preserved during live replacement testing.
  • Cloud-Init contract validated across supported init-system layouts.
  • Unit, artifact, overlay, and live Proxmox validation completed.
  • Disposable VMs, volumes, keys, overlays, and diagnostic artifacts removed.
  • Push branch and open the draft PR only after explicit approval.

@iandk
iandk marked this pull request as ready for review July 17, 2026 23:45
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.

1 participant