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
21 changes: 16 additions & 5 deletions bin/omarchy-sudo-passwordless
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ if [[ $1 && ! $1 =~ ^[0-9]+$ ]]; then
exit 1
fi

arm_expiry() {
if sudo systemd-run --on-active=${MINUTES}m --timer-property=AccuracySec=1s --unit="$TIMER_NAME" \
rm -f -- "$NOPASSWD_FILE"; then
return 0
fi

echo "Failed to schedule passwordless sudo expiry. Revoking access now." >&2
if ! sudo rm -f -- "$NOPASSWD_FILE"; then
echo "CRITICAL: Could not remove $NOPASSWD_FILE. Remove it as root immediately." >&2
fi
return 1
}

echo "Toggle passwordless sudo..."

# Safety: if the file exists but the timer doesn't (e.g. after reboot), clean up
Expand All @@ -24,8 +37,7 @@ fi
if sudo test -f "$NOPASSWD_FILE"; then
if [[ $1 ]]; then
sudo systemctl stop "${TIMER_NAME}.timer" 2>/dev/null
sudo systemd-run --on-active=${MINUTES}m --timer-property=AccuracySec=1s --unit="$TIMER_NAME" \
rm "$NOPASSWD_FILE"
arm_expiry || exit 1
echo "Passwordless sudo timer updated. It will now automatically disable in ${MINUTES} minutes."
else
sudo rm "$NOPASSWD_FILE"
Expand All @@ -48,12 +60,11 @@ else
if gum confirm "Enable passwordless sudo for ${MINUTES} minutes? This is a significant security risk!"; then
echo "${USER} ALL=(ALL) NOPASSWD: ALL" | sudo tee "$NOPASSWD_FILE" > /dev/null
sudo chmod 440 "$NOPASSWD_FILE"
sudo systemd-run --on-active=${MINUTES}m --timer-property=AccuracySec=1s --unit="$TIMER_NAME" \
rm "$NOPASSWD_FILE"
arm_expiry || exit 1

echo ""
echo "Passwordless sudo has been ENABLED. It will automatically disable in ${MINUTES} minutes."
echo "Note: if you restart before then, run omarchy-sudo-passwordless again to disable it."
echo "A restart removes the passwordless sudo rule as well."
else
echo "Aborted. No changes made."
fi
Expand Down
5 changes: 5 additions & 0 deletions etc/tmpfiles.d/omarchy-nopasswd-sudo.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# omarchy-sudo-passwordless writes /etc/sudoers.d/99-omarchy-nopasswd-<user> and
# arms a transient systemd-run timer to remove it again. Transient units do not
# survive a reboot, so remove any remaining grant during early boot. Boot-only
# (r!) ensures a later systemd-tmpfiles --remove cannot cut a live grant short.
r! /etc/sudoers.d/99-omarchy-nopasswd-*
2 changes: 1 addition & 1 deletion manual/48-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ It works by restoring the baseline snapshot the installer takes, so it's only av

## Passwordless sudo

Sometimes you want `sudo` to stop asking, most often when an AI agent is doing a long stretch of system work for you. _Setup > Security > Passwordless Sudo_ turns that off for 15 minutes and then puts it back automatically. Run it again before the timer runs out to end it early, and pass your own number of minutes with `omarchy-sudo-passwordless 30` if 15 isn't enough.
Sometimes you want `sudo` to stop asking, most often when an AI agent is doing a long stretch of system work for you. _Setup > Security > Passwordless Sudo_ turns that off for 15 minutes and then puts it back automatically. Run it again before the timer runs out to end it early, and pass your own number of minutes with `omarchy-sudo-passwordless 30` if 15 isn't enough. A restart removes the passwordless sudo rule as well.

Be clear-eyed about this one: while it's on, anything running as your user can do anything as root without being asked. That's the whole point, and it's also the whole risk.

Expand Down
123 changes: 123 additions & 0 deletions test/shell.d/nopasswd-sudo-expiry-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/bin/bash

set -euo pipefail

source "$(dirname "$0")/base-test.sh"

script="$ROOT/bin/omarchy-sudo-passwordless"
tmpfiles_file="$ROOT/etc/tmpfiles.d/omarchy-nopasswd-sudo.conf"
test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT

mock_bin="$test_tmp/bin"
grant="$test_tmp/grant"
calls="$test_tmp/calls"
mkdir -p "$mock_bin"

cat >"$mock_bin/gum" <<'SH'
#!/bin/bash
exit 0
SH

cat >"$mock_bin/systemctl" <<'SH'
#!/bin/bash

printf 'systemctl %s\n' "$*" >>"$TEST_CALLS"
[[ ${1:-} == "is-active" && ${TEST_TIMER_ACTIVE:-false} == "true" ]]
SH

cat >"$mock_bin/sudo" <<'SH'
#!/bin/bash

printf 'sudo %s\n' "$*" >>"$TEST_CALLS"

case ${1:-} in
test)
[[ ${2:-} == "-f" && -f $TEST_GRANT ]]
;;
tee)
/usr/bin/tee "$TEST_GRANT"
;;
chmod)
/usr/bin/chmod "$2" "$TEST_GRANT"
;;
systemd-run)
[[ ${TEST_FAIL_SYSTEMD_RUN:-false} != "true" ]]
;;
rm)
/usr/bin/rm -f -- "$TEST_GRANT"
;;
systemctl)
exit 0
;;
*)
echo "unexpected sudo command: $*" >&2
exit 90
;;
esac
SH

chmod +x "$mock_bin/gum" "$mock_bin/sudo" "$mock_bin/systemctl"

run_command() {
TEST_CALLS="$calls" TEST_GRANT="$grant" PATH="$mock_bin:$PATH" USER=alice \
"$script" "$@"
}

: >"$calls"
enable_output=$(run_command 15)
[[ -f $grant ]] || fail "successful timer setup leaves the passwordless sudo grant enabled"
[[ $(cat "$grant") == "alice ALL=(ALL) NOPASSWD: ALL" ]] ||
fail "the enabled grant belongs to the current user" "$(cat "$grant")"
grep -q '^sudo systemd-run --on-active=15m .* rm -f -- /etc/sudoers.d/99-omarchy-nopasswd-alice$' "$calls" ||
fail "enabling arms the expiry timer" "$(cat "$calls")"
[[ $enable_output == *"automatically disable in 15 minutes"* ]] ||
fail "success is reported after the timer is armed" "$enable_output"
pass "enabling arms expiry before reporting success"

: >"$calls"
rm -f "$grant"
if failure_output=$(TEST_FAIL_SYSTEMD_RUN=true run_command 15 2>&1); then
fail "enabling fails when the expiry timer cannot be armed"
fi
[[ ! -e $grant ]] || fail "timer setup failure revokes the new passwordless sudo grant"
[[ $failure_output == *"Revoking access now"* ]] ||
fail "timer setup failure explains the fail-closed revocation" "$failure_output"
[[ $failure_output != *"Passwordless sudo has been ENABLED"* ]] ||
fail "timer setup failure does not report that passwordless sudo was enabled" "$failure_output"
pass "timer setup failure revokes a new grant"

: >"$calls"
printf 'alice ALL=(ALL) NOPASSWD: ALL\n' >"$grant"
if update_output=$(TEST_TIMER_ACTIVE=true TEST_FAIL_SYSTEMD_RUN=true run_command 30 2>&1); then
fail "updating fails when the replacement expiry timer cannot be armed"
fi
[[ ! -e $grant ]] || fail "timer update failure revokes the existing passwordless sudo grant"
[[ $update_output != *"timer updated"* ]] ||
fail "timer update failure does not report success" "$update_output"
pass "timer update failure revokes the existing grant"

mapfile -t tmpfiles_rules < <(grep -vE '^[[:space:]]*(#|$)' "$tmpfiles_file")
(( ${#tmpfiles_rules[@]} == 1 )) ||
fail "passwordless sudo ships one tmpfiles rule" "${tmpfiles_rules[*]}"

fake_root="$test_tmp/root"
sudoers_dir="$fake_root/etc/sudoers.d"
mkdir -p "$sudoers_dir"
grant_names=(alice buildbot-2 user.123 'service$')
for grant_name in "${grant_names[@]}"; do
touch "$sudoers_dir/99-omarchy-nopasswd-$grant_name"
done
touch "$sudoers_dir/omarchy-dns"

systemd-tmpfiles --root="$fake_root" --remove --inline "${tmpfiles_rules[@]}"
[[ -f $sudoers_dir/99-omarchy-nopasswd-alice ]] ||
fail "boot-only cleanup leaves a live grant alone outside boot"

systemd-tmpfiles --root="$fake_root" --remove --boot --inline "${tmpfiles_rules[@]}"
for grant_name in "${grant_names[@]}"; do
stale_grant="$sudoers_dir/99-omarchy-nopasswd-$grant_name"
[[ ! -e $stale_grant ]] || fail "boot cleanup removes every generated grant" "$stale_grant"
done
[[ -f $sudoers_dir/omarchy-dns ]] || fail "boot cleanup preserves unrelated sudoers rules"
pass "systemd-tmpfiles removes generated grants only during boot"