Ask for the sudo password once per omarchy update (#13323)
* Ask for the sudo password once per omarchy update Every sudo call in omarchy update prompted, because the no-update wrapper covered the whole run on top of per-phase revokes, and stay-awake revoked the timestamp on its own entry and exit. A single update could ask four times before the snapshot finished (#13319). Authorize once, right after confirmation, starting from a revoked timestamp so the prompt always belongs to this update. A background keepalive refreshes it until the update is done. Prune, snapshot, stay-awake, keyring, system packages, migrations, orphan removal, service restarts, the post-update hook, and mise all share that authorization. AUR builds run third-party PKGBUILD code, so they move to the end and run cold: the keepalive stops, the timestamp is revoked, and yay and any bare sudo use the no-update wrapper. The timestamp is revoked again after AUR and on every exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the single authorization for passwordless sudo and ttyless inhibition Authorize by running a command instead of sudo -v. Under the default verifypw=all, -v prompts even when passwordless sudo is enabled, which would have added a prompt those users never had. Inside an update without a terminal, stay-awake now reuses the update's authorization with a non-interactive sudo instead of asking again through polkit. It falls back to polkit only if that authorization is gone. The test sudo refuses a cold non-interactive call, as the real one does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7b336b1b0d
commit
e1614f2bdb
9 files changed
+163
-51
No files matched your search
+58
-21
@@ -18,20 +18,62 @@ set -e
|
||||
omarchy_security_sanitize_bash_environment "$0" "$@"
|
||||
omarchy_security_require_source_root "$0"
|
||||
# Logging and lock acquisition re-exec this command with a sanitized PATH.
|
||||
# Preserve the caller's path only for the later unprivileged hook/mise phases.
|
||||
# Preserve the caller's path only for the later hook/mise phases.
|
||||
user_path=${OMARCHY_UPDATE_USER_PATH:-$PATH}
|
||||
unset OMARCHY_UPDATE_USER_PATH
|
||||
# Traps first, so a signal or failure during the entry revocation still
|
||||
# exits through the cleanup path.
|
||||
# exits through the cleanup path. Starting cold means the single password
|
||||
# prompt below always belongs to this update, never to an earlier session.
|
||||
omarchy_security_install_sudo_cleanup_traps
|
||||
omarchy_security_revoke_sudo_timestamp || exit 1
|
||||
# Validate the no-update wrapper up front: the AUR phase depends on it. Every
|
||||
# other phase shares the one authorization, so it stays off PATH until then.
|
||||
omarchy_security_enable_no_update_sudo
|
||||
PATH="$OMARCHY_PATH/bin:/usr/bin:/usr/sbin:/bin:/sbin"
|
||||
unset OMARCHY_SUDO_NO_UPDATE
|
||||
# Helpers that manage their own sudo boundary leave this authorization to us.
|
||||
export PATH OMARCHY_UPDATE_SUDO_SESSION=1
|
||||
|
||||
update_stay_awake_stopped=0
|
||||
sudo_keepalive_pid=""
|
||||
|
||||
# Ask for the password once, then keep sudo's timestamp fresh so long package
|
||||
# downloads, migrations, hooks, and mise never outlast it and prompt again.
|
||||
# Authorize by running a command rather than sudo -v: under the default
|
||||
# verifypw=all, -v prompts even when passwordless sudo is enabled.
|
||||
authorize_update() {
|
||||
/usr/bin/sudo /usr/bin/true || return 1
|
||||
|
||||
local update_pid=$$
|
||||
(
|
||||
[[ -z ${OMARCHY_UPDATE_LOCK_FD:-} ]] || exec {OMARCHY_UPDATE_LOCK_FD}>&-
|
||||
sleep_pid=""
|
||||
trap - EXIT HUP INT
|
||||
trap '[[ -z $sleep_pid ]] || kill "$sleep_pid" 2>/dev/null; exit 0' TERM
|
||||
while :; do
|
||||
sleep 60 &
|
||||
sleep_pid=$!
|
||||
wait "$sleep_pid"
|
||||
kill -0 "$update_pid" 2>/dev/null || exit 0
|
||||
/usr/bin/sudo -n /usr/bin/true 2>/dev/null || exit 0
|
||||
done
|
||||
) &
|
||||
sudo_keepalive_pid=$!
|
||||
}
|
||||
|
||||
end_update_authorization() {
|
||||
if [[ -n $sudo_keepalive_pid ]]; then
|
||||
kill "$sudo_keepalive_pid" 2>/dev/null || true
|
||||
wait "$sudo_keepalive_pid" 2>/dev/null || true
|
||||
sudo_keepalive_pid=""
|
||||
fi
|
||||
omarchy_security_revoke_sudo_timestamp
|
||||
}
|
||||
|
||||
cleanup_update() {
|
||||
local status=$?
|
||||
trap - EXIT HUP INT TERM
|
||||
if ! omarchy_security_revoke_sudo_timestamp; then
|
||||
if ! end_update_authorization; then
|
||||
echo "Could not invalidate sudo before update cleanup." >&2
|
||||
omarchy_security_exit_with_revoked_sudo 1
|
||||
fi
|
||||
@@ -61,6 +103,8 @@ omarchy-update-requires-free-space
|
||||
[[ ${1:-} != "-y" ]] || export OMARCHY_UPDATE_UNATTENDED=1
|
||||
|
||||
if [[ ${1:-} == "-y" ]] || omarchy-update-confirm; then
|
||||
authorize_update
|
||||
|
||||
# Before the snapshot: the cache is on the snapshotted subvolume, so pruning
|
||||
# after it frees nothing until that snapshot ages out.
|
||||
omarchy-update-pkg-prune
|
||||
@@ -82,33 +126,26 @@ if [[ ${1:-} == "-y" ]] || omarchy-update-confirm; then
|
||||
# takes the update with it rather than migrating against what is still on disk.
|
||||
omarchy-update-system-pkgs
|
||||
|
||||
# Historical migrations are strictly ordered and mix user hooks/downloaded
|
||||
# tooling with privileged repairs. The no-update sudo wrapper has covered the
|
||||
# whole update, so neither the package transaction nor a later repair can
|
||||
# publish a timestamp to a detached migration child.
|
||||
omarchy_security_revoke_sudo_timestamp
|
||||
omarchy-migrate
|
||||
omarchy-update-orphan-pkgs
|
||||
|
||||
omarchy-update-analyze-logs
|
||||
omarchy-update-status
|
||||
|
||||
# Service restart helpers can need sudo. Run them before any user-controlled
|
||||
# update tooling; the reboot-only phase below performs no privileged work.
|
||||
# Service restart helpers can need sudo. The reboot-only phase below
|
||||
# performs no privileged work.
|
||||
omarchy-update-restart --services-only
|
||||
|
||||
# AUR package installation must also use the no-update wrapper. Finish
|
||||
# update-owned system work before build code, hooks, or mise can run.
|
||||
omarchy_security_revoke_sudo_timestamp
|
||||
omarchy-update-aur-pkgs
|
||||
omarchy_security_revoke_sudo_timestamp
|
||||
# Hooks and mise run with the caller's PATH so user-installed tools resolve.
|
||||
PATH="$user_path" "$OMARCHY_PATH/bin/omarchy-hook" post-update
|
||||
PATH="$user_path" "$OMARCHY_PATH/bin/omarchy-update-mise"
|
||||
|
||||
# Hooks and mise execute user-controlled code. Give each a cold credential
|
||||
# boundary and run mise last so it cannot wait for a legitimate hook sudo.
|
||||
# Only the unprivileged reboot prompt follows them.
|
||||
PATH="$OMARCHY_PATH/default/omarchy/sudo-no-update:$user_path" "$OMARCHY_PATH/bin/omarchy-hook" post-update
|
||||
omarchy_security_revoke_sudo_timestamp
|
||||
PATH="$OMARCHY_PATH/default/omarchy/sudo-no-update:$user_path" "$OMARCHY_PATH/bin/omarchy-update-mise"
|
||||
# AUR builds run third-party PKGBUILD code, so they go last and never see the
|
||||
# update's authorization: revoke it, then authenticate each sudo call with
|
||||
# the no-update wrapper, for yay and any bare sudo a PKGBUILD runs, so an AUR
|
||||
# install prompts without caching anything.
|
||||
end_update_authorization
|
||||
PATH="$OMARCHY_PATH/default/omarchy/sudo-no-update:$PATH" OMARCHY_SUDO_NO_UPDATE=1 omarchy-update-aur-pkgs
|
||||
omarchy_security_revoke_sudo_timestamp
|
||||
|
||||
# The sleep inhibitor covers AUR builds, hooks and mise as well; releasing it
|
||||
|
||||
@@ -15,6 +15,13 @@ omarchy_security_require_privileged_bash_startup || exit 126
|
||||
set -e
|
||||
omarchy_security_sanitize_bash_environment "$0" "$@"
|
||||
omarchy_security_require_source_root "$0"
|
||||
# omarchy update authorizes once and revokes when it is done. Revoking here
|
||||
# would throw that away and prompt again for the rest of the update.
|
||||
update_sudo_session=0
|
||||
if [[ ${OMARCHY_UPDATE_SUDO_SESSION:-} == "1" ]]; then
|
||||
update_sudo_session=1
|
||||
omarchy_security_revoke_sudo_timestamp() { :; }
|
||||
fi
|
||||
# Traps first, so a signal or failure during the entry revocation still
|
||||
# exits through the cleanup path.
|
||||
omarchy_security_install_sudo_cleanup_traps
|
||||
@@ -504,11 +511,15 @@ start_locked() {
|
||||
exec "${hold_command[@]}"
|
||||
) &
|
||||
launcher_pid=$!
|
||||
elif [[ -t 0 ]]; then
|
||||
elif [[ -t 0 ]] || { (( update_sudo_session )) && /usr/bin/sudo -n -N /usr/bin/true 2>/dev/null; }; then
|
||||
# Inside an update without a terminal, reuse its authorization
|
||||
# non-interactively rather than asking again through polkit.
|
||||
sudo_options=(-N -b)
|
||||
[[ -t 0 ]] || sudo_options=(-n -N -b)
|
||||
if ! (
|
||||
[[ -z ${OMARCHY_UPDATE_LOCK_FD:-} ]] || exec {OMARCHY_UPDATE_LOCK_FD}>&-
|
||||
exec {state_lock_fd}>&-
|
||||
exec /usr/bin/sudo -N -b -- "${hold_command[@]}"
|
||||
exec /usr/bin/sudo "${sudo_options[@]}" -- "${hold_command[@]}"
|
||||
); then
|
||||
return 1
|
||||
fi
|
||||
@@ -535,7 +546,7 @@ start_locked() {
|
||||
return 1
|
||||
fi
|
||||
(( readiness_attempts += 1 ))
|
||||
if [[ -t 0 ]] && (( EUID != 0 && readiness_attempts >= 100 )); then
|
||||
if [[ -z $launcher_pid ]] && (( EUID != 0 && readiness_attempts >= 100 )); then
|
||||
echo "The update sleep inhibitor did not become ready." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
Reference in new issue
Block a user