Simplify passwordless sudo grant lifecycle
This commit is contained in:
1 parent
5bfc8b5cc3
commit
c46f321680
6 files changed
+525
-944
No files matched your search
+134
-286
@@ -24,12 +24,11 @@ set -euo pipefail
|
||||
|
||||
readonly DEFAULT_MINUTES=15
|
||||
readonly MAX_MINUTES=1440
|
||||
readonly STATE_DIR=/var/lib/omarchy/sudo-passwordless
|
||||
readonly RUNTIME_DIR=/run/omarchy/sudo-passwordless
|
||||
readonly LOCK_FILE=/run/lock/omarchy-sudo-passwordless.lock
|
||||
readonly BOOT_CLEANUP_FILE=/etc/tmpfiles.d/omarchy-nopasswd-sudo.conf
|
||||
readonly PACKAGE_HOOK=/usr/share/libalpm/hooks/05-omarchy-passwordless-revoke.hook
|
||||
readonly REMOVAL_BLOCKER=/run/omarchy-sudo-passwordless-package-removing
|
||||
readonly MIGRATION_MARKER=/var/lib/omarchy/migrations/1788163635
|
||||
readonly INSTALLED_SELF=/usr/bin/omarchy-sudo-passwordless
|
||||
readonly STATUS_INACTIVE=3
|
||||
|
||||
@@ -73,26 +72,6 @@ verify_sudo_caller() {
|
||||
resolve_account "$requested_uid"
|
||||
}
|
||||
|
||||
prepare_root_state() {
|
||||
omarchy_security_assert_root_directory /var 755 || return 1
|
||||
[[ -d /var/lib && ! -L /var/lib ]] || return 1
|
||||
[[ $(/usr/bin/stat -Lc '%u' /var/lib) == 0 ]] || return 1
|
||||
! ((8#$(/usr/bin/stat -Lc '%a' /var/lib) & 022)) || return 1
|
||||
|
||||
if [[ ! -e /var/lib/omarchy && ! -L /var/lib/omarchy ]]; then
|
||||
/usr/bin/install -d -o root -g root -m 0755 /var/lib/omarchy || return 1
|
||||
fi
|
||||
omarchy_security_assert_root_directory /var/lib/omarchy 755 || return 1
|
||||
omarchy_security_prepare_private_root_directory "$STATE_DIR" /var/lib/omarchy || return 1
|
||||
|
||||
omarchy_security_assert_root_directory /run 755 || return 1
|
||||
if [[ ! -e /run/omarchy && ! -L /run/omarchy ]]; then
|
||||
/usr/bin/install -d -o root -g root -m 0755 /run/omarchy || return 1
|
||||
fi
|
||||
omarchy_security_assert_root_directory /run/omarchy 755 || return 1
|
||||
omarchy_security_prepare_private_root_directory "$RUNTIME_DIR" /run/omarchy
|
||||
}
|
||||
|
||||
with_root_lock() {
|
||||
local fd rc=0
|
||||
# The boot cleanup cannot depend on STATE_DIR or RUNTIME_DIR being healthy:
|
||||
@@ -116,67 +95,23 @@ rule_file() {
|
||||
printf '/etc/sudoers.d/99-omarchy-nopasswd-%s' "$1"
|
||||
}
|
||||
|
||||
state_file() {
|
||||
printf '%s/%s.state' "$STATE_DIR" "$1"
|
||||
}
|
||||
|
||||
read_state_record() {
|
||||
local uid="$1" file state_uid name expires timer canonical_uid
|
||||
local -a lines=()
|
||||
valid_uid "$uid" || return 1
|
||||
canonical_uid=$((10#$uid))
|
||||
file=$(state_file "$uid")
|
||||
[[ -f $file && ! -L $file ]] || return 1
|
||||
mapfile -t lines <"$file" || return 1
|
||||
(( ${#lines[@]} == 4 )) || return 1
|
||||
[[ ${lines[0]} == UID=* && ${lines[1]} == USER=* &&
|
||||
${lines[2]} == EXPIRES=* && ${lines[3]} == TIMER=* ]] || return 1
|
||||
state_uid=${lines[0]#UID=}
|
||||
name=${lines[1]#USER=}
|
||||
expires=${lines[2]#EXPIRES=}
|
||||
timer=${lines[3]#TIMER=}
|
||||
[[ $state_uid == "$canonical_uid" ]] || return 1
|
||||
valid_account_name "$name" || return 1
|
||||
[[ $expires =~ ^[1-9][0-9]{0,10}$ ]] || return 1
|
||||
[[ $timer =~ ^omarchy-nopasswd-expire-${canonical_uid}-[0-9a-f]{32}$ ]] || return 1
|
||||
printf '%s\t%s\t%s' "$name" "$expires" "$timer"
|
||||
}
|
||||
|
||||
read_state_timer() {
|
||||
local record
|
||||
record=$(read_state_record "$1") || return 1
|
||||
printf '%s' "${record##*$'\t'}"
|
||||
}
|
||||
|
||||
current_epoch() {
|
||||
local now
|
||||
now=$(/usr/bin/date +%s) || return 1
|
||||
[[ $now =~ ^[1-9][0-9]{0,10}$ ]] || return 1
|
||||
printf '%s' "$now"
|
||||
}
|
||||
|
||||
valid_expiry() {
|
||||
[[ $1 =~ ^[1-9][0-9]{0,10}$ ]]
|
||||
}
|
||||
|
||||
valid_timer_for_uid() {
|
||||
local uid="$1" timer="$2"
|
||||
valid_uid "$uid" || return 1
|
||||
uid=$((10#$uid))
|
||||
[[ $timer =~ ^omarchy-nopasswd-expire-${uid}-[0-9a-f]{32}$ ]]
|
||||
}
|
||||
|
||||
stop_timer() {
|
||||
local timer="$1"
|
||||
[[ $timer =~ ^omarchy-nopasswd-expire-[0-9]+-[0-9a-f]{32}$ ]] || return 0
|
||||
/usr/bin/systemctl stop "${timer}.timer" "${timer}.service" >/dev/null 2>&1 || true
|
||||
/usr/bin/systemctl reset-failed "${timer}.timer" "${timer}.service" >/dev/null 2>&1 || true
|
||||
# The sudoers rule is the only grant record. A missing file is distinct from
|
||||
# an unreadable, unsafe, or administrator-modified file.
|
||||
read_grant() {
|
||||
local file contents
|
||||
file=$(rule_file "$1")
|
||||
[[ -e $file || -L $file ]] || return "$STATUS_INACTIVE"
|
||||
verify_root_path "$file" && [[ -f $file ]] || return 2
|
||||
contents=$(/usr/bin/cat -- "$file") || return 2
|
||||
[[ $contents =~ ^([a-z_][a-z0-9_-]*\$?)\ ALL=\(ALL\)\ NOTAFTER=([0-9]{14}Z)\ NOPASSWD:\ ALL$ ]] || return 2
|
||||
GRANT_NAME=${BASH_REMATCH[1]}
|
||||
GRANT_DEADLINE=${BASH_REMATCH[2]}
|
||||
valid_account_name "$GRANT_NAME" || return 2
|
||||
}
|
||||
|
||||
classify_generated_rule() {
|
||||
local file=$1 suffix contents name
|
||||
|
||||
GENERATED_RULE_LEGACY_TIMER=""
|
||||
[[ -f $file && ! -L $file ]] || return 1
|
||||
contents=$(/usr/bin/cat -- "$file") || return 2
|
||||
suffix=${file##*/99-omarchy-nopasswd-}
|
||||
@@ -189,81 +124,40 @@ classify_generated_rule() {
|
||||
name=${contents%%' ALL=(ALL) NOTAFTER='*}
|
||||
valid_account_name "$name" && [[ $contents =~ ^[a-z_][a-z0-9_-]*\$?\ ALL=\(ALL\)\ NOTAFTER=[0-9]{14}Z\ NOPASSWD:\ ALL$ ]]
|
||||
elif valid_account_name "$suffix" && [[ $contents == "$suffix ALL=(ALL) NOPASSWD: ALL" ]]; then
|
||||
GENERATED_RULE_LEGACY_TIMER="omarchy-nopasswd-expire-${suffix}"
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
remove_known_legacy_rules() {
|
||||
cleanup_uid_locked() {
|
||||
local file
|
||||
file=$(rule_file "$1")
|
||||
[[ -e $file || -L $file ]] || return 0
|
||||
verify_root_path "$file" && classify_generated_rule "$file" || return 1
|
||||
/usr/bin/rm -f -- "$file" && [[ ! -e $file && ! -L $file ]]
|
||||
}
|
||||
|
||||
cleanup_all_locked() {
|
||||
local file classification failed=0
|
||||
shopt -s nullglob
|
||||
verify_root_path /etc/sudoers.d || return 1
|
||||
for file in /etc/sudoers.d/99-omarchy-nopasswd-*; do
|
||||
[[ -e $file || -L $file ]] || continue
|
||||
if classify_generated_rule "$file"; then
|
||||
# A crash after publishing the numeric rule but before its state rename
|
||||
# must not survive the next boot. Do not require the account to still
|
||||
# exist: a deleted account could otherwise make the rule immortal and a
|
||||
# later username reuse could activate it again.
|
||||
if /usr/bin/rm -f -- "$file" && [[ ! -e $file && ! -L $file ]]; then
|
||||
[[ -z $GENERATED_RULE_LEGACY_TIMER ]] ||
|
||||
/usr/bin/systemctl stop "${GENERATED_RULE_LEGACY_TIMER}.timer" \
|
||||
"${GENERATED_RULE_LEGACY_TIMER}.service" >/dev/null 2>&1 || true
|
||||
else
|
||||
if ! /usr/bin/rm -f -- "$file" || [[ -e $file || -L $file ]]; then
|
||||
failed=1
|
||||
fi
|
||||
else
|
||||
classification=$?
|
||||
# An unreadable candidate cannot be proven inert. A symlink, non-file,
|
||||
# or administrator-authored body is unrelated and remains untouched.
|
||||
(( classification == 1 )) || failed=1
|
||||
fi
|
||||
done
|
||||
shopt -u nullglob
|
||||
return "$failed"
|
||||
}
|
||||
|
||||
cleanup_uid_locked() {
|
||||
local uid="$1" timer=""
|
||||
valid_uid "$uid" || return 1
|
||||
timer=$(read_state_timer "$uid" 2>/dev/null || true)
|
||||
# Remove policy first. A failed timer stop can only leave an inert cleanup
|
||||
# job behind, never extend passwordless access.
|
||||
/usr/bin/rm -f -- "$(rule_file "$uid")" || return 1
|
||||
[[ ! -e $(rule_file "$uid") && ! -L $(rule_file "$uid") ]] || return 1
|
||||
/usr/bin/rm -f -- "$(state_file "$uid")" || return 1
|
||||
[[ -z $timer ]] || stop_timer "$timer"
|
||||
}
|
||||
|
||||
cleanup_all_locked() {
|
||||
local state uid failed=0 file classification
|
||||
shopt -s nullglob
|
||||
for state in "$STATE_DIR"/*.state; do
|
||||
uid=${state##*/}
|
||||
uid=${uid%.state}
|
||||
if valid_uid "$uid" && ! cleanup_uid_locked "$uid"; then failed=1; fi
|
||||
done
|
||||
shopt -u nullglob
|
||||
remove_known_legacy_rules || failed=1
|
||||
|
||||
# Never report a successful boot cleanup while an exact rule emitted by any
|
||||
# Omarchy implementation is still active. Administrator-extended files do
|
||||
# not match these complete bodies and remain untouched.
|
||||
shopt -s nullglob
|
||||
for file in /etc/sudoers.d/99-omarchy-nopasswd-*; do
|
||||
if classify_generated_rule "$file"; then
|
||||
failed=1
|
||||
else
|
||||
classification=$?
|
||||
(( classification == 1 )) || failed=1
|
||||
fi
|
||||
done
|
||||
shopt -u nullglob
|
||||
return "$failed"
|
||||
}
|
||||
|
||||
verify_root_policy_file() {
|
||||
verify_root_path() {
|
||||
local file=$1 owner mode canonical current
|
||||
[[ -f $file && ! -L $file ]] || return 1
|
||||
[[ ( -f $file || -d $file ) && ! -L $file ]] || return 1
|
||||
canonical=$(/usr/bin/realpath -e -- "$file") || return 1
|
||||
[[ $canonical == "$file" ]] || return 1
|
||||
owner=$(/usr/bin/stat -Lc '%u' -- "$file") || return 1
|
||||
@@ -286,10 +180,10 @@ verify_root_policy_file() {
|
||||
verify_boot_cleanup() {
|
||||
local active_rules hook
|
||||
[[ ! -e $REMOVAL_BLOCKER && ! -L $REMOVAL_BLOCKER ]] || return 1
|
||||
verify_root_policy_file "$BOOT_CLEANUP_FILE" || return 1
|
||||
verify_root_path "$BOOT_CLEANUP_FILE" || return 1
|
||||
active_rules=$(/usr/bin/awk '!/^[[:space:]]*(#|$)/ { print }' "$BOOT_CLEANUP_FILE") || return 1
|
||||
[[ $active_rules == 'r! /etc/sudoers.d/99-omarchy-nopasswd-*' ]] || return 1
|
||||
verify_root_policy_file "$PACKAGE_HOOK" || return 1
|
||||
verify_root_path "$PACKAGE_HOOK" || return 1
|
||||
hook=$(/usr/bin/cat -- "$PACKAGE_HOOK") || return 1
|
||||
[[ $hook == '[Trigger]
|
||||
Operation = Upgrade
|
||||
@@ -313,166 +207,113 @@ package_removing_locked() {
|
||||
cleanup_all_locked
|
||||
}
|
||||
|
||||
prepare_state_file() {
|
||||
local uid="$1" name="$2" expires="$3" timer="$4" tmp
|
||||
tmp=$(/usr/bin/mktemp "$STATE_DIR/.state.XXXXXX") || return 1
|
||||
if ! /usr/bin/printf 'UID=%s\nUSER=%s\nEXPIRES=%s\nTIMER=%s\n' \
|
||||
"$uid" "$name" "$expires" "$timer" >"$tmp" ||
|
||||
! /usr/bin/chown root:root "$tmp" || ! /usr/bin/chmod 0600 "$tmp"; then
|
||||
/usr/bin/rm -f -- "$tmp"
|
||||
return 1
|
||||
migration_complete() {
|
||||
[[ -f $MIGRATION_MARKER && ! -s $MIGRATION_MARKER ]] && verify_root_path "$MIGRATION_MARKER"
|
||||
}
|
||||
|
||||
migrate_locked() {
|
||||
local directory
|
||||
if migration_complete; then
|
||||
return 0
|
||||
fi
|
||||
printf '%s' "$tmp"
|
||||
[[ ! -e $MIGRATION_MARKER && ! -L $MIGRATION_MARKER ]] || return 1
|
||||
verify_root_path /var/lib || return 1
|
||||
for directory in /var/lib/omarchy /var/lib/omarchy/migrations; do
|
||||
if [[ ! -e $directory && ! -L $directory ]]; then
|
||||
/usr/bin/install -d -o root -g root -m 0755 -- "$directory" || return 1
|
||||
fi
|
||||
verify_root_path "$directory" || return 1
|
||||
done
|
||||
cleanup_all_locked || return 1
|
||||
# The empty marker is written only after cleanup succeeds, under the same
|
||||
# machine lock. Later accounts need no sudo and cannot revoke newer grants.
|
||||
/usr/bin/install -o root -g root -m 0644 /dev/null "$MIGRATION_MARKER"
|
||||
}
|
||||
|
||||
start_expiry_timer() {
|
||||
local uid="$1" expires="$2" timer="$3"
|
||||
valid_uid "$uid" && valid_expiry "$expires" && valid_timer_for_uid "$uid" "$timer" || return 1
|
||||
# Calendar timers use CLOCK_REALTIME and catch up immediately after resume;
|
||||
# a monotonic OnActiveSec timer pauses while the machine is suspended.
|
||||
/usr/bin/systemd-run --quiet --collect --on-calendar="@${expires}" \
|
||||
--timer-property=AccuracySec=1s --unit="$timer" \
|
||||
-- "$INSTALLED_SELF" __expire "$uid" "$timer" || return 1
|
||||
/usr/bin/systemctl is-active --quiet "${timer}.timer"
|
||||
}
|
||||
|
||||
publish_rule() {
|
||||
local uid="$1" name="$2" expires="$3" destination tmp deadline
|
||||
valid_expiry "$expires" || return 1
|
||||
deadline=$(/usr/bin/date -u -d "@$expires" +%Y%m%d%H%M%SZ) || return 1
|
||||
[[ $deadline =~ ^[0-9]{14}Z$ ]] || return 1
|
||||
destination=$(rule_file "$uid")
|
||||
tmp=$(/usr/bin/mktemp "$STATE_DIR/.sudoers.XXXXXX") || return 1
|
||||
if ! /usr/bin/printf '%s ALL=(ALL) NOTAFTER=%s NOPASSWD: ALL\n' "$name" "$deadline" >"$tmp" ||
|
||||
! /usr/bin/chown root:root "$tmp" || ! /usr/bin/chmod 0440 "$tmp" ||
|
||||
! /usr/sbin/visudo -cf "$tmp" >/dev/null ||
|
||||
! /usr/bin/install -o root -g root -m 0440 -- "$tmp" "$destination"; then
|
||||
/usr/bin/rm -f -- "$tmp"
|
||||
return 1
|
||||
fi
|
||||
/usr/bin/rm -f -- "$tmp"
|
||||
}
|
||||
|
||||
abort_enable_locked() {
|
||||
local uid=$1 timer=$2 old_timer=$3 pending_state=$4
|
||||
# Publication can install policy and then fail while cleaning its temporary
|
||||
# file. Never disarm either expiry job until policy revocation is confirmed.
|
||||
if cleanup_uid_locked "$uid"; then
|
||||
stop_timer "$timer"
|
||||
[[ -z $old_timer ]] || stop_timer "$old_timer"
|
||||
# Old callbacks only remove an expired current rule. Renewing a grant never
|
||||
# needs a second state file or a stored timer generation to identify it.
|
||||
expire_locked() {
|
||||
local status now
|
||||
if read_grant "$1"; then
|
||||
now=$(/usr/bin/date -u +%Y%m%d%H%M%SZ) || return 2
|
||||
[[ $now < $GRANT_DEADLINE ]] && return 0
|
||||
cleanup_uid_locked "$1"
|
||||
else
|
||||
echo "Could not revoke passwordless sudo after a failed grant; expiry jobs remain armed. Administrator cleanup is required." >&2
|
||||
status=$?
|
||||
if (( status == STATUS_INACTIVE )); then
|
||||
return 0
|
||||
else
|
||||
cleanup_uid_locked "$1"
|
||||
fi
|
||||
fi
|
||||
/usr/bin/rm -f -- "$pending_state" || true
|
||||
return 1
|
||||
}
|
||||
|
||||
enable_locked() {
|
||||
local uid="$1" minutes="$2" old_timer="" timer token expires pending_state now
|
||||
resolve_account "$uid" || return 1
|
||||
valid_minutes "$minutes" || return 1
|
||||
prepare_root_state || return 1
|
||||
verify_boot_cleanup || {
|
||||
echo "omarchy-sudo-passwordless: package-owned boot cleanup or transaction hook is missing or unsafe" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
old_timer=$(read_state_timer "$uid" 2>/dev/null || true)
|
||||
token=$(/usr/bin/tr -d '-' </proc/sys/kernel/random/uuid)
|
||||
[[ $token =~ ^[0-9a-f]{32}$ ]] || return 1
|
||||
timer="omarchy-nopasswd-expire-${uid}-${token}"
|
||||
now=$(current_epoch) || return 1
|
||||
expires=$((10#$now + 10#$minutes * 60))
|
||||
|
||||
# State and a verified timer exist before the policy becomes reachable. If
|
||||
# publication fails, cleanup removes both. During an update the old timer is
|
||||
# deliberately kept until the replacement is active, so failure shortens the
|
||||
# grant rather than extending it.
|
||||
pending_state=$(prepare_state_file "$uid" "$ACCOUNT_NAME" "$expires" "$timer") || return 1
|
||||
if ! start_expiry_timer "$uid" "$expires" "$timer"; then
|
||||
abort_enable_locked "$uid" "$timer" "$old_timer" "$pending_state"
|
||||
return 1
|
||||
fi
|
||||
if ! /usr/bin/mv -fT -- "$pending_state" "$(state_file "$uid")"; then
|
||||
abort_enable_locked "$uid" "$timer" "$old_timer" "$pending_state"
|
||||
return 1
|
||||
fi
|
||||
if ! verify_boot_cleanup || ! publish_rule "$uid" "$ACCOUNT_NAME" "$expires"; then
|
||||
abort_enable_locked "$uid" "$timer" "$old_timer" "$pending_state"
|
||||
return 1
|
||||
fi
|
||||
now=$(current_epoch) || {
|
||||
abort_enable_locked "$uid" "$timer" "$old_timer" "$pending_state"
|
||||
return 1
|
||||
}
|
||||
if ((10#$now >= 10#$expires)) || ! /usr/bin/systemctl is-active --quiet "${timer}.timer" || ! verify_boot_cleanup; then
|
||||
# The timer may have expired or failed between its initial verification and
|
||||
# rule publication. Revoke synchronously so a suspended or heavily loaded
|
||||
# machine cannot turn a short grant into a reboot-long one.
|
||||
abort_enable_locked "$uid" "$timer" "$old_timer" "$pending_state"
|
||||
return 1
|
||||
fi
|
||||
[[ -z $old_timer || $old_timer == "$timer" ]] || stop_timer "$old_timer"
|
||||
}
|
||||
|
||||
status_locked() {
|
||||
local uid="$1" record state_name expires timer now remainder
|
||||
resolve_account "$uid" || return 2
|
||||
if [[ ! -e $(rule_file "$uid") && ! -L $(rule_file "$uid") ]]; then
|
||||
local status now
|
||||
resolve_account "$1" || return 2
|
||||
if read_grant "$1"; then
|
||||
[[ $GRANT_NAME == "$ACCOUNT_NAME" ]] || return 2
|
||||
now=$(/usr/bin/date -u +%Y%m%d%H%M%SZ) || return 2
|
||||
if [[ $now < $GRANT_DEADLINE ]]; then
|
||||
return 0
|
||||
fi
|
||||
cleanup_uid_locked "$1" || return 2
|
||||
return "$STATUS_INACTIVE"
|
||||
fi
|
||||
record=$(read_state_record "$uid") || {
|
||||
revoke_inactive_grant "$uid"
|
||||
return $?
|
||||
}
|
||||
state_name=${record%%$'\t'*}
|
||||
remainder=${record#*$'\t'}
|
||||
expires=${remainder%%$'\t'*}
|
||||
timer=${record##*$'\t'}
|
||||
[[ $state_name == "$ACCOUNT_NAME" ]] || {
|
||||
revoke_inactive_grant "$uid"
|
||||
return $?
|
||||
}
|
||||
now=$(current_epoch) || {
|
||||
revoke_inactive_grant "$uid"
|
||||
return $?
|
||||
}
|
||||
((10#$now < 10#$expires)) || {
|
||||
revoke_inactive_grant "$uid"
|
||||
return $?
|
||||
}
|
||||
/usr/bin/systemctl is-active --quiet "${timer}.timer" || {
|
||||
revoke_inactive_grant "$uid"
|
||||
return $?
|
||||
}
|
||||
}
|
||||
|
||||
revoke_inactive_grant() {
|
||||
if cleanup_uid_locked "$1"; then
|
||||
return "$STATUS_INACTIVE"
|
||||
else
|
||||
echo "Could not revoke invalid or expired passwordless sudo. Administrator cleanup is required." >&2
|
||||
return 2
|
||||
fi
|
||||
}
|
||||
|
||||
expire_locked() {
|
||||
local uid=$1 timer=${2:-} current_timer status
|
||||
if [[ -n $timer ]]; then
|
||||
current_timer=$(read_state_timer "$uid" 2>/dev/null || true)
|
||||
# A delayed predecessor must not revoke a newer, independently timed grant.
|
||||
[[ -z $current_timer || $current_timer == "$timer" ]] || return 0
|
||||
cleanup_uid_locked "$uid"
|
||||
elif status_locked "$uid"; then
|
||||
# Compatibility with already scheduled UID-only jobs: enforce the current
|
||||
# grant's expiry instead of letting an old timer shorten its replacement.
|
||||
return 0
|
||||
else
|
||||
status=$?
|
||||
(( status == STATUS_INACTIVE ))
|
||||
return "$status"
|
||||
fi
|
||||
}
|
||||
|
||||
finish_enable() {
|
||||
local status=$?
|
||||
trap - EXIT HUP INT TERM
|
||||
if (( status != 0 )); then
|
||||
if cleanup_uid_locked "$uid"; then
|
||||
[[ -z $timer ]] || /usr/bin/systemctl stop "$timer.timer" "$timer.service" >/dev/null 2>&1 || true
|
||||
else
|
||||
echo "Could not revoke passwordless sudo; expiry remains armed. Administrator cleanup is required." >&2
|
||||
fi
|
||||
fi
|
||||
[[ -z $pending ]] || /usr/bin/rm -f -- "$pending"
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
enable_locked() (
|
||||
local uid=$1 minutes=$2 now expires deadline token timer="" pending="" file status
|
||||
resolve_account "$uid" && valid_minutes "$minutes" || return 1
|
||||
verify_boot_cleanup && verify_root_path /etc/sudoers.d || return 1
|
||||
file=$(rule_file "$uid")
|
||||
if read_grant "$uid"; then
|
||||
[[ $GRANT_NAME == "$ACCOUNT_NAME" ]] || return 1
|
||||
else
|
||||
status=$?
|
||||
(( status == STATUS_INACTIVE )) || return 1
|
||||
fi
|
||||
trap finish_enable EXIT
|
||||
omarchy_security_install_signal_exit_traps
|
||||
now=$(/usr/bin/date +%s) || return 1
|
||||
expires=$((now + 10#$minutes * 60))
|
||||
deadline=$(/usr/bin/date -u -d "@$expires" +%Y%m%d%H%M%SZ) || return 1
|
||||
pending=$(/usr/bin/mktemp /etc/sudoers.d/.omarchy-nopasswd.XXXXXX) || return 1
|
||||
/usr/bin/printf '%s ALL=(ALL) NOTAFTER=%s NOPASSWD: ALL\n' "$ACCOUNT_NAME" "$deadline" >"$pending" || return 1
|
||||
/usr/bin/chown root:root "$pending" && /usr/bin/chmod 0440 "$pending" || return 1
|
||||
/usr/sbin/visudo -cf "$pending" >/dev/null || return 1
|
||||
token=$(/usr/bin/tr -d '-' </proc/sys/kernel/random/uuid) || return 1
|
||||
[[ $token =~ ^[0-9a-f]{32}$ ]] || return 1
|
||||
timer="omarchy-nopasswd-expire-$uid-$token"
|
||||
/usr/bin/systemd-run --quiet --collect --on-calendar="@$expires" \
|
||||
--timer-property=AccuracySec=1s --unit="$timer" \
|
||||
-- "$INSTALLED_SELF" __expire "$uid" || return 1
|
||||
/usr/bin/systemctl is-active --quiet "$timer.timer" || return 1
|
||||
# The temporary filename contains a dot, so sudo ignores it. Rename within
|
||||
# sudoers.d publishes the complete validated policy in one operation.
|
||||
/usr/bin/mv -fT -- "$pending" "$file" || return 1
|
||||
pending=""
|
||||
now=$(/usr/bin/date +%s) || return 1
|
||||
(( now < expires )) && verify_boot_cleanup && /usr/bin/systemctl is-active --quiet "$timer.timer"
|
||||
)
|
||||
|
||||
root_dispatch() {
|
||||
local action="$1"
|
||||
shift
|
||||
@@ -491,9 +332,16 @@ root_dispatch() {
|
||||
;;
|
||||
__expire)
|
||||
(($# == 1 || $# == 2)) && ((EUID == 0)) && valid_uid "$1" || return 1
|
||||
[[ -z ${2:-} ]] || valid_timer_for_uid "$1" "$2" || return 1
|
||||
[[ -z ${2:-} || $2 =~ ^omarchy-nopasswd-expire-${1}-[0-9a-f]{32}$ ]] || return 1
|
||||
with_root_lock expire_locked "$@"
|
||||
;;
|
||||
__migration-complete)
|
||||
(($# == 0)) && migration_complete
|
||||
;;
|
||||
__migrate)
|
||||
(($# == 0)) && ((EUID == 0)) || return 1
|
||||
with_root_lock migrate_locked
|
||||
;;
|
||||
__cleanup-all)
|
||||
(($# == 0)) && ((EUID == 0)) || return 1
|
||||
with_root_lock cleanup_all_locked
|
||||
@@ -507,7 +355,7 @@ root_dispatch() {
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
__status|__enable|__disable|__expire|__cleanup-all|__package-removing)
|
||||
__status|__enable|__disable|__expire|__cleanup-all|__package-removing|__migrate|__migration-complete)
|
||||
action=$1
|
||||
shift
|
||||
root_dispatch "$action" "$@"
|
||||
@@ -542,7 +390,7 @@ if /usr/bin/sudo -N -- "$INSTALLED_SELF" __status "$uid"; then
|
||||
echo "Passwordless sudo has been DISABLED. Sudo will require a password again."
|
||||
else
|
||||
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
|
||||
echo "Passwordless sudo timer updated. It will automatically disable in ${minutes} minutes."
|
||||
echo "Passwordless sudo expiry updated. It will automatically disable in ${minutes} minutes."
|
||||
fi
|
||||
else
|
||||
status=$?
|
||||
@@ -559,7 +407,7 @@ else
|
||||
echo "Anyone or anything with access to your user account gets full root."
|
||||
echo ""
|
||||
echo "Passwordless sudo will automatically disable after ${minutes} minutes,"
|
||||
echo "including if the machine reboots before the timer fires."
|
||||
echo "including if the machine reboots before the deadline."
|
||||
echo "Run this command again to disable it early."
|
||||
echo ""
|
||||
|
||||
|
||||
Reference in new issue
Block a user