fix(host112): verify exact nopasswd sudo entries
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user