From b7decf6ddd3e963688c5a43edd29674be88a6841 Mon Sep 17 00:00:00 2001 From: Simon Gaiser Date: Tue, 14 Oct 2025 20:57:09 +0200 Subject: [PATCH] Implement optional admin authorization via qrexec See included README for details. Issue: QubesOS/qubes-issues#2695 --- Makefile | 1 + archlinux/PKGBUILD.in | 2 +- ...agent-passwordless-root.displace-extension | 1 - ...qubes-core-agent-passwordless-root.install | 8 +- ...ubes-core-agent-passwordless-root.postinst | 12 + debian/rules | 3 +- passwordless-root/.gitignore | 2 + passwordless-root/75-qubes-admin-authz.preset | 4 + passwordless-root/Makefile | 52 ++++- passwordless-root/README.md | 82 +++++++ passwordless-root/admin-authzd.conf | 4 + .../authselect/local/password-auth | 33 +++ .../authselect/local/system-auth | 34 +++ .../authselect/nis/password-auth | 33 +++ passwordless-root/authselect/nis/system-auth | 34 +++ .../authselect/sssd/password-auth | 45 ++++ passwordless-root/authselect/sssd/system-auth | 52 +++++ .../authselect/winbind/password-auth | 41 ++++ .../authselect/winbind/system-auth | 42 ++++ .../pam-configs-qubes-admin-authz | 7 + passwordless-root/pam-configs_su.qubes | 6 - passwordless-root/pam.d_su.qubes | 21 -- passwordless-root/pam_qubes_admin_authz.c | 174 +++++++++++++++ .../polkit-1-qubes-allow-all.rules | 2 - passwordless-root/qubes-admin-authz-common.h | 3 + passwordless-root/qubes-admin-authzd.c | 206 ++++++++++++++++++ passwordless-root/qubes-admin-authzd.service | 11 + passwordless-root/qubes.sudoers | 2 +- rpm_spec/core-agent.spec.in | 50 +++-- 29 files changed, 912 insertions(+), 55 deletions(-) delete mode 100644 debian/qubes-core-agent-passwordless-root.displace-extension create mode 100644 passwordless-root/.gitignore create mode 100644 passwordless-root/75-qubes-admin-authz.preset create mode 100644 passwordless-root/README.md create mode 100644 passwordless-root/admin-authzd.conf create mode 100644 passwordless-root/authselect/local/password-auth create mode 100644 passwordless-root/authselect/local/system-auth create mode 100644 passwordless-root/authselect/nis/password-auth create mode 100644 passwordless-root/authselect/nis/system-auth create mode 100644 passwordless-root/authselect/sssd/password-auth create mode 100644 passwordless-root/authselect/sssd/system-auth create mode 100644 passwordless-root/authselect/winbind/password-auth create mode 100644 passwordless-root/authselect/winbind/system-auth create mode 100644 passwordless-root/pam-configs-qubes-admin-authz delete mode 100644 passwordless-root/pam-configs_su.qubes delete mode 100644 passwordless-root/pam.d_su.qubes create mode 100644 passwordless-root/pam_qubes_admin_authz.c delete mode 100644 passwordless-root/polkit-1-qubes-allow-all.rules create mode 100644 passwordless-root/qubes-admin-authz-common.h create mode 100644 passwordless-root/qubes-admin-authzd.c create mode 100644 passwordless-root/qubes-admin-authzd.service diff --git a/Makefile b/Makefile index 9f5e127dd..d2181157e 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/archlinux/PKGBUILD.in b/archlinux/PKGBUILD.in index 2e0a4d3c5..b0a30ff8a 100644 --- a/archlinux/PKGBUILD.in +++ b/archlinux/PKGBUILD.in @@ -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 } diff --git a/debian/qubes-core-agent-passwordless-root.displace-extension b/debian/qubes-core-agent-passwordless-root.displace-extension deleted file mode 100644 index 7ff75d677..000000000 --- a/debian/qubes-core-agent-passwordless-root.displace-extension +++ /dev/null @@ -1 +0,0 @@ -.qubes diff --git a/debian/qubes-core-agent-passwordless-root.install b/debian/qubes-core-agent-passwordless-root.install index e261ea67f..6a58809e6 100644 --- a/debian/qubes-core-agent-passwordless-root.install +++ b/debian/qubes-core-agent-passwordless-root.install @@ -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 diff --git a/debian/qubes-core-agent-passwordless-root.postinst b/debian/qubes-core-agent-passwordless-root.postinst index 04562d94e..64febbc75 100644 --- a/debian/qubes-core-agent-passwordless-root.postinst +++ b/debian/qubes-core-agent-passwordless-root.postinst @@ -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 + exit 0 # vim: set ts=4 sw=4 sts=4 et : diff --git a/debian/rules b/debian/rules index 5568a4e15..4bf855ab5 100755 --- a/debian/rules +++ b/debian/rules @@ -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 \ diff --git a/passwordless-root/.gitignore b/passwordless-root/.gitignore new file mode 100644 index 000000000..30dafbdaa --- /dev/null +++ b/passwordless-root/.gitignore @@ -0,0 +1,2 @@ +pam_qubes_admin_authz.so +qubes-admin-authzd diff --git a/passwordless-root/75-qubes-admin-authz.preset b/passwordless-root/75-qubes-admin-authz.preset new file mode 100644 index 000000000..bda45f8fa --- /dev/null +++ b/passwordless-root/75-qubes-admin-authz.preset @@ -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 diff --git a/passwordless-root/Makefile b/passwordless-root/Makefile index bcbc7ddd5..0edc6ab3a 100644 --- a/passwordless-root/Makefile +++ b/passwordless-root/Makefile @@ -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 $@ + +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 install: install -d -m 0750 $(DESTDIR)$(SUDOERSDIR) @@ -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 diff --git a/passwordless-root/README.md b/passwordless-root/README.md new file mode 100644 index 000000000..3464a8f62 --- /dev/null +++ b/passwordless-root/README.md @@ -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 + 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). + +There config file have a very simpley 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. diff --git a/passwordless-root/admin-authzd.conf b/passwordless-root/admin-authzd.conf new file mode 100644 index 000000000..fcee6ed18 --- /dev/null +++ b/passwordless-root/admin-authzd.conf @@ -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 diff --git a/passwordless-root/authselect/local/password-auth b/passwordless-root/authselect/local/password-auth new file mode 100644 index 000000000..b11acde92 --- /dev/null +++ b/passwordless-root/authselect/local/password-auth @@ -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"} diff --git a/passwordless-root/authselect/local/system-auth b/passwordless-root/authselect/local/system-auth new file mode 100644 index 000000000..956398a32 --- /dev/null +++ b/passwordless-root/authselect/local/system-auth @@ -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"} diff --git a/passwordless-root/authselect/nis/password-auth b/passwordless-root/authselect/nis/password-auth new file mode 100644 index 000000000..1e346db0e --- /dev/null +++ b/passwordless-root/authselect/nis/password-auth @@ -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"} diff --git a/passwordless-root/authselect/nis/system-auth b/passwordless-root/authselect/nis/system-auth new file mode 100644 index 000000000..2986678a9 --- /dev/null +++ b/passwordless-root/authselect/nis/system-auth @@ -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 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"} diff --git a/passwordless-root/authselect/sssd/password-auth b/passwordless-root/authselect/sssd/password-auth new file mode 100644 index 000000000..9235ba017 --- /dev/null +++ b/passwordless-root/authselect/sssd/password-auth @@ -0,0 +1,45 @@ +auth required pam_env.so +auth required pam_faildelay.so delay=2000000 +auth required pam_deny.so # Smartcard authentication is required {include if "with-smartcard-required"} +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 [default=1 ignore=ignore success=ok] pam_usertype.so isregular +auth [default=1 ignore=ignore success=ok] pam_localuser.so +auth sufficient pam_unix.so {if not "without-nullok":nullok} +auth sufficient pam_systemd_home.so {include if "with-systemd-homed"} +auth [default=1 ignore=ignore success=ok] pam_usertype.so isregular +auth sufficient pam_sss.so forward_pass +auth required pam_faillock.so authfail {include if "with-faillock"} +auth optional pam_gnome_keyring.so 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 +account sufficient pam_localuser.so {exclude if "with-files-access-provider"} +account sufficient pam_usertype.so issystem +account [default=bad success=ok user_unknown=ignore] pam_sss.so +account required pam_permit.so + +password sufficient pam_systemd_home.so {include if "with-systemd-homed"} +password requisite pam_pwquality.so 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 +password [success=1 default=ignore] pam_localuser.so +password sufficient pam_sss.so 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_sss.so +session optional pam_gnome_keyring.so auto_start {include if "with-pam-gnome-keyring"} diff --git a/passwordless-root/authselect/sssd/system-auth b/passwordless-root/authselect/sssd/system-auth new file mode 100644 index 000000000..a2732e586 --- /dev/null +++ b/passwordless-root/authselect/sssd/system-auth @@ -0,0 +1,52 @@ +{imply "with-smartcard" if "with-smartcard-required"} +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 [success=1 default=ignore] pam_succeed_if.so service notin login:gdm:xdm:kdm:kde:xscreensaver:gnome-screensaver:kscreensaver quiet use_uid {include if "with-smartcard-required"} +auth [success=done ignore=ignore default=die] pam_sss.so require_cert_auth ignore_authinfo_unavail {include if "with-smartcard-required"} +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 [default=1 ignore=ignore success=ok] pam_usertype.so isregular +auth [default=1 ignore=ignore success=ok] pam_localuser.so {exclude if "with-smartcard"} +auth [default=2 ignore=ignore success=ok] pam_localuser.so {include if "with-smartcard"} +auth [success=done authinfo_unavail=ignore user_unknown=ignore ignore=ignore default=die] pam_sss.so try_cert_auth {include if "with-smartcard"} +auth sufficient pam_unix.so {if not "without-nullok":nullok} +auth sufficient pam_systemd_home.so {include if "with-systemd-homed"} +auth [default=1 ignore=ignore success=ok] pam_usertype.so isregular {include if "with-gssapi"} +auth sufficient pam_sss_gss.so {include if "with-gssapi"} +auth [default=1 ignore=ignore success=ok] pam_usertype.so isregular +auth sufficient pam_sss.so forward_pass +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 +account sufficient pam_localuser.so {exclude if "with-files-access-provider"} +account sufficient pam_usertype.so issystem +account [default=bad success=ok user_unknown=ignore] pam_sss.so +account required pam_permit.so + +password sufficient pam_systemd_home.so {include if "with-systemd-homed"} +password requisite pam_pwquality.so 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 +password [success=1 default=ignore] pam_localuser.so +password sufficient pam_sss.so 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_sss.so +session optional pam_gnome_keyring.so only_if=login auto_start {include if "with-pam-gnome-keyring"} diff --git a/passwordless-root/authselect/winbind/password-auth b/passwordless-root/authselect/winbind/password-auth new file mode 100644 index 000000000..2204fa133 --- /dev/null +++ b/passwordless-root/authselect/winbind/password-auth @@ -0,0 +1,41 @@ +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 [default=1 ignore=ignore success=ok] pam_usertype.so isregular +auth sufficient pam_winbind.so {if "with-krb5":krb5_auth} use_first_pass +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 +account sufficient pam_localuser.so +account sufficient pam_usertype.so issystem +account [default=bad success=ok user_unknown=ignore] pam_winbind.so {if "with-krb5":krb5_auth} +account required pam_permit.so + +password sufficient pam_systemd_home.so {include if "with-systemd-homed"} +password requisite pam_pwquality.so 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 +password sufficient pam_winbind.so {if "with-krb5":krb5_auth} 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_winbind.so {if "with-krb5":krb5_auth} +session optional pam_gnome_keyring.so only_if=login auto_start {include if "with-pam-gnome-keyring"} diff --git a/passwordless-root/authselect/winbind/system-auth b/passwordless-root/authselect/winbind/system-auth new file mode 100644 index 000000000..b520fc1e2 --- /dev/null +++ b/passwordless-root/authselect/winbind/system-auth @@ -0,0 +1,42 @@ +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 [default=1 ignore=ignore success=ok] pam_usertype.so isregular +auth sufficient pam_winbind.so {if "with-krb5":krb5_auth} use_first_pass +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 +account sufficient pam_localuser.so +account sufficient pam_usertype.so issystem +account [default=bad success=ok user_unknown=ignore] pam_winbind.so {if "with-krb5":krb5_auth} +account required pam_permit.so + +password sufficient pam_systemd_home.so {include if "with-systemd-homed"} +password requisite pam_pwquality.so 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 +password sufficient pam_winbind.so {if "with-krb5":krb5_auth} 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_winbind.so {if "with-krb5":krb5_auth} +session optional pam_gnome_keyring.so only_if=login auto_start {include if "with-pam-gnome-keyring"} diff --git a/passwordless-root/pam-configs-qubes-admin-authz b/passwordless-root/pam-configs-qubes-admin-authz new file mode 100644 index 000000000..a4cb035b3 --- /dev/null +++ b/passwordless-root/pam-configs-qubes-admin-authz @@ -0,0 +1,7 @@ +Name: authorize admin access without password (by package qubes-core-agent-passwordless-root) +Default: yes +Priority: 258 +Auth-Type: Primary +Auth: + sufficient pam_qubes_admin_authz.so + diff --git a/passwordless-root/pam-configs_su.qubes b/passwordless-root/pam-configs_su.qubes deleted file mode 100644 index 2c6a9c25c..000000000 --- a/passwordless-root/pam-configs_su.qubes +++ /dev/null @@ -1,6 +0,0 @@ -Name: allow su without password (by package qubes-core-agent-passwordless-root) -Default: yes -Priority: 258 -Auth-Type: Primary -Auth: - sufficient pam_succeed_if.so use_uid user ingroup qubes service in su:su-l diff --git a/passwordless-root/pam.d_su.qubes b/passwordless-root/pam.d_su.qubes deleted file mode 100644 index e9853f8e1..000000000 --- a/passwordless-root/pam.d_su.qubes +++ /dev/null @@ -1,21 +0,0 @@ -#%PAM-1.0 -auth sufficient pam_rootok.so -# Uncomment the following line to implicitly trust users in the "wheel" group. -#auth sufficient pam_wheel.so trust use_uid -# Uncomment the following line to require a user to be in the "wheel" group. -#auth required pam_wheel.so use_uid - -# {{ Qubes specific modifications begin here -# Prevent su from asking for password -# (by package qubes-core-agent-passwordless-root). -auth sufficient pam_succeed_if.so use_uid user ingroup qubes -# }} Qubes specific modifications end here - -auth substack system-auth -auth include postlogin -account sufficient pam_succeed_if.so uid = 0 use_uid quiet -account include system-auth -password include system-auth -session include system-auth -session include postlogin -session optional pam_xauth.so diff --git a/passwordless-root/pam_qubes_admin_authz.c b/passwordless-root/pam_qubes_admin_authz.c new file mode 100644 index 000000000..c59b6dab3 --- /dev/null +++ b/passwordless-root/pam_qubes_admin_authz.c @@ -0,0 +1,174 @@ +#define _GNU_SOURCE 1 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "qubes-admin-authz-common.h" + +int pam_sm_authenticate(pam_handle_t *pamh, + __attribute__((unused)) int flags, + int argc, + const char **argv) { + bool debug = false; + bool quiet = false; + int rc = PAM_ABORT; + int s = -1; + + for (int i = 0; i < argc; i += 1) { + if (strcmp(argv[i], "debug") == 0) { + debug = true; + } else if (strcmp(argv[i], "quiet") == 0) { + quiet = true; + } else { + pam_syslog(pamh, LOG_ERR, "unkown option: %s", argv[i]); + } + } + + // Only allow users in the "qubes" group to use this module. + uid_t uid = getuid(); + if (!pam_modutil_user_in_group_uid_nam(pamh, uid, "qubes")) { + if (debug) { + pam_syslog(pamh, LOG_DEBUG, "uid %i not in qubes group", uid); + } + rc = PAM_IGNORE; + goto ret; + } + if (debug) { + pam_syslog(pamh, LOG_DEBUG, "uid %i in qubes group", uid); + } + + // Only allow services we know it makes sense for. For example for an ssh + // login attempt it would be a bad idea to allow authentication with this + // module. + const char* service = NULL; + rc = pam_get_item(pamh, PAM_SERVICE, (const void **)&service); + if (rc != PAM_SUCCESS || service == NULL) { + pam_syslog(pamh, LOG_CRIT, "failed to get PAM_SERVICE: %i", rc); + rc = PAM_SYSTEM_ERR; + goto ret; + } + if (!(strcmp(service, "su") == 0 || + strcmp(service, "su-l") == 0 || + strcmp(service, "sudo") == 0 || + strcmp(service, "sudo-i") == 0 || + strcmp(service, "polkit-1") == 0)) { + if (debug) { + pam_syslog(pamh, LOG_DEBUG, "ignoring service %s", service); + } + rc = PAM_IGNORE; + goto ret; + } + if (debug) { + pam_syslog(pamh, LOG_DEBUG, "handling service %s", service); + } + + s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (s == -1) { + pam_syslog(pamh, LOG_CRIT, "failed to create socket: %i", errno); + rc = PAM_SYSTEM_ERR; + goto ret; + } + + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + .sun_path = SOCKET_PATH, + }; + + if (debug) { + pam_syslog(pamh, LOG_DEBUG, "connecting to authzd"); + } + rc = connect(s, + (struct sockaddr*)&addr, + sizeof(addr.sun_family) + sizeof(SOCKET_PATH) - 1); + if (rc != 0) { + pam_syslog(pamh, LOG_ERR, + "failed to connect to authorization daemon: %i", errno); + rc = PAM_AUTHINFO_UNAVAIL; + goto ret; + } + if (debug) { + pam_syslog(pamh, LOG_DEBUG, "connected"); + } + + struct ucred cred = {}; + socklen_t cred_size = sizeof(cred); + rc = getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cred, &cred_size); + if (rc == -1) { + pam_syslog(pamh, LOG_CRIT, + "failed to get peer credentials from socket: %i", errno); + rc = PAM_SYSTEM_ERR; + goto ret; + } + if (cred_size != sizeof(cred)) { + pam_syslog(pamh, LOG_CRIT, + "failed to get peer credentials from socket: unexpected size"); + rc = PAM_SYSTEM_ERR; + goto ret; + } + if (debug) { + pam_syslog(pamh, LOG_DEBUG, + "authz socket opened by uid=%i gid=%i pid=%i", + cred.uid, cred.gid, cred.pid); + } + if (cred.uid != 0) { + pam_syslog(pamh, LOG_CRIT, "socket not opened by root"); + rc = PAM_SYSTEM_ERR; + goto ret; + } + + char res[100] = {}; + ssize_t read_ret = read(s, &res, sizeof(res) - 1); + if (read_ret < 0) { + pam_syslog(pamh, LOG_ERR, "failed to read from socket: %i", errno); + rc = PAM_SYSTEM_ERR; + goto ret; + } + + // Since the other side is trusted this isn't strictly necessary. But it's + // probably still nicer to ensure that we don't put unexpected bytes into + // the log. + for (size_t i = 0; i < sizeof(res) - 1; i += 1) { + if (res[i] == '\0') { + break; + } + if (res[i] < 0x20 || res[i] > 0x7e) { + res[i] = '.'; + } + } + + if (strcmp(res, "authorized") != 0) { + pam_syslog(pamh, LOG_NOTICE, "access not authorized: %s", res); + rc = PAM_AUTH_ERR; + goto ret; + } + + if (!quiet) { + pam_syslog(pamh, LOG_INFO, "access authorized"); + } + + rc = PAM_SUCCESS; + +ret: + if (s >= 0) { + if (close(s) == -1) { + pam_syslog(pamh, LOG_CRIT, "failed to close socket: %i", errno); + rc = PAM_SYSTEM_ERR; + } + } + return rc; +} + +int pam_sm_setcred(__attribute__((unused)) pam_handle_t *pamh, + __attribute__((unused)) int flags, + __attribute__((unused)) int argc, + __attribute__((unused)) const char **argv) { + return PAM_IGNORE; +} diff --git a/passwordless-root/polkit-1-qubes-allow-all.rules b/passwordless-root/polkit-1-qubes-allow-all.rules deleted file mode 100644 index a83c8273a..000000000 --- a/passwordless-root/polkit-1-qubes-allow-all.rules +++ /dev/null @@ -1,2 +0,0 @@ -//allow any action, detailed reasoning in sudoers.d/qubes -polkit.addRule(function(action,subject) { if (subject.isInGroup("qubes")) return polkit.Result.YES; }); diff --git a/passwordless-root/qubes-admin-authz-common.h b/passwordless-root/qubes-admin-authz-common.h new file mode 100644 index 000000000..1eac7a0f2 --- /dev/null +++ b/passwordless-root/qubes-admin-authz-common.h @@ -0,0 +1,3 @@ +#pragma once + +#define SOCKET_PATH "\0qubes.AuthorizeInVMAdminAccess" diff --git a/passwordless-root/qubes-admin-authzd.c b/passwordless-root/qubes-admin-authzd.c new file mode 100644 index 000000000..e3d1171ee --- /dev/null +++ b/passwordless-root/qubes-admin-authzd.c @@ -0,0 +1,206 @@ +#define _GNU_SOURCE 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qubes-admin-authz-common.h" + +#define MODE_DENY 0 +#define MODE_QREXEC 1 +#define MODE_ALLOW 2 + +int str2mode(const char* s, const char* log_name) { + int mode = -1; + if (strcmp(s, "deny") == 0) { + mode = MODE_DENY; + } + if (strcmp(s, "qrexec") == 0) { + mode = MODE_QREXEC; + } + if (strcmp(s, "allow") == 0) { + mode = MODE_ALLOW; + } + if (log_name != NULL) { + if (mode < 0) { + printf("mode in %s not 'deny', 'qrexec' or 'allow'\n", log_name); + } else { + printf("mode read from %s\n", log_name); + } + } + return mode; +} + +int main() { + int rc = -1; + int mode = MODE_DENY; + + if (setvbuf(stdout, NULL, _IONBF, 0) != 0) { + printf("failed to set stdout buffer mode\n"); + return 1; + } + + char* env_mode_str = getenv("QUBES_ADMIN_AUTHZD_MODE"); + if (env_mode_str != NULL && env_mode_str[0] != '\0') { + mode = str2mode(env_mode_str, "QUBES_ADMIN_AUTHZD_MODE"); + if (mode < 0) { + return 1; + } + } else { + char* confs[] = { + "/usr/local/etc/qubes/admin-authzd.conf", + "/etc/qubes/admin-authzd.conf", + }; + for (size_t i = 0; i < sizeof(confs)/sizeof(confs[0]); i += 1) { + int fd = open(confs[i], O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) { + printf("%s does not exits\n", confs[i]); + continue; + } + printf("failed to read %s: %m\n", confs[i]); + return 1; + } + char buf[100] = {}; + ssize_t read_ret = read(fd, &buf, sizeof(buf) - 1); + if (read_ret < 0) { + printf("failed to read from %s: %m\n", confs[i]); + return 1; + } + char* eol = strchr(buf, '\n'); + if (eol != NULL) { + *eol = '\0'; + } + mode = str2mode(buf, confs[i]); + if (mode < 0) { + return 1; + } + break; + } + } + + struct sigaction chld_act = { + .sa_handler = SIG_IGN, + .sa_flags = SA_NOCLDWAIT, + }; + if (sigaction(SIGCHLD, &chld_act, NULL) != 0) { + printf("sigaction failed: %m\n"); + return 1; + } + + int s = -1; + s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (s == -1) { + printf("failed to open socket: %m\n"); + return 1; + } + + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + .sun_path = SOCKET_PATH, + }; + + rc = bind(s, + (struct sockaddr*)&addr, + sizeof(addr.sun_family) + sizeof(SOCKET_PATH) - 1); + if (rc != 0) { + printf("failed to bind socket: %m\n"); + return 1; + } + + if (listen(s, 100) != 0) { + printf("failed to listen on socket: %m\n"); + return 1; + } + + printf("mode: %s\n", + mode == MODE_DENY ? "deny" : + mode == MODE_QREXEC ? "qrexec" : "allow"); + + int client = -1; + while (true) { + if (client >= 0) { + if (close(client) != 0) { + printf("close of client socket failed: %m\n"); + } + } + + printf("waiting for new request\n"); + client = accept(s, NULL, NULL); + if (client < 0) { + printf("accept failed: %m\n"); + return 1; + } + + printf("new request received\n"); + + struct ucred cred = {}; + socklen_t cred_size = sizeof(cred); + rc = getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cred, &cred_size); + if (rc == -1) { + printf("failed to get peer credentials from socket: %m\n"); + continue; + } + if (cred_size != sizeof(cred)) { + printf("failed to get peer credentials from socket: unexpected size\n"); + continue; + } + if (cred.uid != 0) { + printf("client socket not opened by root\n"); + continue; + } + + if (mode == MODE_DENY || mode == MODE_ALLOW) { + char* res_str = mode == MODE_ALLOW ? "authorized" : "denied"; + ssize_t res_str_len = strlen(res_str); + ssize_t write_rc = write(client, res_str, res_str_len); + if (write_rc < 0) { + printf("write failed: %m\n"); + } else if (write_rc != res_str_len) { + printf("short write\n"); + } + continue; + } + + // mode == MODE_QREXEC + + pid_t pid = fork(); + if (pid == -1) { + printf("fork failed: %m\n"); + continue; + } else if (pid != 0) { + continue; + } + + // child + + int new_stdin = open("/dev/null", O_RDONLY); + if (new_stdin < 0) { + printf("child: failed to open /dev/null: %m\n"); + return 1; + } + if (dup2(new_stdin, 0) < 0) { + printf("child: dup2(_, 0) failed: %m\n"); + return 1; + } + if (dup2(client, 1) < 0) { + printf("child: dup2(_, 1) failed: %m\n"); + return 1; + } + if (close(new_stdin) != 0 || close(client) != 0) { + printf("child: close failed: %m\n"); + return 1; + } + + execlp("qrexec-client-vm", "qrexec-client-vm", "@default", "qubes.AuthorizeInVMAdminAccess", NULL); + printf("child: exec failed: %m\n"); + return 1; + } +} diff --git a/passwordless-root/qubes-admin-authzd.service b/passwordless-root/qubes-admin-authzd.service new file mode 100644 index 000000000..78581a8f4 --- /dev/null +++ b/passwordless-root/qubes-admin-authzd.service @@ -0,0 +1,11 @@ +[Unit] +Description=Qubes admin access authorization daemon + +[Service] +Type=simple +ExecStart=/usr/bin/qubes-admin-authzd +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/passwordless-root/qubes.sudoers b/passwordless-root/qubes.sudoers index 50c670d0c..919a6ea65 100644 --- a/passwordless-root/qubes.sudoers +++ b/passwordless-root/qubes.sudoers @@ -1,4 +1,4 @@ Defaults !requiretty -%qubes ALL=(ALL) ROLE=unconfined_r TYPE=unconfined_t NOPASSWD: ALL +%qubes ALL=(ALL) ROLE=unconfined_r TYPE=unconfined_t ALL # vim: ft=sudoers diff --git a/rpm_spec/core-agent.spec.in b/rpm_spec/core-agent.spec.in index 66b6a7ec6..58a617947 100644 --- a/rpm_spec/core-agent.spec.in +++ b/rpm_spec/core-agent.spec.in @@ -357,10 +357,10 @@ Integration of NetworkManager for Qubes VM: * show/hide NetworkManager applet icon %package passwordless-root -BuildArch: noarch Summary: Passwordless root access from normal user Conflicts: qubes-core-vm < 4.0.0 Requires(pre): shadow +Requires: authselect %if 0%{?is_opensuse} # for directory ownership BuildRequires: sudo @@ -495,7 +495,7 @@ make -C config-overrides DESTDIR=$RPM_BUILD_ROOT install make -C filesystem DESTDIR=$RPM_BUILD_ROOT install make -C misc "DESTDIR=$RPM_BUILD_ROOT" UDEVRULESDIR=%_udevrulesdir SYSLIBDIR=/usr/lib install make -C network DESTDIR=$RPM_BUILD_ROOT install -make -C passwordless-root DESTDIR=$RPM_BUILD_ROOT install install-rh +make -C passwordless-root DESTDIR=$RPM_BUILD_ROOT SYSLIBDIR=/usr/lib install install-rh make -C qubes-rpc DESTDIR=$RPM_BUILD_ROOT install make -C qubes-rpc/caja DESTDIR=$RPM_BUILD_ROOT install %if !0%{?is_opensuse} @@ -544,16 +544,6 @@ if ! grep -q /etc/default/grub.qubes /etc/default/grub 2>/dev/null; then echo '. /etc/default/grub.qubes' >> /etc/default/grub fi -%triggerin passwordless-root -- util-linux - -qubesfile=/etc/pam.d/su.qubes -origfile=${qubesfile%.qubes} -backupfile=${origfile}.qubes-orig -if [ -r "$origfile" -a ! -r "$backupfile" ]; then - mv -f "$origfile" "$backupfile" -fi -ln -sf "$qubesfile" "$origfile" - %post # disable some Upstart services @@ -710,6 +700,24 @@ fi %systemd_post qubes-network-uplink.service %systemd_post qubes-updates-proxy.service +%post passwordless-root + +systemctl --no-reload preset qubes-admin-authzd.service + +# We really want to have this running immediately, such that passwordless auth +# keeps working on upgrade. (But still allow the user to override the preset, +# if they really want. + +if [[ -e /run/systemd/system ]]; then + systemctl daemon-reload + + if systemctl is-enabled --quiet qubes-admin-authzd.service; then + systemctl restart qubes-admin-authzd.service + fi +fi + +authselect apply-changes + %post thunar if [ "$1" = 1 ]; then # There is no system-wide Thunar custom actions. There is only a default @@ -742,6 +750,9 @@ fi %systemd_preun qubes-network.service %systemd_preun qubes-updates-proxy.service +%preun passwordless-root +%systemd_preun qubes-admin-authzd.service + %if %{with selinux} %post selinux @@ -872,6 +883,8 @@ if [ -f "$backupfile" ]; then mv -f "$backupfile" "$origfile" fi +authselect apply-changes + %posttrans /usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas &> /dev/null || : @@ -1167,9 +1180,18 @@ rm -f %{name}-%{version} /usr/lib/qubes/show-hide-nm-applet.sh %files passwordless-root -%config(noreplace) /etc/polkit-1/rules.d/00-qubes-allow-all.rules %config(noreplace) /etc/sudoers.d/qubes -%config(noreplace) /etc/pam.d/su.qubes +%config(noreplace) /etc/qubes/admin-authzd.conf + +/usr/lib64/security/pam_qubes_admin_authz.so +/usr/bin/qubes-admin-authzd + +%_unitdir/qubes-admin-authzd.service +%{_unitdir}-preset/75-qubes-admin-authz.preset + +/usr/share/authselect/vendor/*/* + +/usr/share/doc/qubes-core-agent-passwordless-root/README.md %package systemd Summary: Qubes unit files for SystemD init style