Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ all:
ifeq ($(ENABLE_SELINUX),1)
$(MAKE) -C selinux -f /usr/share/selinux/devel/Makefile -- $(selinux_policies)
endif
$(MAKE) -C passwordless-root

clean:
make -C misc clean
Expand Down
2 changes: 1 addition & 1 deletion archlinux/PKGBUILD.in
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ build() {
# Fix for archlinux sbindir
sed 's:/usr/sbin/ntpdate:/usr/bin/ntpdate:g' -i qubes-rpc/sync-ntp-clock

for dir in qubes-rpc misc; do
for dir in qubes-rpc misc passwordless-root; do
make -C "$dir" VERSION=${pkgver}
done
}
Expand Down

This file was deleted.

8 changes: 6 additions & 2 deletions debian/qubes-core-agent-passwordless-root.install
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
etc/polkit-1/rules.d/00-qubes-allow-all.rules
etc/qubes/admin-authzd.conf
etc/sudoers.d/qubes
usr/share/pam-configs/su.qubes
usr/share/pam-configs/qubes-admin-authz
usr/bin/qubes-admin-authzd
usr/lib/*/security/pam_qubes_admin_authz.so
lib/systemd/system/qubes-admin-authzd.service
usr/share/doc/qubes-core-agent-passwordless-root/README.md
12 changes: 12 additions & 0 deletions debian/qubes-core-agent-passwordless-root.postinst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ pam-auth-update --package

#DEBHELPER#

# If systemd is running try starting qubes-admin-authzd even if the user
# blocked the normal start by dh (above) via policy-rc.d. Not running it will
# break passwordless auth, so this is most likely not what the user wanted. But
# if they really want to they still can mask the service.

if [[ -e /run/systemd/system ]] &&
! systemctl is-active --quiet qubes-admin-authzd.service &&
systemctl is-enabled --quiet qubes-admin-authzd.service
then
systemctl start qubes-admin-authzd.service || true
fi
Comment on lines +29 to +39
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a concrete scenario where policy-rc.d blocks startup of something incorrectly? I'm a bit worried about assuming the user's intentions here, since Kicksecure's build system uses a "deny everything" policy-rc.d to prevent everything from starting, because of the fallout it can have on the rest of the build. Bypassing this mechanism seems dangerous at best. If a user does end up blocked, they can always open a DispVM console and log in as root, or use qvm-run -u root ....


exit 0

# vim: set ts=4 sw=4 sts=4 et :
3 changes: 2 additions & 1 deletion debian/rules
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ override_dh_fixperms:
dh_fixperms -a -Xqfile-unpacker

override_dh_systemd_start:
dh_systemd_start --no-restart-on-upgrade
dh_systemd_start --package=qubes-core-agent-passwordless-root --restart-after-upgrade
dh_systemd_start --remaining-packages --no-restart-after-upgrade

override_dh_install:
if [ "$(DISTRIBUTION)" = "Ubuntu" ]; then \
Expand Down
2 changes: 2 additions & 0 deletions passwordless-root/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pam_qubes_admin_authz.so
qubes-admin-authzd
4 changes: 4 additions & 0 deletions passwordless-root/75-qubes-admin-authz.preset
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This will be re-preset on package upgrade. If you really want you can
# override this with a higher higher-priority preset file but that is probably
# a bad idea since it will break the pam module.
enable qubes-admin-authzd.service
52 changes: 46 additions & 6 deletions passwordless-root/Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
BINDIR ?= /usr/bin
SYSCONFDIR ?= /etc
SUDOERSDIR = $(SYSCONFDIR)/sudoers.d
POLKIT1DIR = $(SYSCONFDIR)/polkit-1
PAMDIR = $(SYSCONFDIR)/pam.d
PAMCONFIGSDIR = /usr/share/pam-configs/
SYSLIBDIR ?= /lib

.PHONY: install install-debian install-rh
ifneq ($(DEB_HOST_MULTIARCH),)
PAM_MOD_DIR = /usr/lib/$(DEB_HOST_MULTIARCH)/security
else
PAM_MOD_DIR = /usr/lib64/security
endif

.PHONY: install install-debian install-rh all

all: qubes-admin-authzd pam_qubes_admin_authz.so

qubes-admin-authzd: qubes-admin-authzd.c qubes-admin-authz-common.h
gcc -O2 -Wall -Wextra -Werror -fPIC -pie $< -o $@
Copy link
Member

Choose a reason for hiding this comment

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

Include standard CFLAGS var too (both debian and fedora add preferred hardening options here)


pam_qubes_admin_authz.so: pam_qubes_admin_authz.c qubes-admin-authz-common.h
gcc -O2 -Wall -Wextra -Werror -fPIC -pie -shared $< -o $@ -lpam
Copy link
Member

Choose a reason for hiding this comment

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

and here too


install:
install -d -m 0750 $(DESTDIR)$(SUDOERSDIR)
Expand All @@ -14,11 +29,36 @@ install:
sed -E '/^[^#]/s/\<(ROLE|TYPE)=[A-Za-z0-9_]+[[:space:]]+//g' qubes.sudoers | \
install -D -m 0440 /dev/stdin $(DESTDIR)$(SUDOERSDIR)/qubes; \
fi
install -d -m 0750 $(DESTDIR)$(POLKIT1DIR)/rules.d
install -D -m 0644 polkit-1-qubes-allow-all.rules $(DESTDIR)$(POLKIT1DIR)/rules.d/00-qubes-allow-all.rules
install -D -t $(DESTDIR)$(PAM_MOD_DIR) pam_qubes_admin_authz.so
install -D -t $(DESTDIR)$(BINDIR) qubes-admin-authzd
install -D -m 0644 -t $(DESTDIR)$(SYSLIBDIR)/systemd/system qubes-admin-authzd.service
install -D -m 0644 -t $(DESTDIR)$(SYSCONFDIR)/qubes/ admin-authzd.conf
install -D -m 0644 -t $(DESTDIR)/usr/share/doc/qubes-core-agent-passwordless-root README.md

install-rh:
install -D -m 0644 pam.d_su.qubes $(DESTDIR)$(PAMDIR)/su.qubes
install -D -m 0644 -t $(DESTDIR)$(SYSLIBDIR)/systemd/system-preset 75-qubes-admin-authz.preset
install -d $(DESTDIR)/usr/share/authselect/vendor
for i in \
local \
nis \
sssd \
winbind \
; do \
install -D -t $(DESTDIR)/usr/share/authselect/vendor/$$i \
authselect/$$i/system-auth authselect/$$i/password-auth || exit 1; \
for j in \
README \
REQUIREMENTS \
dconf-db \
dconf-locks \
fingerprint-auth \
nsswitch.conf \
postlogin \
smartcard-auth \
; do \
ln -s ../../default/$$i/$$j $(DESTDIR)/usr/share/authselect/vendor/$$i/$$j || exit 1; \
done; \
done

install-debian:
install -D -m 0644 pam-configs_su.qubes $(DESTDIR)$(PAMCONFIGSDIR)/su.qubes
install -D -m 0644 pam-configs-qubes-admin-authz $(DESTDIR)$(PAMCONFIGSDIR)/qubes-admin-authz
82 changes: 82 additions & 0 deletions passwordless-root/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Passwordless in-qubqube admin authorization

By default all users in the `qubes` group (usually only the standard `user`
user) are allowed to execute things as root via sudo/su/pkexec. This is allowed
without a password prompt.

Separating the user isn't that meaningfull since Qubes' primary
compartimentalization layer are qubes and `user` in a qube has access most
things there anyway (user data, GUI I/O, etc). But some advanced users want
more options therefore this mechanism has been extended.

## Modes

There are 3 modes:

- `allow`: Always allow admin access.
- `deny`: Always deny admin access.
- `qrexec`: Make a qrexec call and use it's result to allow/deny. The main
usage is to have a trivial qrexec service in dom0 and use the qrexex policy
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
usage is to have a trivial qrexec service in dom0 and use the qrexex policy
usage is to have a trivial qrexec service in dom0 and use the qrexec policy

with the ask action to allow only after prompting.

"Admin access" here means being able to run things as another user (including
root), via sudo, su and polkit. This is always limited to users in the `qubes`
group.


## Config

When the `qubes-core-agent-passwordless-root` is installed the
pam_qubes_admin_authz.so is always enabled. This module asks the
`qubes-admin-authzd` daemon which does the actual logic (see below for
technical details).

The mode can be set via /usr/local/etc/qubes/admin-authzd.conf for per-qube
setting or more common via /etc/qubes/admin-authzd.conf in a template (setting
in /usr/local has precendce).
Comment on lines +34 to +36
Copy link
Member

Choose a reason for hiding this comment

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

maybe make it configurable via qvm-service (too?) so it's more compatible with disposable qubes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. Since services are boolean we need 3. Any preferences regarding naming?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe root-access-deny, root-access-prompt and root-access-allow?

Copy link
Contributor

Choose a reason for hiding this comment

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

Whonix would like for this to be configurable in Qube Manager, via a tri-select dropdown with a default setting. Is qvm-service the best tool for this job, or would something like qvm-prefs or qvm-features work better here? (I would assume a property settable via qvm-prefs and readable via qubesdb would be best.)

Copy link
Member

Choose a reason for hiding this comment

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

Indeed a qvm-feature/qvm-prefs might be a better idea. We don't have clear guidelines which one to use when, but since it's mostly opaque config value from dom0 PoV, probably better qvm-feature. And add core-admin part that exposes it in qubesdb - probably with using check_with_template, to allow enabling the feature for all qubes based on the same template at once.

Copy link
Member

@marmarek marmarek Nov 3, 2025

Choose a reason for hiding this comment

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

I just found my own comment: QubesOS/qubes-issues#9512 (comment)
So, it's already configurable in dom0 (although a bit less convenient than qvm-feature/qvm-prefs). It just requires setting vm-side config to "qrexec".
In other words, disregard my request of adding another config method.


There config file have a very simpley syntax. The first line needs to contain
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
There config file have a very simpley syntax. The first line needs to contain
There config file have a very simple syntax. The first line needs to contain

one of the listed mode above without anythings else. Currently the rest of the
file is ignored. Please prefix comments with `#` to allow extension of this
configuration file should the need arise.


## qrexec policy

After setting the mode to qrexec, you need to configure the qrexec policy in
dom0. For example:

```
qubes.AuthorizeInVMAdminAccess * * @default ask target=dom0 default_target=dom0
```

asks for requests from any VM.

Note that in dom0 only a trivial service is run that returns a fixed string
such that the qube knows the result of the policy evaluation. The idea here is
that this way the existing qrexec policy can be reused, including it's ask
prompt.


## Limitaions

Keep in mind that if you have allowed admin access and the qube was compromised
at that point persistence is trivial.


## Technical details

We implement a PAM module named `pam_qubes_admin_authz.so` to permit the
access. Since PAM modules can be run in a setuid context (when called by
sudo/su) we want to keep the code simple there. Therefore we use just some
existing PAM helper functions to check for the requsting users group membership
and check the PAM "service" (`sudo`, `su-l`, etc.) and if they match we make a
connection to an abstract unix socket. With `SO_PEERCRED` we check that this
socket has been opened by root. The rest, including config file handling and
invoking qrexec-client-vm is then handled by the `qubes-admin-authzd` at the
other end of the socket (started by qubes-admin-authzd.service).

## Recovery

Should you have locked you self out you should still be able to use
`qvm-console-dispvm` and login as root there without a password.
4 changes: 4 additions & 0 deletions passwordless-root/admin-authzd.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
allow

# The first line of this file needs to be 'deny', 'allow' or 'qrexec'.
# For more details see /usr/share/doc/qubes-core-agent-passwordless-root/README
33 changes: 33 additions & 0 deletions passwordless-root/authselect/local/password-auth
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
auth required pam_env.so
auth required pam_faildelay.so delay=2000000
auth required pam_faillock.so preauth silent {include if "with-faillock"}
auth sufficient pam_qubes_admin_authz.so
auth sufficient pam_u2f.so cue {include if "with-pam-u2f"}
auth required pam_u2f.so cue {if not "without-pam-u2f-nouserok":nouserok} {include if "with-pam-u2f-2fa"}
auth sufficient pam_unix.so {if not "without-nullok":nullok}
auth sufficient pam_systemd_home.so {include if "with-systemd-homed"}
auth required pam_faillock.so authfail {include if "with-faillock"}
auth optional pam_gnome_keyring.so only_if=login auto_start {include if "with-pam-gnome-keyring"}
auth required pam_deny.so

account required pam_access.so {include if "with-pamaccess"}
account required pam_faillock.so {include if "with-faillock"}
account sufficient pam_systemd_home.so {include if "with-systemd-homed"}
account required pam_unix.so

password sufficient pam_systemd_home.so {include if "with-systemd-homed"}
password requisite pam_pwquality.so
password [default=1 ignore=ignore success=ok] pam_localuser.so {include if "with-pwhistory"}
password requisite pam_pwhistory.so use_authtok {include if "with-pwhistory"}
password sufficient pam_unix.so yescrypt shadow {if not "without-nullok":nullok} use_authtok
password required pam_deny.so

session optional pam_keyinit.so revoke
session required pam_limits.so
session optional pam_ecryptfs.so unwrap {include if "with-ecryptfs"}
session optional pam_systemd_home.so {include if "with-systemd-homed"}
-session optional pam_systemd.so
session optional pam_oddjob_mkhomedir.so {include if "with-mkhomedir"}
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session required pam_unix.so
session optional pam_gnome_keyring.so only_if=login auto_start {include if "with-pam-gnome-keyring"}
34 changes: 34 additions & 0 deletions passwordless-root/authselect/local/system-auth
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
auth required pam_env.so
auth required pam_faildelay.so delay=2000000
auth required pam_faillock.so preauth silent {include if "with-faillock"}
auth sufficient pam_qubes_admin_authz.so
auth sufficient pam_fprintd.so {include if "with-fingerprint"}
auth sufficient pam_u2f.so cue {include if "with-pam-u2f"}
auth required pam_u2f.so cue {if not "without-pam-u2f-nouserok":nouserok} {include if "with-pam-u2f-2fa"}
auth sufficient pam_unix.so {if not "without-nullok":nullok}
auth sufficient pam_systemd_home.so {include if "with-systemd-homed"}
auth required pam_faillock.so authfail {include if "with-faillock"}
auth optional pam_gnome_keyring.so only_if=login auto_start {include if "with-pam-gnome-keyring"}
auth required pam_deny.so

account required pam_access.so {include if "with-pamaccess"}
account required pam_faillock.so {include if "with-faillock"}
account sufficient pam_systemd_home.so {include if "with-systemd-homed"}
account required pam_unix.so

password sufficient pam_systemd_home.so {include if "with-systemd-homed"}
password requisite pam_pwquality.so
password [default=1 ignore=ignore success=ok] pam_localuser.so {include if "with-pwhistory"}
password requisite pam_pwhistory.so use_authtok {include if "with-pwhistory"}
password sufficient pam_unix.so yescrypt shadow {if not "without-nullok":nullok} use_authtok
password required pam_deny.so

session optional pam_keyinit.so revoke
session required pam_limits.so
session optional pam_ecryptfs.so unwrap {include if "with-ecryptfs"}
session optional pam_systemd_home.so {include if "with-systemd-homed"}
-session optional pam_systemd.so
session optional pam_oddjob_mkhomedir.so {include if "with-mkhomedir"}
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session required pam_unix.so
session optional pam_gnome_keyring.so only_if=login auto_start {include if "with-pam-gnome-keyring"}
33 changes: 33 additions & 0 deletions passwordless-root/authselect/nis/password-auth
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
auth required pam_env.so
auth required pam_faildelay.so delay=2000000
auth required pam_faillock.so preauth silent {include if "with-faillock"}
auth sufficient pam_qubes_admin_authz.so
auth sufficient pam_u2f.so cue {include if "with-pam-u2f"}
auth required pam_u2f.so cue {if not "without-pam-u2f-nouserok":nouserok} {include if "with-pam-u2f-2fa"}
auth sufficient pam_unix.so {if not "without-nullok":nullok}
auth sufficient pam_systemd_home.so {include if "with-systemd-homed"}
auth required pam_faillock.so authfail {include if "with-faillock"}
auth optional pam_gnome_keyring.so only_if=login auto_start {include if "with-pam-gnome-keyring"}
auth required pam_deny.so

account required pam_access.so {include if "with-pamaccess"}
account required pam_faillock.so {include if "with-faillock"}
account sufficient pam_systemd_home.so {include if "with-systemd-homed"}
account required pam_unix.so broken_shadow

password sufficient pam_systemd_home.so {include if "with-systemd-homed"}
password requisite pam_pwquality.so {if not "with-nispwquality":local_users_only}
password [default=1 ignore=ignore success=ok] pam_localuser.so {include if "with-pwhistory"}
password requisite pam_pwhistory.so use_authtok {include if "with-pwhistory"}
password sufficient pam_unix.so yescrypt shadow {if not "without-nullok":nullok} use_authtok nis
password required pam_deny.so

session optional pam_keyinit.so revoke
session required pam_limits.so
session optional pam_ecryptfs.so unwrap {include if "with-ecryptfs"}
session optional pam_systemd_home.so {include if "with-systemd-homed"}
-session optional pam_systemd.so
session optional pam_oddjob_mkhomedir.so {include if "with-mkhomedir"}
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session required pam_unix.so
session optional pam_gnome_keyring.so only_if=login auto_start {include if "with-pam-gnome-keyring"}
Loading