Zabbix3.4をdockerで運用してVulsと連携させる 〜 その3 〜 ZabbixとVulsの連携

はじめに

その1ではZabbixを、その2ではVulsをインストールしました。

Zabbix3.4をdockerで運用してVulsと連携させる 〜 その1 〜 Zabbixのインストール
Zabbix3.4をdockerで運用してVulsと連携させる 〜 その2 〜 Vulsのインストール

いよいよ両者を連携させてみます。
Software Design 2017年10月号にVulsとZabbixの連携方法が載っているので、そちらを参考にしました。

目標

  • Zabbixのホスト情報からVulsの設定ファイル(config.toml)を自動生成する
  • Vulsのスキャン結果をZabbixへ連携する
  • Zabbixで脆弱性情報を表示する

Zabbix側での事前準備

Zabbixのホスト情報を用いてVulsの設定ファイルを作成するので、スキャン対象のサーバーをZabbixの監視対象にします。

【スキャン対象サーバー側】Zabbix Agentのインストールと設定

この作業はスキャン対象サーバーで行います。

Zabbix Agentをインストールします。

wget http://repo.zabbix.com/zabbix/3.4/ubuntu/pool/main/z/zabbix-release/zabbix-release_3.4-1+trusty_all.deb
sudo dpkg -i zabbix-release_3.4-1+trusty_all.deb
sudo apt-get update
sudo apt-get install zabbix-agent

Zabbix Agentの設定ファイルを編集します。

sudo vim /etc/zabbix/zabbix_agentd.conf

Server=の後ろをZabbixサーバーのIPアドレスに書き換えましょう。

設定が終わったらZabbix Agentを起動します。

sudo /etc/init.d/zabbix-agent start

スキャン対象サーバーをZabbixの監視対象に登録

Zabbixにログインし、設定→ホストから「ホストの作成」をクリックして、監視対象サーバーのIPアドレスを入力します。
テンプレートはTemplete OS LinuxTemplate_Vulsを選択しました。

スクリーンショット 2017-10-05 16.25.52.png

Template_Vulsは以下のURLからダウンロードしてZabbixにインポートしてください。
https://github.com/usiusi360/vuls_autoscan_for_zabbix

config.tomlの自動生成

Zabbixの設定からVulsの設定ファイルを自動生成する方法です。
VulsとZabbixを連携させるだけなら、この章は飛ばしても構いません。

下記URLからcreate_config.shを取得します。
https://github.com/usiusi360/vuls_autoscan_for_zabbix/

wget https://github.com/usiusi360/vuls_autoscan_for_zabbix/raw/master/create_config.sh
wget chmod 700 create_config.sh

ZABBIX_SERVERZABBIX_USERおよびZABBIX_PASSを自分の環境に合わせて書き換えましょう。

url="http://${ZABBIX_SERVER}/zabbix/api_jsonrpc.php"

は以下のように書き換える必要がありました。

url="http://${ZABBIX_SERVER}/api_jsonrpc.php"

実行にはjqが必要なので、入っていなければインストールします。

brew install jq

config.toml.masterを作成します。
create_config.shconfig.toml.masterの内容とZabbixサーバーのホスト情報を組み合わせてconfig.tomlを生成します。

config.toml.master
[default]
port    = "22"
user    = "vuls"
keyPath = "/home/vuls-user/.ssh/id_rsa"

内容は自分の環境に合わせて書き換えてください。

前回作成したconfig.tomlは退避しておきます。

mv config.toml config.toml.back

create_config.shを実行するとconfig.tomlが生成されました。

./create_config.sh
生成されたconfig.toml
[default]
port    = "22"
user    = "vuls"
keyPath = "/home/vuls-user/.ssh/id_rsa"
[servers]
[servers."Zabbix上のホスト名"]
host = "ホストのIPアドレス"

Zabbix上で、ステータスが「有効」でエージェントの状態が正常(緑色)になっているホストの情報だけが追加されます。
私の環境ではなぜかVirtualBox上のUbuntuに入れたZabbix agentをdocker上のZabbix serverからうまく認識できなかったので、別マシンにZabbix agentを入れて確認しました。
Linuxサーバー以外のホスト設定も拾ってしまうので、MacやWindowsのZabbix Agentが存在する環境ではスクリプトを改良する必要があるでしょう。
自動化する必要がなければ、スクリプトを使わずに手で書いた方が手っ取り早いです。

Vulsのスキャン結果をZabbixへ連携する

スクリプトの準備

vuls_autoscan_for_zabbix.shをダウンロードします。

wget https://github.com/usiusi360/vuls_autoscan_for_zabbix/raw/master/vuls_autoscan_for_zabbix.sh
chmod 700 vuls_autoscan_for_zabbix.sh

このスクリプトはローカル環境のVulsを前提としているので、docker上のVulsで動くように以下のように書き換えました。

vuls_autoscan_for_zabbix.sh
#!/bin/bash

ZABBIX_SERVER="localhost"
RETRY=3
PAST_YEAR=2
#PROXY="-http-proxy=http://proxy.hogehoge.co.jp:10080"

SSH_PATH="/Users/username/.ssh"

########################

VULS_HOME=`cd $(dirname $0) && pwd`
#VULS_LOG="${VULS_HOME}/results"
VULS_LOG="/vuls/results"

vuls_comm="docker run --rm --volumes-from vuls-data -v ${SSH_PATH}:/home/vuls-user/.ssh:ro -v ${VULS_HOME}:/vuls/config -v /etc/localtime:/etc/localtime:ro -e TZ=Asia/Tokyo vuls/vuls"
go_cve_dictionary_comm="docker run --rm -it --volumes-from vuls-data vuls/go-cve-dictionary"
goval_dictionary_comm="docker run --rm -it --volumes-from vuls-data vuls/goval-dictionary"

my_logger() {
    local priority="user.info"
    logger -i -p $priority -t `basename $0` "$1"
}

update() {
  local target=$1

  local last_year=`date +%Y`
  local first_year=`expr ${last_year} - ${PAST_YEAR} + 1`
  local years=""
  for i in `seq ${first_year} 1 ${last_year}`
  do
    years="${years} $i"
  done

  for i in `seq 1 ${RETRY}`
  do
    $go_cve_dictionary_comm fetch${target} ${PROXY} -years $years
    if [ $? -eq 0 ];then
      my_logger "[INFO] Update success. [${target}]"
      break
    else
      if [ $i -lt $RETRY ];then
          my_logger "[INFO] Update retry. [${target}] (count=$i)"
          sleep 10
      else
          my_logger "[ERROR] Update retry over. [${target}] (count=$i)"
      fi
    fi
  done
}

update_oval() {
  local target=$1
  local option="$2"

  for i in `seq 1 ${RETRY}`
  do
    $goval_dictionary_comm fetch-${target} ${PROXY} ${option}
    if [ $? -eq 0 ];then
      my_logger "[INFO] Update-OVAL success. [${target}]"
      break
    else
      if [ $i -lt $RETRY ];then
          my_logger "[INFO] Update-OVAL retry. [${target}] (count=$i)"
          sleep 5
      else
          my_logger "[ERROR] Update-OVAL retry over. [${target}] (count=$i)"
      fi     
    fi
  done
}

scan(){
  $vuls_comm scan -deep -config=/vuls/config/config.toml -ssh-native-insecure
    if [ $? -eq 0 ];then
      my_logger "[INFO] Scan success."
    else
      my_logger "[ERROR] Scan fail."
      exit 1
    fi
}


report(){
  $vuls_comm report -format-json -cvedb-path=/vuls/cve.sqlite3 -ovaldb-path=/vuls/oval.sqlite3 -format-short-text -config=/vuls/config/config.toml -lang=ja
    if [ $? -eq 0 ];then
      my_logger "[INFO] Scan success."
      rm -rf ${VULS_LOG}
      docker cp vuls-data:/vuls/results ${VULS_LOG}
    else
      my_logger "[ERROR] Scan fail."
      exit 1
    fi
}

send_zabbix(){
  files="${VULS_LOG}/current/*.json"
  for filepath in $files; do
    TARGET_NAME=`basename $filepath .json`
    if [ "${TARGET_NAME}" == "all" ]; then
      continue
    fi
      zabbix_sender -z ${ZABBIX_SERVER} -s ${TARGET_NAME} -k nvd_count -o `cat $filepath | jq '.ScannedCves? | length'`
      zabbix_sender -z ${ZABBIX_SERVER} -s ${TARGET_NAME} -k nvd_max -o `cat $filepath | jq '[.ScannedCves[] | .CveContents.nvd.Cvss2Score]+[0] | max'`
   done
}

rotate(){
  firstDay=`date '+%Y-%m-01'`
  agoYear=`date -d "$firstDay 1 months ago" '+%Y'`
  agoMonth=`date -d "$firstDay 1 months ago" '+%m'`
  mkdir ${VULS_LOG}/$agoYear-$agoMonth > /dev/null 2>&1
  mv ${VULS_LOG}/$agoYear$agoMonth* ${VULS_LOG}/$agoYear-$agoMonth > /dev/null 2>&1
}


#======

cd ${VULS_HOME} 
  if [ $? -ne 0 ];then
    my_logger "[ERROR] path not found [${VULS_HOME}]"
    exit 1
  fi

## update ##
update nvd
update jvn

## update oval ##
#update_oval redhat "5 6 7"
#update_oval debian "7 8 9 10"
#update_oval ubuntu "12 14 16"
#update_oval oracle

## vuls scan ##
scan

## vuls report ##
report

## send zabbix ##
send_zabbix

## rotate ##
#rotate

exit 0

ZABBIX_SERVERSSH_PATHは環境に合わせて書き換えてください。
コメントアウトは必要に応じて外してください。
rotateはdocker用に修正していないので、コメントアウトを外しても正常に動かないはずです。

(2017/10/12追記) 最初、dockerに-itオプションを付けていましたが、cronから実行すると"the input device is not a TTY"というエラーが出て動かなかったため、外しました。

スキャン結果格納用フォルダの作成

Vulsのスキャン結果を格納するフォルダを作成します。
/vuls/results/currentが絶対パスのリンクになっているので、絶対パスで作らないと動きません。
改良したいところです。

sudo mkdir /vuls
sudo chmod 777 /vuls

zabbix_senderのインストール

スクリプトの実行にはzabbix_senderが必要です。
残念ながら公式dockerイメージはないようなのでローカルにインストールします。

Macでは--without-server-proxyオプション付きでzabbixをインストールすると、Zabbix Agentのみがインストールされますが、この時zabbix_senderも一緒にインストールされます。

brew install --without-server-proxy zabbix

Zabbix連携スクリプトの実行

./vuls_autoscan_for_zabbix.sh

スクリプトを実行すると以下の処理が行われます。

  • 脆弱性データベースの更新(NVD, JVN)
  • OVALデータの更新(コメントアウトを外した場合)
  • スキャン実行
  • レポート作成
  • Zabbixへレポートを連携

正常に連携が完了するとZabbix側でmax vulnerability(脆弱性スコアの最大値)とnumber of vulnerabilities(脆弱性の件数)が更新されます。
スクリーンショット 2017-10-06 16.01.10.png

前回チェック時から検知数が増えた場合にアラートが上がります。
スクリーンショット 2017-10-06 16.24.23.png

グラフも表示できます。
これはapt-get upgradeを行った直後にスクリプトを動かした結果です。
脆弱性の件数が減っていることがわかりますね。
スクリーンショット 2017-10-06 16.19.24.png

あとはスクリプトをcronで毎日実行すれば脆弱性の検知はばっちりですね。

課題

  • Vuls設定ファイルの自動生成スクリプト
    • Zabbix上の設定からVulsの設定ファイルを生成する際に、Vuls未対応のOSでも生成されてしまう
  • Zabbix連携スクリプト
    • zabbix_senderもdockerで動かしたい(公式イメージにこだわらなければある)
    • Vulsのスキャン結果を任意の場所に格納できるようにしたい
  • Vuls本体
    • Mac上のdockerコンテナもVulsのスキャン対象にできればいいのに(Linux上のコンテナはスキャン可能)
      • 今はホストOSがVuls非対応の時点でエラーになってコンテナスキャンされない
      • ホストOSはスキャンせず、コンテナだけスキャン対象にするオプションができれば解決

最後に

ZabbixとVulsは単独でも強力なツールですが、組み合わせるといっそう便利になります。
Docker上で動かせば、環境の移行も楽ですよ。