Meta adminVM question

I have read some things about the adminVM/adminAPI and it seems like it would be an elegant way to automate some stuff i do.

My general question is:

Can an adminVM invoke changes to qubes-rpc policy related to the qubes it controls? Kind of like meta qrexec rules for the admin API.

This seems like a useful and powerful but equally dangerous feature that is currently missing/not planned.

Explicitly i want to create sets of two new qubes and allow unmans qubes-rsync between them conveniently.

For this i need a script in dom0 to modify the related qrexec rules, but i would like to do this from an adminVM by restricting the adminVM to something like “You can add and remove rsync capabilities between qubes, but only the ones you created”. There are some good, specific reasons why i would prefer an adminVM over dom0 doing this, but they would require extensive explanation.

Is such meta administrative control something planned or something to work around?

I see that this increases complexity, but at least i would like you to think about what would be possible with such a feature. Also think this is aligned with where qubes wants to go (especially in the corporate setting).

I would love to read your thoughts about this!

This is not missing - it’s already implemented.

In the adminVM you must have installed qubes-core-admin-client.
You also have to set the necessary permissions - you can do this
globally by editing /etc/qubes/policy.d/include/admin-policy-rwx, or
by setting individual policies in (e.g.) /etc/qubes/policy.d/30-admin-policy.policy

Then, in the adminVM, use qvm-policy -r NAME to create the rules you
want in a new policy NAME.

I never presume to speak for the Qubes team.
When I comment in the Forum or in the mailing lists I speak for myself.

1 Like

Thank you very much!

This seems like the thing i want to do, i will look into it.

I took a look at it but i am unable to find qvm-policy. It is not in the qubes-core-admin-client.

@unman could you link me to some documentation on this command?

App qube

For the creation of app qubes within an adminvm (other than dom0),
you will need to install qubes-core-admin-client inside that qube.

As @unman said, you also will need to set the correct permissions.
This topic may help you:
https://forum.qubes-os.org/t/how-to-create-an-adminvm-in-r4-1/1941

I guess you already read them, but here, some related documentations:
https://www.qubes-os.org/doc/qrexec/
https://www.qubes-os.org/doc/admin-api/

RPC policies

The tool isn’t qvm-policy, it’s qubes-policy.
And it’s part of qubes-core-qrexec which should already be installed in your template.

The closer thing of documentation is:
https://www.qubes-os.org/doc/admin-api/#policy-admin-api

and

qubes-policy -h
usage: qubes-policy {[-l]|-g|-r|-d} [include/][RPCNAME[+ARGUMENT]]

positional arguments:
  [include/][name]  specify qubes RPC name or filename to operate on; with
                    "include/", operate on files in include subdirectory

options:
  -h, --help        show this help message and exit
  -l, --list        list present policy files
  -g, --get         fetch the content of the policy file
  -r, --replace     replace given policy with the one provided on standard
                    input
  -d, --remove      remove a policy file

For the later, forget the [RPCNAME[+ARGUMENT] and the specify qubes RPC name.
It only works with policy filename (according to my test, I may be wrong, but I don’t think so).
I guess it’s a relic of the old policy format.

It should be something like:
usage: qubes-policy {[-l]|-g|-r|-d} [include/][name]

You have two options:

  • qrexec-client-vm
  • qubes-policy

qrexec-client-vm

With the admin API (qrexec-client-vm) (see the doc link just above):
The usage is: e.g.

[user@some_qube ~]$ echo -e 'new\nqubes.foobar * src dest ask' | qrexec-client-vm dom0 policy.Replace+30-custom-rules

The argument of policy.Replace (i.e. +30-custom-rules) is the name of your policy file.
If you forget to define an argument (qrexec-client-vm dom0 policy.Replace), the created file will be .policy.
Therefore it will be an hidden file.

[user@dom0 ~]$ cat /etc/qubes/policy.d/30-custom-rules.policy 
qubes.foobar * src dest ask

with the token new, if the file exist, it will raise an error that you can’t override the file.

[user@some_qube ~]$ echo -e 'new\nqubes.foobar * xxx xxx ask' | qrexec-client-vm dom0 policy.Replace+30-custom-rules
Internal error. See /var/log/qubes/policy-admin.log in dom0 for details.

[user@dom0 ~]$ cat /var/log/qubes/policy-admin.log
[...]
qrexec.policy.admin.PolicyAdminTokenException: File exists but token is 'new'

To override an existing file, use the any token: 'any\nqubes.foobar * xxx xxx ask'

qubes-policy

The second option is the tool qubes-policy.
The code doesn’t mention any token, so the policy file will always be erased, existing or not.
https://github.com/QubesOS/qubes-core-qrexec/blob/main/qrexec/tools/qubes_policy.py

The usage is: e.g.

[user@some_qube ~]$ echo 'qubes.foobar * src dest ask' | qubes-policy -r 30-custom-rules

As with qrexec-client-vm, 30-custom-rules is the name of your policy file.

Both tools will replace the entire file.
If you have several rules to add, it would be better to use a file and cat:

[user@some_qube ~]$ cat "some_file.policy" | qubes-policy -r 30-custom-rules

Permissions

For both tools, you will need to create the correct permissions.
For example, to make the creation of RPC policy rules: e.g.

[user@dom0 ~]$ cat /etc/qubes/policy.d/30-admin-policy.policy
policy.Replace * some_qube dom0 ask default_target=dom0

Remarks

If you don’t have a fresh installation, check if the log file /var/log/qubes/policy-admin.log
can be written by its group (qubes).

log permission error
[user@dom0 ~]$ ll /var/log/qubes/policy-admin.log
-rw-r--r-- 1 root qubes 59K Sep  9 21:30 /var/log/qubes/policy-admin.log

[user@some_qube ~]$ echo 'qubes.Filecopy * src dst ask' | qubes-policy -r 30-custom-rules
Command failed

[user@dom0 ~]$ sudo journalctl
Sep 09 xx:xx:xx dom0 policy.Replace+30-custom-rules-some-qube[12704]: PermissionError: [Errno 13] Permission denied: '/var/log/qubes/policy-admin.log'

If you don’t have the correct permissions for this log file, fix them:
sudo chmod 664 /var/log/qubes/policy-admin.log

According to this:
https://github.com/QubesOS/qubes-issues/issues/8014#issuecomment-1417649920

I think it’s fixed for a while, but my system and this file was probably created before the fix.
I mention it, just in case.

Have fun.

1 Like

Forgot about this part.
AFAIK, there is no way to tell policy.Replace to allow the creation of only specific policy rules.

You can still achieve that goal.

policy.Replace use qrexec.tools.qubes_policy_admin:

cat /etc/qubes-rpc/policy.Replace
[user@dom0 ~]$ cat /etc/qubes-rpc/policy.Replace
#!/usr/bin/python3
from qrexec.tools.qubes_policy_admin import main
import sys
if __name__ == '__main__':
	sys.exit(main())

The code:
https://github.com/QubesOS/qubes-core-qrexec/blob/main/qrexec/tools/qubes_policy_admin.py
In dom0, it’s stored at: /usr/lib/python3.11/site-packages/qrexec/tools/

Permission

First, modify the policy rule, so your adminvm cannot delete other policy files:

[user@dom0 ~]$ cat /etc/qubes/policy.d/30-admin-policy.policy
policy.Replace +30-some-qube-rsync some_qube dom0 ask default_target=dom0

The argument +30-some-qube-rsync will allow some_qube to only create/modify this file.
(the file will be /etc/qubes/policy.d/30-some-qube-rsync.policy

Custom policy.Replace

Copy the code of qubes_policy_admin.py into a new file (policy.Replace+30-some-qube-rsync).

[user@dom0 ~]$ sudo cp /usr/lib/python3.11/site-packages/qrexec/tools/qubes_policy_admin.py /etc/qubes-rpc/policy.Replace+30-some-qube-rsync

Make sure the script is executable (if not use sudo chmod +x file)
If you want to store the code at an other place, make an symlink.

[user@dom0 ~]$ sudo ln -s /path/to/your/script /etc/qubes-rpc/policy.Replace+30-some-qube-rsync

Modify the code of the new script:

[user@dom0 ~]$ sudo vim /etc/qubes-rpc/policy.Replace+30-some-qube-rsync

At the top of the file modify the import to use absolute path:

from qrexec.policy.admin import PolicyAdmin, PolicyAdminException
from qrexec import POLICYPATH

After payload = sys.stdin.buffer.read() add some code to parse the content and allow only the policies you want:

    allowed_policies = ('qubes.Rsync')
    content = payload.decode().rstrip().split('\n')
    for policy in content[1:]:
        if not policy.lstrip().startswith(allowed_policies):
            print('Error: Create "' + policy + '" is not allowed')
            print('Allowed policies creation: ' + allowed_policies)
            sys.exit(42)

The allowed_policies = ('qubes.Rsync') is a tuple.
You could add other policies (e.g. allowed_policies = ('qubes.Rsync', 'qubes.Foobar').

The first line is skipped, because it’s the token: any or new.

You will need to use qrexec-client-vm and not qubes-policy.
Because if you modify qubes-policy, you must do it in the adminvm, and therefore it could be modified by this admin qube.

request accepted
[user@some-qube ~]$ echo -e 'any\nqubes.Rsync * xxx xxx ask' | qrexec-client-vm dom0 policy.Replace+30-some-qube-rsync

[user@dom0 ~]$ cat /etc/qubes/policy.d/30-some-qube-rsync.policy 
qubes.Rsync * xxx xxx ask
request refused
[user@some-qube ~]$ echo -e 'any\nqubes.Replace * xxx xxx allow' | qrexec-client-vm dom0 policy.Replace+30-some-qube-rsync
Error: Create "qubes.Replace * xxx xxx ask" is not allowed
Allowed policies creation: qubes.Rsync

[user@some-qube ~]$ echo -e 'any\nqubes.Rsync * xxx xxx ask' | qrexec-client-vm dom0 policy.Replace
Request refused

[user@some-qube ~]$ echo -e 'any\nqubes.Rsync * xxx xxx ask' | qrexec-client-vm dom0 policy.Replace+30-another-file
Request refused

Remarks

It’s a way to do it. Probably not the only one.

You could extend the code.
By example you could use some regex and allow only some client and server.

When dom0 is updated, you should check if qrexec is among the updated packages.
If it is, check if qubes_policy_admin.py have changed, and adjust policy.Replace+30-some-qube-rsync accordingly.
qubes_policy_admin.py is pretty small, shouldn’t happens very often.

Have fun (again :slight_smile: )

1 Like