rsyslog で CEF (Common Event Format) っぽくしてみる。CEF にはめ込むための情報がログにすべて含まれているわけじゃない (ベンダーとか製品情報とか…) ので、CEF「っぽい」が限界。
RainerScript でがんばる。RainerScript の概要を掴むならこちらを。公式ドキュメントを読む前に大変おせわになりました。
ログごとに変換処理を行うことになるので、ログ量が多い環境では負荷に注意したほうが良いかもしれない。
テンプレートの準備
まずはテンプレートを準備する。テンプレートは、ファイル名やログ形式で使える、書式指定みたいなもの。
「$!~」はユーザー定義プロパティ。まぁ変数みたいなもの。
#### TEMPLATES ####
# CEF (Common Event Format)
template(name="ceftmpl" type="list"){
property(name="timegenerated")
constant(value=" ")
property(name="hostname")
constant(value=" CEF:0|")
property(name="$!cefprodvendor")
constant(value="|")
property(name="$!cefprodname")
constant(value="|")
property(name="$!cefprodversion")
constant(value="|")
property(name="syslogfacility")
constant(value=".")
property(name="syslogseverity")
constant(value="|")
property(name="syslogfacility-text")
constant(value=".")
property(name="syslogseverity-text")
constant(value="|")
property(name="$!cefsvrty")
constant(value="|")
property(name="$!cefext")
constant(value="\n")
}
各プロパティに情報を格納
で、このテンプレート中で使っているユーザー定義プロパティ
-
$!cefprodvendor
(製品ベンダー) -
$!cefprodname
(製品名) -
$!cefprodversion
(製品バージョン) -
$!cefsvrty
(イベントの優先度。10 ~ 0。10 が最重要) -
$!cefext
(拡張情報)
を何とかでっちあげる。
RainerScript には「変数の定義」みたいな概念はないから、テンプレートの前に書かなきゃならん、みたいな記載順の前後に気を付ける必要はない。
#### RULES ####
# Initialize CEF information.
set $!cefprodname = replace(replace($programname, "\\", "\\\\"), "|", "\\|");
set $!cefprodvendor = $!cefprodname;
set $!cefsvrty = 7 - $syslogseverity;
-
$!cefprodname
は、rsyslog の既定プロパティ$programname
を使う。 -
$!cefprodvendor
は、とりあえず$cefprodname
を流用。SIEM とかで絞りやすくするために「rsyslog」とかの固定値にしてもいいかもしれない。 -
$!cefsvrty
は、CEF の優先度と Syslog の優先度の上下が逆転してるから、とりあえず emerg を最大 (7) として debug が 0 になるようにした。
replace(str, substr_to_replace, replace_with)
は RainerScript の組み込み関数。これで CEF の特殊文字をエスケープしてる。
用意したテンプレートなどを使ってみる
旧来の syslogd 的な書き方でいうと
*.info;mail.none;authpriv.none;cron.none /var/log/messages
*.info;mail.none;authpriv.none;cron.none @@192.0.2.1
みたいなのを RainerScript で書きつつ、テンプレートを適用してみる。自サーバーの /var/log/messages には既定の形式のまま書き込み、192.0.2.1 に CEF 形式で転送する。
if ($syslogseverity < 7
and $syslogfacility-text != "mail"
and $syslogfacility-text != "authpriv"
and $syslogfacility-text != "cron") then {
set $!cefprodversion = "0";
set $!cefext = "dvchost=" & $hostname & " msg=" & replace(replace($syslogtag & " " & $msg, "\\", "\\\\"), "=", "\\=");
action(type="omfile" file="/var/log/messages")
action(type="omfwd"
target="192.0.2.1" protocol="tcp" port="514"
keepalive="on"
template="ceftmpl")
}
$!cefext
は、rsyslog の既定プロパティ$syslogtag
と$msg
を使う。
…$!cefprodversion
は「0」固定。でっち上げるにも元情報がない…。
「action(type="omfile"~」がファイル出力、「action(type="omfwd"~」が転送。今回はファイル出力のほうにテンプレートを適用しなかったけど、したければしてもいい。
コンフィグ全体
CentOS のコンフィグに上記の変更を加えた、コンフィグ全体を貼っておく。
# rsyslog configuration file
# For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html
# If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html
#### MODULES ####
# The imjournal module bellow is now used as a message source instead of imuxsock.
$ModLoad imuxsock # provides support for local system logging (e.g. via logger command)
$ModLoad imjournal # provides access to the systemd journal
#$ModLoad imklog # reads kernel messages (the same are read from journald)
#$ModLoad immark # provides --MARK-- message capability
# Provides UDP syslog reception
#$ModLoad imudp
#$UDPServerRun 514
# Provides TCP syslog reception
#$ModLoad imtcp
#$InputTCPServerRun 514
#### GLOBAL DIRECTIVES ####
# Where to place auxiliary files
$WorkDirectory /var/lib/rsyslog
# Use default timestamp format
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
# File syncing capability is disabled by default. This feature is usually not required,
# not useful and an extreme performance hit
#$ActionFileEnableSync on
# Include all config files in /etc/rsyslog.d/
$IncludeConfig /etc/rsyslog.d/*.conf
# Turn off message reception via local log socket;
# local messages are retrieved through imjournal now.
$OmitLocalLogging on
# File to store the position in the journal
$IMJournalStateFile imjournal.state
#### TEMPLATES ####
template(name="ceftmpl" type="list"){
property(name="timegenerated")
constant(value=" ")
property(name="hostname")
constant(value=" CEF:0|")
property(name="$!cefprodvendor")
constant(value="|")
property(name="$!cefprodname")
constant(value="|")
property(name="$!cefprodversion")
constant(value="|")
property(name="syslogfacility")
constant(value=".")
property(name="syslogseverity")
constant(value="|")
property(name="syslogfacility-text")
constant(value=".")
property(name="syslogseverity-text")
constant(value="|")
property(name="$!cefsvrty")
constant(value="|")
property(name="$!cefext")
constant(value="\n")
}
#### RULES ####
# Initialize CEF information.
set $!cefprodname = replace(replace($programname, "\\", "\\\\"), "|", "\\|");
set $!cefprodvendor = $!cefprodname;
set $!cefsvrty = 7 - $syslogseverity;
# Log all kernel messages to the console.
# Logging much else clutters up the screen.
#kern.* /dev/console
# Log anything (except mail) of level info or higher.
# Don't log private authentication messages!
#*.info;mail.none;authpriv.none;cron.none /var/log/messages
if ($syslogseverity < 7
and $syslogfacility-text != "mail"
and $syslogfacility-text != "authpriv"
and $syslogfacility-text != "cron") then {
set $!cefprodversion = "0";
set $!cefext = "dvchost=" & $hostname & " msg=" & replace(replace($syslogtag & " " & $msg, "\\", "\\\\"), "=", "\\=");
action(type="omfile" file="/var/log/messages")
action(type="omfwd"
target="192.0.2.1" protocol="tcp" port="514"
keepalive="on"
template="ceftmpl")
}
# The authpriv file has restricted access.
authpriv.* /var/log/secure
# Log all the mail messages in one place.
mail.* -/var/log/maillog
# Log cron stuff
cron.* /var/log/cron
# Everybody gets emergency messages
*.emerg :omusrmsg:*
# Save news errors of level crit and higher in a special file.
uucp,news.crit /var/log/spooler
# Save boot messages also to boot.log
local7.* /var/log/boot.log
# ### begin forwarding rule ###
# The statement between the begin ... end define a SINGLE forwarding
# rule. They belong together, do NOT split them. If you create multiple
# forwarding rules, duplicate the whole block!
# Remote Logging (we use TCP for reliable delivery)
#
# An on-disk queue is created for this action. If the remote host is
# down, messages are spooled to disk and sent when it is up again.
#$ActionQueueFileName fwdRule1 # unique name prefix for spool files
#$ActionQueueMaxDiskSpace 1g # 1gb space limit (use as much as possible)
#$ActionQueueSaveOnShutdown on # save messages to disk on shutdown
#$ActionQueueType LinkedList # run asynchronously
#$ActionResumeRetryCount -1 # infinite retries if host is down
# remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional
#*.* @@remote-host:514
# ### end of the forwarding rule ###
もういっそ syslogd 形式はやめて、全部 RainerScript 的に書きたい気もするけど、そこは気が向いたら()
おまけ
特定プログラムに特化すれば、もうちょいマシに組める。例えば Postfix。
# Log all the mail messages in one place.
#mail.* -/var/log/maillog
if ($syslogfacility-text == "mail") then {
set $!cefprodversion = "3.5";
set $!cefext = "devhost=" & $hostname;
set $!cefext = "dvchost=" & $hostname;
if (re_match($msg, "[0-9A-F]+: ")) then {
set $!cefext = $!cefext & " cs1="
& replace(replace(re_extract($msg, "([0-9A-F]+): ", 0, 1, "(none)"), "\\", "\\\\"), "=", "\\=");
}
if (re_match($msg, "from=[^ ]+@[^ ,]+")) then {
set $!cefext = $!cefext & " suser="
& replace(replace(re_extract($msg, "(from=)([^ ]+@[^ ,]+)", 0, 2, "(none)"), "\\", "\\\\"), "=", "\\=");
}
if (re_match($msg, "to=[^ ]+@[^ ,]+")) then {
set $!cefext = $!cefext & " duser="
& replace(replace(re_extract($msg, "(to=)([^ ]+@[^ ,]+)", 0, 2, "(none)"), "\\", "\\\\"), "=", "\\=");
}
if (re_match($msg, "connect to [^ :]+")) then {
set $!cefext = $!cefext & " dhost="
& replace(replace(re_extract($msg, "(connect to )([^ :]+)", 0, 2, "(none)"), "\\", "\\\\"), "=", "\\=");
}
if (re_match($msg, "connect from [^ :]+")) then {
set $!cefext = $!cefext & " shost="
& replace(replace(re_extract($msg, "(connect from )([^ :]+)", 0, 2, "(none)"), "\\", "\\\\"), "=", "\\=");
}
set $!cefext = $!cefext & " msg=" & replace(replace($syslogtag & " " & $msg, "\\", "\\\\"), "=", "\\=");
action(type="omfile" file="/var/log/maillog")
action(type="omfwd"
target="192.0.2.1" protocol="tcp" port="514"
keepalive="on"
template="ceftmpl")
stop
}
ちなみにstop
は、これ以降他に出力したくないなら書く。syslogd の「-出力先」みたいな感じ。
これでログ的には
Feb 3 07:04:38 testhost CEF:0|postfix|postfix|3.5|2.6|mail.info|1|dvchost=testhost msg=postfix/smtpd[30065]: warning: unknown[80.82.XX.XX]: SASL LOGIN authentication failed: authentication failure
Feb 3 07:04:38 testhost CEF:0|postfix|postfix|3.5|2.6|mail.info|1|dvchost=testhost shost=unknown[80.82.XX.XX] msg=postfix/smtpd[30065]: disconnect from unknown[80.82.XX.XX]
Feb 3 07:04:38 testhost CEF:0|postfix|postfix|3.5|2.6|mail.info|1|dvchost=testhost cs1=03D037E257 suser=aaa@aaa.com, msg=postfix/qmgr[1388]: 03D037E257: from\=aaa@aaa.com, size\=20169, nrcpt\=1 (queue active)
Feb 3 07:04:38 testhost CEF:0|postfix|postfix|3.5|2.6|mail.info|1|dvchost=testhost cs1=03D037E257 duser=bbb@bbb.com, msg=postfix/smtp[29975]: 03D037E257: to\=bbb@bbb.com, relay\=none, delay\=60457, delays\=60456/0.01/0.87/0, dsn\=4.4.3, status\=deferred (Host or domain name not found. Name service error for name\=gmail.co.jp type\=MX: Host not found, try again)
Feb 3 07:04:38 testhost CEF:0|postfix|postfix|3.5|2.6|mail.info|1|dvchost=testhost cs1=813897E249 suser=ccc@ccc.com, msg=postfix/qmgr[1388]: 813897E249: from\=ccc@ccc.com, size\=15939, nrcpt\=1 (queue active)
Feb 3 07:04:38 testhost CEF:0|postfix|postfix|3.5|2.6|mail.info|1|dvchost=testhost dhost=aaa.com[63.240.XXX.XXX] msg=postfix/smtp[29975]: connect to aaa.com[63.240.XXX.XXX]: Connection timed out (port 25)
Feb 3 07:04:38 testhost CEF:0|postfix|postfix|3.5|2.6|mail.info|1|dvchost=testhost cs1=813897E249 duser=ddd@ddd.com, dhost=aaa.com[63.240.178.216] msg=postfix/smtp[29975]: 813897E249: to\=ddd@ddd.com, relay\=none, delay\=60582, delays\=60552/0/30/0, dsn\=4.4.1, status\=deferred (connect to aaa.com[63.240.178.216]: Connection timed out)
Feb 3 07:04:38 testhost CEF:0|postfix|postfix|3.5|2.6|mail.info|1|dvchost=testhost msg=postfix/anvil[30067]: statistics: max connection rate 1/60s for (smtp:80.82.XX.XX) at Jul 24 16:47:27
Feb 3 07:04:38 testhost CEF:0|postfix|postfix|3.5|2.6|mail.info|1|dvchost=testhost msg=postfix/anvil[30067]: statistics: max connection count 1 for (smtp:80.82.XX.XX) at Jul 24 16:47:27
Feb 3 07:04:38 testhost CEF:0|postfix|postfix|3.5|2.6|mail.info|1|dvchost=testhost msg=postfix/anvil[30067]: statistics: max cache size 1 at Jul 24 16:47:27
みたいになる。Postfix のバージョン情報が直打ちなのはイケてないから、そこは改善したいところ。
re_extract
の第 3 引数 (match) と第 4 引数 (submatch) の使い方が良く解らず、手探り状態。「なんやねんこれ」と思ってたら、他の人も同じこと思ってたw
ここまで rsyslog に正規表現処理させて、サーバー負荷がどんなもんかは不明…。
あとは…
mmfields モジュールでより負荷の少ない方法で実現できそうか検討してみたい。みたいだけ。未定。