Skip to content

Support Ansible prepare step in image mode#4636

Draft
thrix wants to merge 1 commit intomainfrom
ansible-prepare-image-mode
Draft

Support Ansible prepare step in image mode#4636
thrix wants to merge 1 commit intomainfrom
ansible-prepare-image-mode

Conversation

@thrix
Copy link
Contributor

@thrix thrix commented Mar 3, 2026

Summary

  • Add image mode (bootc) support for the ansible prepare plugin
  • When running on a bootc guest, starts a container, runs ansible-playbook with containers.podman.podman connection plugin, commits the result, switches via bootc switch, and reboots
  • Includes integration test following the tests/prepare/bootc/ pattern

Details

On image mode guests, /usr is read-only and system changes must be baked into the container image. The existing shell and install prepare plugins handle this via Containerfile directives, but the ansible plugin had no image mode support.

The implementation:

  1. Flushes any pending containerfile directives from prior shell/install phases
  2. Ensures ansible-playbook is available on the guest (installs ansible-core if needed)
  3. Gets the current bootc image and ensures it's in podman storage
  4. Starts a container from the image
  5. Runs each playbook with ansible-playbook -c containers.podman.podman
  6. Commits the container to a new image
  7. Switches via bootc switch --transport containers-storage and reboots

Test plan

  • Run existing prepare tests for regressions
  • Run new tests/prepare/bootc-ansible/ test with an image mode guest
  • Verify log output shows: container start, ansible-playbook with podman connection, commit, switch, reboot

Closes #4624

Add image mode support for the ansible prepare plugin. When running
on a bootc guest, the plugin:

1. Flushes any pending containerfile directives from prior phases
2. Ensures ansible-playbook is available on the guest
3. Starts a container from the current bootc image
4. Runs ansible-playbook with the containers.podman.podman connection
   plugin targeting the container
5. Commits the container to a new image
6. Switches via bootc switch and reboots

This enables running Ansible playbooks (e.g. system roles) against
image mode guests where /usr is read-only and changes must be baked
into the container image.

Closes #4511

Assisted-by Claude Code
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds support for the Ansible prepare step in image mode (bootc), including a new integration test. However, a high-severity Path Traversal vulnerability and several instances of potential Command Injection were identified due to improper handling of user-supplied paths and command arguments, which could allow an attacker to read arbitrary files or execute unintended commands. It is recommended to use existing path sanitization helpers and switch from shell-based command execution to structured command objects.

Comment on lines +273 to +278
if lowercased.startswith('file://'):
rel_path = Path(raw_playbook[7:])
else:
rel_path = Path(raw_playbook)

local_path = self.step.plan.anchor_path / rel_path
Copy link
Contributor

Choose a reason for hiding this comment

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

security-high high

A high-severity Path Traversal vulnerability exists in the _resolve_playbook_for_guest method. It resolves local playbook paths by concatenating the plan's anchor path with a user-supplied path without proper validation, potentially allowing an attacker to read arbitrary files. The method fails to use the unrooted() helper or verify that the resulting path is within the anchor path. Additionally, consider refactoring the duplicated playbook resolution logic to improve maintainability.

Suggested change
if lowercased.startswith('file://'):
rel_path = Path(raw_playbook[7:])
else:
rel_path = Path(raw_playbook)
local_path = self.step.plan.anchor_path / rel_path
if lowercased.startswith('file://'):
rel_path = Path(raw_playbook[7:])
else:
rel_path = Path(raw_playbook)
local_path = self.step.plan.anchor_path / rel_path.unrooted()
if not local_path.is_relative_to(self.step.plan.anchor_path):
raise PrepareError(f"Playbook path '{raw_playbook}' is outside the plan directory.")

Comment on lines +346 to +352
f'{guest.facts.sudo_prefix} podman run -d'
f' --name {container_name}'
f' -v {guest.run_workdir}:{guest.run_workdir}:Z'
f' {current_image}'
f' sleep infinity'
)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The podman run command is constructed using an f-string and executed via ShellScript. Variables such as guest.run_workdir and current_image are included without proper shell quoting. While paths internally generated and controlled by the application, like guest.run_workdir, do not strictly require quoting for security against command injection, lack of quoting can still cause the command to fail if paths contain spaces. For current_image, if it's user-provided, this poses a command injection risk. Using the Command class is preferred as it handles argument quoting safely for all variables and improves robustness.

            # Start a container from the current bootc image
            logger.info('ansible', f'starting container {container_name}', 'green')
            podman_run_cmd = Command('podman', 'run', '-d')
            if guest.facts.sudo_prefix:
                podman_run_cmd = Command(guest.facts.sudo_prefix) + podman_run_cmd
            podman_run_cmd += Command('--name', container_name)
            podman_run_cmd += Command('-v', f'{guest.run_workdir}:{guest.run_workdir}:Z')
            podman_run_cmd += Command(current_image, 'sleep', 'infinity')
            guest.execute(podman_run_cmd)
References
  1. Paths that are internally generated and controlled by the application do not need to be quoted in shell commands, as they are not considered user-provided input.

Comment on lines +377 to +387
ansible_cmd = (
f'{guest.facts.sudo_prefix} ansible-playbook'
f' -c containers.podman.podman'
f" -i '{container_name},'"
f' {guest_playbook}'
)

if self.data.extra_args:
ansible_cmd += f' {self.data.extra_args}'

return guest.execute(ShellScript(ansible_cmd))
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The ansible-playbook command is constructed using an f-string and executed via ShellScript. Variables such as container_name, guest_playbook, and self.data.extra_args are included without proper shell quoting. While paths internally generated and controlled by the application, like container_name, do not strictly require quoting for security against command injection, lack of quoting can still cause the command to fail if paths contain spaces. For guest_playbook and especially self.data.extra_args (which is user-provided), this poses a significant command injection risk. Structured Command objects should be used instead of raw shell strings to handle argument quoting safely.

                    # Build the ansible-playbook command to run on the guest
                    ansible_cmd = Command('ansible-playbook')
                    if guest.facts.sudo_prefix:
                        ansible_cmd = Command(guest.facts.sudo_prefix) + ansible_cmd
                    ansible_cmd += Command('-c', 'containers.podman.podman', '-i', f'{container_name},', guest_playbook)

                    if self.data.extra_args:
                        import shlex
                        ansible_cmd += Command(*shlex.split(self.data.extra_args))

                    return guest.execute(ansible_cmd)
References
  1. Paths that are internally generated and controlled by the application do not need to be quoted in shell commands, as they are not considered user-provided input.

@thrix thrix added this to planning Mar 4, 2026
@github-project-automation github-project-automation bot moved this to backlog in planning Mar 4, 2026
@thrix thrix moved this from backlog to implement in planning Mar 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: implement

Development

Successfully merging this pull request may close these issues.

Implement ansible prepare step support in image mode

1 participant