fix(host112): verify exact nopasswd sudo entries

This commit is contained in:
ogt
2026-07-14 16:05:24 +08:00
parent 12fc4b8914
commit 9ca6dff2ea
2 changed files with 80 additions and 7 deletions

View File

@@ -162,12 +162,37 @@ resolve_target_user() {
AUTH_FILE="${AUTH_DIR}/authorized_keys"
}
sudo_policy_allows() {
sudo_policy_output_has_nopasswd_match() {
LC_ALL=C awk '
/^Sudoers entry:/ {
if (entry_nopasswd && entry_matched) found = 1
entry_nopasswd = 0
entry_matched = 0
next
}
/^[[:space:]]*Options:/ && /(^|[ ,])!authenticate([ ,]|$)/ {
entry_nopasswd = 1
next
}
/^[[:space:]]*Matched:/ {
entry_matched = 1
next
}
END {
if (entry_nopasswd && entry_matched) found = 1
exit(found ? 0 : 1)
}
'
}
sudo_policy_is_nopasswd() {
local policy_output
if [ "${EUID:-$(id -u)}" -eq 0 ]; then
sudo -n -U "$TARGET_USER" -l "$@" >/dev/null 2>&1
policy_output="$(LC_ALL=C sudo -n -U "$TARGET_USER" -ll "$@" 2>/dev/null)" || return 1
else
sudo -n -l "$@" >/dev/null 2>&1
policy_output="$(LC_ALL=C sudo -n -ll "$@" 2>/dev/null)" || return 1
fi
printf '%s\n' "$policy_output" | sudo_policy_output_has_nopasswd_match
}
agent99_direct_authorization_ready() {
@@ -246,25 +271,25 @@ refresh_gate_state() {
SUDOERS_VALID=1
fi
SUDO_CORRELATED_APPLY_NOPASSWD=0
if sudo_policy_allows "$READINESS_TARGET" --apply \
if sudo_policy_is_nopasswd "$READINESS_TARGET" --apply \
--trace-id host112-policy-check \
--run-id host112-policy-check \
--work-item-id HOST112-WAZUH-MANAGER-RECOVERY; then
SUDO_CORRELATED_APPLY_NOPASSWD=1
fi
SUDO_CORRELATED_DRY_RUN_NOPASSWD=0
if sudo_policy_allows "$READINESS_TARGET" --dry-run \
if sudo_policy_is_nopasswd "$READINESS_TARGET" --dry-run \
--trace-id host112-policy-check \
--run-id host112-policy-check \
--work-item-id HOST112-WAZUH-MANAGER-RECOVERY; then
SUDO_CORRELATED_DRY_RUN_NOPASSWD=1
fi
SUDO_LEGACY_UNCORRELATED_APPLY_NOPASSWD=0
if sudo_policy_allows "$READINESS_TARGET" --apply; then
if sudo_policy_is_nopasswd "$READINESS_TARGET" --apply; then
SUDO_LEGACY_UNCORRELATED_APPLY_NOPASSWD=1
fi
SUDO_UNSAFE_IDENTITY_NOPASSWD=0
if sudo_policy_allows "$READINESS_TARGET" --apply \
if sudo_policy_is_nopasswd "$READINESS_TARGET" --apply \
--trace-id 'unsafe value' \
--run-id host112-policy-check \
--work-item-id HOST112-WAZUH-MANAGER-RECOVERY; then

View File

@@ -49,6 +49,54 @@ def test_sudo_policy_has_exact_correlated_apply_and_dry_run_only() -> None:
assert 'SUDO_LEGACY_UNCORRELATED_APPLY_NOPASSWD' in text
assert 'SUDO_UNSAFE_IDENTITY_NOPASSWD' in text
assert 'SUDO_POLICY_READY' in text
assert 'sudo_policy_is_nopasswd' in text
assert 'Options:' in text
assert '!authenticate' in text
def test_sudo_policy_parser_requires_nopasswd_and_match_in_same_entry() -> None:
text = source()
parser = text[
text.index("sudo_policy_output_has_nopasswd_match() {") : text.index(
"agent99_direct_authorization_ready() {"
)
]
broad_password_rule = """Sudoers entry: /etc/sudoers
RunAsUsers: ALL
Commands:
ALL
Matched: /usr/local/sbin/readiness --apply
"""
exact_nopasswd_rule = broad_password_rule + """Sudoers entry: /etc/sudoers.d/agent99
RunAsUsers: root
Options: !authenticate
Commands:
/usr/local/sbin/readiness ^--apply .*$
Matched: /usr/local/sbin/readiness --apply --trace-id safe
"""
split_entry_false_positive = """Sudoers entry: /etc/sudoers.d/one
Options: !authenticate
Commands:
/usr/bin/true
Sudoers entry: /etc/sudoers
Commands:
ALL
Matched: /usr/local/sbin/readiness --apply
"""
def parse(sample: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["bash", "-c", parser + "\nsudo_policy_output_has_nopasswd_match"],
input=sample,
text=True,
capture_output=True,
check=False,
timeout=10,
)
assert parse(broad_password_rule).returncode != 0
assert parse(exact_nopasswd_rule).returncode == 0
assert parse(split_entry_false_positive).returncode != 0
def test_windows99_direct_route_is_fingerprint_bound_and_not_forced() -> None: