Skip to content

Commit 1d81ca0

Browse files
authored
Merge pull request #49 from stacknil/stacknil/loglens-maxauth-error-prefix
test(parser): normalize max-auth error prefix
2 parents da3ffcb + a6d1e12 commit 1d81ca0

5 files changed

Lines changed: 38 additions & 9 deletions

File tree

assets/parser_fixture_matrix_journalctl_short_full.log

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ Tue 2026-03-10 09:05:34 UTC example-host sshd[3007]: Timeout, client not respond
2525
Tue 2026-03-10 09:05:46 UTC example-host sshd[3010]: Received disconnect from 203.0.113.55 port 52015:11: disconnected by user
2626
Tue 2026-03-10 09:05:58 UTC example-host sshd[3011]: Unable to negotiate with 203.0.113.56 port 52016: no matching host key type found. Their offer: ssh-rsa
2727
Tue 2026-03-10 09:06:10 UTC example-host pam_unix(sshd:session): session closed for user alice
28+
Tue 2026-03-10 09:06:24 UTC example-host sshd[3023]: error: maximum authentication attempts exceeded for invalid user svc-error-maxauth from 203.0.113.57 port 52019 ssh2 [preauth]

assets/parser_fixture_matrix_syslog.log

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ Mar 10 09:05:34 example-host sshd[2007]: Timeout, client not responding from 203
2525
Mar 10 09:05:46 example-host sshd[2010]: Received disconnect from 203.0.113.55 port 52015:11: disconnected by user
2626
Mar 10 09:05:58 example-host sshd[2011]: Unable to negotiate with 203.0.113.56 port 52016: no matching host key type found. Their offer: ssh-rsa
2727
Mar 10 09:06:10 example-host pam_unix(sshd:session): session closed for user alice
28+
Mar 10 09:06:24 example-host sshd[2023]: error: maximum authentication attempts exceeded for invalid user svc-error-maxauth from 203.0.113.57 port 52019 ssh2 [preauth]

docs/parser-contract.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ The parser currently recognizes common authentication evidence from:
2626
- selected `pam_faillock(...)` variants
2727
- selected `pam_sss(...)` variants
2828

29-
Recognized SSH failure families include failed password, invalid user, illegal user, failed publickey, failed keyboard-interactive/pam, and maximum-authentication-attempts-exceeded lines. `illegal user` is treated as an OpenSSH wording variant of `invalid user`. Invalid or illegal-user variants of keyboard-interactive and maximum-authentication-attempts-exceeded lines are normalized into `ssh_invalid_user` events. Recognized SSH failures can become detection signals through the configured signal mapping.
29+
Recognized SSH failure families include failed password, invalid user, illegal user, failed publickey, failed keyboard-interactive/pam, and maximum-authentication-attempts-exceeded lines. `illegal user` is treated as an OpenSSH wording variant of `invalid user`. Maximum-authentication-attempts lines may include OpenSSH's leading `error:` marker and still normalize into the same event family. Invalid or illegal-user variants of keyboard-interactive and maximum-authentication-attempts-exceeded lines are normalized into `ssh_invalid_user` events. Recognized SSH failures can become detection signals through the configured signal mapping.
3030

3131
Recognized success or audit families include accepted password, accepted publickey, accepted keyboard-interactive/pam, sudo command audit lines, sudo password failures, sudoers policy denials, su success/failure audit lines, and selected PAM session/auth lines.
3232

src/parser.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,11 @@ bool parse_ssh_failed_keyboard_interactive_message(std::string_view message, Eve
418418

419419
bool parse_ssh_max_auth_tries_message(std::string_view message, Event& event) {
420420
static constexpr std::string_view max_auth_prefix = "maximum authentication attempts exceeded for ";
421+
static constexpr std::string_view error_prefix = "error: ";
422+
if (message.starts_with(error_prefix)) {
423+
message.remove_prefix(error_prefix.size());
424+
}
425+
421426
if (!message.starts_with(max_auth_prefix)) {
422427
return false;
423428
}

tests/test_parser.cpp

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,19 @@ void test_max_auth_tries_event() {
291291
"expected ssh max-auth-tries failure type");
292292
}
293293

294+
void test_max_auth_tries_error_prefix_event() {
295+
const auto parser = make_syslog_parser();
296+
const auto event = parser.parse_line(
297+
"Mar 10 08:27:25 example-host sshd[1251]: error: maximum authentication attempts exceeded for frank from 203.0.113.84 port 51248 ssh2 [preauth]",
298+
5);
299+
300+
expect(event.has_value(), "expected error-prefixed max-auth-tries event");
301+
expect(event->username == "frank", "expected parsed error-prefixed max-auth-tries username");
302+
expect(event->source_ip == "203.0.113.84", "expected parsed error-prefixed max-auth-tries source ip");
303+
expect(event->event_type == loglens::EventType::SshMaxAuthTries,
304+
"expected error-prefixed ssh max-auth-tries failure type");
305+
}
306+
294307
void test_max_auth_tries_invalid_user_event() {
295308
const auto parser = make_syslog_parser();
296309
const auto event = parser.parse_line(
@@ -632,12 +645,12 @@ void test_syslog_fixture_matrix_file() {
632645
const auto parser = make_syslog_parser();
633646
const auto result = parser.parse_file(asset_path("parser_fixture_matrix_syslog.log"));
634647

635-
expect(result.events.size() == 19, "expected nineteen recognized syslog fixture events");
648+
expect(result.events.size() == 20, "expected twenty recognized syslog fixture events");
636649
expect(result.warnings.size() == 8, "expected eight syslog fixture warnings");
637-
expect(result.quality.total_lines == 27, "expected twenty-seven syslog fixture lines");
638-
expect(result.quality.parsed_lines == 19, "expected nineteen parsed syslog fixture lines");
650+
expect(result.quality.total_lines == 28, "expected twenty-eight syslog fixture lines");
651+
expect(result.quality.parsed_lines == 20, "expected twenty parsed syslog fixture lines");
639652
expect(result.quality.unparsed_lines == 8, "expected eight unparsed syslog fixture lines");
640-
expect_close(result.quality.parse_success_rate, 19.0 / 27.0, 1e-9, "expected syslog fixture parse success rate");
653+
expect_close(result.quality.parse_success_rate, 20.0 / 28.0, 1e-9, "expected syslog fixture parse success rate");
641654

642655
expect(result.events[0].event_type == loglens::EventType::SshInvalidUser, "expected invalid-user failed password");
643656
expect(result.events[1].event_type == loglens::EventType::SshFailedPublicKey, "expected failed publickey variant");
@@ -684,6 +697,10 @@ void test_syslog_fixture_matrix_file() {
684697
expect(result.events[18].event_type == loglens::EventType::SshInvalidUser,
685698
"expected direct illegal-user variant");
686699
expect(result.events[18].username == "legacy-backup", "expected direct illegal username");
700+
expect(result.events[19].event_type == loglens::EventType::SshInvalidUser,
701+
"expected error-prefixed max-auth-tries invalid-user variant");
702+
expect(result.events[19].username == "svc-error-maxauth",
703+
"expected error-prefixed max-auth-tries invalid username");
687704

688705
expect(result.quality.top_unknown_patterns.size() == 4, "expected four unknown syslog buckets");
689706
expect(result.quality.top_unknown_patterns[0].pattern == "sshd_connection_closed_preauth",
@@ -706,12 +723,12 @@ void test_journalctl_fixture_matrix_file() {
706723
std::nullopt});
707724
const auto result = parser.parse_file(asset_path("parser_fixture_matrix_journalctl_short_full.log"));
708725

709-
expect(result.events.size() == 19, "expected nineteen recognized journalctl fixture events");
726+
expect(result.events.size() == 20, "expected twenty recognized journalctl fixture events");
710727
expect(result.warnings.size() == 8, "expected eight journalctl fixture warnings");
711-
expect(result.quality.total_lines == 27, "expected twenty-seven journalctl fixture lines");
712-
expect(result.quality.parsed_lines == 19, "expected nineteen parsed journalctl fixture lines");
728+
expect(result.quality.total_lines == 28, "expected twenty-eight journalctl fixture lines");
729+
expect(result.quality.parsed_lines == 20, "expected twenty parsed journalctl fixture lines");
713730
expect(result.quality.unparsed_lines == 8, "expected eight unparsed journalctl fixture lines");
714-
expect_close(result.quality.parse_success_rate, 19.0 / 27.0, 1e-9, "expected journalctl fixture parse success rate");
731+
expect_close(result.quality.parse_success_rate, 20.0 / 28.0, 1e-9, "expected journalctl fixture parse success rate");
715732

716733
expect(result.events[0].event_type == loglens::EventType::SshInvalidUser, "expected journalctl invalid-user failed password");
717734
expect(result.events[1].event_type == loglens::EventType::SshFailedPublicKey, "expected journalctl failed publickey variant");
@@ -748,6 +765,10 @@ void test_journalctl_fixture_matrix_file() {
748765
expect(result.events[18].event_type == loglens::EventType::SshInvalidUser,
749766
"expected journalctl direct illegal-user variant");
750767
expect(result.events[18].username == "legacy-backup", "expected journalctl direct illegal username");
768+
expect(result.events[19].event_type == loglens::EventType::SshInvalidUser,
769+
"expected journalctl error-prefixed max-auth-tries invalid-user variant");
770+
expect(result.events[19].username == "svc-error-maxauth",
771+
"expected journalctl error-prefixed max-auth-tries invalid username");
751772

752773
expect(result.quality.top_unknown_patterns.size() == 4, "expected four unknown journalctl buckets");
753774
expect(result.quality.top_unknown_patterns[0].pattern == "sshd_connection_closed_preauth",
@@ -785,6 +806,7 @@ int main() {
785806
test_failed_keyboard_interactive_invalid_user_event();
786807
test_failed_keyboard_interactive_illegal_user_event();
787808
test_max_auth_tries_event();
809+
test_max_auth_tries_error_prefix_event();
788810
test_max_auth_tries_invalid_user_event();
789811
test_max_auth_tries_illegal_user_event();
790812
test_pam_auth_failure_event();

0 commit comments

Comments
 (0)