##はじめに
最近、DoS攻撃検知させる案件があり、
FluentdとNorikraで検証したときの構成を投稿します。
##参考
以下サイトを参考にさせていただきました。
https://dev.classmethod.jp/cloud/aws/block_dos_attack_by_norikra/
##インストール環境
- CentOS 6
- Apache access_log
- Fluentd (v1.0 td-agent3)
- jdk 1.8
- rbenv
- jruby
- Norikra
##構築
###rbenvインストール
yum install -y git gcc-c++
git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
exec $SHELL -l
#確認
rbenv --version
###JRubyインストール
今回はとりあえず最新で jruby-9.2.0.0 をインストールしました。
git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv install -l|grep jruby
rbenv install jruby-9.2.0.0
rbenv global jruby-9.2.0.0
#確認
ruby -v
###Norikraインストール
gem install norikra --no-ri --no-rdoc
rbenv rehash
mkdir /{etc,var/log,var/run}/norikra
#確認
which norikra
gem list --local | grep norikra
####Norikra Initスクリプト作成
@Kedamariさんのものを参考にさせていただきました。
https://qiita.com/Kedamari/items/6ee6252025ca6ddc6b21
####環境変数系の設定
# 環境変数系の設定
cat <<'EOT' > /etc/sysconfig/norikra
NORIKRA_OPT="-Xmx512m -d"
EOT
####Initスクリプト
起動ユーザはrootに変えています。
※サボってます、、
cat <<'EOT' > /etc/init.d/norikra
#!/bin/bash
#
# Norikra init shellscript
# Copyright 2015, Kedamari
#
# /etc/rc.d/init.d/norikra
#
# chkconfig: - 80 20
# description: norikra
# processname: norikra
# pidfile: /var/run/norikra/norikra.pid
##########################
### Disabled http_proxy
##########################
unset http_proxy
unset HTTP_PROXY
##########################
### Definition
##########################
export PATH=/sbin:/usr/sbin:/bin:/usr/bin
NORIKRA_NAME=Norikra
NORIKRA_HOME=/etc/norikra
NORIKRA_DEFAULT=/etc/sysconfig/norikra
NORIKRA_USER=root
NORIKRA_GROUP=root
NORIKRA_LOG_DIR=/var/log/norikra
NORIKRA_BIN_FILE=/root/.rbenv/shims/norikra
NORIKRA_PID_FILE=/var/run/norikra/norikra.pid
NORIKRA_LOCK_FILE=/var/run/norikra/norikra.lock
NORIKRA_STATS_FILE=${NORIKRA_HOME}/norikra.json
. "${NORIKRA_DEFAULT}"
NORIKRA_OPTIONS="start ${NORIKRA_OPT} --pidfile=${NORIKRA_PID_FILE} --stats=${NORIKRA_STATS_FILE} --logdir=${NORIKRA_LOG_DIR} --middle"
START_STOP_DAEMON_ARGS="-l ${NORIKRA_USER} -g ${NORIKRA_GROUP}"
# timeout can be overridden from /etc/sysconfig/td-agent
STOPTIMEOUT=120
RETVAL=0
# Source function library.
. /etc/init.d/functions
##########################
### Function that starts
##########################
do_start() {
ulimit -n 65536 1>/dev/null 2>&1 || true
echo -n "Starting ${NORIKRA_NAME}: "
local RETVAL=0
runuser ${START_STOP_DAEMON_ARGS} -c bash -c "${NORIKRA_BIN_FILE} ${NORIKRA_OPTIONS}" || RETVAL="$?"
[ $RETVAL -eq 0 ] && success && touch "${NORIKRA_LOCK_FILE}" || failure
echo
return $RETVAL
}
##########################
### Function that stops
##########################
do_stop() {
echo -n "Shutting down ${NORIKRA_NAME}: "
local RETVAL=0
if [ -e "${NORIKRA_PID_FILE}" ]; then
# Use own process termination instead of killproc because killproc can't wait SIGTERM
NORIKRA_PID=`cat "${NORIKRA_PID_FILE}" 2>/dev/null`
${NORIKRA_BIN_FILE} stop >/dev/null 2>&1 || RETVAL="$?"
if [ $RETVAL -eq 0 ]; then
TIMEOUT="$STOPTIMEOUT"
while [ $TIMEOUT -gt 0 ]; do
${NORIKRA_BIN_FILE} stop >/dev/null 2>&1 || break
sleep 1
let TIMEOUT="${TIMEOUT}-1" || true
done
if [ "$TIMEOUT" -eq 0 ]; then
echo -n "Timeout error occurred trying to stop ${NORIKRA_NAME}..."
RETVAL=1
failure || true
else
RETVAL=0
success
fi
else
failure || true
fi
else
failure || true
RETVAL=4
fi
echo
[ $RETVAL -eq 0 ] && rm -f "${NORIKRA_PID_FILE}" && rm -f "${NORIKRA_LOCK_FILE}"
return $RETVAL
}
##########################
### Function that restarts
##########################
do_restart() {
do_stop || true
do_start
}
case "$1" in
"start" )
test -f ${NORIKRA_LOCK_FILE} || false
do_start
;;
"stop" )
do_stop
;;
"restart" )
do_restart
;;
"status" )
status -p "${NORIKRA_PID_FILE}" "${NORIKRA_NAME}"
;;
* )
echo "Usage: $0 {start|stop|restart|status}" >&2
exit 1
;;
esac
EOT
####自動起動
chkconfig --add norikra
chkconfig norikra on
chmod +x /etc/init.d/norikra
####Norikraクエリ設定
例です。SQL文は初心者なのですみません。
- 画像、CSS、JSなどは対象外
- プライベートIPからのアクセスは除外
- 1分間で60以上アクセスで検知
select CURRENT_TIMESTAMP() as timestamp,
host,
count(*) as requests
from access.win:time_batch(1 min)
where not (
path like '%.gif' or
path like '%.jpg' or
path like '%.jpeg' or
path like '%.png' or
path like '%.JPG' or
path like '%.ico' or
path like '%.js' or
path like '%.css' or
path like '%.svg' or
path like '%.woff'
) and not (
host like '10.%' or
host like '172.16.%' or
host like '172.17.%' or
host like '172.18.%' or
host like '172.19.%' or
host like '172.2%' or
host like '172.30.%' or
host like '172.31.%' or
host like '192.168.%'
)
group by host
having count(*) >= 60
###Fluentdインストール
公式サイト手順
https://docs.fluentd.org/v1.0/articles/install-by-rpm
curl -L https://toolbelt.treasuredata.com/sh/install-redhat-td-agent3.sh | sh
####プラグインインストール
今回使うのは、fluent-plugin-norikraだけです。
td-agent-gem install fluent-plugin-norikra
####Initスクリプト調整
またサボってます、、
# 起動ユーザをrootに変更
vi /etc/init.d/td-agent
TD_AGENT_USER=root
TD_AGENT_GROUP=root
####自動起動
chkconfig td-agent on
####Fluentd設定
ログフォーマットはApacheのcombinedに%Dをつけたもの
LogFormat "%h %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\""
cp -p /etc/td-agent/td-agent.conf /etc/td-agent/td-agent.conf_org &&\
cat <<'EOT' > /etc/td-agent/td-agent.conf
#### Global Setting
<source>
@type monitor_agent
bind 0.0.0.0
port 24220
</source>
#### Input
### Apache
<source>
@type tail
tag apache.access
format /^(?<remote_host>[^ ]*) (?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*) (?<response_time>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
time_format %d/%b/%Y:%H:%M:%S %z
types code:integer,size:integer,response_time:float
path /var/log/httpd/access_log
pos_file /var/log/td-agent/apache.access.pos
read_from_head true
</source>
###norikra
<source>
@type norikra
norikra localhost:26571
<fetch>
method sweep
tag query_name
tag_prefix norikra.query
interval 1s
</fetch>
</source>
#### Output
### Apache
<match apache.access>
@type norikra
norikra localhost:26571
buffer_queue_limit 1
retry_limit 0
remove_tag_prefix apache
target_map_tag true
<buffer>
@type file
path /var/log/td-agent/buffer/apache.access
flush_interval 1s
</buffer>
</match>
### ExecuteCommand
<match norikra.query.**>
@type exec
command /bin/bash /usr/local/bin/action.sh
<format>
@type json
keys timestamp,host,requests
</format>
<inject>
time_key timestamp
time_format %Y-%m-%dT%H:%M:%S
timezone Asia/Tokyo
</inject>
<buffer>
@type file
path /var/log/td-agent/buffer/norikra.query
flush_interval 1s
</buffer>
</match>
EOT
###jqコマンドインストール
exec Output Pluginでjson形式のファイルをシェルスクリプトに渡します。
jqを使うとパースがとても簡単なので入れます。
# ここからダウンロード
https://stedolan.github.io/jq/
# とりあえずダウンロードしたものをPATHの通っているところに置く
mv jq-linux64 /usr/bin/jq
chmod +x /usr/bin/jq
###exec output pluginで実行されるスクリプト
Fluentdからは目的のjsonが複数行渡されることもあるので、
一応すべての行を読み取るようにしてあります。
#!/bin/bash
logdir=/usr/local/bin
logfile=action.log
mail_server=localhost
mail_port=25
mail_from=norikra@example.com
mail_subject="題名入力"
mail_to[0]=user1@example.com
mail_to[1]=user2@example.com
mail_msg=$(cat <<EOT
・・・メール本文・・・
EOT
)
{
# Parse Json
mapfile -t msg < <(cat "$1")
for ((i=0; i<${#msg[@]}; i++)); do
if [ $i -gt 0 ]; then
mail_body+="\n"
fi
host="$(echo "${msg[$i]}" | jq -r '.host')"
requests="$(echo "${msg[$i]}" | jq -r '.requests')"
timestamp="$(echo "${msg[$i]}" | jq -r '.timestamp')"
request_data+="検知日時: $timestamp, 送信元IP: $host, リクエスト数: $requests"
done
# ログ出力
echo -e "$request_data"
# メール本文合成
mail_body=$request_data
mail_body+=$mail_msg
# Send Mail
mail_tos=""
for ((i=0; i<${#mail_to[@]}; i++)); do
mail_tos+="${mail_to[$i]} "
done
echo -e "$mail_body" | mail -S "smtp=smtp://$mail_server:$mail_port" \
-s "$mail_subject" -r "$mail_from" "$mail_tos"
} 2>&1 | awk '{print strftime("%Y-%m-%dT%H:%M:%S "),$0 } { fflush() } ' >> "${logdir}/${logfile}"
※execプラグインでは、実行するコマンドに引数として、バッファファイルが指定されて実行されます。
例えば今回の場合、以下コマンドをFluentdが実行します。
/bin/bash /usr/local/bin/action.sh /var/log/td-agent/buffer/norikra.query/<バッファファイル>