Conversation
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
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| 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.") |
| 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' | ||
| ) | ||
| ) |
There was a problem hiding this comment.
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
- 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.
| 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)) |
There was a problem hiding this comment.
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
- 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.
Summary
ansibleprepare pluginansible-playbookwithcontainers.podman.podmanconnection plugin, commits the result, switches viabootc switch, and rebootstests/prepare/bootc/patternDetails
On image mode guests,
/usris 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:
ansible-playbookis available on the guest (installsansible-coreif needed)ansible-playbook -c containers.podman.podmanbootc switch --transport containers-storageand rebootsTest plan
tests/prepare/bootc-ansible/test with an image mode guestCloses #4624