Recognize legacy sudo grants for any account name
The legacy command wrote the caller's unvalidated name into both the sudoers filename and the rule. Cleanup applied the current lower-case account pattern to that suffix, so an exact legacy grant for an account such as Alice was classified as administrator policy, left active, and the machine-wide migration marker was written anyway. Match a legacy grant by its exact filename and rule relationship instead of the account policy, and cover it in the lifecycle suite through both the unit cleanup and the real migration runner. The account pattern itself stays lower-case: sudoers reads an upper-case word such as ALICE as a User_Alias reference, and ALL as every user, so such names must never reach the generated rule. Pin that with a test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
c46f321680
commit
3eb3142313
4 files changed
+34
-18
No files matched your search
@@ -56,9 +56,10 @@ resolve_account() {
|
||||
entry=$(/usr/bin/getent passwd "$((10#$uid))") || return 1
|
||||
IFS=: read -r ACCOUNT_NAME _ ACCOUNT_UID _ _ _ _ <<<"$entry"
|
||||
[[ $ACCOUNT_UID == "$((10#$uid))" ]] || return 1
|
||||
# Sudoers names and the legacy filename both have metacharacters. Omarchy
|
||||
# accounts use this portable subset; refusing anything else is safer than
|
||||
# attempting to quote privileged policy syntax.
|
||||
# Sudoers has metacharacters, and it reads an upper-case word such as ALICE
|
||||
# as an alias reference rather than a user. Accounts use this portable
|
||||
# lower-case subset; refusing anything else is safer than attempting to
|
||||
# quote privileged policy syntax.
|
||||
valid_account_name "$ACCOUNT_NAME" || return 1
|
||||
ACCOUNT_UID=$((10#$uid))
|
||||
}
|
||||
@@ -109,6 +110,8 @@ read_grant() {
|
||||
valid_account_name "$GRANT_NAME" || return 2
|
||||
}
|
||||
|
||||
# Returns 0 for a rule this command generated, 1 for anything else under the
|
||||
# owned prefix (preserved as administrator policy), and 2 when unreadable.
|
||||
classify_generated_rule() {
|
||||
local file=$1 suffix contents name
|
||||
|
||||
@@ -116,18 +119,20 @@ classify_generated_rule() {
|
||||
contents=$(/usr/bin/cat -- "$file") || return 2
|
||||
suffix=${file##*/99-omarchy-nopasswd-}
|
||||
|
||||
if [[ $suffix =~ ^[0-9]+$ ]]; then
|
||||
name=${contents%' ALL=(ALL) NOPASSWD: ALL'}
|
||||
if valid_account_name "$name" && [[ $contents == "$name ALL=(ALL) NOPASSWD: ALL" ]]; then
|
||||
return 0
|
||||
fi
|
||||
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
|
||||
# The legacy command wrote the caller's unvalidated name into both the
|
||||
# filename and the rule. That exact relationship is its fingerprint, so an
|
||||
# account the current policy would reject still has its old grant removed.
|
||||
if [[ $contents == "$suffix ALL=(ALL) NOPASSWD: ALL" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
|
||||
[[ $suffix =~ ^[0-9]+$ ]] || return 1
|
||||
name=${contents%' ALL=(ALL) NOPASSWD: ALL'}
|
||||
if valid_account_name "$name" && [[ $contents == "$name ALL=(ALL) NOPASSWD: ALL" ]]; then
|
||||
return 0
|
||||
fi
|
||||
name=${contents%%' ALL=(ALL) NOTAFTER='*}
|
||||
valid_account_name "$name" && [[ $contents =~ ^[a-z_][a-z0-9_-]*\$?\ ALL=\(ALL\)\ NOTAFTER=[0-9]{14}Z\ NOPASSWD:\ ALL$ ]]
|
||||
}
|
||||
|
||||
cleanup_uid_locked() {
|
||||
|
||||
Reference in new issue
Block a user