LoginSignup
0
1

More than 3 years have passed since last update.

rsyslog で無理矢理 CEF っぽくする

Last updated at Posted at 2021-02-14

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 モジュールでより負荷の少ない方法で実現できそうか検討してみたい。みたいだけ。未定。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1