vulsを使って脆弱性の自動スキャンを実現する


**改訂版記事を [こちら](http://qiita.com/Ets/items/4ea663ed2b272d6ab93a) にアップしています！**

++++++

最近、脆弱性発見が相次いでいて、嬉しいようなツラいような状態ですね。
[vuls](https://github.com/future-architect/vuls)は脆弱性確認を楽にしてくれる可能性を秘めた、サーバー運用者にとって期待のツールです。

導入手順は基本的に[オフィシャル](https://github.com/future-architect/vuls)に従えば良いのですが、AWSベースで書かれているので、オンプレ用に端折った手順をメモしておきます。

以下、環境はCentOS7で、2016/06/01に入れた際の手順です。

# 事前準備

事前に必要なパッケージをインストールします。
goはtarでダウンロードするので、ちょっとめんどくさいですね。

```bash
$ yum -y install sqlite git gcc
$ wget https://storage.googleapis.com/golang/go1.6.2.linux-amd64.tar.gz
$ tar -C /usr/local -xzf go1.6.linux-amd64.tar.gz
```

`/etc/profile.d/goenv.sh`を作成してこのように記載します。

```/etc/profile.d/goenv.sh
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
```


# 導入手順

## cve-dictionaryサーバーの構築

### ユーザーの用意

変な権限でサーバーが動くのは嫌なので、cve-dictionaryサーバー用ユーザー(vuls)を作ります。
UIDとか指定する場合は、よしなに。

```bash
$ useradd -s "/sbin/nologin" vuls
```


### go-cve-dictionaryインストール

go-cve-dictionaryのダウンロードしましょう。結構重たいです。

```bash
$ go get github.com/kotakanbe/go-cve-dictionary
```

ところが2016/06/01のインストール時、ここでエラーが発生しました。

```bash
$ go get github.com/kotakanbe/go-cve-dictionary
# github.com/kotakanbe/go-cve-dictionary/server
go/src/github.com/kotakanbe/go-cve-dictionary/server/server.go:49: cannot use standard.New(bindURL) (type *standard.Server) as type engine.Server in argument to e.Run:
	*standard.Server does not implement engine.Server (wrong type for SetLogger method)
		have SetLogger(*"github.com/labstack/gommon/log".Logger)
		want SetLogger("github.com/labstack/echo/log".Logger)
```

[使っているライブラリの更新](https://github.com/labstack/echo/commit/a98843b6e58dcda9364099cc43c4d5b888baf00c)が災いしているようです。

暫定対処ですが、問題のライブラリを前のバージョンに戻します。

```bash
$ pushd go/src/github.com/labstack/echo
$ git log

commit a98843b6e58dcda9364099cc43c4d5b888baf00c
Author: Vishal Rana <vr@labstack.com>
Date:   Tue May 31 18:29:11 2016 -0700
    Logger as interface, fixed #533, fixed #349, fixed #304
    Signed-off-by: Vishal Rana <vr@labstack.com>

commit 3e04718bf42e434bd027067272afc1947ddf2f8e
Merge: f73681b 9225ce0
Author: Vishal Rana <vr@labstack.com>
Date:   Tue May 31 07:49:48 2016 -0700
    Merge pull request #532 from 0x616E676572/add_cors_method
    Add PATCH to default allowed methods


### Logger周りを変える前に戻す
$ git reset 3e04718bf42e434bd027067272afc1947ddf2f8e --hard

$ popd
$ go get github.com/kotakanbe/go-cve-dictionary
```

強引ですが、`go-cve-dictionary`がインストールされました。

### daemon化の準備

ビルドされた`go-cve-dictionary`をOSのPATHにコピー。
※ こうしないとrootユーザーのホームにバイナリがあり、vulsユーザーが実行できないため。

```
$ cp -p go/bin/go-cve-dictionary /usr/local/sbin/
```

ログディレクトリおよび辞書ディレクトリを作って、vulsユーザーに権限を渡しておきます。

```
$ mkdir /var/log/vuls
$ chown vuls:vuls /var/log/vuls

$ mkdir -p /var/vuls/dict
$ chown vuls:vuls /var/vuls/dict

```


### 脆弱性辞書の準備

2002年〜2016年に発生した脆弱性について、辞書を取得します。
数分は時間が掛かるので注意。

```bash
$ for i in {2002..2016}; do go-cve-dictionary fetchnvd -years $i; done

### sqliteファイルができたことを確認
$ ls -l cve.sqlite3

```

権限を変えて、辞書ディレクトリにsqlite3ファイルを移動します。

```bash
$ chown vuls:vuls cve.sqlite3
$ mv cve.sqlite3 /var/vuls/dict

$ ls -l /var/vuls/dict/cve.sqlite3
-rw-r--r-- 1 vuls vuls 574898176 Jun  1 12:00 cve.sqlite3
```


### 起動スクリプトの準備
起動スクリプト(systemdファイル)を作成します。

```bash
### 環境設定ファイル作成
$ vim /etc/sysconfig/go-cve-dictionary

BIND=0.0.0.0
PORT=1323
DBPATH=/var/vuls/dict/cve.sqlite3

### 起動スクリプト作成
$ vim /usr/lib/systemd/system/go-cve-dictionary.service
### 下記参照
```

`go-cve-dictionary.service`の中身はこんな感じです。
httpdを参考にしているので、ちょっと過剰かもしれません。

```go-cve-dictionary.service
[Unit]
Description=Go CVE Dictionary Server
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=simple
EnvironmentFile=/etc/sysconfig/go-cve-dictionary
ExecStart=/usr/local/sbin/go-cve-dictionary server -bind=${BIND} -port=${PORT} -dbpath=${DBPATH}
ExecStop=/bin/kill ${MAINPID}
User=vuls
Group=vuls
PrivateTmp=true

[Install]
WantedBy=multi-user.target
```

### go-cve-dictinaryを起動

ようやく準備ができました。`go-cve-dictinary`を起動しましょう。

```bash
$ systemctl daemon-reload
$ systemctl start go-cve-dictionary

### 確認
$ systemctl start go-cve-dictionary

$ ps -ef | grep go-cve-dictionary

vuls     25721     1  0 12:27 ?        00:00:00 /usr/local/sbin/go-cve-dictionary server -bind=0.0.0.0 -port=1323 -dbpath=/var/vuls/dict/cve.sqlite3

$ netstat -alnp | grep go-cve | grep LISTEN

tcp6       0      0 :::1323                 :::*                    LISTEN      25721/go-cve-dictio
```


## vulsを準備


vulsを手に入れる・・・のですが、 **ここで詰まる可能性がある** ので、続く手順を先に眺めてください。

```
$ go get github.com/future-architect/vuls
```

CentOS6の標準yumリポジトリでは、git-1.X系列しかダウンロード出来ないはずです。
この場合、gitのバージョンが古いために`go get`が詰まってしまいます。

そこでwandiscoのリポジトリからgit-2.xをインストールします。

### git-2.xのインストール

`/etc/yum.repos.d/wandisco.repo`を作成します。

```/etc/yum.repos.d/wandisco.repo
[wandisco-git]
name=WANdisco Distribution of git
baseurl=http://opensource.wandisco.com/centos/7/git/$basearch
enabled=0
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-WANdisco
```

gitのアップデート

```bash
### GPGキーを取り込み
$ rpm --import http://opensource.wandisco.com/RPM-GPG-KEY-WANdisco
$ yum update git --enablerepo=wandisco-git

### バージョンアップ確認
$ git --version
git version 2.8.0
```

### 改めてvulsをインストール

改めてvulsを`go get`しましょう。

```bash
$ go get github.com/future-architect/vuls
```

入りました。

`vuls`もOSのパスにコピーしておきましょう。

```bash
$ cp -p go/bin/vuls /usr/local/bin/
```

確認

```bash
$ /usr/local/bin/vuls -v
vuls 0.1.4
```

# vulsによる脆弱性検査

## vulsの使用準備

### vuls設定ファイルの作成

先ほど作ったvulsサーバーにて`config.toml`ファイルを作成します。
現在のvulsはカレントディレクトリ以下にある`config.toml`ファイルを読み、カレントディレクトリ以下に結果を出力するので、 **検査のたびに適当なフォルダを作って、その中で作業するのが良さそうです。**

```bash
$ mkdir hoge
$ cd hoge
$ vim config.toml
```

中身はこのように書いていきます。
詳しくはオフィシャルを参照してください。

```config.toml
[servers]

[servers.localhost]
host        = "127.0.0.1"
port        = "22"
user        = "root"
keyPath     = "/root/.ssh/id_rsa"
```

### SSH公開鍵認証の設定

先ほどの設定ファイルで指定していることから想像できると思いますが、vulsはSSHの公開鍵認証を使用しているようです。検査対象サーバー（ここではローカルホスト）には事前にSSH周りの設定をしておきましょう。

なお、このようにSSHをしたりする関係で、 **vulsユーザー(daemon用ユーザー)とvuls実行ユーザー(スキャン実行用ユーザー)は分けておいたほうが無難な気がします。**

このあたり、AnsibleやChefなどのSSHユーザー管理の話と同じですので、ここでは割愛します。
私の環境ではAnsibleに使用しているユーザーを使用することにしました。

なお、以下はSSH公開鍵設定の手順例です。

```bash
### 認証用の公開鍵を作る
[[vulsサーバー]]
$ su - vuls実行ユーザー

[vuls実行ユーザー] $ ssh-keygen -t rsa
[vuls実行ユーザー] $ cat ~/.ssh/id_rsa.pub

### この内容を検査対象サーバーのSSHログインユーザーに設定
[[検査対象サーバー]]
[vuls実行ユーザー] $ vim ~/.ssh/authorized_keys
[vuls実行ユーザー] $ chmod 600 ~/.ssh/authorized_keys
```

SSHで対象サーバーに入れることを確認しましょう。

## 脆弱性スキャンの実行

### vuls prepare

SSH公開鍵ログイン設定が終わったら`vuls prepare`を実行します。
これにより、SSHの動作確認ができるとともに、[動作に必要なyumプラグイン
](https://github.com/future-architect/vuls#usage-prepare)が検査対象サーバーにインストールされます。

```bash
vuls prepare
INFO[0000] Start Preparing (config: /root/config.toml)
[May 24 12:29:25]  INFO [localhost] Detecting OS...
[May 24 12:29:25]  INFO [localhost] (1/1) Detected localhost: centos 6.4
[May 24 12:29:25]  INFO [localhost] Detecting Container OS...
[May 24 12:29:25]  INFO [localhost] Installing...
[May 24 12:29:25]  INFO [localhost] Ignored: yum-plugin-security already installed
[May 24 12:29:25]  INFO [localhost] Installing yum-plugin-changelog...
[May 24 12:29:29]  INFO [localhost] Installed: yum-plugin-changelog
[May 24 12:29:29]  INFO [localhost] Success
```

### vuls scan

ここまで準備出来れば、脆弱性スキャンが実行できます。
**config.tomlに書いたサーバーに対しては一気に実行してしまいます** ので、稼働中のホストへのスキャンは気をつけてください。

レポートの出力形式はヘルプを見て、適当なものを選びましょう。

```bash
$ cd vulsの設定ファイルを作ったフォルダ

$ vuls scan -report-text
```

結果はカレントディレクトリに出力されています。

```bash
$ ls -l results/current
```

# 定期的な実行を考える

定期的な実行をし続けることで、運用をもっと楽したいと思います。


## 脆弱性情報の定期的な更新

とりあえずcronに仕込んじゃいましょう。
コマンドの中身は簡単なワンライナーで、「過去２年間の最新情報を取得する」というものです。

```/etc/cron.d/go-cve-dictionary_update
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
MAILTO=""
HOME=/home/vuls

# Update CVE dict
37 05 * * * vuls go-cve-dictionary fetchnvd -last2y -dbpath=/var/vuls/dict/cve.sqlite3 >/dev/null 2>&1
```

実行ユーザーは環境に合わせてください。
また、実行時間は **負荷をかけないように適当に変えてください。**


## 定期的な脆弱性診断

この設定は、どうやって通知するのが適切か考える必要があります。
vulsはslackに投げる機能もあるのですが、脆弱性発見時は結構な文章量になることを想定して、メール通知で始めてみることにしました。

### メール通知を設定する

Cronで実行するためのconfigファイルを作成します。

```bash
$ mkdir /var/vuls/cron/config.toml
$ vim /var/vuls/cron/config.toml
```

自動実行して負荷をかけても問題ないサーバーをスキャン対象にしておきましょう。

このとき、configファイルの先頭に`[mail]`ブロックを作成します。
詳しくはオフィシャルの設定を見るとして、こんな感じでローカルのsmtpサーバーを使って送信してしまいます。

```/var/vuls/cron/config.toml
[mail]
smtpAddr      = "localhost"
smtpPort      = "25"
from          = "xxxxxx@mailaddress.xxxx"
to            = ["xxxxxx@mailaddress.xxxx"]
subjectPrefix = "[vuls]"

[servers]
### 以下スキャン対象のサーバーの指定
```

なお、オフィシャルでは`smtpPort`オプションはintegerに見えるのですが、文字列(ダブルクォートで囲む)じゃないとうまく動作しませんでした。バグ？

### Cronに設定する
この設定ファイルを使って`vuls scan`を実行する設定をCronに仕込みます。
vulsを実行するユーザー(対象ホストに公開鍵でSSHログインできるユーザー)のCronとして設定する必要があります。

私の場合、先ほどのconfigファイルはそのユーザーの`/var/vuls/cron`に置いているので、vulsコマンド実行前に`cd`しています。

```/etc/cron.d/go-cve-dictionary_scan
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
MAILTO=""
HOME=vuls実行ユーザーのホームディレクトリ

# Scan target machines
37 06 * * * vuls実行ユーザー cd /var/vuls/cron && vuls scan -report-text -report-mail >/dev/null 2>&1
```

これで **毎朝嫌というほど脆弱性通知メールが来ることが期待できます。**
*# 当たり障りない脆弱性は対応を手抜きしちゃいますよね・・・*



# 最後に
以上で、かなり強引ながら、脆弱性を自動スキャンしてメール通知する仕組みを構築しました。
作ってみてすぐに察しましたが、毎日脆弱性通知が来ると **すぐに慣れて見なくなってしまう** という懸念がありますね。このあたり、通知方法などをブラッシュアップしていかないといけない気がしています。

投稿日時を見るとわかりますが、ほとんどテストすることなく書いた記事なので間違っていたらすみません！
*# 間違いに気付き次第修正します・・・*