mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 12:33:02 +02:00
Merge pull request #1268 from kernelkit/syslog-matching
Syslog Enhancements
This commit is contained in:
@@ -31,6 +31,8 @@ bind "set completion-ignore-case on"
|
|||||||
# show all completions immediately instead of ringing bell
|
# show all completions immediately instead of ringing bell
|
||||||
bind "set show-all-if-ambiguous on"
|
bind "set show-all-if-ambiguous on"
|
||||||
|
|
||||||
|
export LANG=C.UTF-8
|
||||||
|
|
||||||
log()
|
log()
|
||||||
{
|
{
|
||||||
local fn="/var/log/syslog"
|
local fn="/var/log/syslog"
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
set COLORTERM=yes
|
set COLORTERM=yes
|
||||||
rlimit soft core infinity
|
set LANG=C.UTF-8
|
||||||
|
rlimit soft core infinity
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ alias la='ls -A'
|
|||||||
alias ll='ls -alF'
|
alias ll='ls -alF'
|
||||||
alias ls='ls --color=auto'
|
alias ls='ls --color=auto'
|
||||||
|
|
||||||
|
export LANG=C.UTF-8
|
||||||
export EDITOR=/usr/bin/edit
|
export EDITOR=/usr/bin/edit
|
||||||
export VISUAL=/usr/bin/edit
|
export VISUAL=/usr/bin/edit
|
||||||
export LESS="-P %f (press h for help or q to quit)"
|
export LESS="-P %f (press h for help or q to quit)"
|
||||||
|
|||||||
@@ -48,6 +48,17 @@ All notable changes to the project are documented in this file.
|
|||||||
- Add CLI commands for managing boot partition order: `show boot-order` and
|
- Add CLI commands for managing boot partition order: `show boot-order` and
|
||||||
`set boot-order` allow viewing and changing the boot order from the CLI,
|
`set boot-order` allow viewing and changing the boot order from the CLI,
|
||||||
complementing the existing YANG RPC support, issue #1032
|
complementing the existing YANG RPC support, issue #1032
|
||||||
|
- Extended syslog filtering capabilities, issue #1091:
|
||||||
|
- Add support for pattern matching using POSIX extended regular expressions
|
||||||
|
on message content (IETF `select-match` feature)
|
||||||
|
- Add support for advanced severity comparison: exact match (`equals`) and
|
||||||
|
exclusion (`block`/`stop`) in addition to the default equals-or-higher
|
||||||
|
(IETF `select-adv-compare` feature)
|
||||||
|
- Add support for hostname-based filtering, useful when acting as a log
|
||||||
|
server to route messages from different devices to separate log files
|
||||||
|
- Add support for property-based filtering with operators (contains, isequal,
|
||||||
|
startswith, regex, ereregex) on message properties (msg, msgid, programname,
|
||||||
|
hostname, source, data), with optional case-insensitive and negate modifiers
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
|||||||
+108
-2
@@ -191,8 +191,114 @@ admin@example:/>
|
|||||||
```
|
```
|
||||||
|
|
||||||
See the above [Log to File](#log-to-file) section on how to set up
|
See the above [Log to File](#log-to-file) section on how to set up
|
||||||
filtering of received logs to local files. Please note, filtering based
|
filtering of received logs to local files. Advanced filtering based
|
||||||
on property, e.g., hostname, is not supported yet.
|
on hostname and message properties is also available, see the next
|
||||||
|
section for details.
|
||||||
|
|
||||||
|
## Advanced Filtering
|
||||||
|
|
||||||
|
The syslog subsystem supports several advanced filtering options that
|
||||||
|
allow fine-grained control over which messages are logged. These can
|
||||||
|
be combined with facility and severity filters to create sophisticated
|
||||||
|
logging rules.
|
||||||
|
|
||||||
|
### Pattern Matching
|
||||||
|
|
||||||
|
Messages can be filtered using regular expressions (POSIX extended regex)
|
||||||
|
on the message content. This is useful when you want to log only messages
|
||||||
|
containing specific keywords or patterns:
|
||||||
|
|
||||||
|
```
|
||||||
|
admin@example:/config/> edit syslog actions log-file file:errors
|
||||||
|
admin@example:/config/syslog/…/file:errors/> set pattern-match "ERROR|CRITICAL|FATAL"
|
||||||
|
admin@example:/config/syslog/…/file:errors/> set facility-list all severity info
|
||||||
|
admin@example:/config/syslog/…/file:errors/> leave
|
||||||
|
admin@example:/>
|
||||||
|
```
|
||||||
|
|
||||||
|
This will log all messages containing ERROR, CRITICAL, or FATAL.
|
||||||
|
|
||||||
|
### Advanced Severity Comparison
|
||||||
|
|
||||||
|
By default, severity filtering uses "equals-or-higher" comparison,
|
||||||
|
meaning a severity of `error` will match error, critical, alert, and
|
||||||
|
emergency messages. You can change this behavior:
|
||||||
|
|
||||||
|
```
|
||||||
|
admin@example:/config/> edit syslog actions log-file file:daemon-errors
|
||||||
|
admin@example:/config/syslog/…/file:daemon-errors/> set facility-list daemon
|
||||||
|
admin@example:/config/syslog/…/daemon/> set severity error
|
||||||
|
admin@example:/config/syslog/…/daemon/> set advanced-compare compare equals
|
||||||
|
admin@example:/config/syslog/…/daemon/> leave
|
||||||
|
admin@example:/>
|
||||||
|
```
|
||||||
|
|
||||||
|
This will log only `error` severity messages, not higher severities.
|
||||||
|
|
||||||
|
You can also block specific severities:
|
||||||
|
|
||||||
|
```
|
||||||
|
admin@example:/config/syslog/…/daemon/> set advanced-compare action block
|
||||||
|
```
|
||||||
|
|
||||||
|
This will exclude `error` messages from the log.
|
||||||
|
|
||||||
|
### Hostname Filtering
|
||||||
|
|
||||||
|
When acting as a log server, you can filter messages by hostname. This
|
||||||
|
is useful for directing logs from different devices to separate files:
|
||||||
|
|
||||||
|
```
|
||||||
|
admin@example:/config/> edit syslog actions log-file file:router1
|
||||||
|
admin@example:/config/syslog/…/file:router1/> set hostname-filter router1
|
||||||
|
admin@example:/config/syslog/…/file:router1/> set facility-list all severity info
|
||||||
|
admin@example:/config/syslog/…/file:router1/> leave
|
||||||
|
admin@example:/>
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple hostnames can be added to the filter list.
|
||||||
|
|
||||||
|
### Property-Based Filtering
|
||||||
|
|
||||||
|
For more advanced filtering, you can match on specific message properties
|
||||||
|
using various comparison operators:
|
||||||
|
|
||||||
|
```
|
||||||
|
admin@example:/config/> edit syslog actions log-file file:myapp
|
||||||
|
admin@example:/config/syslog/…/file:myapp/> edit property-filter
|
||||||
|
admin@example:/config/syslog/…/property-filter/> set property programname
|
||||||
|
admin@example:/config/syslog/…/property-filter/> set operator isequal
|
||||||
|
admin@example:/config/syslog/…/property-filter/> set value myapp
|
||||||
|
admin@example:/config/syslog/…/property-filter/> leave
|
||||||
|
admin@example:/>
|
||||||
|
```
|
||||||
|
|
||||||
|
Available properties:
|
||||||
|
- `msg`: Message body
|
||||||
|
- `msgid`: RFC5424 message identifier
|
||||||
|
- `programname`: Program/tag name
|
||||||
|
- `hostname`: Source hostname
|
||||||
|
- `source`: Alias for hostname
|
||||||
|
- `data`: RFC5424 structured data
|
||||||
|
|
||||||
|
Available operators:
|
||||||
|
- `contains`: Substring match
|
||||||
|
- `isequal`: Exact equality
|
||||||
|
- `startswith`: Prefix match
|
||||||
|
- `regex`: Basic regular expression
|
||||||
|
- `ereregex`: Extended regular expression (POSIX ERE)
|
||||||
|
|
||||||
|
The comparison can be made case-insensitive:
|
||||||
|
|
||||||
|
```
|
||||||
|
admin@example:/config/syslog/…/property-filter/> set case-insensitive true
|
||||||
|
```
|
||||||
|
|
||||||
|
Or negated to exclude matching messages:
|
||||||
|
|
||||||
|
```
|
||||||
|
admin@example:/config/syslog/…/property-filter/> set negate true
|
||||||
|
```
|
||||||
|
|
||||||
### Facilities
|
### Facilities
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# -l :: Keep kernel looging to console
|
# -H :: Log remote messages using hostname from the message
|
||||||
|
# -l :: Keep kernel logging to console
|
||||||
# -m0 :: Disable periodic syslog MARK entries
|
# -m0 :: Disable periodic syslog MARK entries
|
||||||
|
# -n :: Disable DNS query for every request, trust hostname in message
|
||||||
# -s :: Enable secure mode, don't listen to remote logs
|
# -s :: Enable secure mode, don't listen to remote logs
|
||||||
# -r 1M:5 :: Log rotation every 1 MiB and keep 5 rotated ones
|
# -r 1M:5 :: Log rotation every 1 MiB and keep 5 rotated ones
|
||||||
SYSLOGD_ARGS="-l -m0"
|
SYSLOGD_ARGS="-H -l -m0"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
From 31dfe0d16461d2852f3fc56cb82aed3a23db022f Mon Sep 17 00:00:00 2001
|
From 31dfe0d16461d2852f3fc56cb82aed3a23db022f Mon Sep 17 00:00:00 2001
|
||||||
From: Joachim Wiberg <troglobit@gmail.com>
|
From: Joachim Wiberg <troglobit@gmail.com>
|
||||||
Date: Thu, 25 Sep 2025 16:48:31 +0200
|
Date: Thu, 25 Sep 2025 16:48:31 +0200
|
||||||
Subject: [PATCH] syslogd: fix UTF-8 handling with -8 flag, for RFC5424
|
Subject: [PATCH 1/4] syslogd: fix UTF-8 handling with -8 flag, for RFC5424
|
||||||
compliance
|
compliance
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
|||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
From f9b6531430c6485af4697a5868a6f43c93d20419 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Joachim Wiberg <troglobit@gmail.com>
|
||||||
|
Date: Fri, 26 Sep 2025 08:42:57 +0200
|
||||||
|
Subject: [PATCH 2/4] syslogd: fix parentheses handling in RFC 3164 tag parsing
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Some applications use tag names like "(polkit-agent)" which sysklogd do
|
||||||
|
not handle well. This patch aims to address this and also add a bit of
|
||||||
|
logic to normalize such tags:
|
||||||
|
|
||||||
|
- Allow parentheses in tag character set for broader compatibility
|
||||||
|
- Smart parsing: strip surrounding parentheses from complete tags like
|
||||||
|
"(polkit-agent):" → "polkit-agent", while preserving partial parentheses
|
||||||
|
like "app(version):" unchanged
|
||||||
|
- Maintain RFC compliance: both RFC3164 and RFC5424 allow parentheses
|
||||||
|
- Preserve all existing functionality and edge case handling
|
||||||
|
|
||||||
|
Fixes #104
|
||||||
|
|
||||||
|
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||||
|
---
|
||||||
|
src/syslogd.c | 35 ++++++++++++++++++++++++++++-------
|
||||||
|
1 file changed, 28 insertions(+), 7 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/syslogd.c b/src/syslogd.c
|
||||||
|
index fa82d98..37e1920 100644
|
||||||
|
--- a/src/syslogd.c
|
||||||
|
+++ b/src/syslogd.c
|
||||||
|
@@ -1309,15 +1309,36 @@ parsemsg_rfc3164_app_name_procid(char **msg, char **app_name, char **procid)
|
||||||
|
m = *msg;
|
||||||
|
|
||||||
|
/* Application name. */
|
||||||
|
- app_name_begin = m;
|
||||||
|
- app_name_length = strspn(m,
|
||||||
|
- "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
- "0123456789"
|
||||||
|
- "._-/");
|
||||||
|
+ /* Check if tag is surrounded by parentheses like (polkit-agent) */
|
||||||
|
+ if (*m == '(') {
|
||||||
|
+ char *closing = strchr(m + 1, ')');
|
||||||
|
+ if (closing && (closing[1] == ':' || closing[1] == '[' || isblank(closing[1]) || closing[1] == '\0')) {
|
||||||
|
+ /* Found complete parenthetical tag, strip parentheses */
|
||||||
|
+ app_name_begin = m + 1;
|
||||||
|
+ app_name_length = closing - (m + 1);
|
||||||
|
+ m = closing + 1;
|
||||||
|
+ } else {
|
||||||
|
+ /* Incomplete or malformed, treat normally */
|
||||||
|
+ app_name_begin = m;
|
||||||
|
+ app_name_length = strspn(m,
|
||||||
|
+ "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
+ "0123456789"
|
||||||
|
+ "._-/()");
|
||||||
|
+ m += app_name_length;
|
||||||
|
+ }
|
||||||
|
+ } else {
|
||||||
|
+ app_name_begin = m;
|
||||||
|
+ app_name_length = strspn(m,
|
||||||
|
+ "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
+ "0123456789"
|
||||||
|
+ "._-/()");
|
||||||
|
+ m += app_name_length;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
if (app_name_length == 0)
|
||||||
|
goto bad;
|
||||||
|
- m += app_name_length;
|
||||||
|
|
||||||
|
/* Process identifier (optional). */
|
||||||
|
if (*m == '[') {
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
From 10372296093cc70f3d21b46359a4cfbd8312c968 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Joachim Wiberg <troglobit@gmail.com>
|
||||||
|
Date: Fri, 26 Sep 2025 10:07:45 +0200
|
||||||
|
Subject: [PATCH 3/4] test: verify parenthetical tag parsing
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Regression test for issue #104.
|
||||||
|
|
||||||
|
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||||
|
---
|
||||||
|
test/Makefile.am | 5 +-
|
||||||
|
test/parens.sh | 119 +++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
2 files changed, 122 insertions(+), 2 deletions(-)
|
||||||
|
create mode 100755 test/parens.sh
|
||||||
|
|
||||||
|
diff --git a/test/Makefile.am b/test/Makefile.am
|
||||||
|
index 7162e6c..59a38cf 100644
|
||||||
|
--- a/test/Makefile.am
|
||||||
|
+++ b/test/Makefile.am
|
||||||
|
@@ -3,10 +3,10 @@ EXTRA_DIST += api.sh local.sh unicode.sh remote.sh fwd.sh mark.sh \
|
||||||
|
memleak.sh facility.sh notify.sh rotate_all.sh secure.sh \
|
||||||
|
logger.sh listen.sh sighup.sh tag.sh hostname.sh \
|
||||||
|
property.sh raw.sh regression.sh multicast.sh \
|
||||||
|
- mcast-fwd.sh mcast-iface.sh
|
||||||
|
+ mcast-fwd.sh mcast-iface.sh parens.sh
|
||||||
|
CLEANFILES = *~ *.trs *.log
|
||||||
|
TEST_EXTENSIONS = .sh
|
||||||
|
-TESTS_ENVIRONMENT= unshare -mrun
|
||||||
|
+TESTS_ENVIRONMENT= unshare -mrun --map-auto
|
||||||
|
|
||||||
|
check_PROGRAMS = api
|
||||||
|
api_SOURCES = api.c
|
||||||
|
@@ -30,6 +30,7 @@ TESTS += rotate_all.sh
|
||||||
|
TESTS += secure.sh
|
||||||
|
TESTS += sighup.sh
|
||||||
|
TESTS += tag.sh
|
||||||
|
+TESTS += parens.sh
|
||||||
|
TESTS += hostname.sh
|
||||||
|
TESTS += property.sh
|
||||||
|
TESTS += raw.sh
|
||||||
|
diff --git a/test/parens.sh b/test/parens.sh
|
||||||
|
new file mode 100755
|
||||||
|
index 0000000..43b8744
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/test/parens.sh
|
||||||
|
@@ -0,0 +1,119 @@
|
||||||
|
+#!/bin/sh
|
||||||
|
+# Verify parentheses handling in log tags for RFC3164 messages.
|
||||||
|
+# Regression test for issue #104.
|
||||||
|
+#
|
||||||
|
+. "${srcdir:-.}/lib.sh"
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+MSG1="Failed to execute /usr/bin/pkttyagent: No such file or directory"
|
||||||
|
+MSG2="Normal application message"
|
||||||
|
+MSG3="Version specific message"
|
||||||
|
+MSG4="Service startup message"
|
||||||
|
+
|
||||||
|
+LOGDIR="$DIR/log"
|
||||||
|
+SYSLOG="${LOGDIR}/syslog"
|
||||||
|
+
|
||||||
|
+setup_syslogd()
|
||||||
|
+{
|
||||||
|
+ mkdir -p "$LOGDIR"
|
||||||
|
+ cat <<-EOF >"${CONF}"
|
||||||
|
+ # Log everything for testing
|
||||||
|
+ *.* -$SYSLOG
|
||||||
|
+ EOF
|
||||||
|
+ setup -m0
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+extract_tag()
|
||||||
|
+{
|
||||||
|
+ msg="$1"
|
||||||
|
+ actual_line=$(grep "$msg" "$SYSLOG" | tail -1)
|
||||||
|
+ if [ -n "$actual_line" ]; then
|
||||||
|
+ echo "$actual_line" | sed -n 's/.*[0-9][0-9] [^ ]* \([^:]*\):.*/\1/p'
|
||||||
|
+ fi
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+show_result()
|
||||||
|
+{
|
||||||
|
+ input="$1"
|
||||||
|
+ expected="$2"
|
||||||
|
+ got="$3"
|
||||||
|
+
|
||||||
|
+ echo "Input: '$input'"
|
||||||
|
+ echo "Expected: '$expected'"
|
||||||
|
+ echo "Got: '$got'"
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+verify()
|
||||||
|
+{
|
||||||
|
+ input_tag="$1"
|
||||||
|
+ expected_tag="$2"
|
||||||
|
+ msg="$3"
|
||||||
|
+ expected_pattern="${4:-$expected_tag}"
|
||||||
|
+
|
||||||
|
+ actual_tag=$(extract_tag "$msg")
|
||||||
|
+ if [ -n "$actual_tag" ]; then
|
||||||
|
+ show_result "$input_tag" "$expected_tag" "$actual_tag"
|
||||||
|
+ if grep -q "$expected_pattern.*$msg" "$SYSLOG"; then
|
||||||
|
+ return 0
|
||||||
|
+ else
|
||||||
|
+ echo "Log contents:"
|
||||||
|
+ cat "$SYSLOG"
|
||||||
|
+ return 1
|
||||||
|
+ fi
|
||||||
|
+ else
|
||||||
|
+ echo "Message not found in log"
|
||||||
|
+ echo "Log contents:"
|
||||||
|
+ cat "$SYSLOG"
|
||||||
|
+ return 1
|
||||||
|
+ fi
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+test_normal_tag()
|
||||||
|
+{
|
||||||
|
+ logger -b -ip user.info -t "normal-app" "$MSG2"
|
||||||
|
+ verify "normal-app" "normal-app" "$MSG2"
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+test_paren_stripping()
|
||||||
|
+{
|
||||||
|
+ logger -b -ip user.info -t "(polkit-agent)" "$MSG1"
|
||||||
|
+ verify "(polkit-agent)" "polkit-agent" "$MSG1"
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+test_service_stripping()
|
||||||
|
+{
|
||||||
|
+ logger -b -ip user.info -t "(service-name)" "$MSG4"
|
||||||
|
+ verify "(service-name)" "service-name" "$MSG4"
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+test_partial_parens()
|
||||||
|
+{
|
||||||
|
+ logger -b -ip user.info -t "app(version)" "$MSG3"
|
||||||
|
+ verify "app(version)" "app(version)" "$MSG3"
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+test_normal_tag_with_pid()
|
||||||
|
+{
|
||||||
|
+ pid="$$"
|
||||||
|
+
|
||||||
|
+ logger -b -ip user.info -t "normal-app[${pid}]" "$MSG2"
|
||||||
|
+ verify "normal-app[${pid}]" "normal-app[${pid}]" "$MSG2" "normal-app\[${pid}\]"
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+test_paren_with_pid()
|
||||||
|
+{
|
||||||
|
+ pid="$$"
|
||||||
|
+
|
||||||
|
+ logger -b -ip user.info -t "(polkit-agent)[$pid]" "$MSG1"
|
||||||
|
+ verify "(polkit-agent)[$pid]" "polkit-agent[$pid]" "$MSG1" "polkit-agent\[$pid\]"
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+run_step "Set up syslogd for parentheses testing" setup_syslogd
|
||||||
|
+run_step "Test parenthetical tag stripping" test_paren_stripping
|
||||||
|
+run_step "Test normal tag preservation" test_normal_tag
|
||||||
|
+run_step "Test partial parentheses preservation" test_partial_parens
|
||||||
|
+run_step "Test service parenthetical tag" test_service_stripping
|
||||||
|
+run_step "Test parenthetical tag with PID" test_paren_with_pid
|
||||||
|
+run_step "Test normal tag with PID" test_normal_tag_with_pid
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
+142
@@ -0,0 +1,142 @@
|
|||||||
|
From c1c3f253a8963c6e9e41fafcf9b888f3f5a34906 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Joachim Wiberg <troglobit@gmail.com>
|
||||||
|
Date: Mon, 3 Nov 2025 13:11:56 +0100
|
||||||
|
Subject: [PATCH 4/4] syslogd: unescape Linux /dev/kmsg messages before
|
||||||
|
sanitization
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Linux's /dev/kmsg interface escapes all non-printable characters and
|
||||||
|
backslashes using C-style hex encoding (\xHH format). Preventing the
|
||||||
|
recent UTF-8 sanitization improvements from working correctly, since
|
||||||
|
UTF-8 sequences arrive as escaped text like "\xe2\x80\x94" rather than
|
||||||
|
as actual bytes, see [1] for details.
|
||||||
|
|
||||||
|
This commit implements "unescaping" of the kernel's C-style format
|
||||||
|
before applying the UTF-8-aware sanitization. Ensuring that UTF-8
|
||||||
|
content also in kernel messages is properly preserved when using the
|
||||||
|
-8 flag.
|
||||||
|
|
||||||
|
[1]: https://www.kernel.org/doc/Documentation/ABI/testing/dev-kmsg
|
||||||
|
|
||||||
|
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||||
|
---
|
||||||
|
src/syslogd.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++---
|
||||||
|
1 file changed, 77 insertions(+), 4 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/syslogd.c b/src/syslogd.c
|
||||||
|
index 37e1920..0b6bc30 100644
|
||||||
|
--- a/src/syslogd.c
|
||||||
|
+++ b/src/syslogd.c
|
||||||
|
@@ -155,6 +155,7 @@ static int KernLog = 1; /* Track kernel logs by default */
|
||||||
|
static int KeepKernFac; /* Keep remotely logged kernel facility */
|
||||||
|
static int KeepKernTime; /* Keep kernel timestamp, evern after initial read */
|
||||||
|
static int KeepKernConsole; /* Keep kernel logging to console */
|
||||||
|
+static int IsLinuxKmsg; /* Set if reading from Linux /dev/kmsg */
|
||||||
|
|
||||||
|
static int rotate_opt; /* Set if command line option has been given (wins) */
|
||||||
|
static off_t RotateSz = 0; /* Max file size (bytes) before rotating, disabled by default */
|
||||||
|
@@ -651,8 +652,10 @@ int main(int argc, char *argv[])
|
||||||
|
_PATH_KLOG);
|
||||||
|
else
|
||||||
|
kern_console_off();
|
||||||
|
- } else
|
||||||
|
+ } else {
|
||||||
|
+ IsLinuxKmsg = 1;
|
||||||
|
kern_console_off();
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
no_klogd:
|
||||||
|
consfile.f_type = F_CONSOLE;
|
||||||
|
@@ -1062,6 +1065,61 @@ utf8_valid(const unsigned char *in, size_t len)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
+/*
|
||||||
|
+ * Unescapes Linux /dev/kmsg messages that use C-style hex encoding.
|
||||||
|
+ * Converts "\xHH" sequences back to bytes and "\\" back to "\".
|
||||||
|
+ * Returns the new length of the unescaped string.
|
||||||
|
+ */
|
||||||
|
+static size_t kmsg_unescape(char *msg)
|
||||||
|
+{
|
||||||
|
+ char *src, *dst;
|
||||||
|
+ int hi, lo;
|
||||||
|
+
|
||||||
|
+ src = dst = msg;
|
||||||
|
+ while (*src) {
|
||||||
|
+ if (*src == '\\' && src[1]) {
|
||||||
|
+ if (src[1] == 'x' && src[2] && src[3]) {
|
||||||
|
+ /* Decode \xHH */
|
||||||
|
+ hi = src[2];
|
||||||
|
+ lo = src[3];
|
||||||
|
+
|
||||||
|
+ /* Convert hex digits to values */
|
||||||
|
+ if (hi >= '0' && hi <= '9')
|
||||||
|
+ hi = hi - '0';
|
||||||
|
+ else if (hi >= 'a' && hi <= 'f')
|
||||||
|
+ hi = hi - 'a' + 10;
|
||||||
|
+ else if (hi >= 'A' && hi <= 'F')
|
||||||
|
+ hi = hi - 'A' + 10;
|
||||||
|
+ else
|
||||||
|
+ goto copy_literal;
|
||||||
|
+
|
||||||
|
+ if (lo >= '0' && lo <= '9')
|
||||||
|
+ lo = lo - '0';
|
||||||
|
+ else if (lo >= 'a' && lo <= 'f')
|
||||||
|
+ lo = lo - 'a' + 10;
|
||||||
|
+ else if (lo >= 'A' && lo <= 'F')
|
||||||
|
+ lo = lo - 'A' + 10;
|
||||||
|
+ else
|
||||||
|
+ goto copy_literal;
|
||||||
|
+
|
||||||
|
+ *dst++ = (char)((hi << 4) | lo);
|
||||||
|
+ src += 4;
|
||||||
|
+ continue;
|
||||||
|
+ } else if (src[1] == '\\') {
|
||||||
|
+ /* Decode \\ to \ */
|
||||||
|
+ *dst++ = '\\';
|
||||||
|
+ src += 2;
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+copy_literal:
|
||||||
|
+ *dst++ = *src++;
|
||||||
|
+ }
|
||||||
|
+ *dst = '\0';
|
||||||
|
+
|
||||||
|
+ return dst - msg;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
/*
|
||||||
|
* Removes characters from log messages that are unsafe to display.
|
||||||
|
* Preserves valid UTF-8 sequences, including BOM, with -8 flag.
|
||||||
|
@@ -1773,9 +1831,24 @@ void printsys(char *msg)
|
||||||
|
parsemsg_rfc3164_app_name_procid(&p, &buffer.app_name, &buffer.proc_id);
|
||||||
|
|
||||||
|
q = lp;
|
||||||
|
- while (*p != '\0' && (c = *p++) != '\n' && q < &line[MAXLINE])
|
||||||
|
- *q++ = c;
|
||||||
|
- *q = '\0';
|
||||||
|
+ if (IsLinuxKmsg) {
|
||||||
|
+ char tmp[MAXLINE + 1];
|
||||||
|
+ char *t = tmp;
|
||||||
|
+
|
||||||
|
+ while (*p != '\0' && *p != '\n' && t < &tmp[MAXLINE])
|
||||||
|
+ *t++ = *p++;
|
||||||
|
+ *t = '\0';
|
||||||
|
+
|
||||||
|
+ /* Unescape \xHH sequences and \\ */
|
||||||
|
+ kmsg_unescape(tmp);
|
||||||
|
+
|
||||||
|
+ /* Sanitize the unescaped message with UTF-8 support */
|
||||||
|
+ parsemsg_remove_unsafe_characters(tmp, lp, MAXLINE);
|
||||||
|
+ } else {
|
||||||
|
+ while (*p != '\0' && (c = *p++) != '\n' && q < &line[MAXLINE])
|
||||||
|
+ *q++ = c;
|
||||||
|
+ *q = '\0';
|
||||||
|
+ }
|
||||||
|
|
||||||
|
logmsg(&buffer);
|
||||||
|
}
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
+119
-20
@@ -6,17 +6,19 @@
|
|||||||
|
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
|
|
||||||
#define XPATH_BASE_ "/ietf-syslog:syslog"
|
#define XPATH_BASE_ "/ietf-syslog:syslog"
|
||||||
#define XPATH_FILE_ XPATH_BASE_"/actions/file/log-file"
|
#define XPATH_FILE_ XPATH_BASE_"/actions/file"
|
||||||
#define XPATH_REMOTE_ XPATH_BASE_"/actions/remote/destination"
|
#define XPATH_LOG_FILE XPATH_BASE_"/actions/file/log-file"
|
||||||
#define XPATH_ROTATE_ XPATH_BASE_"/infix-syslog:file-rotation"
|
#define XPATH_REMOTE_ XPATH_BASE_"/actions/remote"
|
||||||
#define XPATH_SERVER_ XPATH_BASE_"/infix-syslog:server"
|
#define XPATH_REMOTE_DST XPATH_BASE_"/actions/remote/destination"
|
||||||
|
#define XPATH_ROTATE_ XPATH_BASE_"/infix-syslog:file-rotation"
|
||||||
|
#define XPATH_SERVER_ XPATH_BASE_"/infix-syslog:server"
|
||||||
|
|
||||||
#define SYSLOG_D_ "/etc/syslog.d"
|
#define SYSLOG_D_ "/etc/syslog.d"
|
||||||
#define SYSLOG_FILE SYSLOG_D_"/log-file-%s.conf"
|
#define SYSLOG_FILE SYSLOG_D_"/log-file-%s.conf"
|
||||||
#define SYSLOG_REMOTE SYSLOG_D_"/remote-%s.conf"
|
#define SYSLOG_REMOTE SYSLOG_D_"/remote-%s.conf"
|
||||||
#define SYSLOG_ROTATE SYSLOG_D_"/rotate.conf"
|
#define SYSLOG_ROTATE SYSLOG_D_"/rotate.conf"
|
||||||
#define SYSLOG_SERVER SYSLOG_D_"/server.conf"
|
#define SYSLOG_SERVER SYSLOG_D_"/server.conf"
|
||||||
|
|
||||||
struct addr {
|
struct addr {
|
||||||
char *address;
|
char *address;
|
||||||
@@ -94,22 +96,86 @@ static const char *sxlate(const char *severity)
|
|||||||
|
|
||||||
static size_t selector(sr_session_ctx_t *session, struct action *act)
|
static size_t selector(sr_session_ctx_t *session, struct action *act)
|
||||||
{
|
{
|
||||||
char xpath[strlen(act->xpath) + 32];
|
char xpath[strlen(act->xpath) + 64];
|
||||||
|
bool has_pattern = false;
|
||||||
sr_val_t *list = NULL;
|
sr_val_t *list = NULL;
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
size_t num = 0;
|
size_t num = 0;
|
||||||
|
char *pattern;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
|
/* Check for hostname-filter (infix-syslog augment) */
|
||||||
|
snprintf(xpath, sizeof(xpath), "%s/infix-syslog:hostname-filter", act->xpath);
|
||||||
|
rc = sr_get_items(session, xpath, 0, 0, &list, &count);
|
||||||
|
if (rc == SR_ERR_OK && count > 0) {
|
||||||
|
fprintf(act->fp, "+");
|
||||||
|
for (size_t i = 0; i < count; i++)
|
||||||
|
fprintf(act->fp, "%s%s", i ? "," : "", list[i].data.string_val);
|
||||||
|
fprintf(act->fp, "\n");
|
||||||
|
|
||||||
|
sr_free_values(list, count);
|
||||||
|
list = NULL;
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for property-filter (infix-syslog augment) */
|
||||||
|
char *property = srx_get_str(session, "%s/infix-syslog:property-filter/property", act->xpath);
|
||||||
|
if (property) {
|
||||||
|
char *operator = srx_get_str(session, "%s/infix-syslog:property-filter/operator", act->xpath);
|
||||||
|
char *value = srx_get_str(session, "%s/infix-syslog:property-filter/value", act->xpath);
|
||||||
|
char *case_str = srx_get_str(session, "%s/infix-syslog:property-filter/case-insensitive", act->xpath);
|
||||||
|
char *negate_str = srx_get_str(session, "%s/infix-syslog:property-filter/negate", act->xpath);
|
||||||
|
|
||||||
|
if (operator && value) {
|
||||||
|
/* Build operator prefix: [!][icase_]operator */
|
||||||
|
char op_prefix[32] = "";
|
||||||
|
|
||||||
|
/* Only apply negate if explicitly set to true */
|
||||||
|
if (negate_str && !strcmp(negate_str, "true"))
|
||||||
|
strlcat(op_prefix, "!", sizeof(op_prefix));
|
||||||
|
|
||||||
|
/* Only apply icase_ if explicitly set to true */
|
||||||
|
if (case_str && !strcmp(case_str, "true"))
|
||||||
|
strlcat(op_prefix, "icase_", sizeof(op_prefix));
|
||||||
|
|
||||||
|
strlcat(op_prefix, operator, sizeof(op_prefix));
|
||||||
|
|
||||||
|
/* Property-based filter: :property, [!][icase_]operator, "value" */
|
||||||
|
fprintf(act->fp, ":%s, %s, \"%s\"\n", property, op_prefix, value);
|
||||||
|
has_pattern = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(property);
|
||||||
|
free(operator);
|
||||||
|
free(value);
|
||||||
|
free(case_str);
|
||||||
|
free(negate_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for pattern-match (select-match feature) */
|
||||||
|
pattern = srx_get_str(session, "%s/pattern-match", act->xpath);
|
||||||
|
if (pattern) {
|
||||||
|
/* Property-based filter: :msg, ereregex, "pattern" */
|
||||||
|
fprintf(act->fp, ":msg, ereregex, \"%s\"\n", pattern);
|
||||||
|
has_pattern = true;
|
||||||
|
free(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
snprintf(xpath, sizeof(xpath), "%s/facility-filter/facility-list", act->xpath);
|
snprintf(xpath, sizeof(xpath), "%s/facility-filter/facility-list", act->xpath);
|
||||||
rc = sr_get_items(session, xpath, 0, 0, &list, &count);
|
rc = sr_get_items(session, xpath, 0, 0, &list, &count);
|
||||||
if (rc != SR_ERR_OK) {
|
if (rc || !count) {
|
||||||
ERROR("Cannot find facility-list for syslog file:%s: %s\n", act->name, sr_strerror(rc));
|
if (has_pattern) {
|
||||||
|
fprintf(act->fp, "*.*");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < count; ++i) {
|
for (size_t i = 0; i < count; ++i) {
|
||||||
sr_val_t *entry = &list[i];
|
sr_val_t *entry = &list[i];
|
||||||
char *facility, *severity;
|
char *facility, *severity, *compare, *action_str;
|
||||||
|
const char *prefix = "";
|
||||||
|
|
||||||
facility = srx_get_str(session, "%s/facility", entry->xpath);
|
facility = srx_get_str(session, "%s/facility", entry->xpath);
|
||||||
if (!facility)
|
if (!facility)
|
||||||
@@ -120,16 +186,45 @@ static size_t selector(sr_session_ctx_t *session, struct action *act)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(act->fp, "%s%s.%s", i ? ";" : "", fxlate(facility), sxlate(severity));
|
/* Check for advanced-compare (select-adv-compare feature) */
|
||||||
|
compare = srx_get_str(session, "%s/advanced-compare/compare", entry->xpath);
|
||||||
|
action_str = srx_get_str(session, "%s/advanced-compare/action", entry->xpath);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle compare + action combinations:
|
||||||
|
* - compare: equals -> use '=' after dot (facility.=severity)
|
||||||
|
* - compare: equals-or-higher (default) -> no prefix
|
||||||
|
* - action: block/stop -> use '!' after dot (facility.!severity)
|
||||||
|
* - action: log (default) -> no prefix modification
|
||||||
|
*
|
||||||
|
* Note: action block/stop takes precedence over compare equals
|
||||||
|
*/
|
||||||
|
/* First check compare */
|
||||||
|
if (compare && strstr(compare, "equals") && !strstr(compare, "equals-or-higher")) {
|
||||||
|
prefix = "=";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Then check action (can override compare) */
|
||||||
|
if (action_str) {
|
||||||
|
const char *a = action_str;
|
||||||
|
if (strstr(a, "block") || strstr(a, "stop")) {
|
||||||
|
prefix = "!";
|
||||||
|
}
|
||||||
|
free(action_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(act->fp, "%s%s.%s%s", i ? ";" : "", fxlate(facility), prefix, sxlate(severity));
|
||||||
num++;
|
num++;
|
||||||
|
|
||||||
free(facility);
|
free(facility);
|
||||||
free(severity);
|
free(severity);
|
||||||
|
free(compare);
|
||||||
}
|
}
|
||||||
|
|
||||||
sr_free_values(list, count);
|
sr_free_values(list, count);
|
||||||
|
|
||||||
return num;
|
/* Return non-zero if we have either pattern or facilities */
|
||||||
|
return has_pattern ? (num > 0 ? num : 1) : num;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void action(sr_session_ctx_t *session, const char *name, const char *xpath, struct addr *addr)
|
static void action(sr_session_ctx_t *session, const char *name, const char *xpath, struct addr *addr)
|
||||||
@@ -142,10 +237,12 @@ static void action(sr_session_ctx_t *session, const char *name, const char *xpat
|
|||||||
char *sz, *cnt, *fmt;
|
char *sz, *cnt, *fmt;
|
||||||
char opts[80] = "\t";
|
char opts[80] = "\t";
|
||||||
char *sep = ";";
|
char *sep = ";";
|
||||||
|
const char *fn;
|
||||||
|
|
||||||
act.fp = fopen(filename(name, addr ? true : false, act.path, sizeof(act.path)), "w");
|
fn = filename(name, addr ? true : false, act.path, sizeof(act.path));
|
||||||
|
act.fp = fopen(fn, "w");
|
||||||
if (!act.fp) {
|
if (!act.fp) {
|
||||||
ERRNO("Failed opening %s", act.path);
|
ERRNO("Failed opening %s", fn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +331,7 @@ static int file_change(sr_session_ctx_t *session, struct lyd_node *config,struct
|
|||||||
LYX_LIST_FOR_EACH(files, file, "log-file") {
|
LYX_LIST_FOR_EACH(files, file, "log-file") {
|
||||||
struct lyd_node *node = lydx_get_child(file, "name");
|
struct lyd_node *node = lydx_get_child(file, "name");
|
||||||
enum lydx_op op = lydx_get_op(node);
|
enum lydx_op op = lydx_get_op(node);
|
||||||
char path[512] = XPATH_FILE_;
|
char path[512] = XPATH_LOG_FILE;
|
||||||
const char *name;
|
const char *name;
|
||||||
|
|
||||||
name = getnm(node, path, sizeof(path));
|
name = getnm(node, path, sizeof(path));
|
||||||
@@ -263,7 +360,7 @@ static int remote_change(sr_session_ctx_t *session, struct lyd_node *config, str
|
|||||||
LYX_LIST_FOR_EACH(dremotes, remote, "destination") {
|
LYX_LIST_FOR_EACH(dremotes, remote, "destination") {
|
||||||
struct lyd_node *node = lydx_get_child(remote, "name");
|
struct lyd_node *node = lydx_get_child(remote, "name");
|
||||||
enum lydx_op op = lydx_get_op(node);
|
enum lydx_op op = lydx_get_op(node);
|
||||||
char path[512] = XPATH_REMOTE_;
|
char path[512] = XPATH_REMOTE_DST;
|
||||||
const char *name;
|
const char *name;
|
||||||
|
|
||||||
name = getnm(node, path, sizeof(path));
|
name = getnm(node, path, sizeof(path));
|
||||||
@@ -365,9 +462,11 @@ done:
|
|||||||
|
|
||||||
return SR_ERR_OK;
|
return SR_ERR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ietf_syslog_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd)
|
int ietf_syslog_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd)
|
||||||
{
|
{
|
||||||
int rc = SR_ERR_OK;
|
int rc = SR_ERR_OK;
|
||||||
|
|
||||||
if ((rc = file_change(session, config, diff, event, confd)))
|
if ((rc = file_change(session, config, diff, event, confd)))
|
||||||
return rc;
|
return rc;
|
||||||
if ((rc = remote_change(session, config, diff, event, confd)))
|
if ((rc = remote_change(session, config, diff, event, confd)))
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ static int change_clock(sr_session_ctx_t *session, struct lyd_node *config, stru
|
|||||||
|
|
||||||
if (diff && !lydx_get_xpathf(diff, XPATH_BASE_"/clock"))
|
if (diff && !lydx_get_xpathf(diff, XPATH_BASE_"/clock"))
|
||||||
return SR_ERR_OK;
|
return SR_ERR_OK;
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case SR_EV_ENABLED: /* first time, on register. */
|
case SR_EV_ENABLED: /* first time, on register. */
|
||||||
case SR_EV_CHANGE: /* regular change (copy cand running) */
|
case SR_EV_CHANGE: /* regular change (copy cand running) */
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ MODULES=(
|
|||||||
"iana-if-type@2023-01-26.yang"
|
"iana-if-type@2023-01-26.yang"
|
||||||
# NOTE: ietf-tls-client must be version matched with ietf-tls-server, used by netopeer2!
|
# NOTE: ietf-tls-client must be version matched with ietf-tls-server, used by netopeer2!
|
||||||
"ietf-tls-client@2023-12-28.yang"
|
"ietf-tls-client@2023-12-28.yang"
|
||||||
"ietf-syslog@2024-03-21.yang -e file-action -e file-limit-size -e remote-action"
|
"ietf-syslog@2024-03-21.yang -e file-action -e file-limit-size -e remote-action -e select-adv-compare -e select-match"
|
||||||
"infix-syslog@2024-07-19.yang"
|
"infix-syslog@2025-11-17.yang"
|
||||||
"iana-hardware@2018-03-13.yang"
|
"iana-hardware@2018-03-13.yang"
|
||||||
"ietf-hardware@2018-03-13.yang -e hardware-state -e hardware-sensor"
|
"ietf-hardware@2018-03-13.yang -e hardware-state -e hardware-sensor"
|
||||||
"infix-hardware@2025-10-30.yang"
|
"infix-hardware@2025-10-30.yang"
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ module infix-syslog {
|
|||||||
contact "kernelkit@googlegroups.com";
|
contact "kernelkit@googlegroups.com";
|
||||||
description "Infix augments and deviations to ietf-syslog, draft 32.";
|
description "Infix augments and deviations to ietf-syslog, draft 32.";
|
||||||
|
|
||||||
|
revision 2025-11-17 {
|
||||||
|
description "Add hostname-filter support.";
|
||||||
|
reference "internal";
|
||||||
|
}
|
||||||
|
|
||||||
revision 2024-07-19 {
|
revision 2024-07-19 {
|
||||||
description "Initial revision, based on IETF syslog YANG draft 32.
|
description "Initial revision, based on IETF syslog YANG draft 32.
|
||||||
The following changes have been made in this model:
|
The following changes have been made in this model:
|
||||||
@@ -164,6 +169,89 @@ module infix-syslog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
leaf-list hostname-filter {
|
||||||
|
type inet:domain-name;
|
||||||
|
description "Filter messages by hostname. Messages from the listed
|
||||||
|
hostnames will be logged to this file.";
|
||||||
|
}
|
||||||
|
|
||||||
|
container property-filter {
|
||||||
|
presence "Enable property-based filtering";
|
||||||
|
description "Advanced property-based message filtering. Allows filtering
|
||||||
|
on specific message fields with various comparison operators.";
|
||||||
|
|
||||||
|
leaf property {
|
||||||
|
type enumeration {
|
||||||
|
enum msg {
|
||||||
|
description "The message body (SYSLOG-MSG field).";
|
||||||
|
}
|
||||||
|
enum msgid {
|
||||||
|
description "The RFC5424 message identifier (MSGID field).";
|
||||||
|
}
|
||||||
|
enum programname {
|
||||||
|
description "The originating program or tag name.";
|
||||||
|
}
|
||||||
|
enum hostname {
|
||||||
|
description "The message source hostname.";
|
||||||
|
}
|
||||||
|
enum source {
|
||||||
|
description "The message source (alias for hostname).";
|
||||||
|
}
|
||||||
|
enum data {
|
||||||
|
description "The RFC5424 structured data (SD field).";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mandatory true;
|
||||||
|
description "The message property to match against.";
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf operator {
|
||||||
|
type enumeration {
|
||||||
|
enum contains {
|
||||||
|
description "Substring match (case-sensitive).";
|
||||||
|
}
|
||||||
|
enum isequal {
|
||||||
|
description "Exact equality match.";
|
||||||
|
}
|
||||||
|
enum startswith {
|
||||||
|
description "Prefix match.";
|
||||||
|
}
|
||||||
|
enum regex {
|
||||||
|
description "Basic regular expression match.";
|
||||||
|
}
|
||||||
|
enum ereregex {
|
||||||
|
description "Extended regular expression match (POSIX ERE).";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mandatory true;
|
||||||
|
description "The comparison operator to use.";
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf value {
|
||||||
|
type string;
|
||||||
|
mandatory true;
|
||||||
|
description "The value to compare against.";
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf case-insensitive {
|
||||||
|
type boolean;
|
||||||
|
description "Perform case-insensitive comparison.";
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf negate {
|
||||||
|
type boolean;
|
||||||
|
description "Negate the comparison result. When set to true, the
|
||||||
|
filter matches messages that do NOT satisfy the
|
||||||
|
specified condition.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
- operator: contains, value: 'ERROR', negate: false
|
||||||
|
→ matches messages containing 'ERROR'
|
||||||
|
- operator: contains, value: 'ERROR', negate: true
|
||||||
|
→ matches messages NOT containing 'ERROR'";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
augment "/syslog:syslog/syslog:actions/syslog:remote/syslog:destination" {
|
augment "/syslog:syslog/syslog:actions/syslog:remote/syslog:destination" {
|
||||||
|
|||||||
@@ -5,9 +5,29 @@ Tests verifying IETF standard syslog configuration and operation:
|
|||||||
|
|
||||||
- Basic local syslog functionality and log generation
|
- Basic local syslog functionality and log generation
|
||||||
- Remote syslog forwarding and network logging
|
- Remote syslog forwarding and network logging
|
||||||
|
- Pattern matching using POSIX regex on message content
|
||||||
|
- Advanced comparison operators and actions (equals vs equals-or-higher)
|
||||||
|
- Hostname-based filtering for incoming messages
|
||||||
|
- Property-based filtering with operators and negation
|
||||||
|
|
||||||
include::basic/Readme.adoc[]
|
include::basic/Readme.adoc[]
|
||||||
|
|
||||||
<<<
|
<<<
|
||||||
|
|
||||||
include::remote/Readme.adoc[]
|
include::remote/Readme.adoc[]
|
||||||
|
|
||||||
|
<<<
|
||||||
|
|
||||||
|
include::pattern_match/Readme.adoc[]
|
||||||
|
|
||||||
|
<<<
|
||||||
|
|
||||||
|
include::advanced_compare/Readme.adoc[]
|
||||||
|
|
||||||
|
<<<
|
||||||
|
|
||||||
|
include::hostname_filter/Readme.adoc[]
|
||||||
|
|
||||||
|
<<<
|
||||||
|
|
||||||
|
include::property_filter/Readme.adoc[]
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
test.adoc
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
=== Syslog Advanced Compare
|
||||||
|
|
||||||
|
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ietf_syslog/advanced_compare]
|
||||||
|
|
||||||
|
==== Description
|
||||||
|
|
||||||
|
Verify the select-adv-compare feature: filtering syslog messages based on
|
||||||
|
severity with advanced comparison operators (equals vs equals-or-higher) and
|
||||||
|
actions (log vs block/stop).
|
||||||
|
|
||||||
|
==== Topology
|
||||||
|
|
||||||
|
image::topology.svg[Syslog Advanced Compare topology, align=center, scaledwidth=75%]
|
||||||
|
|
||||||
|
==== Sequence
|
||||||
|
|
||||||
|
. Set up topology and attach to target DUT
|
||||||
|
. Clean up old log files from previous test runs
|
||||||
|
. Configure syslog with advanced-compare
|
||||||
|
. Send test messages at all severity levels
|
||||||
|
. Verify exact-errors log contains only error messages
|
||||||
|
. Verify no-debug log blocks all messages
|
||||||
|
. Verify baseline log contains info and higher
|
||||||
|
|
||||||
|
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Syslog Advanced Compare
|
||||||
|
|
||||||
|
Verify the select-adv-compare feature: filtering syslog messages based on
|
||||||
|
severity with advanced comparison operators (equals vs equals-or-higher) and
|
||||||
|
actions (log vs block/stop).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import infamy
|
||||||
|
import time
|
||||||
|
|
||||||
|
with infamy.Test() as test:
|
||||||
|
with test.step("Set up topology and attach to target DUT"):
|
||||||
|
env = infamy.Env()
|
||||||
|
target = env.attach("target", "mgmt")
|
||||||
|
tgtssh = env.attach("target", "mgmt", "ssh")
|
||||||
|
|
||||||
|
with test.step("Clean up old log files from previous test runs"):
|
||||||
|
tgtssh.runsh("sudo rm -f /var/log/{exact-errors,no-debug,baseline}")
|
||||||
|
|
||||||
|
with test.step("Configure syslog with advanced-compare"):
|
||||||
|
target.put_config_dicts({
|
||||||
|
"ietf-syslog": {
|
||||||
|
"syslog": {
|
||||||
|
"actions": {
|
||||||
|
"file": {
|
||||||
|
"log-file": [{
|
||||||
|
"name": "file:exact-errors",
|
||||||
|
"facility-filter": {
|
||||||
|
"facility-list": [{
|
||||||
|
"facility": "daemon",
|
||||||
|
"severity": "error",
|
||||||
|
"advanced-compare": {
|
||||||
|
"compare": "equals"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "file:no-debug",
|
||||||
|
"facility-filter": {
|
||||||
|
"facility-list": [{
|
||||||
|
"facility": "daemon",
|
||||||
|
"severity": "debug",
|
||||||
|
"advanced-compare": {
|
||||||
|
"action": "block"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "file:baseline",
|
||||||
|
"facility-filter": {
|
||||||
|
"facility-list": [{
|
||||||
|
"facility": "daemon",
|
||||||
|
"severity": "info"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
with test.step("Send test messages at all severity levels"):
|
||||||
|
tgtssh.runsh("logger -t advtest -p daemon.emerg 'Emergency: system is unusable'")
|
||||||
|
tgtssh.runsh("logger -t advtest -p daemon.alert 'Alert: immediate action required'")
|
||||||
|
tgtssh.runsh("logger -t advtest -p daemon.crit 'Critical: critical condition'")
|
||||||
|
tgtssh.runsh("logger -t advtest -p daemon.err 'Error: error condition'")
|
||||||
|
tgtssh.runsh("logger -t advtest -p daemon.warning 'Warning: warning condition'")
|
||||||
|
tgtssh.runsh("logger -t advtest -p daemon.notice 'Notice: normal but significant'")
|
||||||
|
tgtssh.runsh("logger -t advtest -p daemon.info 'Info: informational message'")
|
||||||
|
tgtssh.runsh("logger -t advtest -p daemon.debug 'Debug: debug-level message'")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
with test.step("Verify exact-errors log contains only error messages"):
|
||||||
|
rc = tgtssh.runsh("grep -c 'advtest' /var/log/exact-errors 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 1:
|
||||||
|
test.fail(f"Expected 1 message in /var/log/exact-errors (error only), got {count}")
|
||||||
|
|
||||||
|
rc = tgtssh.runsh("grep -q 'Error: error condition' /var/log/exact-errors 2>/dev/null")
|
||||||
|
if rc.returncode != 0:
|
||||||
|
test.fail("Expected error message in /var/log/exact-errors")
|
||||||
|
|
||||||
|
rc = tgtssh.runsh("grep -c 'Emergency\\|Alert\\|Critical' /var/log/exact-errors 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 0:
|
||||||
|
test.fail(f"Expected 0 higher severity messages in /var/log/exact-errors, got {count}")
|
||||||
|
|
||||||
|
with test.step("Verify no-debug log blocks all messages"):
|
||||||
|
rc = tgtssh.runsh("grep -c 'advtest' /var/log/no-debug 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 0:
|
||||||
|
test.fail(f"Expected 0 messages in /var/log/no-debug (all blocked), got {count}")
|
||||||
|
|
||||||
|
with test.step("Verify baseline log contains info and higher"):
|
||||||
|
rc = tgtssh.runsh("grep -c 'advtest' /var/log/baseline 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 7:
|
||||||
|
test.fail(f"Expected 7 messages in /var/log/baseline (info and higher), got {count}")
|
||||||
|
|
||||||
|
rc = tgtssh.runsh("grep -c 'Debug: debug-level' /var/log/baseline 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 0:
|
||||||
|
test.fail(f"Expected 0 debug messages in /var/log/baseline, got {count}")
|
||||||
|
|
||||||
|
rc = tgtssh.runsh("grep -q 'Info: informational' /var/log/baseline 2>/dev/null")
|
||||||
|
if rc.returncode != 0:
|
||||||
|
test.fail("Expected info message in /var/log/baseline")
|
||||||
|
|
||||||
|
test.succeed()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../../infamy/topologies/1x1.dot
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
|
||||||
|
<!-- Title: 1x1 Pages: 1 -->
|
||||||
|
<svg width="424pt" height="45pt"
|
||||||
|
viewBox="0.00 0.00 424.03 45.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 41)">
|
||||||
|
<title>1x1</title>
|
||||||
|
<polygon fill="white" stroke="transparent" points="-4,4 -4,-41 420.03,-41 420.03,4 -4,4"/>
|
||||||
|
<!-- host -->
|
||||||
|
<g id="node1" class="node">
|
||||||
|
<title>host</title>
|
||||||
|
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 100,-36.5 100,-0.5 0,-0.5"/>
|
||||||
|
<text text-anchor="middle" x="25" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||||
|
<polyline fill="none" stroke="black" points="50,-0.5 50,-36.5 "/>
|
||||||
|
<text text-anchor="middle" x="75" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||||
|
</g>
|
||||||
|
<!-- target -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>target</title>
|
||||||
|
<polygon fill="none" stroke="black" points="300.03,-0.5 300.03,-36.5 416.03,-36.5 416.03,-0.5 300.03,-0.5"/>
|
||||||
|
<text text-anchor="middle" x="325.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||||
|
<polyline fill="none" stroke="black" points="350.03,-0.5 350.03,-36.5 "/>
|
||||||
|
<text text-anchor="middle" x="383.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
|
||||||
|
</g>
|
||||||
|
<!-- host--target -->
|
||||||
|
<g id="edge1" class="edge">
|
||||||
|
<title>host:mgmt--target:mgmt</title>
|
||||||
|
<path fill="none" stroke="lightgray" stroke-width="2" d="M100,-18.5C100,-18.5 300.03,-18.5 300.03,-18.5"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -4,3 +4,15 @@
|
|||||||
|
|
||||||
- name: Remote syslog
|
- name: Remote syslog
|
||||||
case: remote/test.py
|
case: remote/test.py
|
||||||
|
|
||||||
|
- name: Syslog Pattern Matching
|
||||||
|
case: pattern_match/test.py
|
||||||
|
|
||||||
|
- name: Syslog Advanced Compare
|
||||||
|
case: advanced_compare/test.py
|
||||||
|
|
||||||
|
- name: Syslog Hostname Filtering
|
||||||
|
case: hostname_filter/test.py
|
||||||
|
|
||||||
|
- name: Syslog Property Filtering
|
||||||
|
case: property_filter/test.py
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ietf_syslog/basic]
|
|||||||
|
|
||||||
==== Description
|
==== Description
|
||||||
|
|
||||||
Add syslog actions to log to local files, then verify new log files have been created.
|
Add syslog actions matching on different facility and severity levels to
|
||||||
|
log to local files, then verify new log files have been created. In one
|
||||||
|
case we use an absolute `file:/path/to/bar.log` and in another a relative
|
||||||
|
path `file:foo`.
|
||||||
|
|
||||||
==== Topology
|
==== Topology
|
||||||
|
|
||||||
@@ -13,7 +16,7 @@ image::topology.svg[Syslog Basic topology, align=center, scaledwidth=75%]
|
|||||||
==== Sequence
|
==== Sequence
|
||||||
|
|
||||||
. Set up topology and attach to target DUT
|
. Set up topology and attach to target DUT
|
||||||
. Configure syslog on DUT to log to files '/log/bar.log' (absolute path) and 'foo' (non-absolute).
|
. Configure syslog on DUT to log to files '/log/bar.log' and 'foo'
|
||||||
. Verify log files /var/log/foo and /var/log/bar.log have been created
|
. Verify log files have been created
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""Syslog Basic
|
||||||
Syslog Basic
|
|
||||||
|
Add syslog actions matching on different facility and severity levels to
|
||||||
|
log to local files, then verify new log files have been created. In one
|
||||||
|
case we use an absolute `file:/path/to/bar.log` and in another a relative
|
||||||
|
path `file:foo`.
|
||||||
|
|
||||||
Add syslog actions to log to local files, then verify new log files have been created.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import infamy
|
import infamy
|
||||||
@@ -16,51 +19,42 @@ with infamy.Test() as test:
|
|||||||
factory = env.get_password("target")
|
factory = env.get_password("target")
|
||||||
address = target.get_mgmt_ip()
|
address = target.get_mgmt_ip()
|
||||||
|
|
||||||
with test.step("Configure syslog on DUT to log to files '/log/bar.log' (absolute path) and 'foo' (non-absolute)."):
|
with test.step("Configure syslog on DUT to log to files '/log/bar.log' and 'foo'"):
|
||||||
target.put_config_dicts({
|
target.put_config_dicts({
|
||||||
"ietf-syslog": {
|
"ietf-syslog": {
|
||||||
"syslog": {
|
"syslog": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"file": {
|
"file": {
|
||||||
"log-file": [
|
"log-file": [{
|
||||||
{
|
|
||||||
"name": "file:foo",
|
"name": "file:foo",
|
||||||
"facility-filter": {
|
"facility-filter": {
|
||||||
"facility-list": [
|
"facility-list": [{
|
||||||
{
|
"facility": "auth",
|
||||||
"facility": "auth",
|
"severity": "info"
|
||||||
"severity": "info"
|
}, {
|
||||||
},
|
"facility": "authpriv",
|
||||||
{
|
"severity": "debug"
|
||||||
"facility": "authpriv",
|
}]
|
||||||
"severity": "debug"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
"name": "file:/log/bar.log",
|
"name": "file:/log/bar.log",
|
||||||
"facility-filter": {
|
"facility-filter": {
|
||||||
"facility-list": [
|
"facility-list": [{
|
||||||
{
|
"facility": "all",
|
||||||
"facility": "all",
|
"severity": "critical"
|
||||||
"severity": "critical"
|
}, {
|
||||||
},
|
"facility": "mail",
|
||||||
{
|
"severity": "warning"
|
||||||
"facility": "mail",
|
}]
|
||||||
"severity": "warning"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
with test.step("Verify log files /var/log/foo and /var/log/bar.log have been created"):
|
with test.step("Verify log files have been created"):
|
||||||
user = tgtssh.runsh("ls /var/log/{foo,bar.log}").stdout
|
user = tgtssh.runsh("ls /var/log/{foo,bar.log}").stdout
|
||||||
if "/var/log/foo" not in user:
|
if "/var/log/foo" not in user:
|
||||||
test.fail()
|
test.fail()
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
test.adoc
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
=== Syslog Hostname Filtering
|
||||||
|
|
||||||
|
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ietf_syslog/hostname_filter]
|
||||||
|
|
||||||
|
==== Description
|
||||||
|
|
||||||
|
Verify hostname-filter feature: filtering incoming syslog messages based
|
||||||
|
on the hostname. Tests log sink scenario with multiple remote clients.
|
||||||
|
|
||||||
|
==== Topology
|
||||||
|
|
||||||
|
image::topology.svg[Syslog Hostname Filtering topology, align=center, scaledwidth=75%]
|
||||||
|
|
||||||
|
==== Sequence
|
||||||
|
|
||||||
|
. Set up topology and attach to DUTs
|
||||||
|
. Clean up old log files on server
|
||||||
|
. Configure server as syslog sink with hostname filtering
|
||||||
|
. Configure client to forward logs to server
|
||||||
|
. Send log messages with different hostnames
|
||||||
|
. Verify router1 log contains only router1 messages
|
||||||
|
. Verify router2 log contains only router2 messages
|
||||||
|
. Verify all-hosts log contains all messages
|
||||||
|
|
||||||
|
|
||||||
+179
@@ -0,0 +1,179 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Syslog Hostname Filtering
|
||||||
|
|
||||||
|
Verify hostname-filter feature: filtering incoming syslog messages based
|
||||||
|
on the hostname. Tests log sink scenario with multiple remote clients.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import infamy
|
||||||
|
import time
|
||||||
|
|
||||||
|
TEST_MESSAGES = [
|
||||||
|
("router1", "Message from router1"),
|
||||||
|
("router1", "Another from router1"),
|
||||||
|
("router2", "Message from router2"),
|
||||||
|
("other", "Message from a different host"),
|
||||||
|
]
|
||||||
|
|
||||||
|
with infamy.Test() as test:
|
||||||
|
with test.step("Set up topology and attach to DUTs"):
|
||||||
|
env = infamy.Env()
|
||||||
|
client = env.attach("client", "mgmt")
|
||||||
|
server = env.attach("server", "mgmt")
|
||||||
|
clientssh = env.attach("client", "mgmt", "ssh")
|
||||||
|
serverssh = env.attach("server", "mgmt", "ssh")
|
||||||
|
|
||||||
|
with test.step("Clean up old log files on server"):
|
||||||
|
serverssh.runsh("sudo rm -f /var/log/{router1,router2,all-hosts}")
|
||||||
|
|
||||||
|
with test.step("Configure server as syslog sink with hostname filtering"):
|
||||||
|
_, server_link = env.ltop.xlate("server", "link")
|
||||||
|
|
||||||
|
server.put_config_dicts({
|
||||||
|
"ietf-interfaces": {
|
||||||
|
"interfaces": {
|
||||||
|
"interface": [{
|
||||||
|
"name": server_link,
|
||||||
|
"enabled": True,
|
||||||
|
"ipv4": {
|
||||||
|
"address": [{
|
||||||
|
"ip": "10.0.0.1",
|
||||||
|
"prefix-length": 24,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ietf-syslog": {
|
||||||
|
"syslog": {
|
||||||
|
"actions": {
|
||||||
|
"file": {
|
||||||
|
"log-file": [{
|
||||||
|
"name": "file:router1",
|
||||||
|
"infix-syslog:hostname-filter": ["router1"],
|
||||||
|
"facility-filter": {
|
||||||
|
"facility-list": [{
|
||||||
|
"facility": "all",
|
||||||
|
"severity": "info"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "file:router2",
|
||||||
|
"infix-syslog:hostname-filter": ["router2"],
|
||||||
|
"facility-filter": {
|
||||||
|
"facility-list": [{
|
||||||
|
"facility": "all",
|
||||||
|
"severity": "info"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "file:all-hosts",
|
||||||
|
"facility-filter": {
|
||||||
|
"facility-list": [{
|
||||||
|
"facility": "all",
|
||||||
|
"severity": "info"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"infix-syslog:server": {
|
||||||
|
"enabled": True,
|
||||||
|
"listen": {
|
||||||
|
"udp": [{
|
||||||
|
"port": 514,
|
||||||
|
"address": "10.0.0.1"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
with test.step("Configure client to forward logs to server"):
|
||||||
|
_, client_link = env.ltop.xlate("client", "link")
|
||||||
|
|
||||||
|
client.put_config_dicts({
|
||||||
|
"ietf-interfaces": {
|
||||||
|
"interfaces": {
|
||||||
|
"interface": [{
|
||||||
|
"name": client_link,
|
||||||
|
"enabled": True,
|
||||||
|
"ipv4": {
|
||||||
|
"address": [{
|
||||||
|
"ip": "10.0.0.2",
|
||||||
|
"prefix-length": 24,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ietf-syslog": {
|
||||||
|
"syslog": {
|
||||||
|
"actions": {
|
||||||
|
"remote": {
|
||||||
|
"destination": [{
|
||||||
|
"name": "server",
|
||||||
|
"udp": {
|
||||||
|
"address": "10.0.0.1"
|
||||||
|
},
|
||||||
|
"facility-filter": {
|
||||||
|
"facility-list": [{
|
||||||
|
"facility": "all",
|
||||||
|
"severity": "info"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
with test.step("Send log messages with different hostnames"):
|
||||||
|
for hostname, message in TEST_MESSAGES:
|
||||||
|
clientssh.runsh(f"logger -t test -p daemon.info -H {hostname} -h 10.0.0.1 '{message}'")
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
with test.step("Verify router1 log contains only router1 messages"):
|
||||||
|
rc = serverssh.runsh("cat /var/log/router1 2>/dev/null")
|
||||||
|
log_content = rc.stdout if rc.returncode == 0 else ""
|
||||||
|
|
||||||
|
router1_messages = [msg for host, msg in TEST_MESSAGES if host == "router1"]
|
||||||
|
missing = [msg for msg in router1_messages if msg not in log_content]
|
||||||
|
if missing:
|
||||||
|
test.fail(f"Missing router1 messages in /var/log/router1: {missing}")
|
||||||
|
|
||||||
|
unwanted_messages = [msg for host, msg in TEST_MESSAGES if host != "router1"]
|
||||||
|
found = [msg for msg in unwanted_messages if msg in log_content]
|
||||||
|
if found:
|
||||||
|
test.fail(f"Found unwanted messages in /var/log/router1: {found}")
|
||||||
|
|
||||||
|
with test.step("Verify router2 log contains only router2 messages"):
|
||||||
|
rc = serverssh.runsh("cat /var/log/router2 2>/dev/null")
|
||||||
|
log_content = rc.stdout if rc.returncode == 0 else ""
|
||||||
|
|
||||||
|
router2_messages = [msg for host, msg in TEST_MESSAGES if host == "router2"]
|
||||||
|
missing = [msg for msg in router2_messages if msg not in log_content]
|
||||||
|
if missing:
|
||||||
|
test.fail(f"Missing router2 messages in /var/log/router2: {missing}")
|
||||||
|
|
||||||
|
unwanted_messages = [msg for host, msg in TEST_MESSAGES if host != "router2"]
|
||||||
|
found = [msg for msg in unwanted_messages if msg in log_content]
|
||||||
|
if found:
|
||||||
|
test.fail(f"Found unwanted messages in /var/log/router2: {found}")
|
||||||
|
|
||||||
|
with test.step("Verify all-hosts log contains all messages"):
|
||||||
|
rc = serverssh.runsh("cat /var/log/all-hosts 2>/dev/null")
|
||||||
|
log_content = rc.stdout if rc.returncode == 0 else ""
|
||||||
|
|
||||||
|
expected_messages = [msg for _, msg in TEST_MESSAGES]
|
||||||
|
missing = [msg for msg in expected_messages if msg not in log_content]
|
||||||
|
|
||||||
|
if missing:
|
||||||
|
test.fail(f"Missing messages in /var/log/all-hosts: {missing}")
|
||||||
|
|
||||||
|
test.succeed()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../remote/topology.dot
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
|
||||||
|
<!-- Title: 2x2 Pages: 1 -->
|
||||||
|
<svg width="432pt" height="305pt"
|
||||||
|
viewBox="0.00 0.00 432.03 304.62" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 300.62)">
|
||||||
|
<title>2x2</title>
|
||||||
|
<polygon fill="white" stroke="transparent" points="-4,4 -4,-300.62 428.03,-300.62 428.03,4 -4,4"/>
|
||||||
|
<!-- host -->
|
||||||
|
<g id="node1" class="node">
|
||||||
|
<title>host</title>
|
||||||
|
<polygon fill="none" stroke="black" points="0,-125.31 0,-171.31 108,-171.31 108,-125.31 0,-125.31"/>
|
||||||
|
<text text-anchor="middle" x="25" y="-144.61" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||||
|
<polyline fill="none" stroke="black" points="50,-125.31 50,-171.31 "/>
|
||||||
|
<text text-anchor="middle" x="79" y="-156.11" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
|
||||||
|
<polyline fill="none" stroke="black" points="50,-148.31 108,-148.31 "/>
|
||||||
|
<text text-anchor="middle" x="79" y="-133.11" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
|
||||||
|
</g>
|
||||||
|
<!-- client -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>client</title>
|
||||||
|
<polygon fill="none" stroke="black" points="308.03,-250.12 308.03,-296.12 424.03,-296.12 424.03,-250.12 308.03,-250.12"/>
|
||||||
|
<text text-anchor="middle" x="333.03" y="-280.92" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||||
|
<polyline fill="none" stroke="black" points="308.03,-273.12 358.03,-273.12 "/>
|
||||||
|
<text text-anchor="middle" x="333.03" y="-257.92" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
|
||||||
|
<polyline fill="none" stroke="black" points="358.03,-250.12 358.03,-296.12 "/>
|
||||||
|
<text text-anchor="middle" x="391.03" y="-269.42" font-family="DejaVu Sans Mono, Book" font-size="14.00">client</text>
|
||||||
|
</g>
|
||||||
|
<!-- host--client -->
|
||||||
|
<g id="edge1" class="edge">
|
||||||
|
<title>host:mgmt1--client:mgmt</title>
|
||||||
|
<path fill="none" stroke="lightgrey" stroke-width="2" d="M108,-160.31C108,-160.31 308.03,-285.12 308.03,-285.12"/>
|
||||||
|
</g>
|
||||||
|
<!-- server -->
|
||||||
|
<g id="node3" class="node">
|
||||||
|
<title>server</title>
|
||||||
|
<polygon fill="none" stroke="black" points="308.03,-0.5 308.03,-46.5 424.03,-46.5 424.03,-0.5 308.03,-0.5"/>
|
||||||
|
<text text-anchor="middle" x="333.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
|
||||||
|
<polyline fill="none" stroke="black" points="308.03,-23.5 358.03,-23.5 "/>
|
||||||
|
<text text-anchor="middle" x="333.03" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||||
|
<polyline fill="none" stroke="black" points="358.03,-0.5 358.03,-46.5 "/>
|
||||||
|
<text text-anchor="middle" x="391.03" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">server</text>
|
||||||
|
</g>
|
||||||
|
<!-- host--server -->
|
||||||
|
<g id="edge2" class="edge">
|
||||||
|
<title>host:mgmt2--server:mgmt</title>
|
||||||
|
<path fill="none" stroke="lightgrey" stroke-width="2" d="M108,-136.31C108,-136.31 308.03,-11.5 308.03,-11.5"/>
|
||||||
|
</g>
|
||||||
|
<!-- client--server -->
|
||||||
|
<g id="edge3" class="edge">
|
||||||
|
<title>client:link--server:link</title>
|
||||||
|
<path fill="none" stroke="black" stroke-width="2" d="M333.03,-250.12C333.03,-250.12 333.03,-46.5 333.03,-46.5"/>
|
||||||
|
<text text-anchor="middle" x="292.03" y="-50.3" font-family="DejaVu Serif, Book" font-size="14.00">10.0.0.1/24</text>
|
||||||
|
<text text-anchor="middle" x="292.03" y="-238.92" font-family="DejaVu Serif, Book" font-size="14.00">10.0.0.2/24</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1 @@
|
|||||||
|
test.adoc
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
=== Syslog Pattern Matching
|
||||||
|
|
||||||
|
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ietf_syslog/pattern_match]
|
||||||
|
|
||||||
|
==== Description
|
||||||
|
|
||||||
|
Verify the select-match feature: filtering syslog messages based on
|
||||||
|
pattern-match (POSIX regex) on message content. Tests both simple
|
||||||
|
substring matching and complex regex patterns.
|
||||||
|
|
||||||
|
==== Topology
|
||||||
|
|
||||||
|
image::topology.svg[Syslog Pattern Matching topology, align=center, scaledwidth=75%]
|
||||||
|
|
||||||
|
==== Sequence
|
||||||
|
|
||||||
|
. Set up topology and attach to target DUT
|
||||||
|
. Clean up old log files from previous test runs
|
||||||
|
. Configure syslog with pattern-match filters
|
||||||
|
. Send test messages with various patterns
|
||||||
|
. Verify errors log contains ERROR and CRITICAL messages
|
||||||
|
. Verify routers log contains matching router[0-9]+ pattern
|
||||||
|
. Verify all-messages log contains all test messages
|
||||||
|
|
||||||
|
|
||||||
Executable
+103
@@ -0,0 +1,103 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Syslog Pattern Matching
|
||||||
|
|
||||||
|
Verify the select-match feature: filtering syslog messages based on
|
||||||
|
pattern-match (POSIX regex) on message content. Tests both simple
|
||||||
|
substring matching and complex regex patterns.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import infamy
|
||||||
|
import time
|
||||||
|
|
||||||
|
with infamy.Test() as test:
|
||||||
|
with test.step("Set up topology and attach to target DUT"):
|
||||||
|
env = infamy.Env()
|
||||||
|
target = env.attach("target", "mgmt")
|
||||||
|
tgtssh = env.attach("target", "mgmt", "ssh")
|
||||||
|
|
||||||
|
with test.step("Clean up old log files from previous test runs"):
|
||||||
|
tgtssh.runsh("sudo rm -f /var/log/{errors,routers,all-messages}")
|
||||||
|
|
||||||
|
with test.step("Configure syslog with pattern-match filters"):
|
||||||
|
target.put_config_dicts({
|
||||||
|
"ietf-syslog": {
|
||||||
|
"syslog": {
|
||||||
|
"actions": {
|
||||||
|
"file": {
|
||||||
|
"log-file": [{
|
||||||
|
"name": "file:errors",
|
||||||
|
"pattern-match": "ERROR|CRITICAL",
|
||||||
|
}, {
|
||||||
|
"name": "file:routers",
|
||||||
|
"pattern-match": "router[0-9]+",
|
||||||
|
"facility-filter": {
|
||||||
|
"facility-list": [{
|
||||||
|
"facility": "all",
|
||||||
|
"severity": "info"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "file:all-messages",
|
||||||
|
"facility-filter": {
|
||||||
|
"facility-list": [{
|
||||||
|
"facility": "all",
|
||||||
|
"severity": "info"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
with test.step("Send test messages with various patterns"):
|
||||||
|
tgtssh.runsh("logger -t test -p daemon.info 'ERROR: Connection failed on interface eth0'")
|
||||||
|
tgtssh.runsh("logger -t test -p daemon.info 'CRITICAL: System temperature high'")
|
||||||
|
tgtssh.runsh("logger -t test -p daemon.info 'Status update from router1: link up'")
|
||||||
|
tgtssh.runsh("logger -t test -p daemon.info 'Status update from router42: link down'")
|
||||||
|
tgtssh.runsh("logger -t test -p daemon.info 'INFO: Normal operation message'")
|
||||||
|
tgtssh.runsh("logger -t test -p daemon.info 'DEBUG: Verbose logging enabled'")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
with test.step("Verify errors log contains ERROR and CRITICAL messages"):
|
||||||
|
rc = tgtssh.runsh("grep -c 'ERROR\\|CRITICAL' /var/log/errors 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 2:
|
||||||
|
test.fail(f"Expected 2 ERROR/CRITICAL messages in /var/log/errors, got {count}")
|
||||||
|
|
||||||
|
# Verify it does NOT contain other messages
|
||||||
|
rc = tgtssh.runsh("grep -c 'router1\\|Normal operation\\|Verbose' /var/log/errors 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 0:
|
||||||
|
test.fail(f"Expected 0 non-error messages in /var/log/errors, got {count}")
|
||||||
|
|
||||||
|
with test.step("Verify routers log contains matching router[0-9]+ pattern"):
|
||||||
|
rc = tgtssh.runsh("grep -c 'router[0-9]\\+' /var/log/routers 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 2:
|
||||||
|
test.fail(f"Expected 2 router messages in /var/log/routers, got {count}")
|
||||||
|
|
||||||
|
# Verify both router1 and router42 are present
|
||||||
|
rc = tgtssh.runsh("grep -q 'router1' /var/log/routers 2>/dev/null")
|
||||||
|
if rc.returncode != 0:
|
||||||
|
test.fail("Expected router1 message in /var/log/routers")
|
||||||
|
|
||||||
|
rc = tgtssh.runsh("grep -q 'router42' /var/log/routers 2>/dev/null")
|
||||||
|
if rc.returncode != 0:
|
||||||
|
test.fail("Expected router42 message in /var/log/routers")
|
||||||
|
|
||||||
|
# Verify it does NOT contain error or normal messages
|
||||||
|
rc = tgtssh.runsh("grep -c 'ERROR\\|CRITICAL\\|Normal operation\\|Verbose' /var/log/routers 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 0:
|
||||||
|
test.fail(f"Expected 0 non-router messages in /var/log/routers, got {count}")
|
||||||
|
|
||||||
|
with test.step("Verify all-messages log contains all test messages"):
|
||||||
|
rc = tgtssh.runsh("grep -c 'test' /var/log/all-messages 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 6:
|
||||||
|
test.fail(f"Expected 6 total messages in /var/log/all-messages, got {count}")
|
||||||
|
|
||||||
|
test.succeed()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../../infamy/topologies/1x1.dot
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
|
||||||
|
<!-- Title: 1x1 Pages: 1 -->
|
||||||
|
<svg width="424pt" height="45pt"
|
||||||
|
viewBox="0.00 0.00 424.03 45.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 41)">
|
||||||
|
<title>1x1</title>
|
||||||
|
<polygon fill="white" stroke="transparent" points="-4,4 -4,-41 420.03,-41 420.03,4 -4,4"/>
|
||||||
|
<!-- host -->
|
||||||
|
<g id="node1" class="node">
|
||||||
|
<title>host</title>
|
||||||
|
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 100,-36.5 100,-0.5 0,-0.5"/>
|
||||||
|
<text text-anchor="middle" x="25" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||||
|
<polyline fill="none" stroke="black" points="50,-0.5 50,-36.5 "/>
|
||||||
|
<text text-anchor="middle" x="75" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||||
|
</g>
|
||||||
|
<!-- target -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>target</title>
|
||||||
|
<polygon fill="none" stroke="black" points="300.03,-0.5 300.03,-36.5 416.03,-36.5 416.03,-0.5 300.03,-0.5"/>
|
||||||
|
<text text-anchor="middle" x="325.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||||
|
<polyline fill="none" stroke="black" points="350.03,-0.5 350.03,-36.5 "/>
|
||||||
|
<text text-anchor="middle" x="383.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
|
||||||
|
</g>
|
||||||
|
<!-- host--target -->
|
||||||
|
<g id="edge1" class="edge">
|
||||||
|
<title>host:mgmt--target:mgmt</title>
|
||||||
|
<path fill="none" stroke="lightgray" stroke-width="2" d="M100,-18.5C100,-18.5 300.03,-18.5 300.03,-18.5"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
|||||||
|
test.adoc
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
=== Syslog Property Filtering
|
||||||
|
|
||||||
|
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ietf_syslog/property_filter]
|
||||||
|
|
||||||
|
==== Description
|
||||||
|
|
||||||
|
Verify property-filter feature: filtering syslog messages based on various
|
||||||
|
message properties with different operators, case-insensitivity, and negation.
|
||||||
|
|
||||||
|
==== Topology
|
||||||
|
|
||||||
|
image::topology.svg[Syslog Property Filtering topology, align=center, scaledwidth=75%]
|
||||||
|
|
||||||
|
==== Sequence
|
||||||
|
|
||||||
|
. Set up topology and attach to target DUT
|
||||||
|
. Clean up old log files
|
||||||
|
. Configure syslog with property filters
|
||||||
|
. Send test messages
|
||||||
|
. Verify myapp log contains only myapp messages
|
||||||
|
. Verify not-error log excludes ERROR messages
|
||||||
|
. Verify case-test log matches case-insensitive 'warning'
|
||||||
|
. Verify baseline log contains all messages
|
||||||
|
|
||||||
|
|
||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Syslog Property Filtering
|
||||||
|
|
||||||
|
Verify property-filter feature: filtering syslog messages based on various
|
||||||
|
message properties with different operators, case-insensitivity, and negation.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import infamy
|
||||||
|
import time
|
||||||
|
|
||||||
|
TEST_MESSAGES = [
|
||||||
|
("myapp", "Application startup"),
|
||||||
|
("myapp", "Processing request"),
|
||||||
|
("otherapp", "Different program"),
|
||||||
|
("test", "ERROR: Connection failed"),
|
||||||
|
("test", "INFO: Normal message"),
|
||||||
|
("test", "WARNING: Check config"),
|
||||||
|
("test", "Warning: lowercase"),
|
||||||
|
]
|
||||||
|
|
||||||
|
with infamy.Test() as test:
|
||||||
|
with test.step("Set up topology and attach to target DUT"):
|
||||||
|
env = infamy.Env()
|
||||||
|
target = env.attach("target", "mgmt")
|
||||||
|
tgtssh = env.attach("target", "mgmt", "ssh")
|
||||||
|
|
||||||
|
with test.step("Clean up old log files"):
|
||||||
|
tgtssh.runsh("sudo rm -f /var/log/{myapp,not-error,case-test,baseline}")
|
||||||
|
|
||||||
|
with test.step("Configure syslog with property filters"):
|
||||||
|
target.put_config_dicts({
|
||||||
|
"ietf-syslog": {
|
||||||
|
"syslog": {
|
||||||
|
"actions": {
|
||||||
|
"file": {
|
||||||
|
"log-file": [{
|
||||||
|
"name": "file:myapp",
|
||||||
|
"infix-syslog:property-filter": {
|
||||||
|
"property": "programname",
|
||||||
|
"operator": "isequal",
|
||||||
|
"value": "myapp"
|
||||||
|
},
|
||||||
|
"facility-filter": {
|
||||||
|
"facility-list": [{
|
||||||
|
"facility": "all",
|
||||||
|
"severity": "info"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "file:not-error",
|
||||||
|
"infix-syslog:property-filter": {
|
||||||
|
"property": "msg",
|
||||||
|
"operator": "contains",
|
||||||
|
"value": "ERROR",
|
||||||
|
"negate": True
|
||||||
|
},
|
||||||
|
"facility-filter": {
|
||||||
|
"facility-list": [{
|
||||||
|
"facility": "all",
|
||||||
|
"severity": "info"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "file:case-test",
|
||||||
|
"infix-syslog:property-filter": {
|
||||||
|
"property": "msg",
|
||||||
|
"operator": "contains",
|
||||||
|
"value": "warning",
|
||||||
|
"case-insensitive": True
|
||||||
|
},
|
||||||
|
"facility-filter": {
|
||||||
|
"facility-list": [{
|
||||||
|
"facility": "all",
|
||||||
|
"severity": "info"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "file:baseline",
|
||||||
|
"facility-filter": {
|
||||||
|
"facility-list": [{
|
||||||
|
"facility": "all",
|
||||||
|
"severity": "info"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
with test.step("Send test messages"):
|
||||||
|
for tag, msg in TEST_MESSAGES:
|
||||||
|
tgtssh.runsh(f"logger -t {tag} -p daemon.info '{msg}'")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
with test.step("Verify myapp log contains only myapp messages"):
|
||||||
|
rc = tgtssh.runsh("grep -c 'myapp' /var/log/myapp 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 2:
|
||||||
|
test.fail(f"Expected 2 myapp messages in /var/log/myapp, got {count}")
|
||||||
|
|
||||||
|
rc = tgtssh.runsh("grep -c 'otherapp' /var/log/myapp 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 0:
|
||||||
|
test.fail(f"Expected 0 otherapp messages in /var/log/myapp, got {count}")
|
||||||
|
|
||||||
|
with test.step("Verify not-error log excludes ERROR messages"):
|
||||||
|
rc = tgtssh.runsh("grep -c 'test' /var/log/not-error 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 3:
|
||||||
|
test.fail(f"Expected 3 non-ERROR messages in /var/log/not-error, got {count}")
|
||||||
|
|
||||||
|
rc = tgtssh.runsh("grep -c 'ERROR' /var/log/not-error 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 0:
|
||||||
|
test.fail(f"Expected 0 ERROR messages in /var/log/not-error, got {count}")
|
||||||
|
|
||||||
|
with test.step("Verify case-test log matches case-insensitive 'warning'"):
|
||||||
|
rc = tgtssh.runsh("grep -c 'WARNING\\|Warning' /var/log/case-test 2>/dev/null")
|
||||||
|
count = int(rc.stdout.strip()) if rc.returncode == 0 else 0
|
||||||
|
if count != 2:
|
||||||
|
test.fail(f"Expected 2 warning messages in /var/log/case-test, got {count}")
|
||||||
|
|
||||||
|
with test.step("Verify baseline log contains all messages"):
|
||||||
|
for tag, msg in TEST_MESSAGES:
|
||||||
|
rc = tgtssh.runsh(f"grep -q '{msg}' /var/log/baseline 2>/dev/null")
|
||||||
|
if rc.returncode != 0:
|
||||||
|
test.fail(f"Expected message '{msg}' not found in /var/log/baseline")
|
||||||
|
|
||||||
|
test.succeed()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../../infamy/topologies/1x1.dot
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
|
||||||
|
<!-- Title: 1x1 Pages: 1 -->
|
||||||
|
<svg width="424pt" height="45pt"
|
||||||
|
viewBox="0.00 0.00 424.03 45.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 41)">
|
||||||
|
<title>1x1</title>
|
||||||
|
<polygon fill="white" stroke="transparent" points="-4,4 -4,-41 420.03,-41 420.03,4 -4,4"/>
|
||||||
|
<!-- host -->
|
||||||
|
<g id="node1" class="node">
|
||||||
|
<title>host</title>
|
||||||
|
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 100,-36.5 100,-0.5 0,-0.5"/>
|
||||||
|
<text text-anchor="middle" x="25" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||||
|
<polyline fill="none" stroke="black" points="50,-0.5 50,-36.5 "/>
|
||||||
|
<text text-anchor="middle" x="75" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||||
|
</g>
|
||||||
|
<!-- target -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>target</title>
|
||||||
|
<polygon fill="none" stroke="black" points="300.03,-0.5 300.03,-36.5 416.03,-36.5 416.03,-0.5 300.03,-0.5"/>
|
||||||
|
<text text-anchor="middle" x="325.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||||
|
<polyline fill="none" stroke="black" points="350.03,-0.5 350.03,-36.5 "/>
|
||||||
|
<text text-anchor="middle" x="383.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
|
||||||
|
</g>
|
||||||
|
<!-- host--target -->
|
||||||
|
<g id="edge1" class="edge">
|
||||||
|
<title>host:mgmt--target:mgmt</title>
|
||||||
|
<path fill="none" stroke="lightgray" stroke-width="2" d="M100,-18.5C100,-18.5 300.03,-18.5 300.03,-18.5"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -30,15 +30,13 @@ with infamy.Test() as test:
|
|||||||
"name": client_link,
|
"name": client_link,
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"ipv4": {
|
"ipv4": {
|
||||||
"address": [
|
"address": [{
|
||||||
{
|
|
||||||
"ip": "10.0.0.2",
|
"ip": "10.0.0.2",
|
||||||
"prefix-length": 24,
|
"prefix-length": 24,
|
||||||
}
|
}]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}]
|
||||||
]}
|
}
|
||||||
},
|
},
|
||||||
"ietf-syslog": {
|
"ietf-syslog": {
|
||||||
"syslog": {
|
"syslog": {
|
||||||
@@ -47,100 +45,81 @@ with infamy.Test() as test:
|
|||||||
"log-file": [{
|
"log-file": [{
|
||||||
"name": "file:security",
|
"name": "file:security",
|
||||||
"facility-filter": {
|
"facility-filter": {
|
||||||
"facility-list": [
|
"facility-list": [{
|
||||||
{
|
|
||||||
"facility": "auth",
|
"facility": "auth",
|
||||||
"severity": "all"
|
"severity": "all"
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
"facility": "audit",
|
"facility": "audit",
|
||||||
"severity": "all"
|
"severity": "all"
|
||||||
}
|
}]
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"infix-syslog:log-format": "rfc5424"
|
"infix-syslog:log-format": "rfc5424"
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
"destination": [
|
"destination": [{
|
||||||
{
|
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"udp": {
|
"udp": {
|
||||||
"address": "10.0.0.1"
|
"address": "10.0.0.1"
|
||||||
},
|
},
|
||||||
"facility-filter": {
|
"facility-filter": {
|
||||||
"facility-list": [
|
"facility-list": [{
|
||||||
{
|
|
||||||
"facility": "audit",
|
"facility": "audit",
|
||||||
"severity": "all"
|
"severity": "all"
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
"facility": "auth",
|
"facility": "auth",
|
||||||
"severity": "all"
|
"severity": "all"
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
"infix-syslog:log-format": "rfc5424"
|
"infix-syslog:log-format": "rfc5424"
|
||||||
}
|
}]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
server.put_config_dicts({
|
server.put_config_dicts({
|
||||||
"ietf-interfaces": {
|
"ietf-interfaces": {
|
||||||
"interfaces": {
|
"interfaces": {
|
||||||
"interface": [
|
"interface": [{
|
||||||
{
|
"name": server_link,
|
||||||
"name": server_link,
|
"enabled": True,
|
||||||
"enabled": True,
|
"ipv4": {
|
||||||
"ipv4": {
|
"address": [{
|
||||||
"address": [
|
"ip": "10.0.0.1",
|
||||||
{
|
"prefix-length": 24,
|
||||||
"ip": "10.0.0.1",
|
}]
|
||||||
"prefix-length": 24,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ietf-syslog": {
|
"ietf-syslog": {
|
||||||
"syslog": {
|
"syslog": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"file": {
|
"file": {
|
||||||
"log-file": [
|
"log-file": [{
|
||||||
{
|
"name": "file:security",
|
||||||
"name": "file:security",
|
"facility-filter": {
|
||||||
"facility-filter": {
|
"facility-list": [{
|
||||||
"facility-list": [
|
"facility": "auth",
|
||||||
{
|
"severity": "all"
|
||||||
"facility": "auth",
|
}, {
|
||||||
"severity": "all"
|
"facility": "audit",
|
||||||
},
|
"severity": "all"
|
||||||
{
|
}]
|
||||||
"facility": "audit",
|
},
|
||||||
"severity": "all"
|
"infix-syslog:log-format": "rfc5424"
|
||||||
}
|
}]
|
||||||
]
|
|
||||||
},
|
|
||||||
"infix-syslog:log-format": "rfc5424"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"infix-syslog:server": {
|
"infix-syslog:server": {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"listen": {
|
"listen": {
|
||||||
"udp": [
|
"udp": [{
|
||||||
{
|
"port": 514,
|
||||||
"port": 514,
|
"address": "10.0.0.1"
|
||||||
"address": "10.0.0.1"
|
}]
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,7 +127,6 @@ with infamy.Test() as test:
|
|||||||
})
|
})
|
||||||
|
|
||||||
with test.step("Send security:notice log message from client and verify reception of client log message, incl. sorting to /log/security on server"):
|
with test.step("Send security:notice log message from client and verify reception of client log message, incl. sorting to /log/security on server"):
|
||||||
infamy.until(lambda: syslog_check(clientssh, serverssh) == True)
|
infamy.until(lambda: syslog_check(clientssh, serverssh) == True)
|
||||||
|
|
||||||
|
|
||||||
test.succeed()
|
test.succeed()
|
||||||
|
|||||||
Reference in New Issue
Block a user