標準化した、Vuls/VulsRepoの導入

この記事は、[Vuls Advent Calendar 2016](http://qiita.com/advent-calendar/2016/vuls)の7日目の記事です。

# 概要

Vulsは、vulsコマンド自体のインストールやVulsサーバ/スキャンされるサーバの構成について、自由度が高い設計のようです。その為、ベストプラクティスが見つけずらい状況となっています。

本記事では、PCI/DSSを考慮に入れた、比較的安全と思われる Vuls導入パターンを提示します。

- 全体の構成としては、[usiusi360さんの記事](http://qiita.com/usiusi360/items/5a7125350ed57b327636)を参考にするとよいと思います。
- 以前公開した [(比較的)標準化をした Vuls/VulsRepo の導入](http://qiita.com/hogehogehugahuga/items/e0c21d0ca25b742e86c3)の更新版となります。
- 本ページの情報でVulsサーバ構築後、[Vulsでowasp dependency-checkを使う](http://qiita.com/hogehogehugahuga/items/7527a8f4e4155645ede7)の設定をすることで、アプリケーションのチェックも可能となります。

## 本記事の、20秒まとめ

- vulsは、/opt/vuls に入れて
- スキャン対象の鍵は /opt/vuls/ssh_keys に入れて
- アクセス権は、必要最小限に抑えよう(構築例後半に例示)
- httpdはapacheで動かし、vulsのデータには限定的にアクセスさせたい

# 全体の構成
## 方針

以下の方針とします。

- Vuls及びVulsRepoを稼働させます。
    - Vulsを稼働させるユーザは、vuls アカウントとします。
    - Webサーバは、Apacheを使います。
- vulsアカウント は、他のアカウントと分離します。
    - ホームディレクトリを/home から切り離し、/opt/vuls とします。
    - httpd等がスキャン結果にアクセスをする必要はありますが、Results以外のディレクトリにはアクセス出来ないようにします。
    - Vulsスキャン以外では利用しない事とします。
- スキャン対象サーバの ssh鍵 を保護します。
    - Vulsサーバのvulsユーザ配下、/opt/vuls/ssh_keys 以下に配置し、vulsユーザのみがアクセスできるように保護します。
- VulsRepoは、管理者のみがアクセスできるように、WEBサーバ側の機能を利用して制限を行う

### httpd実行アカウントについて

httpdの実行ユーザについては、色々な考え方が出来る。その為、本記事では以下のように考え、デフォルトのapacheを利用している。

- VulsRepoを動かすだけであれば、vuls:vulsでhttpdを稼働させても問題ない
- しかしながら
    - vulsユーザ以外が、vulsユーザアカウントでvulsデータにアクセスすると、アクセス制限が出来ない
        - ファイルシステム的には、vulsユーザの対話的操作なのか、httpdのvulsアカウントでの操作なのか、判別はできない。
    - 仮に、Vuls導入を検討している段階であれば、既存の Zabbix等の監視サーバとの相乗りとなっても、まずは使ってほしい。
        - その為には、なるべく変更点が少ない方がよく、デフォルトのapache:apacheを利用するほうが良さそうだ。
    - また、VulsRepo以外のWEBコンテンツを稼働させてdirectory traversalが発生した場合、httpdがvulsのデータを閲覧することが出来てしまう
        - 本来は、Vuls/VulsRepoサーバと、他のWebは同居させるべきではないが、先述の まずは使ってもらう 事を考慮すると、並行稼働はやむを得ない
    - 用途に対して過大なアクセス権となる
        - result 以下を見るだけなので、HOME以下の全てのファイルを見る 事が出来る 権限は不要と考えるられる。
        - Need to knowの原則に従い、最小限のアクセスとする。

## PCIDSS対応時の考慮点

PCI/DSSへ対応する場合は、以下の点を考慮する必要があります。

- Need to Knowの原則を守る
    - 秘密に接する権限は当該内容を知る必要のある人のみに限定する必要があります。
        - 「秘密」とは、Vulsであれば、Vulsスキャンデータや ssh鍵 などの、データとなります。
        - これは、脆弱性情報は「攻撃する為の情報」にもなる為です。
        - 運用上、 **Vulsサーバは Vulsの為だけに稼働させる** のがセキュリティ上は良いと考えられます。
            - 顧客情報の入ったDBでは　WEBサーバを共存させない、のと同じ意味合いです。
- パスワード/パスフレーズを使わない
    - PCI/DSSでは、パスワードを最長で90日以内に交換しなければならない、というルールが存在する。
    - その為、VulsRepoのページをBASIC認証で保護した場合は、そのパスワードを定期的に維持/変更するコストが発生する。
    - 故に、なるべく代替の手段を利用する事が、管理上望ましい。

実際の適用は以下のようになると思われます。

- Vulsサーバ
    - Vulsでのスキャン以外の用途には使用しないのが望ましい
        - SELinuxで保護、jailやコンテナなどの仮想化で分離する、などの方法も考えられる
        - Vuls専用のサーバを立てる事が出来ないからあきらめる、のであれば、他の監視サーバ等に相乗りでも「まずは使う」事を優先したほうが良い
    - 管理者以外がログインできないようにする
        - 一般ユーザを作らない。Vuls専用サーバであれば、root及びvuls以外ではログインできないようにする
    - スキャン結果のディレクトリ Results は、Vuls実行ユーザのみアクセスできるようにする。
- パスワード関連
    - ssh鍵にパスフレーズを付けない
        - パスフレーズも定期更新対象になる
        - 自動化の際にパスフレーズが流出しないよにする為、設定しない
          - 上記ではダメな可能性があるとの指摘有り。その際はパスワードを付けて保護し、定期的に更新する。
    - VulsRepoページは、クライアント証明書で保護する
        - BASIC認証とする場合、パスワードのメンテナンスコストが掛かる
        - クライアント証明書の場合も証明書配布コストが掛かるので、運用状況により検討が必要
          - 証明書自身は自分で発行したもので構わない
          - 有効期限はあまり長くせず、失効管理を正しく行う
- WEBサーバ(HTTPD)
    - vulsと別ユーザで起動させ(デフォルトのapache等を利用)、vulsのResultsへのみアクセス権限を付ける
        - ディレクトリトラバーサル等が仮に実行できたとしても、ssh_keysなどのデータについては、ディレクトリ/ファイル アクセス権限により保護を行える。
    - ディレクトリインデックス表示を拒否する設定を、httpd.conf等に入れる

## ディレクトリ構成

```
/ (root)
   +- /opt/vuls [vuls:apache](750)
   |  | [default vuls:vuls](xx0)
   |  +- go/ [vuls:vuls]
   |  |  +- bin/
   |  |  |  +- go-cve-dictionary
   |  |  |  +- vuls
   |  |  +- src/github.com/
   |  |     +- Masterminds/glide
   |  |     +- future-architect/vuls/
   |  |     +- kotakanbe/go-cve-dictionary/
   |  +- results [vuls:apache](750)
   |  |  +- current (Newest folder link)
   |  |  +- YYYY-MM-DDThh:mm:ss+TZ
   |  |  +- YYYY-MM-DDThh:mm:ss+TZ
   |  |  +- YYYY-MM-DDThh:mm:ss+TZ
   |  |  +- YYYY-MM-DDThh:mm:ss+TZ
   |  +- ssh_keys/ [vuls:vuls](700)
   |  |  +- local_id_rsa
   |  |  +- <remote-servers1_id_rsa>
   |  |  +- ...
   |  |  +- <remote-serversN_id_rsa>
   |  +- config.toml
   |  +- cve.sqlite3
   +- /var/log/vuls [vuls:vuls](700)
      +- <remote-servers1> [vuls:vuls](664)
      +- ...
      +- <remote-serversN> [vuls:vuls](664)
      +- cve-dictionary.log [vuls:vuls](664)
      +- report.log [vuls:vuls](500)
```

## ユーザ/グループ構成

apache, vulsユーザは、追加でグループに参加する必要はない。
また、デフォルトで作られる apache, vuls グループにも、他のユーザを参加させない。

```bash:groups
$ groups apache
apache : apache
$ groups vuls
vuls : vuls
$ 
```

```bash:/etc/group
$ cat /etc/group
...
vuls:x:1001:
apache:x:48:
$ 
```

# 構築例
## 構築シナリオ

以下に、Vulsサーバ構成の例を記載していきます。

- CentOS7(非SELinux環境)を新規作成し、Vulsサーバに構成する
- スキャン対象は、Vulsサーバ自分自身のみとする
- VulsRepoは、アクセス制限しない
    - WEBサーバ側の設定になる為。
    - クライアント証明書認証やIPアドレスでのアクセス制限を検討する。BASIC認証はあまりお勧めしません。

## OSインストール

今回は CentOS7 を最小インストールとします。
インターネットに接続できる環境で構成し最新版に更新をする。

## 必要なパッケージを導入する

以下のパッケージを導入します。

```bash:package_install
[root:~]# yum install sqlite git gcc wget yum-plugin-changelog
```

## vulsユーザの作成

vulsユーザを、/opt/vuls をホームディレクトリとするように作成します。

- パスワードログインができないように、パスワードを付けません
  - `#su -vuls`でvulsユーザになります
- sshは鍵認証とするため、ssh-keygenで鍵を作ります。
  - vulsユーザのssh鍵も、他のサーバのssh鍵同様に /opt/vuls/ssh_keys 配下に収納します。
  - アクセス権は vuls:vuls 700 で保護します。
- /etc/sudoersの編集をします
  - パスワードなし、yum, echoを実行可能

```bash:add_vuls_account
[root:~]# useradd -d /opt/vuls vuls
```
```bash:modify_sudoers
[root:~]# visudo
vuls  ALL=(root)  NOPASSWD: /usr/bin/yum, /bin/echo
[root:~]# 
```
```bash:create_keys
[root:~]# su -vuls
[vuls:~]$ pwd
/opt/vuls
[vuls:~]$ mkdir .ssh ssh_keys
[vuls:~]$ chmod 700 .ssh/ ssh_keys/
[vuls:~]$ ssh-keygen -t rsa
[vuls:~]$ cat .ssh/id_rsa.pub >> .ssh/authorized_keys
[vuls:~]$ chmod 600 .ssh/authorized_keys
[vuls:~]$ mv .ssh/id_rsa ssh_keys/local_id_rsa
[vuls:~]$ exit
[root:~]# 
```

## go言語の導入

Vulsの README.md 通り、/usr/local にgo言語を入れます。
go言語の最新バージョンについては、[go言語のページ](https://golang.org/)を参照してください。

```bash:install_go_lang
[root:~]# cd /tmp
[root:/tmp]# wget https://storage.googleapis.com/golang/go1.8.linux-amd64.tar.gz
[root:/tmp]# tar -C /usr/local -xzf go1.8.linux-amd64.tar.gz
[root:/tmp]# cd
[root:~]# vi /etc/profile.d/goenv.sh
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
[root:~]# su - vuls
[vuls:~]$ mkdir go
[vuls:~]$ go version
go version go1.8 linux/amd64
[vuls:~]$ 
```

## go-cve-dictionary, Vulsの導入

これらについても、README.md 通りです。

```bash:setup_log_dir
[root:~]# mkdir /var/log/vuls
[root:~]# chown vuls /var/log/vuls
[root:~]# chmod 700 /var/log/vuls
```
```bash:setup_go-cve-dictionary
[root:~]# su - vuls
[vuls:~]$ mkdir -p $GOPATH/src/github.com/kotakanbe
[vuls:~]$ cd $GOPATH/src/github.com/kotakanbe
[vuls:kotakanbe]$ git clone https://github.com/kotakanbe/go-cve-dictionary.git
[vuls:kotakanbe]$ cd go-cve-dictionary
[vuls:go-cve-dictionary]$ make install
```
```bash:fetchnvd
[vuls:go-cve-dictionary]$ cd
[vuls:~]$ for i in {2002..2017}; do go-cve-dictionary fetchnvd -years $i; done
[vuls:~]$ 
```
```bash:setup_Vuls
[vuls:~]$ mkdir -p $GOPATH/src/github.com/future-architect
[vuls:~]$ cd $GOPATH/src/github.com/future-architect
[vuls:future-architect]$ git clone https://github.com/future-architect/vuls.git
[vuls:future-architect]$ cd vuls
[vuls:vuls]$ make install
[vuls:vuls]$ cd
[vuls:~]$ vuls -v
vuls v0.2.0 2f9c307
[vuls:~]$ 
```

## Vulsのconfigを作成

Vulsサーバ自身のスキャンをするように、config.tomlを作成します。

```bash:create_config-toml
[vuls:~]$ vi /opt/vuls/config.toml
-------以下の内容を記載-------
[default]
port     = "22"
user     = "vuls"

[servers]

[servers.vuls-server]
host = "127.0.0.1"
keyPath = "/opt/vuls/ssh_keys/local_id_rsa"
------------------------------
[vuls:~]$ vuls configtest
[vuls:~]$ 
```

## ディレクトリアクセス権の再調整

全てのディレクトリがそろったので、アクセス権の最終調整を行います。

- chown --recursive vuls:vuls /opt/vuls
    - vuls以外にはアクセスさせない
- chmod --recursive go= /opt/vuls
    - 念の為、Group/Otherはアクセス権なしにする
- chmod --recursive 700 /opt/vuls/ssh_keys
    - 鍵は念の為、明示的に保護する

## 自分自身のスキャンを実施

これも README.md通り、prepareの後、scanを実施します。
ここまでで、Vulsでのスキャンは完了です。

- scan実行時のディレクトリは注意が必要
    - config.toml, result/ はカレントディレクトリ以下に作られる
    - その為、今回の想定では vulsのホームディレクトリである、/opt/vuls で実行する必要がある
    - **scanオプションは、scanとreportに分離されました。**
      - scanでは、resultsにchangelogのjsonをはくだけになっています。
      - reportにて、上記でできたjsonにNVD等の情報を追加するようになりました。

```bash:vuls_scan_and_preview
[vuls:~]$ cd 
[vuls:~]$ vuls prepare
[vuls:~]$ vuls scan
[vuls:~]$ vuls report -to-localfile -format-json -cvedb-path=/opt/vuls/cve.sqlite3
```
**reportオプションは多数の選択肢が取れるため、READMEを参照してください。**
概要のみを記載しておきます。

- -to-xxxx でレポート出力先をして指定可能
  - ローカルファイルとして出力する -to-localfile
  - その他 -to-email, -to-slack, -to-s3, -to-azure-blobなどがある
- -format-xxxx で出力形式を指定することが可能
  - -format-json, -format-xmlで、JSONやXML
  - -format-one-line-text, -format-one-email, -format-short-text はメール通知時に適した形式
  - -format-full-textはテキスト形式での処理向けかも
- **vulsrepoを利用する際は以下でよい**
  - report -to-localfile -format-json
    - scanで出力されたJSONに、cveデータを付与してJSONで上書き

## VulsRepoを導入

Vulsでスキャンした情報をより有効活用するために、[VulsRepo](https://github.com/usiusi360/vulsrepo)を導入します。

- ディレクトリ構成等は、パッケージデフォルトで進めます。

```bash:setup_vulsrepo
[root:~]# yum install httpd perl-CGI perl-JSON
[root:~]# cd /var/www/html
[root:html]# git clone https://github.com/usiusi360/vulsrepo.git
[root:html]# ln -s /opt/vuls/results vulsrepo/results
[root:html]# cp vulsrepo/dist/cgi/vulsrepo.conf.sample /etc/httpd/conf.d/vulsrepo.conf
```
```bash:replace_acl
[root:html]# chown vuls:apache /opt/vuls
[root:html]# chown --recursive vuls:apache /opt/vuls/results/
[root:html]# chmod g+xr /opt/vuls
[root:html]# chmod --recursive g+xr /opt/vuls/results/
[root:html]# 
```
```bash:start_httpd
[root:html]# service httpd restart
```

これで、Vulsサーバの設定と、自分自身のスキャン設定が完了しました。

# 運用に関する検討事項
## スキャン対象サーバを追加する

今回のVulsサーバ構築と同様に、Vulsユーザを作成するのが良いでしょう

- /Opt/Vulsをホームとした、Vulsスキャン用ユーザを作成
- 複数のスキャン対象サーバを用意する際に、鍵の扱いをどうするかを検討する必要があります。
    - 全スキャン対象で同一の鍵を使う事で、confg上の管理が容易になります。但し、鍵が流出した場合は、全てのサーバにログオンできてしまうリスクがあります。
    - サーバ毎にssk-keygenすることで、鍵が流出しても被害を最小限に抑えられます。但し、config.tomlのホスト記載 及び ssh_keys以下の鍵の管理が必要になります。数十台規模では管理負荷がかなり高まる可能性があります。
        - 妥協案として、サーバグループごとに鍵を共有化する、という方法もあります。
        - ここら辺は、運用セキュリティポリシーしだいなので、現場に合った方法を選択する必要があります。

## configのdefault値を利用する

config.tomlの [default] 行を活用しましょう

- 共通項目は [default]に入れ、各サーバ用のエントリを減らす

```
[default]
port = "22"
user = "vuls"

[servers]

[servers.vuls-server]
host = "127.0.0.1"
keyPath = "/home/vuls/.ssh/id_rsa"

[servers.Cent511]
host = "xxx.xxx.xxx.xxx"
keyPath = "/opt/vuls/ssh_keys/Cent511-id_rsa"

[servers.Cent608]
host = "xxx.xxx.xxx.xxx"
keyPath = "/opt/vuls/ssh_keys/Cent608-id_rsa"
```

## アクセス権について

おおよそは以下のような方針でいいと思います。

- /opt/vuls へは、vuls:apache(750)を付与
- /opt/vuls/* については基本的に、vuls:vuls(700)を付与
- 例外的に以下の物だけは vuls:apache(750)を付与
    - /opt/vuls/results 以下へrecursiveに付与

httpd実行ユーザをvulsにすることで簡単に利用はできますが、Vuls/VulsRepo以外での「WEBアプリケーションの脆弱性」があった場合に、ssh鍵などのvulsユーザ情報が奪取される可能性があります。

![vuls-acl.png](https://qiita-image-store.s3.amazonaws.com/0/124868/e97b1e07-00d8-d321-51f3-e3c0ad28cb94.png)


## アップデートについて

VulsのREADME.mdで提供されている、方法を利用します。
vendorは削除しても良いかな。

- 可用性を期すなら、/opt/vuls/go/bin のアップデート前のバイナリを回収しておくとよいです。
  - config.tomlやcve.sqlite3とバイナリがあれば動くはず。

```bash:update_go-cve-dictionary
[vuls:~]$ cd $GOPATH/src/github.com/kotakanbe/go-cve-dictionary
[vuls:go-cve-dictionary]$ git pull
[vuls:go-cve-dictionary]$ rm -rf vendor
[vuls:go-cve-dictionary]$ make install
[vuls:go-cve-dictionary]$ 
```
```bash:update_vuls
[vuls:~]$ cd $GOPATH/src/github.com/future-architect/vuls
[vuls:Vuls]$ git pull
[vuls:Vuls]$ rm -rf vendor
[vuls:Vuls]$ make install
[vuls:Vuls]$ 
```
上記で上手くいかない場合は、一旦 /opt/vuls/go を削除して、ゼロから入れ直すことを検討してください。

## go-cve-dictionaryデータについて

- 定期的に、fetchnvd -last2y 等で更新する必要があります。
- CVEデータを別で活用したい場合は、Sqlite3ではなく mysqlに保存することをお勧めします。
    - 速度的な観点のみでは、MySQLにしたから早くなる、という感じはあまりしませんでした。
    - 他のシステムやアプリケーションでCVEデータを扱いたい場合は、Sqlite3だとつかいづらいので、MySQL化が必要です。

## トラブルシュート

- prepareは通るけど、scanではsshが応答しなくなる
    - スキャン中は多数のSSHアクセスが発生する為、IPSでブロックされる場合があります。
        - ホワイトリスト等に登録が必要です。
- sshがうまくいかない
    - 古いCentOSなどでは ecdsaなどの「古くはないかもしれない」暗号化形式がサポートされていません。
    - 一旦、ssh-keygen -t rsa で、おそらくどのssh-serverもサポートしているであろうRSA鍵を作り、これで問題ないのかを確認してください。
- VulsRepoで、Results以下のデータが選択できない
    - /opt/vuls/results へ WEBサーバがアクセスできないときに発生します。
    - 本ページの想定(apacheでhttpdを稼働)の場合は、以下を確認してください。
        - /opt/vuls が vuls:apache(750)であること
        - /opt/vuls/resultsが vuls:apache(750)であること
    - 必要に応じて、一般ユーザを作成/apacheグループに登録し、当該ディレクトリにアクセス可能かを確認する、という方法もあります。
    - /var/log/httpd/error_logをみてください。
    - vuls scanコマンドを/opt/vuls 以外のディレクトリで実行している
        - config.tomlやresultsは、カレントディレクトリを利用するのがデフォルトです。
        - /opt/vuls でvuls scan をする、/opt/vuls/resultsが存在する事、などを確認してください。
    - 時折、httpd.confで mod_cgi.so がLoadModuleされていない場合があります。
        - 有効にしてみましょう
- vuls prepareで Faild: <HOSTNAME>, err: [Unknown OS Type]でスキャンできない。
    - vulsユーザのログインシェルが bash ではない場合に発生します。
    - /etc/passwdのvuls行で、/bin/bash等が指定されている必要があります。
    - Ubuntuのaddusrだと、シェルを明示的に指定しないと駄目かもしれません。
      - 特にcron登録の際は export SHELL=/bin/bash などをしておくこと。
- scan時に、コマンドがないといわれる。
    - vulsでは、暗黙的に特定のコマンドを要求している。
    - 以下のコマンドへのPATHがあるか/インストールされているか、を確認しましょう。
      - bash, vurl, ls, pkg(yum,aptなど), rm, uname, wget
    - cron登録時は、 export PATH= で上記のパスを含めること

```
稼働するパターン
-------------------------
vuls@ns23:~$ grep vuls /etc/passwd
vuls:x:1001:1001::/opt/vuls:/bin/bash

OS特定できないパターン(bashではない)
-------------------------
vuls@ns23:~$ grep vuls /etc/passwd
vuls:x:1001:1001::/opt/vuls:
```
 
## PCIDSSに向けて

以下の追加操作を行うと、よりセキュアになります。

- ssh鍵は、Vulsサーバに複製後は、スキャン対象サーバからは削除する
- 複数のスキャン対象ホストで、ssh鍵は共有化しない
    - 鍵は共有しても問題ないと思われる
    - しかしながら安全の為、スキャン対象をグルーピングし、グループごとでssh鍵を共有するのが良いと思われる。
    - そもそも、ログイン用のpasswdとパスフレーズを変える事、は忘れずに実施する
- Vulsサーバは、VulsRepo以外のWEBアプリケーションをホストしない
    - 若しくは代替のデータ閲覧方法だけにする

## その他謝辞

- seirios さんから、PCI/DSS関連記載の指摘/助言を頂きました。内容反映しています。
- usiusi360 さんから、VulsRepoの記載について指摘頂きました。内容反映しています。