Skip to content

Commit f38c1d4

Browse files
committed
use qubes.VMRootExec/qubes.VMRootShell services when root needed
When running a command as root is requested, use qubes.VMRootExec/qubes.VMRootShell services, instead of specifying target user directly. The explicit target user can be specified only from dom0, but alternative qrexec service can be used also from GUI domain - if policy allows of course. QubesOS/qubes-issues#4186
1 parent a5ea121 commit f38c1d4

3 files changed

Lines changed: 135 additions & 3 deletions

File tree

qubesadmin/tests/tools/qvm_run.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,3 +1444,114 @@ def test_032_argparse_bug_workaround_unnamed_dispvm(self):
14441444
],
14451445
)
14461446
self.assertAllCalled()
1447+
1448+
def test_040_run_root_shell(self):
1449+
self.app.expected_calls[("dom0", "admin.vm.List", None, None)] = (
1450+
b"0\x00test-vm class=AppVM state=Running\n"
1451+
)
1452+
self.app.expected_calls[
1453+
("test-vm", "admin.vm.feature.CheckWithTemplate", "os", None)
1454+
] = b"2\x00QubesFeatureNotFoundError\x00\x00Feature 'os' not set\x00"
1455+
self.app.expected_calls[
1456+
("test-vm", "admin.vm.CurrentState", None, None)
1457+
] = b"0\x00power_state=Running"
1458+
ret = qubesadmin.tools.qvm_run.main(
1459+
["--no-gui", "-u", "root", "test-vm", "shell command"], app=self.app
1460+
)
1461+
self.assertEqual(ret, 0)
1462+
self.assertEqual(
1463+
self.app.service_calls,
1464+
[
1465+
(
1466+
"test-vm",
1467+
"qubes.VMRootShell",
1468+
{
1469+
"stdout": subprocess.DEVNULL,
1470+
"stderr": subprocess.DEVNULL,
1471+
"user": None,
1472+
},
1473+
),
1474+
("test-vm", "qubes.VMRootShell", b"shell command; exit\n"),
1475+
],
1476+
)
1477+
self.assertAllCalled()
1478+
1479+
def test_041_run_root_exec(self):
1480+
self.app.expected_calls[("dom0", "admin.vm.List", None, None)] = (
1481+
b"0\x00test-vm class=AppVM state=Running\n"
1482+
)
1483+
self.app.expected_calls[
1484+
("test-vm", "admin.vm.feature.CheckWithTemplate", "vmexec", None)
1485+
] = b"0\x001"
1486+
self.app.expected_calls[
1487+
(
1488+
"test-vm",
1489+
"admin.vm.feature.CheckWithTemplate",
1490+
"supported-rpc.qubes.VMRootExec",
1491+
None,
1492+
)
1493+
] = b"0\x001"
1494+
self.app.expected_calls[
1495+
("test-vm", "admin.vm.CurrentState", None, None)
1496+
] = b"0\x00power_state=Running"
1497+
ret = qubesadmin.tools.qvm_run.main(
1498+
["--no-gui", "-u", "root", "test-vm", "command", "arg"],
1499+
app=self.app,
1500+
)
1501+
self.assertEqual(ret, 0)
1502+
self.assertEqual(
1503+
self.app.service_calls,
1504+
[
1505+
(
1506+
"test-vm",
1507+
"qubes.VMRootExec+command+arg",
1508+
{
1509+
"stdout": subprocess.DEVNULL,
1510+
"stderr": subprocess.DEVNULL,
1511+
"user": None,
1512+
},
1513+
),
1514+
("test-vm", "qubes.VMRootExec+command+arg", b""),
1515+
],
1516+
)
1517+
self.assertAllCalled()
1518+
1519+
def test_041_run_root_exec_not_supported(self):
1520+
self.app.expected_calls[("dom0", "admin.vm.List", None, None)] = (
1521+
b"0\x00test-vm class=AppVM state=Running\n"
1522+
)
1523+
self.app.expected_calls[
1524+
("test-vm", "admin.vm.feature.CheckWithTemplate", "vmexec", None)
1525+
] = b"0\x001"
1526+
self.app.expected_calls[
1527+
(
1528+
"test-vm",
1529+
"admin.vm.feature.CheckWithTemplate",
1530+
"supported-rpc.qubes.VMRootExec",
1531+
None,
1532+
)
1533+
] = b"2\x00QubesFeatureNotFoundError\x00\x00Feature '...' not set\x00"
1534+
self.app.expected_calls[
1535+
("test-vm", "admin.vm.CurrentState", None, None)
1536+
] = b"0\x00power_state=Running"
1537+
ret = qubesadmin.tools.qvm_run.main(
1538+
["--no-gui", "-u", "root", "test-vm", "command", "arg"],
1539+
app=self.app,
1540+
)
1541+
self.assertEqual(ret, 0)
1542+
self.assertEqual(
1543+
self.app.service_calls,
1544+
[
1545+
(
1546+
"test-vm",
1547+
"qubes.VMExec+command+arg",
1548+
{
1549+
"stdout": subprocess.DEVNULL,
1550+
"stderr": subprocess.DEVNULL,
1551+
"user": "root",
1552+
},
1553+
),
1554+
("test-vm", "qubes.VMExec+command+arg", b""),
1555+
],
1556+
)
1557+
self.assertAllCalled()

qubesadmin/tools/qvm_run.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,16 +271,27 @@ def run_command_single(args, vm):
271271
service = "qubes.VMExec"
272272
if args.gui and args.dispvm:
273273
service = "qubes.VMExecGUI"
274+
elif args.user == "root" and vm.features.check_with_template(
275+
"supported-rpc.qubes.VMRootExec", False
276+
):
277+
service = "qubes.VMRootExec"
278+
args.user = None
274279
service += "+" + qubesadmin.utils.encode_for_vmexec(all_args)
275280
else:
276281
service = "qubes.VMShell"
277282
if args.gui and args.dispvm:
278283
service += "+WaitForSession"
284+
elif args.user == "root":
285+
service = "qubes.VMRootShell"
286+
args.user = None
279287
shell_cmd = " ".join(shlex.quote(arg) for arg in all_args)
280288
else:
281289
service = "qubes.VMShell"
282290
if args.gui and args.dispvm:
283291
service += "+WaitForSession"
292+
elif args.user == "root":
293+
service = "qubes.VMRootShell"
294+
args.user = None
284295
shell_cmd = args.cmd
285296

286297
proc = vm.run_service(service, user=args.user, **run_kwargs)

qubesadmin/vm/__init__.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,12 @@ def run(self, command, input=None, **kwargs):
355355
"""Run a shell command inside the domain using qubes.VMShell qrexec."""
356356
# pylint: disable=redefined-builtin
357357
try:
358+
service = "qubes.VMShell"
359+
if kwargs.get("user", None) == "root":
360+
kwargs.pop("user")
361+
service = "qubes.VMRootShell"
358362
return self.run_service_for_stdio(
359-
"qubes.VMShell",
363+
service,
360364
input=self.prepare_input_for_vmshell(command, input),
361365
**kwargs
362366
)
@@ -374,9 +378,15 @@ def run_with_args(self, *args, **kwargs):
374378
""" # pylint: disable=redefined-builtin
375379
if self.features.check_with_template("vmexec", False):
376380
try:
381+
service = "qubes.VMExec+"
382+
if kwargs.get("user", None) == "root":
383+
if self.features.check_with_template(
384+
"supported-rpc.qubes.VMRootExec", False
385+
):
386+
kwargs.pop("user")
387+
service = "qubes.VMRootExec+"
377388
return self.run_service_for_stdio(
378-
"qubes.VMExec+" + qubesadmin.utils.encode_for_vmexec(args),
379-
**kwargs
389+
service + qubesadmin.utils.encode_for_vmexec(args), **kwargs
380390
)
381391
except subprocess.CalledProcessError as e:
382392
e.cmd = str(args)

0 commit comments

Comments
 (0)