NO-ISSUE: Cache the etcd image at snapshot time so recert never needs a live pull secret - #33
Conversation
…ll secret cmd_boot's recert step starts a standalone etcd container with --authfile /var/lib/kubelet/config.json -- the golden snapshot's frozen copy of that file, which cmd_boot must never overwrite out-of-band (MCO owns it; see test_boot_pull_secret_not_written_to_node). If the credential baked into that frozen file is ever revoked or rotated independently (e.g. a leaked registry credential getting rotated), every future boot from that snapshot fails at this exact pull with no way to recover short of rebuilding the snapshot. RECERT_IMAGE and the nodeip-configuration image already avoid this problem by being cached into the VM's local podman store at snapshot-creation time (cmd_snapshot), so cmd_boot's podman run never needs the network or the authfile for them. Cache the etcd image the same way: once it's present locally, podman's default --pull=missing behavior skips the pull (and the credential check) entirely, regardless of whether the snapshot's baked-in pull secret is still valid. Existing snapshots still need to be rebuilt once for this to take effect -- this only prevents the failure for snapshots created after this change. Signed-off-by: Elior Erez <eerez@redhat.com>
WalkthroughSnapshot boot-image caching now includes the detected standalone etcd image through a ChangesSnapshot etcd image caching
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: Caution Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional.
❌ Failed checks (1 error)
✅ Passed checks (10 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cluster-tool`:
- Line 802: Validate the YAML-derived etcd_image at the call into cache_image
using a strict image-reference allow-list, rejecting unsafe values before any
SSH command executes. Update cache_image to shell-quote the validated image
wherever it builds podman image exists, podman pull, and podman create commands,
or use argv-based execution consistently throughout.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: osac-project/coderabbit/.coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 6780a82a-57b8-4e69-82b7-7b3354c242fb
📒 Files selected for processing (2)
cluster-tooltest_cluster_tool.py
| # podman's default --pull=missing behavior skips the network pull (and | ||
| # the credential check) entirely, regardless of whether the pull secret | ||
| # baked into the snapshot is still valid by the time this snapshot boots. | ||
| cache_image(vm_ip, etcd_image, "cache-etcd") |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
Validate and shell-quote the detected etcd image.
Line 802 passes YAML-derived data into cache_image, which interpolates it into podman image exists, podman pull, and podman create shell commands. A crafted image value could execute arbitrary commands in the SSH session.
Validate the image reference with a strict allow-list and use shell quoting or argv-based execution throughout cache_image.
As per path instructions, validate at trust boundaries with allow-lists and prevent command injection.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cluster-tool` at line 802, Validate the YAML-derived etcd_image at the call
into cache_image using a strict image-reference allow-list, rejecting unsafe
values before any SSH command executes. Update cache_image to shell-quote the
validated image wherever it builds podman image exists, podman pull, and podman
create commands, or use argv-based execution consistently throughout.
Source: Path instructions
Summary
cmd_boot's recert step starts a standalone etcd container with--authfile /var/lib/kubelet/config.json— the golden snapshot's frozen copy of that file, whichcmd_bootmust never overwrite out-of-band (MCO owns it; seetest_boot_pull_secret_not_written_to_node). If the credential baked into that frozen file is ever revoked or rotated independently of the live pull secretcmd_bootis given (e.g. a leaked registry credential getting rotated), every future boot from that snapshot fails at this exact pull, with no way to recover short of rebuilding the snapshot.Observed in production: osac-installer#30327475992, reproduced on two different machines (osac-8, osac-9), same image digest —
unauthorized: access to the requested resource is not authorizedpullingquay.io/openshift-release-dev/ocp-v4.0-art-dev. Rotating the pull secret in Vault did not fix it, because it was never the credential recert actually uses.Fix
RECERT_IMAGEand the nodeip-configuration image already avoid this problem by being cached into the VM's local podman store at snapshot-creation time (cmd_snapshot), socmd_boot'spodman runnever needs the network or the authfile for them. This caches the etcd image the same way — once it's present locally, podman's default--pull=missingbehavior skips the pull (and the credential check) entirely, regardless of whether the snapshot's baked-in pull secret is still valid.Also added
cache-etcdto the post-snapshot prune/cleanup command, matchingcache-recert/cache-nodeip, so the new anchor container doesn't leak.Existing snapshots still need to be rebuilt once for this to take effect — this only prevents the failure for snapshots created after this change merges.
Test plan
test_snapshot_caches_etcd_image(mirrorstest_snapshot_caches_recert_before_prune): asserts the etcd image is pulled and anchored before pruningtest_snapshot_skips_pull_when_image_already_cachedto expect all three anchors (recert/nodeip/etcd) when images are already localtest_snapshot_prunes_container_imagesto assert the newcache-etcdanchor is cleaned uppython3 -m pytest test_cluster_tool.py— 158 passed, same 11 pre-existing failures as onmainbefore this change (confirmed viagit stashcomparison — unrelatedDNSMASQ_DIRenvironment issue, not caused by this PR)python3 -m py_compile cluster-tool test_cluster_tool.py— cleanSummary by CodeRabbit