Skip to content

Commit 06acb19

Browse files
mwilckalisonlhart
andauthored
Allow overriding podman user ID with --container-option (#2079) (#2080)
* feat: allow overriding podman user ID with --container-option (#2079) ansible-navigator hard-codes `--user=root` for podman, even if the user has set a different user id with a command line option like `--container-option="--user=$UID"`. Check the list of container options passed by the user, and avoid setting `--user` if the user has already done so. This allows running an EE with `--userns=keep-id --user=$UID"`, which is necessary in order to access th DBus session bus. To make this actually work, additional container options are necessary. If `--userns=keep-id` is set and the user in question doesn't exist in the container, podman automatically adds an /etc/passwd entry setting HOME to the container's workdir, which causes the EE's ssh setup to fail. To fix that, the `--passwd-entry` option must be passed to podman (see containers/podman#13616), using a user-writable directory that should exist and must be mounted into the container in the default HOME location (/home/runner). The user's actual home directory can't be used for this purpose, because SELinux prevents relabeling it. Sample command line: CONT_HOME=/tmp/runner mkdir -p "$CONT_HOME" python3 -m ansible_navigator run test.yml \ --execution-environment-image ee_with_keyring:latest \ --pull-policy missing \ --mode stdout \ -i inventory \ --eev "/run/user/$UID/bus:/run/user/$UID/bus" \ --penv DBUS_SESSION_BUS_ADDRESS \ --container-options="--userns=keep-id" \ --container-options="--user=$UID" \ --container-options="--security-opt=label=disable" \ --eev "$CONT_HOME:/home/runner" \ --container-options=--passwd-entry='$USERNAME:x:$UID:0:$NAME:/home/runner:/bin/sh' * Add -u checks and tests * Pre-commit fixes --------- Co-authored-by: Alison Hart <alhart@redhat.com>
1 parent a215205 commit 06acb19

File tree

3 files changed

+137
-2
lines changed

3 files changed

+137
-2
lines changed

.config/dictionary.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ maxsplit
4848
mergeable
4949
mkdocstrings
5050
mqueue # https://github.com/ansible/ansible-runner/issues/984
51+
myuser
5152
myproject
5253
netcommon # Ansible network collection, seen in tests and README
5354
netconf
55+
nofile
5456
nonblocking
5557
oldmask
5658
oneline

src/ansible_navigator/runner/base.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,13 @@ def __init__(
105105

106106
container_options = container_options or []
107107

108-
# when the ce is podman, set the container user to root
108+
# when the ce is podman, set the container user to root unless already specified
109109
if self._ce == "podman":
110-
container_options.append("--user=root")
110+
has_user_flag = any(
111+
opt.startswith(("--user", "-u=", "-u ")) or opt == "-u" for opt in container_options
112+
)
113+
if not has_user_flag:
114+
container_options.append("--user=root")
111115

112116
# Fix SSH agent socket when running Docker on macOS.
113117
if sys.platform == "darwin" and self._ce == "docker":

tests/unit/runner/test_base.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,132 @@ def test_ssh_agent_options_order(monkeypatch: pytest.MonkeyPatch) -> None:
6767
assert "--test" in opts
6868
# Assert that SSH agent fix options are set first to allow overriding them.
6969
assert opts[2] == "--test"
70+
71+
72+
def test_podman_user_root_default() -> None:
73+
"""Test that podman sets --user=root by default."""
74+
base = Base(
75+
container_engine="podman",
76+
execution_environment=True,
77+
)
78+
79+
opts = base._runner_args.get("container_options") or []
80+
81+
assert "--user=root" in opts
82+
83+
84+
@pytest.mark.parametrize(
85+
"user_option",
86+
(
87+
"--user=myuser",
88+
"--user",
89+
"-u",
90+
"-u=myuser",
91+
),
92+
)
93+
def test_podman_user_override(user_option: str) -> None:
94+
"""Test that podman respects user-provided --user or -u options.
95+
96+
Args:
97+
user_option: The user option to test
98+
"""
99+
base = Base(
100+
container_engine="podman",
101+
execution_environment=True,
102+
container_options=[user_option],
103+
)
104+
105+
opts = base._runner_args.get("container_options") or []
106+
107+
assert "--user=root" not in opts
108+
assert user_option in opts
109+
110+
111+
def test_podman_user_override_with_other_options() -> None:
112+
"""Test that podman respects --user when mixed with other options."""
113+
base = Base(
114+
container_engine="podman",
115+
execution_environment=True,
116+
container_options=["--volume=/tmp:/tmp", "--user=custom", "--env=TEST=1"],
117+
)
118+
119+
opts = base._runner_args.get("container_options") or []
120+
121+
assert "--user=root" not in opts
122+
assert "--user=custom" in opts
123+
assert "--volume=/tmp:/tmp" in opts
124+
assert "--env=TEST=1" in opts
125+
126+
127+
def test_docker_no_user_root() -> None:
128+
"""Test that docker does not set --user=root."""
129+
base = Base(
130+
container_engine="docker",
131+
execution_environment=True,
132+
)
133+
134+
opts = base._runner_args.get("container_options") or []
135+
136+
assert "--user=root" not in opts
137+
138+
139+
def test_podman_other_u_flags_dont_match() -> None:
140+
"""Test that other flags starting with -u don't prevent --user=root."""
141+
base = Base(
142+
container_engine="podman",
143+
execution_environment=True,
144+
container_options=["--ulimit=nofile:1024", "-unknown"],
145+
)
146+
147+
opts = base._runner_args.get("container_options") or []
148+
149+
# --user=root should be added because neither --ulimit nor -unknown are user flags
150+
assert "--user=root" in opts
151+
assert "--ulimit=nofile:1024" in opts
152+
assert "-unknown" in opts
153+
154+
155+
def test_podman_user_space_separated() -> None:
156+
"""Test that space-separated user flags are detected (as separate list items)."""
157+
# When passed as --container-options="-u myuser", the parser may split into ["-u", "myuser"]
158+
base = Base(
159+
container_engine="podman",
160+
execution_environment=True,
161+
container_options=["-u", "myuser"], # Space-separated, two list items
162+
)
163+
164+
opts = base._runner_args.get("container_options") or []
165+
166+
assert "--user=root" not in opts
167+
assert "-u" in opts
168+
assert "myuser" in opts
169+
170+
171+
def test_podman_user_long_space_separated() -> None:
172+
"""Test that space-separated --user flags are detected (as separate list items)."""
173+
base = Base(
174+
container_engine="podman",
175+
execution_environment=True,
176+
container_options=["--user", "myuser"], # Space-separated, two list items
177+
)
178+
179+
opts = base._runner_args.get("container_options") or []
180+
181+
assert "--user=root" not in opts
182+
assert "--user" in opts
183+
assert "myuser" in opts
184+
185+
186+
def test_podman_user_quoted_with_space() -> None:
187+
"""Test detection when user passes quoted string with space (edge case)."""
188+
# Edge case: if someone passes --co "-u myuser" as a single quoted argument
189+
base = Base(
190+
container_engine="podman",
191+
execution_environment=True,
192+
container_options=["-u myuser"], # Single item with space inside
193+
)
194+
195+
opts = base._runner_args.get("container_options") or []
196+
197+
assert "--user=root" not in opts
198+
assert "-u myuser" in opts

0 commit comments

Comments
 (0)