diff --git a/Makefile b/Makefile
index f47ffa49c3..f30593ca4d 100644
--- a/Makefile
+++ b/Makefile
@@ -56,7 +56,7 @@ fmtcheck:
pretest: lint vet fmtcheck
test: pretest
- $(foreach pkg,$(PKGS),go test -v $(pkg) || exit;)
+ $(foreach pkg,$(PKGS),go test -cover -v $(pkg) || exit;)
unused :
$(foreach pkg,$(PKGS),unused $(pkg);)
diff --git a/README.ja.md b/README.ja.md
index c659953644..7a4069ca00 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -89,6 +89,7 @@ Hello Vulsチュートリアルでは手動でのセットアップ方法で説
1. 設定
1. Prepare
1. Scan
+1. Reporting
1. TUI(Terminal-Based User Interface)で結果を参照する
1. Web UI([VulsRepo](https://github.com/usiusi360/vulsrepo))で結果を参照する
@@ -125,7 +126,7 @@ Vulsセットアップに必要な以下のソフトウェアをインストー
- SQLite3 or MySQL
- git
- gcc
-- go v1.7.1 or later
+- go v1.7.1 or later (The latest version is recommended)
- https://golang.org/doc/install
```bash
@@ -203,6 +204,7 @@ Vulsの設定ファイルを作成する(TOMLフォーマット)
設定ファイルのチェックを行う
```
+$ cd $HOME
$ cat config.toml
[servers]
@@ -224,42 +226,82 @@ $ vuls prepare
## Step8. Start Scanning
+
+```
+$ vuls scan
+... snip ...
+
+Scan Summary
+============
+172-31-4-82 amazon 2015.09 94 CVEs 103 updatable packages
+
+```
+
+## Step9. Reporting
+
+View one-line summary
+
+```
+$ vuls report -format-one-line-text -cvedb-path=$PWD/cve.sqlite3
+
+One Line Summary
+================
+172-31-4-82 Total: 94 (High:19 Medium:54 Low:7 ?:14) 103 updatable packages
+
+```
+
+View short summary.
+
```
-$ vuls scan -cve-dictionary-dbpath=$PWD/cve.sqlite3 -report-json
-INFO[0000] Start scanning (config: /home/ec2-user/config.toml)
-INFO[0000] Start scanning
-INFO[0000] config: /home/ec2-user/config.toml
-INFO[0000] cve-dictionary: /home/ec2-user/cve.sqlite3
+$ vuls report -format-short-text -cvedb-path=$PWD/cve.sqlite3
+172-31-4-8 (amazon 2015.09)
+===========================
+Total: 94 (High:19 Medium:54 Low:7 ?:14) 103 updatable packages
+
+CVE-2016-0705 10.0 (High) Double free vulnerability in the dsa_priv_decode function in
+ crypto/dsa/dsa_ameth.c in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g
+ allows remote attackers to cause a denial of service (memory corruption) or
+ possibly have unspecified other impact via a malformed DSA private key.
+ http://www.cvedetails.com/cve/CVE-2016-0705
+ http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0705
+ libssl1.0.0-1.0.2f-2ubuntu1 -> libssl1.0.0-1.0.2g-1ubuntu4.5
+ openssl-1.0.2f-2ubuntu1 -> openssl-1.0.2g-1ubuntu4.5
... snip ...
+````
+
+View full report.
+
+```
+$ vuls report -format-full-text -cvedb-path=$PWD/cve.sqlite3
172-31-4-82 (amazon 2015.09)
============================
-CVE-2016-0494 10.0 Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle
- Java SE 6u105, 7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to
- affect confidentiality, integrity, and availability via unknown vectors related to
- 2D.
-... snip ...
+Total: 94 (High:19 Medium:54 Low:7 ?:14) 103 updatable packages
-CVE-2016-0494
+
+CVE-2016-0705
-------------
Score 10.0 (High)
Vector (AV:N/AC:L/Au:N/C:C/I:C/A:C)
-Summary Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle Java SE 6u105,
- 7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to affect confidentiality,
- integrity, and availability via unknown vectors related to 2D.
-NVD https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0494
-MITRE https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0494
-CVE Details http://www.cvedetails.com/cve/CVE-2016-0494
-CVSS Calculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0494&vector=(AV:N/AC:L/Au:N/C:C/I:C/A:C)
-RHEL-CVE https://access.redhat.com/security/cve/CVE-2016-0494
-ALAS-2016-643 https://alas.aws.amazon.com/ALAS-2016-643.html
-Package/CPE java-1.7.0-openjdk-1.7.0.91-2.6.2.2.63.amzn1 -> java-1.7.0-openjdk-1:1.7.0.95-2.6.4.0.65.amzn1
+Summary Double free vulnerability in the dsa_priv_decode function in
+ crypto/dsa/dsa_ameth.c in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g
+ allows remote attackers to cause a denial of service (memory corruption) or
+ possibly have unspecified other impact via a malformed DSA private key.
+CWE https://cwe.mitre.org/data/definitions/.html
+NVD https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0705
+MITRE https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0705
+CVE Details http://www.cvedetails.com/cve/CVE-2016-0705
+CVSS Claculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0705&vector=(AV:N/AC:L/...
+Ubuntu-CVE http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0705
+Package libssl1.0.0-1.0.2f-2ubuntu1 -> libssl1.0.0-1.0.2g-1ubuntu4.5
+ openssl-1.0.2f-2ubuntu1 -> openssl-1.0.2g-1ubuntu4.5
+... snip ...
```
-## Step9. TUI
+## Step10. TUI
Vulsにはスキャン結果の詳細を参照できるイカしたTUI(Terminal-Based User Interface)が付属している。
@@ -269,7 +311,7 @@ $ vuls tui
![Vuls-TUI](img/hello-vuls-tui.png)
-## Step10. Web UI
+## Step11. Web UI
[VulsRepo](https://github.com/usiusi360/vulsrepo)はスキャン結果をビボットテーブルのように分析可能にするWeb UIである。
[Online Demo](http://usiusi360.github.io/vulsrepo/)があるので試してみて。
@@ -369,7 +411,7 @@ iconEmoji = ":ghost:"
authUser = "username"
notifyUsers = ["@username"]
-[mail]
+[email]
smtpAddr = "smtp.gmail.com"
smtpPort = "587"
user = "username"
@@ -450,7 +492,7 @@ host = "172.31.4.82"
- Mail section
```
- [mail]
+ [email]
smtpAddr = "smtp.gmail.com"
smtpPort = "587"
user = "username"
@@ -571,7 +613,7 @@ Prepareサブコマンドは、Vuls内部で利用する以下のパッケージ
| CentOS | 5| yum-changelog |
| CentOS | 6, 7| yum-plugin-changelog |
| Amazon | All | - |
-| RHEL | 4, 5, 6, 7 | - |
+| RHEL | 6, 7 | - |
| FreeBSD | 10 | - |
@@ -603,90 +645,31 @@ prepare:
$ vuls scan -help
scan:
scan
- [-lang=en|ja]
[-config=/path/to/config.toml]
[-results-dir=/path/to/results]
- [-cve-dictionary-dbtype=sqlite3|mysql]
- [-cve-dictionary-dbpath=/path/to/cve.sqlite3 or mysql connection string]
- [-cve-dictionary-url=http://127.0.0.1:1323]
- [-cache-dbpath=/path/to/cache.db]
- [-cvss-over=7]
- [-ignore-unscored-cves]
+ [-cachedb-path=/path/to/cache.db]
[-ssh-external]
[-containers-only]
[-skip-broken]
- [-report-azure-blob]
- [-report-json]
- [-report-mail]
- [-report-s3]
- [-report-slack]
- [-report-text]
- [-report-xml]
[-http-proxy=http://192.168.0.1:8080]
[-ask-key-password]
[-debug]
- [-debug-sql]
- [-aws-profile=default]
- [-aws-region=us-west-2]
- [-aws-s3-bucket=bucket_name]
- [-azure-account=accout]
- [-azure-key=key]
- [-azure-container=container]
- [SERVER]...
-
+ [SERVER]...
-ask-key-password
Ask ssh privatekey password before scanning
- -aws-profile string
- AWS Profile to use (default "default")
- -aws-region string
- AWS Region to use (default "us-east-1")
- -aws-s3-bucket string
- S3 bucket name
- -azure-account string
- Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
- -azure-container string
- Azure storage container name
- -azure-key string
- Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
- -cache-dbpath string
- /path/to/cache.db (local cache of changelog for Ubuntu/Debian) (default "$PWD/cache.db")
+ -cachedb-path string
+ /path/to/cache.db (local cache of changelog for Ubuntu/Debian)
-config string
- /path/to/toml (default "$PWD/config.toml")
+ /path/to/toml
-containers-only
- Scan concontainers Only. Default: Scan both of hosts and containers
- -cve-dictionary-dbpath string
- /path/to/sqlite3 (For get cve detail from cve.sqlite3)
- -cve-dictionary-dbtype string
- DB type for fetching CVE dictionary (sqlite3 or mysql) (default "sqlite3")
- -cve-dictionary-url string
- http://CVE.Dictionary (default "http://127.0.0.1:1323")
- -cvss-over float
- -cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))
+ Scan containers only. Default: Scan both of hosts and containers
-debug
debug mode
- -debug-sql
- SQL debug mode
-http-proxy string
http://proxy-url:port (default: empty)
- -ignore-unscored-cves
- Don't report the unscored CVEs
- -lang string
- [en|ja] (default "en")
- -report-json
- Write report to JSON files ($PWD/results/current)
- -report-mail
- Send report via Email
- -report-s3
- Write report to S3 (bucket/yyyyMMdd_HHmm)
- -report-slack
- Send report via Slack
- -report-text
- Write report to text files ($PWD/results/current)
- -report-xml
- Write report to XML files ($PWDresults/current)
-results-dir string
- /path/to/results (default "$PWD/results")
+ /path/to/results
-skip-broken
[For CentOS] yum update changelog with --skip-broken option
-ssh-external
@@ -714,38 +697,167 @@ Defaults:vuls !requiretty
| empty password | - | |
| with password | required | or use ssh-agent |
-## -report-json , -report-text , -report-xml option
-
-結果をファイルに出力したい場合に指定する。出力先は、`$PWD/result/current/`
-`servername.(json|txt|xml)`には、サーバごとのスキャン結果が出力される。
-
## Example: Scan all servers defined in config file
```
-$ vuls scan \
- -report-slack \
- -report-mail \
- -cvss-over=7 \
- -ask-key-password \
- -cve-dictionary-dbpath=$PWD/cve.sqlite3
+$ vuls scan -ask-key-password
```
この例では、
- SSH公開鍵認証(秘密鍵パスフレーズ)を指定
- configに定義された全サーバをスキャン
-- レポートをslack, emailに送信
-- CVSSスコアが 7.0 以上の脆弱性のみレポート
-- go-cve-dictionaryにはHTTPではなくDBに直接アクセス(go-cve-dictionaryをサーバモードで起動しない)
## Example: Scan specific servers
```
-$ vuls scan \
- -cve-dictionary-dbpath=$PWD/cve.sqlite3 \
- server1 server2
+$ vuls scan server1 server2
```
この例では、
- SSH公開鍵認証(秘密鍵パスフレーズなし)
- ノーパスワードでsudoが実行可能
- configで定義されているサーバの中の、server1, server2のみスキャン
+## Example: Scan Docker containers
+
+DockerコンテナはSSHデーモンを起動しないで運用するケースが一般的。
+ [Docker Blog:Why you don't need to run SSHd in your Docker containers](https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/)
+
+Vulsは、DockerホストにSSHで接続し、`docker exec`でDockerコンテナにコマンドを発行して脆弱性をスキャンする。
+詳細は、[Architecture section](https://github.com/future-architect/vuls#architecture)を参照
+
+- 全ての起動中のDockerコンテナをスキャン
+ `"${running}"` をcontainersに指定する
+ ```
+ [servers]
+
+ [servers.172-31-4-82]
+ host = "172.31.4.82"
+ user = "ec2-user"
+ keyPath = "/home/username/.ssh/id_rsa"
+ containers = ["${running}"]
+ ```
+
+- あるコンテナのみスキャン
+ コンテナID、または、コンテナ名を、containersに指定する。
+ 以下の例では、`container_name_a`と、`4aa37a8b63b9`のコンテナのみスキャンする
+ スキャン実行前に、コンテナが起動中か確認すること。もし起動してない場合はエラーメッセージを出力してスキャンを中断する。
+ ```
+ [servers]
+
+ [servers.172-31-4-82]
+ host = "172.31.4.82"
+ user = "ec2-user"
+ keyPath = "/home/username/.ssh/id_rsa"
+ containers = ["container_name_a", "4aa37a8b63b9"]
+ ```
+- コンテナのみをスキャンする場合(ホストはスキャンしない)
+ --containers-onlyオプションを指定する
+
+
+# Usage: Report
+
+```
+report:
+ report
+ [-lang=en|ja]
+ [-config=/path/to/config.toml]
+ [-results-dir=/path/to/results]
+ [-refresh-cve]
+ [-cvedb-type=sqlite3|mysql]
+ [-cvedb-path=/path/to/cve.sqlite3]
+ [-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
+ [-cvss-over=7]
+ [-ignore-unscored-cves]
+ [-to-email]
+ [-to-slack]
+ [-to-localfile]
+ [-to-s3]
+ [-to-azure-blob]
+ [-format-json]
+ [-format-xml]
+ [-format-one-line-text]
+ [-format-short-text]
+ [-format-full-text]
+ [-gzip]
+ [-aws-profile=default]
+ [-aws-region=us-west-2]
+ [-aws-s3-bucket=bucket_name]
+ [-azure-account=accout]
+ [-azure-key=key]
+ [-azure-container=container]
+ [-http-proxy=http://192.168.0.1:8080]
+ [-debug]
+ [-debug-sql]
+
+ [SERVER]...
+ -aws-profile string
+ AWS profile to use (default "default")
+ -aws-region string
+ AWS region to use (default "us-east-1")
+ -aws-s3-bucket string
+ S3 bucket name
+ -azure-account string
+ Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
+ -azure-container string
+ Azure storage container name
+ -azure-key string
+ Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
+ -config string
+ /path/to/toml
+ -cvedb-path string
+ /path/to/sqlite3 (For get cve detail from cve.sqlite3)
+ -cvedb-type string
+ DB type for fetching CVE dictionary (sqlite3 or mysql) (default "sqlite3")
+ -cvedb-url string
+ http://cve-dictionary.com:8080 or mysql connection string
+ -cvss-over float
+ -cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))
+ -debug
+ debug mode
+ -debug-sql
+ SQL debug mode
+ -format-full-text
+ Detail report in plain text
+ -format-json
+ JSON format
+ -format-one-line-text
+ One line summary in plain text
+ -format-short-text
+ Summary in plain text
+ -format-xml
+ XML format
+ -gzip
+ gzip compression
+ -http-proxy string
+ http://proxy-url:port (default: empty)
+ -ignore-unscored-cves
+ Don't report the unscored CVEs
+ -lang string
+ [en|ja] (default "en")
+ -refresh-cve
+ Refresh CVE information in JSON file under results dir
+ -results-dir string
+ /path/to/results
+ -to-azure-blob
+ Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/xml/txt)
+ -to-email
+ Send report via Email
+ -to-localfile
+ Write report to localfile
+ -to-s3
+ Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/xml/txt)
+ -to-slack
+ Send report via Slack
+```
+
+## Example: Send scan results to Slack
+```
+$ vuls report \
+ -to-slack \
+ -cvss-over=7 \
+ -cvedb-path=$PWD/cve.sqlite3
+```
+With this sample command, it will ..
+- Slack通知
+- CVSS score が 7.0以上のもののみ通知
+
## Example: Put results in S3 bucket
事前にAWS関連の設定を行う
@@ -755,15 +867,14 @@ $ vuls scan \
```
$ vuls scan \
- -cve-dictionary-dbpath=$PWD/cve.sqlite3 \
- -report-s3
+ -cvedb-path=$PWD/cve.sqlite3 \
+ -to-s3 \
+ -format-json \
-aws-region=ap-northeast-1 \
-aws-s3-bucket=vuls \
-aws-profile=default
```
この例では、
-- SSH公開鍵認証(秘密鍵パスフレーズなし)
-- configに定義された全サーバをスキャン
- 結果をJSON形式でS3に格納する。
- バケット名 ... vuls
- リージョン ... ap-northeast-1
@@ -772,20 +883,19 @@ $ vuls scan \
## Example: Put results in Azure Blob storage
事前にAzure Blob関連の設定を行う
-- Containerを作成
+- Azure Blob Containerを作成
```
$ vuls scan \
- -cve-dictionary-dbpath=$PWD/cve.sqlite3 \
- -report-azure-blob \
+ -cvedb-path=$PWD/cve.sqlite3 \
+ -to-azure-blob \
+ -format-xml \
-azure-container=vuls \
-azure-account=test \
-azure-key=access-key-string
```
この例では、
-- SSH公開鍵認証(秘密鍵パスフレーズなし)
-- configに定義された全サーバをスキャン
-- 結果をJSON形式でAzure Blobに格納する。
+- 結果をXML形式でBlobに格納する。
- コンテナ名 ... vuls
- ストレージアカウント名 ... test
- アクセスキー ... access-key-string
@@ -802,7 +912,7 @@ $ vuls scan \
## Example: IgnoreCves
-Slack, Mail, テキスト出力しないくないCVE IDがある場合は、設定ファイルに定義することでレポートされなくなる。
+Slack, EMail, テキスト出力しないくないCVE IDがある場合は、設定ファイルに定義することでレポートされなくなる。
ただ、JSONファイルには以下のように出力される。
- config.toml
@@ -938,43 +1048,6 @@ VulsとDependency Checkの連携すると以下の利点がある
- Dependency Checkは日本語レポートに対応していない
-# Usage: Scan Docker containers
-
-DockerコンテナはSSHデーモンを起動しないで運用するケースが一般的。
- [Docker Blog:Why you don't need to run SSHd in your Docker containers](https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/)
-
-Vulsは、DockerホストにSSHで接続し、`docker exec`でDockerコンテナにコマンドを発行して脆弱性をスキャンする。
-詳細は、[Architecture section](https://github.com/future-architect/vuls#architecture)を参照
-
-- 全ての起動中のDockerコンテナをスキャン
- `"${running}"` をcontainersに指定する
- ```
- [servers]
-
- [servers.172-31-4-82]
- host = "172.31.4.82"
- user = "ec2-user"
- keyPath = "/home/username/.ssh/id_rsa"
- containers = ["${running}"]
- ```
-
-- あるコンテナのみスキャン
- コンテナID、または、コンテナ名を、containersに指定する。
- 以下の例では、`container_name_a`と、`4aa37a8b63b9`のコンテナのみスキャンする
- スキャン実行前に、コンテナが起動中か確認すること。もし起動してない場合はエラーメッセージを出力してスキャンを中断する。
- ```
- [servers]
-
- [servers.172-31-4-82]
- host = "172.31.4.82"
- user = "ec2-user"
- keyPath = "/home/username/.ssh/id_rsa"
- containers = ["container_name_a", "4aa37a8b63b9"]
- ```
-- コンテナのみをスキャンする場合(ホストはスキャンしない)
- --containers-onlyオプションを指定する
-
-
# Usage: TUI
## Display the latest scan results
@@ -982,13 +1055,26 @@ Vulsは、DockerホストにSSHで接続し、`docker exec`でDockerコンテナ
```
$ vuls tui -h
tui:
- tui [-results-dir=/path/to/results]
+ tui
+ [-cvedb-type=sqlite3|mysql]
+ [-cvedb-path=/path/to/cve.sqlite3]
+ [-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
+ [-results-dir=/path/to/results]
+ [-refresh-cve]
+ [-debug-sql]
- -results-dir string
- /path/to/results (default "$PWD/results")
+ -cvedb-path string
+ /path/to/sqlite3 (For get cve detail from cve.sqlite3)
+ -cvedb-type string
+ DB type for fetching CVE dictionary (sqlite3 or mysql)
+ -cvedb-url string
+ http://cve-dictionary.com:8080 or mysql connection string
-debug-sql
- debug SQL
-
+ debug SQL
+ -refresh-cve
+ Refresh CVE information in JSON file under results dir
+ -results-dir string
+ /path/to/results
```
Key binding is below.
diff --git a/README.md b/README.md
index 0a28a6e6ea..48715b4b18 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ Vuls is a tool created to solve the problems listed above. It has the following
- Auto generation of configuration file template
- Auto detection of servers set using CIDR, generate configuration file template
- Email and Slack notification is possible (supports Japanese language)
-- Scan result is viewable on accessory software, TUI Viewer terminal or Web UI ([VulsRepo](https://github.com/usiusi360/vulsrepo)).
+- Scan result is viewable on accessory software, TUI Viewer on terminal or Web UI ([VulsRepo](https://github.com/usiusi360/vulsrepo)).
----
@@ -69,16 +69,13 @@ Vuls is a tool created to solve the problems listed above. It has the following
# Setup Vuls
-There are 3 ways to setup Vuls.
+There are 2 ways to setup Vuls.
- Docker container
Dockernized-Vuls with vulsrepo UI in it.
You can run install and run Vuls on your machine with only a few commands.
see https://github.com/future-architect/vuls/tree/master/setup/docker
-- Chef
-see https://github.com/sadayuki-matsuno/vuls-cookbook
-
- Manually
Hello Vuls Tutorial shows how to setup vuls manually.
@@ -97,6 +94,7 @@ This can be done in the following steps.
1. Configuration
1. Prepare
1. Scan
+1. Reporting
1. TUI(Terminal-Based User Interface)
1. Web UI ([VulsRepo](https://github.com/usiusi360/vulsrepo))
@@ -133,7 +131,7 @@ Vuls requires the following packages.
- SQLite3 or MySQL
- git
- gcc
-- go v1.7.1 or later
+- go v1.7.1 or later (The latest version is recommended)
- https://golang.org/doc/install
```bash
@@ -200,6 +198,7 @@ Create a config file(TOML format).
Then check the config.
```
+$ cd $HOME
$ cat config.toml
[servers]
@@ -222,51 +221,90 @@ see [Usage: Prepare](https://github.com/future-architect/vuls#usage-prepare)
## Step8. Start Scanning
```
-$ vuls scan -cve-dictionary-dbpath=$PWD/cve.sqlite3 -report-json
-INFO[0000] Start scanning (config: /home/ec2-user/config.toml)
-INFO[0000] Start scanning
-INFO[0000] config: /home/ec2-user/config.toml
-INFO[0000] cve-dictionary: /home/ec2-user/cve.sqlite3
+$ vuls scan
+... snip ...
+
+Scan Summary
+============
+172-31-4-82 amazon 2015.09 94 CVEs 103 updatable packages
+
+```
+
+## Step9. Reporting
+
+View one-line summary
+
+```
+$ vuls report -format-one-line-text -cvedb-path=$PWD/cve.sqlite3
+
+One Line Summary
+================
+172-31-4-82 Total: 94 (High:19 Medium:54 Low:7 ?:14) 103 updatable packages
+
+```
+
+View short summary.
+
+```
+$ vuls report -format-short-text
+172-31-4-8 (amazon 2015.09)
+===========================
+Total: 94 (High:19 Medium:54 Low:7 ?:14) 103 updatable packages
+
+CVE-2016-0705 10.0 (High) Double free vulnerability in the dsa_priv_decode function in
+ crypto/dsa/dsa_ameth.c in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g
+ allows remote attackers to cause a denial of service (memory corruption) or
+ possibly have unspecified other impact via a malformed DSA private key.
+ http://www.cvedetails.com/cve/CVE-2016-0705
+ http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0705
+ libssl1.0.0-1.0.2f-2ubuntu1 -> libssl1.0.0-1.0.2g-1ubuntu4.5
+ openssl-1.0.2f-2ubuntu1 -> openssl-1.0.2g-1ubuntu4.5
... snip ...
+````
+
+View full report.
+
+```
+$ vuls report -format-full-text
172-31-4-82 (amazon 2015.09)
============================
-CVE-2016-0494 10.0 Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle
- Java SE 6u105, 7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to
- affect confidentiality, integrity, and availability via unknown vectors related to
- 2D.
-... snip ...
+Total: 94 (High:19 Medium:54 Low:7 ?:14) 103 updatable packages
-CVE-2016-0494
+
+CVE-2016-0705
-------------
Score 10.0 (High)
Vector (AV:N/AC:L/Au:N/C:C/I:C/A:C)
-Summary Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle Java SE 6u105,
- 7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to affect confidentiality,
- integrity, and availability via unknown vectors related to 2D.
-NVD https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0494
-MITRE https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0494
-CVE Details http://www.cvedetails.com/cve/CVE-2016-0494
-CVSS Calculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0494&vector=(AV:N/AC:L/Au:N/C:C/I:C/A:C)
-RHEL-CVE https://access.redhat.com/security/cve/CVE-2016-0494
-ALAS-2016-643 https://alas.aws.amazon.com/ALAS-2016-643.html
-Package/CPE java-1.7.0-openjdk-1.7.0.91-2.6.2.2.63.amzn1 -> java-1.7.0-openjdk-1:1.7.0.95-2.6.4.0.65.amzn1
+Summary Double free vulnerability in the dsa_priv_decode function in
+ crypto/dsa/dsa_ameth.c in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g
+ allows remote attackers to cause a denial of service (memory corruption) or
+ possibly have unspecified other impact via a malformed DSA private key.
+CWE https://cwe.mitre.org/data/definitions/.html
+NVD https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0705
+MITRE https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0705
+CVE Details http://www.cvedetails.com/cve/CVE-2016-0705
+CVSS Claculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0705&vector=(AV:N/AC:L/...
+Ubuntu-CVE http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0705
+Package libssl1.0.0-1.0.2f-2ubuntu1 -> libssl1.0.0-1.0.2g-1ubuntu4.5
+ openssl-1.0.2f-2ubuntu1 -> openssl-1.0.2g-1ubuntu4.5
+... snip ...
```
-## Step9. TUI
+## Step10. TUI
Vuls has Terminal-Based User Interface to display the scan result.
```
-$ vuls tui
+$ vuls tui
```
![Vuls-TUI](img/hello-vuls-tui.png)
-## Step10. Web UI
+## Step11. Web UI
[VulsRepo](https://github.com/usiusi360/vulsrepo) is a awesome Web UI for Vuls.
Check it out the [Online Demo](http://usiusi360.github.io/vulsrepo/).
@@ -288,11 +326,8 @@ see https://github.com/future-architect/vuls/tree/master/setup/docker
## Scanning Flow
![Vuls-Scan-Flow](img/vuls-scan-flow.png)
-- Scan vulnerabilities on the servers via SSH and create a list of the CVE ID
+- Scan vulnerabilities on the servers via SSH and collect a list of the CVE ID
- To scan Docker containers, Vuls connect via ssh to the Docker host and then `docker exec` to the containers. So, no need to run sshd daemon on the containers.
-- Fetch more detailed information of the detected CVE from go-cve-dictionary
-- Send a report by Slack and Email
-- Write scan results to JSON file to show the latest report on your terminal
----
# Performance Considerations
@@ -323,16 +358,20 @@ High speed scan and resource usage is light because Vuls can get CVE IDs by usin
# Use Cases
-## Scan all servers
+## Scan All Servers
![Vuls-Usecase1](img/vuls-usecase-elb-rails-rds-all.png)
-## Scan a single server
+## Scan a Single Server
web/app server in the same configuration under the load balancer
![Vuls-Usecase2](img/vuls-usecase-elb-rails-rds-single.png)
+## Scan Staging Environment
+
+If there is a staging environment with the same configuration as the production environment, you can scan the server in staging environment
+
----
# Support OS
@@ -373,7 +412,7 @@ iconEmoji = ":ghost:"
authUser = "username"
notifyUsers = ["@username"]
-[mail]
+[email]
smtpAddr = "smtp.gmail.com"
smtpPort = "587"
user = "username"
@@ -457,9 +496,9 @@ You can customize your configuration using this template.
If you set `["@foo", "@bar"]` to notifyUsers, @foo @bar will be included in text.
So @foo, @bar can receive mobile push notifications on their smartphone.
-- Mail section
+- EMail section
```
- [mail]
+ [email]
smtpAddr = "smtp.gmail.com"
smtpPort = "587"
user = "username"
@@ -577,7 +616,7 @@ Prepare subcommand installs required packages on each server.
| CentOS | 5| yum-changelog |
| CentOS | 6, 7| yum-plugin-changelog |
| Amazon | All | - |
-| RHEL | 4, 5, 6, 7 | - |
+| RHEL | 6, 7 | - |
| FreeBSD | 10 | - |
@@ -610,94 +649,34 @@ prepare:
# Usage: Scan
```
-
$ vuls scan -help
scan:
scan
- [-lang=en|ja]
[-config=/path/to/config.toml]
[-results-dir=/path/to/results]
- [-cve-dictionary-dbtype=sqlite3|mysql]
- [-cve-dictionary-dbpath=/path/to/cve.sqlite3 or mysql connection string]
- [-cve-dictionary-url=http://127.0.0.1:1323]
- [-cache-dbpath=/path/to/cache.db]
- [-cvss-over=7]
- [-ignore-unscored-cves]
+ [-cachedb-path=/path/to/cache.db]
[-ssh-external]
[-containers-only]
[-skip-broken]
- [-report-azure-blob]
- [-report-json]
- [-report-mail]
- [-report-s3]
- [-report-slack]
- [-report-text]
- [-report-xml]
[-http-proxy=http://192.168.0.1:8080]
[-ask-key-password]
[-debug]
- [-debug-sql]
- [-aws-profile=default]
- [-aws-region=us-west-2]
- [-aws-s3-bucket=bucket_name]
- [-azure-account=accout]
- [-azure-key=key]
- [-azure-container=container]
- [SERVER]...
-
+ [SERVER]...
-ask-key-password
Ask ssh privatekey password before scanning
- -aws-profile string
- AWS Profile to use (default "default")
- -aws-region string
- AWS Region to use (default "us-east-1")
- -aws-s3-bucket string
- S3 bucket name
- -azure-account string
- Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
- -azure-container string
- Azure storage container name
- -azure-key string
- Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
- -cache-dbpath string
- /path/to/cache.db (local cache of changelog for Ubuntu/Debian) (default "$PWD/cache.db")
+ -cachedb-path string
+ /path/to/cache.db (local cache of changelog for Ubuntu/Debian)
-config string
- /path/to/toml (default "$PWD/config.toml")
+ /path/to/toml
-containers-only
- Scan concontainers Only. Default: Scan both of hosts and containers
- -cve-dictionary-dbpath string
- /path/to/sqlite3 (For get cve detail from cve.sqlite3)
- -cve-dictionary-dbtype string
- DB type for fetching CVE dictionary (sqlite3 or mysql) (default "sqlite3")
- -cve-dictionary-url string
- http://CVE.Dictionary (default "http://127.0.0.1:1323")
- -cvss-over float
- -cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))
+ Scan containers only. Default: Scan both of hosts and containers
-debug
debug mode
- -debug-sql
- SQL debug mode
-http-proxy string
http://proxy-url:port (default: empty)
- -ignore-unscored-cves
- Don't report the unscored CVEs
- -lang string
- [en|ja] (default "en")
- -report-json
- Write report to JSON files ($PWD/results/current)
- -report-mail
- Send report via Email
- -report-s3
- Write report to S3 (bucket/yyyyMMdd_HHmm)
- -report-slack
- Send report via Slack
- -report-text
- Write report to text files ($PWD/results/current)
- -report-xml
- Write report to XML files ($PWDresults/current)
-results-dir string
- /path/to/results (default "$PWD/results")
+ /path/to/results
-skip-broken
[For CentOS] yum update changelog with --skip-broken option
-ssh-external
@@ -726,73 +705,200 @@ Defaults:vuls !requiretty
| empty password | - | |
| with password | required | or use ssh-agent |
-## -report-json , -report-text , -report-xml option
-
-At the end of the scan, scan results will be available in the `$PWD/result/current/` directory.
-`servername.(json|txt|xml)` includes the scan result of the server.
-
## Example: Scan all servers defined in config file
```
-$ vuls scan \
- --report-slack \
- --report-mail \
- --cvss-over=7 \
- -ask-key-password \
- -cve-dictionary-dbpath=$PWD/cve.sqlite3
+$ vuls scan -ask-key-password
```
With this sample command, it will ..
- Ask SSH key password before scanning
- Scan all servers defined in config file
-- Send scan results to slack and email
-- Only Report CVEs that CVSS score is over 7
-- Print scan result to terminal
## Example: Scan specific servers
```
-$ vuls scan \
- -cve-dictionary-dbpath=$PWD/cve.sqlite3 \
- server1 server2
+$ vuls scan server1 server2
```
With this sample command, it will ..
- Use SSH Key-Based authentication with empty password (without -ask-key-password option)
- Scan only 2 servers (server1, server2)
-- Print scan result to terminal
+
+## Example: Scan Docker containers
+
+It is common that keep Docker containers running without SSHd daemon.
+see [Docker Blog:Why you don't need to run SSHd in your Docker containers](https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/)
+
+Vuls scans Docker containers via `docker exec` instead of SSH.
+For more details, see [Architecture section](https://github.com/future-architect/vuls#architecture)
+
+- To scan all of running containers
+ `"${running}"` needs to be set in the containers item.
+ ```
+ [servers]
+
+ [servers.172-31-4-82]
+ host = "172.31.4.82"
+ user = "ec2-user"
+ keyPath = "/home/username/.ssh/id_rsa"
+ containers = ["${running}"]
+ ```
+
+- To scan specific containers
+ The container ID or container name needs to be set in the containers item.
+ In the following example, only `container_name_a` and `4aa37a8b63b9` will be scanned.
+ Be sure to check these containers are running state before scanning.
+ If specified containers are not running, Vuls gives up scanning with printing error message.
+ ```
+ [servers]
+
+ [servers.172-31-4-82]
+ host = "172.31.4.82"
+ user = "ec2-user"
+ keyPath = "/home/username/.ssh/id_rsa"
+ containers = ["container_name_a", "4aa37a8b63b9"]
+ ```
+- To scan containers only
+ - --containers-only option is available.
+
+----
+
+# Usage: Report
+
+```
+report:
+ report
+ [-lang=en|ja]
+ [-config=/path/to/config.toml]
+ [-results-dir=/path/to/results]
+ [-refresh-cve]
+ [-cvedb-type=sqlite3|mysql]
+ [-cvedb-path=/path/to/cve.sqlite3]
+ [-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
+ [-cvss-over=7]
+ [-ignore-unscored-cves]
+ [-to-email]
+ [-to-slack]
+ [-to-localfile]
+ [-to-s3]
+ [-to-azure-blob]
+ [-format-json]
+ [-format-xml]
+ [-format-one-line-text]
+ [-format-short-text]
+ [-format-full-text]
+ [-gzip]
+ [-aws-profile=default]
+ [-aws-region=us-west-2]
+ [-aws-s3-bucket=bucket_name]
+ [-azure-account=accout]
+ [-azure-key=key]
+ [-azure-container=container]
+ [-http-proxy=http://192.168.0.1:8080]
+ [-debug]
+ [-debug-sql]
+
+ [SERVER]...
+ -aws-profile string
+ AWS profile to use (default "default")
+ -aws-region string
+ AWS region to use (default "us-east-1")
+ -aws-s3-bucket string
+ S3 bucket name
+ -azure-account string
+ Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
+ -azure-container string
+ Azure storage container name
+ -azure-key string
+ Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
+ -config string
+ /path/to/toml
+ -cvedb-path string
+ /path/to/sqlite3 (For get cve detail from cve.sqlite3)
+ -cvedb-type string
+ DB type for fetching CVE dictionary (sqlite3 or mysql) (default "sqlite3")
+ -cvedb-url string
+ http://cve-dictionary.com:8080 or mysql connection string
+ -cvss-over float
+ -cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))
+ -debug
+ debug mode
+ -debug-sql
+ SQL debug mode
+ -format-full-text
+ Detail report in plain text
+ -format-json
+ JSON format
+ -format-one-line-text
+ One line summary in plain text
+ -format-short-text
+ Summary in plain text
+ -format-xml
+ XML format
+ -gzip
+ gzip compression
+ -http-proxy string
+ http://proxy-url:port (default: empty)
+ -ignore-unscored-cves
+ Don't report the unscored CVEs
+ -lang string
+ [en|ja] (default "en")
+ -refresh-cve
+ Refresh CVE information in JSON file under results dir
+ -results-dir string
+ /path/to/results
+ -to-azure-blob
+ Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/xml/txt)
+ -to-email
+ Send report via Email
+ -to-localfile
+ Write report to localfile
+ -to-s3
+ Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/xml/txt)
+ -to-slack
+ Send report via Slack
+```
+
+## Example: Send scan results to Slack
+```
+$ vuls report \
+ -to-slack \
+ -cvss-over=7 \
+ -cvedb-path=$PWD/cve.sqlite3
+```
+With this sample command, it will ..
+- Send scan results to slack
+- Only Report CVEs that CVSS score is over 7
## Example: Put results in S3 bucket
-To put results in S3 bucket, configure following settings in AWS before scanning.
+To put results in S3 bucket, configure following settings in AWS before reporting.
- Create S3 bucket. see [Creating a Bucket](http://docs.aws.amazon.com/AmazonS3/latest/UG/CreatingaBucket.html)
- Create access key. The access key must have read and write access to the AWS S3 bucket. see [Managing Access Keys for IAM Users](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html)
- Configure the security credentials. see [Configuring the AWS Command Line Interface](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html)
```
-$ vuls scan \
- -cve-dictionary-dbpath=$PWD/cve.sqlite3 \
- -report-s3 \
+$ vuls report \
+ -cvedb-path=$PWD/cve.sqlite3 \
+ -to-s3 \
+ -format-json \
-aws-region=ap-northeast-1 \
-aws-s3-bucket=vuls \
-aws-profile=default
```
With this sample command, it will ..
-- Use SSH Key-Based authentication with empty password (without -ask-key-password option)
-- Scan all servers defined in config file
- Put scan result(JSON) in S3 bucket. The bucket name is "vuls" in ap-northeast-1 and profile is "default"
## Example: Put results in Azure Blob storage
-To put results in Azure Blob Storage, configure following settings in Azure before scanning.
-- Create a container
+To put results in Azure Blob Storage, configure following settings in Azure before reporting.
+- Create a Azure Blob container
```
$ vuls scan \
- -cve-dictionary-dbpath=$PWD/cve.sqlite3 \
+ -cvedb-path=$PWD/cve.sqlite3 \
-report-azure-blob \
-azure-container=vuls \
-azure-account=test \
-azure-key=access-key-string
```
With this sample command, it will ..
-- Use SSH Key-Based authentication with empty password (without -ask-key-password option)
-- Scan all servers defined in config file
- Put scan result(JSON) in Azure Blob Storage. The container name is "vuls", storage account is "test" and accesskey is "access-key-string"
account and access key can be defined in environment variables.
@@ -800,14 +906,14 @@ account and access key can be defined in environment variables.
$ export AZURE_STORAGE_ACCOUNT=test
$ export AZURE_STORAGE_ACCESS_KEY=access-key-string
$ vuls scan \
- -cve-dictionary-dbpath=$PWD/cve.sqlite3 \
+ -cvedb-path=$PWD/cve.sqlite3 \
-report-azure-blob \
-azure-container=vuls
```
## Example: IgnoreCves
-Define ignoreCves in config if you don't want to report(slack, mail, text...) specific CVE IDs. But these ignoreCves will be output to JSON file like below.
+Define ignoreCves in config if you don't want to report(Slack, EMail, Text...) specific CVE IDs. But these ignoreCves will be output to JSON file like below.
- config.toml
```toml
@@ -886,8 +992,8 @@ optional = [
```
$ vuls scan \
- -cve-dictionary-dbtype=mysql \
- -cve-dictionary-dbpath="user:pass@tcp(localhost:3306)/dbname?parseTime=true"
+ -cvedb-type=mysql \
+ -cvedb-url="user:pass@tcp(localhost:3306)/dbname?parseTime=true"
```
----
@@ -941,42 +1047,6 @@ How to integrate Vuls with OWASP Dependency Check
```
-# Usage: Scan Docker containers
-
-It is common that keep Docker containers running without SSHd daemon.
-see [Docker Blog:Why you don't need to run SSHd in your Docker containers](https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/)
-
-Vuls scans Docker containers via `docker exec` instead of SSH.
-For more details, see [Architecture section](https://github.com/future-architect/vuls#architecture)
-
-- To scan all of running containers
- `"${running}"` needs to be set in the containers item.
- ```
- [servers]
-
- [servers.172-31-4-82]
- host = "172.31.4.82"
- user = "ec2-user"
- keyPath = "/home/username/.ssh/id_rsa"
- containers = ["${running}"]
- ```
-
-- To scan specific containers
- The container ID or container name needs to be set in the containers item.
- In the following example, only `container_name_a` and `4aa37a8b63b9` will be scanned.
- Be sure to check these containers are running state before scanning.
- If specified containers are not running, Vuls gives up scanning with printing error message.
- ```
- [servers]
-
- [servers.172-31-4-82]
- host = "172.31.4.82"
- user = "ec2-user"
- keyPath = "/home/username/.ssh/id_rsa"
- containers = ["container_name_a", "4aa37a8b63b9"]
- ```
-- To scan containers only
- - --containers-only option is available.
# Usage: TUI
@@ -984,15 +1054,27 @@ For more details, see [Architecture section](https://github.com/future-architect
## Display the latest scan results
```
-$ vuls tui -h
tui:
- tui [-results-dir=/path/to/results]
+ tui
+ [-cvedb-type=sqlite3|mysql]
+ [-cvedb-path=/path/to/cve.sqlite3]
+ [-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
+ [-results-dir=/path/to/results]
+ [-refresh-cve]
+ [-debug-sql]
- -results-dir string
- /path/to/results (default "$PWD/results")
+ -cvedb-path string
+ /path/to/sqlite3 (For get cve detail from cve.sqlite3) (default "/Users/kotakanbe/go/src/github.com/future-architect/vuls/cve.sqlite3")
+ -cvedb-type string
+ DB type for fetching CVE dictionary (sqlite3 or mysql) (default "sqlite3")
+ -cvedb-url string
+ http://cve-dictionary.com:8080 or mysql connection string
-debug-sql
- debug SQL
-
+ debug SQL
+ -refresh-cve
+ Refresh CVE information in JSON file under results dir
+ -results-dir string
+ /path/to/results (default "/Users/kotakanbe/go/src/github.com/future-architect/vuls/results")
```
Key binding is below.
@@ -1011,18 +1093,14 @@ For details, see https://github.com/future-architect/vuls/blob/master/report/tui
- Display the list of scan results.
```
$ vuls history
-20160524_1950 scanned 1 servers: amazon2
-20160524_1940 scanned 2 servers: amazon1, romantic_goldberg
-```
-
-- Display the result of scan 20160524_1949
-```
-$ vuls tui 20160524_1950
+2016-12-30T10:34:38+09:00 1 servers: u16
+2016-12-28T19:15:19+09:00 1 servers: ama
+2016-12-28T19:10:03+09:00 1 servers: cent6
```
-- Display the result of scan 20160524_1948
+- Display the result of scan 2016-12-30T10:34:38+09:00
```
-$ vuls tui 20160524_1940
+$ vuls tui 2016-12-30T10:34:38+09:00
```
# Display the previous scan results using peco
@@ -1040,10 +1118,10 @@ Run go-cve-dictionary as server mode before scanning on 192.168.10.1
$ go-cve-dictionary server -bind=192.168.10.1 -port=1323
```
-Run Vuls with -cve-dictionary-url option.
+Run Vuls with -cvedb-url option.
```
-$ vuls scan -cve-dictionary-url=http://192.168.0.1:1323
+$ vuls scan -cvedb-url=http://192.168.0.1:1323
```
# Usage: Update NVD Data
diff --git a/commands/configtest.go b/commands/configtest.go
index e80f6f4f8a..2bcda75089 100644
--- a/commands/configtest.go
+++ b/commands/configtest.go
@@ -146,7 +146,7 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
Log := util.NewCustomLogger(c.ServerInfo{})
Log.Info("Validating Config...")
- if !c.Conf.Validate() {
+ if !c.Conf.ValidateOnConfigtest() {
return subcommands.ExitUsageError
}
diff --git a/commands/discover.go b/commands/discover.go
index 4aa32e2f57..7d674250ee 100644
--- a/commands/discover.go
+++ b/commands/discover.go
@@ -98,7 +98,7 @@ iconEmoji = ":ghost:"
authUser = "username"
notifyUsers = ["@username"]
-[mail]
+[email]
smtpAddr = "smtp.gmail.com"
smtpPort = "587"
user = "username"
diff --git a/commands/history.go b/commands/history.go
index 9593286650..1c6acf4b94 100644
--- a/commands/history.go
+++ b/commands/history.go
@@ -27,7 +27,6 @@ import (
"strings"
c "github.com/future-architect/vuls/config"
- "github.com/future-architect/vuls/report"
"github.com/google/subcommands"
)
@@ -70,11 +69,11 @@ func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{
c.Conf.ResultsDir = p.resultsDir
var err error
- var jsonDirs report.JSONDirs
- if jsonDirs, err = report.GetValidJSONDirs(); err != nil {
+ var dirs jsonDirs
+ if dirs, err = lsValidJSONDirs(); err != nil {
return subcommands.ExitFailure
}
- for _, d := range jsonDirs {
+ for _, d := range dirs {
var files []os.FileInfo
if files, err = ioutil.ReadDir(d); err != nil {
return subcommands.ExitFailure
@@ -89,7 +88,7 @@ func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{
}
splitPath := strings.Split(d, string(os.PathSeparator))
timeStr := splitPath[len(splitPath)-1]
- fmt.Printf("%s scanned %d servers: %s\n",
+ fmt.Printf("%s %d servers: %s\n",
timeStr,
len(hosts),
strings.Join(hosts, ", "),
diff --git a/commands/prepare.go b/commands/prepare.go
index 02389ea4c4..cf1519acce 100644
--- a/commands/prepare.go
+++ b/commands/prepare.go
@@ -50,8 +50,9 @@ func (*PrepareCmd) Synopsis() string {
return `Install required packages to scan.
CentOS: yum-plugin-security, yum-plugin-changelog
Amazon: None
- RHEL: TODO
+ RHEL: None
Ubuntu: None
+ Debian: aptitude
`
}
@@ -155,7 +156,7 @@ func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{
c.Conf.AssumeYes = p.assumeYes
logrus.Info("Validating Config...")
- if !c.Conf.Validate() {
+ if !c.Conf.ValidateOnPrepare() {
return subcommands.ExitUsageError
}
// Set up custom logger
diff --git a/commands/report.go b/commands/report.go
new file mode 100644
index 0000000000..17095dccf5
--- /dev/null
+++ b/commands/report.go
@@ -0,0 +1,386 @@
+/* Vuls - Vulnerability Scanner
+Copyright (C) 2016 Future Architect, Inc. Japan.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+package commands
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "path/filepath"
+
+ c "github.com/future-architect/vuls/config"
+ "github.com/future-architect/vuls/cveapi"
+ "github.com/future-architect/vuls/models"
+ "github.com/future-architect/vuls/report"
+ "github.com/future-architect/vuls/util"
+ "github.com/google/subcommands"
+ "github.com/kotakanbe/go-cve-dictionary/log"
+)
+
+// ReportCmd is subcommand for reporting
+type ReportCmd struct {
+ lang string
+ debug bool
+ debugSQL bool
+ configPath string
+ resultsDir string
+ refreshCve bool
+
+ cvssScoreOver float64
+ ignoreUnscoredCves bool
+ httpProxy string
+
+ cvedbtype string
+ cvedbpath string
+ cveDictionaryURL string
+
+ toSlack bool
+ toEMail bool
+ toLocalFile bool
+ toS3 bool
+ toAzureBlob bool
+
+ formatJSON bool
+ formatXML bool
+ formatOneLineText bool
+ formatShortText bool
+ formatFullText bool
+
+ gzip bool
+
+ awsProfile string
+ awsS3Bucket string
+ awsRegion string
+
+ azureAccount string
+ azureKey string
+ azureContainer string
+}
+
+// Name return subcommand name
+func (*ReportCmd) Name() string { return "report" }
+
+// Synopsis return synopsis
+func (*ReportCmd) Synopsis() string { return "Reporting" }
+
+// Usage return usage
+func (*ReportCmd) Usage() string {
+ return `report:
+ report
+ [-lang=en|ja]
+ [-config=/path/to/config.toml]
+ [-results-dir=/path/to/results]
+ [-refresh-cve]
+ [-cvedb-type=sqlite3|mysql]
+ [-cvedb-path=/path/to/cve.sqlite3]
+ [-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
+ [-cvss-over=7]
+ [-ignore-unscored-cves]
+ [-to-email]
+ [-to-slack]
+ [-to-localfile]
+ [-to-s3]
+ [-to-azure-blob]
+ [-format-json]
+ [-format-xml]
+ [-format-one-line-text]
+ [-format-short-text]
+ [-format-full-text]
+ [-gzip]
+ [-aws-profile=default]
+ [-aws-region=us-west-2]
+ [-aws-s3-bucket=bucket_name]
+ [-azure-account=accout]
+ [-azure-key=key]
+ [-azure-container=container]
+ [-http-proxy=http://192.168.0.1:8080]
+ [-debug]
+ [-debug-sql]
+
+ [SERVER]...
+`
+}
+
+// SetFlags set flag
+func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
+ f.StringVar(&p.lang, "lang", "en", "[en|ja]")
+ f.BoolVar(&p.debug, "debug", false, "debug mode")
+ f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
+
+ wd, _ := os.Getwd()
+
+ defaultConfPath := filepath.Join(wd, "config.toml")
+ f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
+
+ defaultResultsDir := filepath.Join(wd, "results")
+ f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
+
+ f.BoolVar(
+ &p.refreshCve,
+ "refresh-cve",
+ false,
+ "Refresh CVE information in JSON file under results dir")
+
+ f.StringVar(
+ &p.cvedbtype,
+ "cvedb-type",
+ "sqlite3",
+ "DB type for fetching CVE dictionary (sqlite3 or mysql)")
+
+ defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
+ f.StringVar(
+ &p.cvedbpath,
+ "cvedb-path",
+ defaultCveDBPath,
+ "/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
+
+ f.StringVar(
+ &p.cveDictionaryURL,
+ "cvedb-url",
+ "",
+ "http://cve-dictionary.com:8080 or mysql connection string")
+
+ f.Float64Var(
+ &p.cvssScoreOver,
+ "cvss-over",
+ 0,
+ "-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
+
+ f.BoolVar(
+ &p.ignoreUnscoredCves,
+ "ignore-unscored-cves",
+ false,
+ "Don't report the unscored CVEs")
+
+ f.StringVar(
+ &p.httpProxy,
+ "http-proxy",
+ "",
+ "http://proxy-url:port (default: empty)")
+
+ f.BoolVar(&p.formatJSON,
+ "format-json",
+ false,
+ fmt.Sprintf("JSON format"))
+
+ f.BoolVar(&p.formatXML,
+ "format-xml",
+ false,
+ fmt.Sprintf("XML format"))
+
+ f.BoolVar(&p.formatOneLineText,
+ "format-one-line-text",
+ false,
+ fmt.Sprintf("One line summary in plain text"))
+
+ f.BoolVar(&p.formatShortText,
+ "format-short-text",
+ false,
+ fmt.Sprintf("Summary in plain text"))
+
+ f.BoolVar(&p.formatFullText,
+ "format-full-text",
+ false,
+ fmt.Sprintf("Detail report in plain text"))
+
+ f.BoolVar(&p.gzip, "gzip", false, "gzip compression")
+
+ f.BoolVar(&p.toSlack, "to-slack", false, "Send report via Slack")
+ f.BoolVar(&p.toEMail, "to-email", false, "Send report via Email")
+ f.BoolVar(&p.toLocalFile,
+ "to-localfile",
+ false,
+ fmt.Sprintf("Write report to localfile"))
+
+ f.BoolVar(&p.toS3,
+ "to-s3",
+ false,
+ "Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/xml/txt)")
+ f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
+ f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
+ f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
+
+ f.BoolVar(&p.toAzureBlob,
+ "to-azure-blob",
+ false,
+ "Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/xml/txt)")
+ f.StringVar(&p.azureAccount,
+ "azure-account",
+ "",
+ "Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified")
+ f.StringVar(&p.azureKey,
+ "azure-key",
+ "",
+ "Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified")
+ f.StringVar(&p.azureContainer, "azure-container", "", "Azure storage container name")
+}
+
+// Execute execute
+func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
+ c.Conf.Debug = p.debug
+ c.Conf.DebugSQL = p.debugSQL
+ Log := util.NewCustomLogger(c.ServerInfo{})
+
+ if err := c.Load(p.configPath, ""); err != nil {
+ Log.Errorf("Error loading %s, %s", p.configPath, err)
+ return subcommands.ExitUsageError
+ }
+
+ c.Conf.Lang = p.lang
+ c.Conf.ResultsDir = p.resultsDir
+ c.Conf.CveDBType = p.cvedbtype
+ c.Conf.CveDBPath = p.cvedbpath
+ c.Conf.CveDictionaryURL = p.cveDictionaryURL
+ c.Conf.CvssScoreOver = p.cvssScoreOver
+ c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
+ c.Conf.HTTPProxy = p.httpProxy
+
+ jsonDir, err := jsonDir(f.Args())
+ if err != nil {
+ log.Errorf("Failed to read from JSON: %s", err)
+ return subcommands.ExitFailure
+ }
+
+ c.Conf.FormatXML = p.formatXML
+ c.Conf.FormatJSON = p.formatJSON
+ c.Conf.FormatOneLineText = p.formatOneLineText
+ c.Conf.FormatShortText = p.formatShortText
+ c.Conf.FormatFullText = p.formatFullText
+
+ c.Conf.GZIP = p.gzip
+
+ // report
+ reports := []report.ResultWriter{
+ report.StdoutWriter{},
+ }
+
+ if p.toSlack {
+ reports = append(reports, report.SlackWriter{})
+ }
+
+ if p.toEMail {
+ reports = append(reports, report.EMailWriter{})
+ }
+
+ if p.toLocalFile {
+ reports = append(reports, report.LocalFileWriter{
+ CurrentDir: jsonDir,
+ })
+ }
+
+ if p.toS3 {
+ c.Conf.AwsRegion = p.awsRegion
+ c.Conf.AwsProfile = p.awsProfile
+ c.Conf.S3Bucket = p.awsS3Bucket
+ if err := report.CheckIfBucketExists(); err != nil {
+ Log.Errorf("Check if there is a bucket beforehand: %s, err: %s", c.Conf.S3Bucket, err)
+ return subcommands.ExitUsageError
+ }
+ reports = append(reports, report.S3Writer{})
+ }
+
+ if p.toAzureBlob {
+ c.Conf.AzureAccount = p.azureAccount
+ if len(c.Conf.AzureAccount) == 0 {
+ c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
+ }
+
+ c.Conf.AzureKey = p.azureKey
+ if len(c.Conf.AzureKey) == 0 {
+ c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
+ }
+
+ c.Conf.AzureContainer = p.azureContainer
+ if len(c.Conf.AzureContainer) == 0 {
+ Log.Error("Azure storage container name is requied with --azure-container option")
+ return subcommands.ExitUsageError
+ }
+ if err := report.CheckIfAzureContainerExists(); err != nil {
+ Log.Errorf("Check if there is a container beforehand: %s, err: %s", c.Conf.AzureContainer, err)
+ return subcommands.ExitUsageError
+ }
+ reports = append(reports, report.AzureBlobWriter{})
+ }
+
+ if !(p.formatJSON || p.formatOneLineText ||
+ p.formatShortText || p.formatFullText || p.formatXML) {
+ c.Conf.FormatShortText = true
+ }
+
+ Log.Info("Validating Config...")
+ if !c.Conf.ValidateOnReport() {
+ return subcommands.ExitUsageError
+ }
+ if ok, err := cveapi.CveClient.CheckHealth(); !ok {
+ Log.Errorf("CVE HTTP server is not running. err: %s", err)
+ Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with --cvedb-path option")
+ return subcommands.ExitFailure
+ }
+ if c.Conf.CveDictionaryURL != "" {
+ Log.Infof("cve-dictionary: %s", c.Conf.CveDictionaryURL)
+ } else {
+ if c.Conf.CveDBType == "sqlite3" {
+ Log.Infof("cve-dictionary: %s", c.Conf.CveDBPath)
+ }
+ }
+
+ history, err := loadOneScanHistory(jsonDir)
+
+ var results []models.ScanResult
+ for _, r := range history.ScanResults {
+ if p.refreshCve || needToRefreshCve(r) {
+ Log.Debugf("need to refresh")
+ if c.Conf.CveDBType == "sqlite3" {
+ if _, err := os.Stat(c.Conf.CveDBPath); os.IsNotExist(err) {
+ log.Errorf("SQLite3 DB(CVE-Dictionary) is not exist: %s",
+ c.Conf.CveDBPath)
+ return subcommands.ExitFailure
+ }
+ }
+
+ filled, err := fillCveInfoFromCveDB(r)
+ if err != nil {
+ Log.Errorf("Failed to fill CVE information: %s", err)
+ return subcommands.ExitFailure
+ }
+ filled.Lang = c.Conf.Lang
+
+ if err := overwriteJSONFile(jsonDir, filled); err != nil {
+ Log.Errorf("Failed to write JSON: %s", err)
+ return subcommands.ExitFailure
+ }
+ results = append(results, filled)
+ } else {
+ Log.Debugf("no need to refresh")
+ results = append(results, r)
+ }
+ }
+
+ var res models.ScanResults
+ for _, r := range results {
+ res = append(res, r.FilterByCvssOver())
+ }
+ for _, w := range reports {
+ if err := w.Write(res...); err != nil {
+ Log.Errorf("Failed to report: %s", err)
+ return subcommands.ExitFailure
+ }
+ }
+ return subcommands.ExitSuccess
+}
diff --git a/commands/scan.go b/commands/scan.go
index 17ebd7fa38..f06316bbdd 100644
--- a/commands/scan.go
+++ b/commands/scan.go
@@ -25,12 +25,9 @@ import (
"os"
"path/filepath"
"strings"
- "time"
"github.com/Sirupsen/logrus"
c "github.com/future-architect/vuls/config"
- "github.com/future-architect/vuls/cveapi"
- "github.com/future-architect/vuls/report"
"github.com/future-architect/vuls/scan"
"github.com/future-architect/vuls/util"
"github.com/google/subcommands"
@@ -39,46 +36,15 @@ import (
// ScanCmd is Subcommand of host discovery mode
type ScanCmd struct {
- lang string
- debug bool
- debugSQL bool
-
- configPath string
-
- resultsDir string
- cvedbtype string
- cvedbpath string
- cveDictionaryURL string
- cacheDBPath string
-
- cvssScoreOver float64
- ignoreUnscoredCves bool
-
- httpProxy string
- askSudoPassword bool
- askKeyPassword bool
-
+ debug bool
+ configPath string
+ resultsDir string
+ cacheDBPath string
+ httpProxy string
+ askKeyPassword bool
containersOnly bool
skipBroken bool
-
- // reporting
- reportSlack bool
- reportMail bool
- reportJSON bool
- reportText bool
- reportS3 bool
- reportAzureBlob bool
- reportXML bool
-
- awsProfile string
- awsS3Bucket string
- awsRegion string
-
- azureAccount string
- azureKey string
- azureContainer string
-
- sshExternal bool
+ sshExternal bool
}
// Name return subcommand name
@@ -91,35 +57,15 @@ func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities" }
func (*ScanCmd) Usage() string {
return `scan:
scan
- [-lang=en|ja]
[-config=/path/to/config.toml]
[-results-dir=/path/to/results]
- [-cve-dictionary-dbtype=sqlite3|mysql]
- [-cve-dictionary-dbpath=/path/to/cve.sqlite3 or mysql connection string]
- [-cve-dictionary-url=http://127.0.0.1:1323]
- [-cache-dbpath=/path/to/cache.db]
- [-cvss-over=7]
- [-ignore-unscored-cves]
+ [-cachedb-path=/path/to/cache.db]
[-ssh-external]
[-containers-only]
[-skip-broken]
- [-report-azure-blob]
- [-report-json]
- [-report-mail]
- [-report-s3]
- [-report-slack]
- [-report-text]
- [-report-xml]
[-http-proxy=http://192.168.0.1:8080]
[-ask-key-password]
[-debug]
- [-debug-sql]
- [-aws-profile=default]
- [-aws-region=us-west-2]
- [-aws-s3-bucket=bucket_name]
- [-azure-account=accout]
- [-azure-key=key]
- [-azure-container=container]
[SERVER]...
`
@@ -127,9 +73,7 @@ func (*ScanCmd) Usage() string {
// SetFlags set flag
func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
- f.StringVar(&p.lang, "lang", "en", "[en|ja]")
f.BoolVar(&p.debug, "debug", false, "debug mode")
- f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
wd, _ := os.Getwd()
@@ -139,44 +83,13 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
defaultResultsDir := filepath.Join(wd, "results")
f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
- f.StringVar(
- &p.cvedbtype,
- "cve-dictionary-dbtype",
- "sqlite3",
- "DB type for fetching CVE dictionary (sqlite3 or mysql)")
-
- f.StringVar(
- &p.cvedbpath,
- "cve-dictionary-dbpath",
- "",
- "/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
-
- defaultURL := "http://127.0.0.1:1323"
- f.StringVar(
- &p.cveDictionaryURL,
- "cve-dictionary-url",
- defaultURL,
- "http://CVE.Dictionary")
-
defaultCacheDBPath := filepath.Join(wd, "cache.db")
f.StringVar(
&p.cacheDBPath,
- "cache-dbpath",
+ "cachedb-path",
defaultCacheDBPath,
"/path/to/cache.db (local cache of changelog for Ubuntu/Debian)")
- f.Float64Var(
- &p.cvssScoreOver,
- "cvss-over",
- 0,
- "-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
-
- f.BoolVar(
- &p.ignoreUnscoredCves,
- "ignore-unscored-cves",
- false,
- "Don't report the unscored CVEs")
-
f.BoolVar(
&p.sshExternal,
"ssh-external",
@@ -202,55 +115,12 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
"http://proxy-url:port (default: empty)",
)
- f.BoolVar(&p.reportSlack, "report-slack", false, "Send report via Slack")
- f.BoolVar(&p.reportMail, "report-mail", false, "Send report via Email")
- f.BoolVar(&p.reportJSON,
- "report-json",
- false,
- fmt.Sprintf("Write report to JSON files (%s/results/current)", wd),
- )
- f.BoolVar(&p.reportText,
- "report-text",
- false,
- fmt.Sprintf("Write report to text files (%s/results/current)", wd),
- )
- f.BoolVar(&p.reportXML,
- "report-xml",
- false,
- fmt.Sprintf("Write report to XML files (%s/results/current)", wd),
- )
-
- f.BoolVar(&p.reportS3,
- "report-s3",
- false,
- "Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json)",
- )
- f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
- f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
- f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
-
- f.BoolVar(&p.reportAzureBlob,
- "report-azure-blob",
- false,
- "Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json)",
- )
- f.StringVar(&p.azureAccount, "azure-account", "", "Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified")
- f.StringVar(&p.azureKey, "azure-key", "", "Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified")
- f.StringVar(&p.azureContainer, "azure-container", "", "Azure storage container name")
-
f.BoolVar(
&p.askKeyPassword,
"ask-key-password",
false,
"Ask ssh privatekey password before scanning",
)
-
- f.BoolVar(
- &p.askSudoPassword,
- "ask-sudo-password",
- false,
- "[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on target servers and use SSH key-based authentication",
- )
}
// Execute execute
@@ -264,10 +134,6 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
return subcommands.ExitFailure
}
}
- if p.askSudoPassword {
- logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on target servers and use SSH key-based authentication")
- return subcommands.ExitFailure
- }
c.Conf.Debug = p.debug
err = c.Load(p.configPath, keyPass)
@@ -278,13 +144,6 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
logrus.Info("Start scanning")
logrus.Infof("config: %s", p.configPath)
- if p.cvedbpath != "" {
- if p.cvedbtype == "sqlite3" {
- logrus.Infof("cve-dictionary: %s", p.cvedbpath)
- }
- } else {
- logrus.Infof("cve-dictionary: %s", p.cveDictionaryURL)
- }
var servernames []string
if 0 < len(f.Args()) {
@@ -324,91 +183,21 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
}
logrus.Debugf("%s", pp.Sprintf("%v", target))
- c.Conf.Lang = p.lang
- c.Conf.DebugSQL = p.debugSQL
-
// logger
Log := util.NewCustomLogger(c.ServerInfo{})
- scannedAt := time.Now()
-
- // report
- reports := []report.ResultWriter{
- report.StdoutWriter{},
- report.LogrusWriter{},
- }
- if p.reportSlack {
- reports = append(reports, report.SlackWriter{})
- }
- if p.reportMail {
- reports = append(reports, report.MailWriter{})
- }
- if p.reportJSON {
- reports = append(reports, report.JSONWriter{ScannedAt: scannedAt})
- }
- if p.reportText {
- reports = append(reports, report.TextFileWriter{ScannedAt: scannedAt})
- }
- if p.reportXML {
- reports = append(reports, report.XMLWriter{ScannedAt: scannedAt})
- }
- if p.reportS3 {
- c.Conf.AwsRegion = p.awsRegion
- c.Conf.AwsProfile = p.awsProfile
- c.Conf.S3Bucket = p.awsS3Bucket
- if err := report.CheckIfBucketExists(); err != nil {
- Log.Errorf("Failed to access to the S3 bucket. err: %s", err)
- Log.Error("Ensure the bucket or check AWS config before scanning")
- return subcommands.ExitUsageError
- }
- reports = append(reports, report.S3Writer{})
- }
- if p.reportAzureBlob {
- c.Conf.AzureAccount = p.azureAccount
- if len(c.Conf.AzureAccount) == 0 {
- c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
- }
-
- c.Conf.AzureKey = p.azureKey
- if len(c.Conf.AzureKey) == 0 {
- c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
- }
-
- c.Conf.AzureContainer = p.azureContainer
- if len(c.Conf.AzureContainer) == 0 {
- Log.Error("Azure storage container name is requied with --azure-container option")
- return subcommands.ExitUsageError
- }
- if err := report.CheckIfAzureContainerExists(); err != nil {
- Log.Errorf("Failed to access to the Azure Blob container. err: %s", err)
- Log.Error("Ensure the container or check Azure config before scanning")
- return subcommands.ExitUsageError
- }
- reports = append(reports, report.AzureBlobWriter{})
- }
c.Conf.ResultsDir = p.resultsDir
- c.Conf.CveDBType = p.cvedbtype
- c.Conf.CveDBPath = p.cvedbpath
- c.Conf.CveDictionaryURL = p.cveDictionaryURL
c.Conf.CacheDBPath = p.cacheDBPath
- c.Conf.CvssScoreOver = p.cvssScoreOver
- c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
c.Conf.SSHExternal = p.sshExternal
c.Conf.HTTPProxy = p.httpProxy
c.Conf.ContainersOnly = p.containersOnly
c.Conf.SkipBroken = p.skipBroken
Log.Info("Validating Config...")
- if !c.Conf.Validate() {
+ if !c.Conf.ValidateOnScan() {
return subcommands.ExitUsageError
}
- if ok, err := cveapi.CveClient.CheckHealth(); !ok {
- Log.Errorf("CVE HTTP server is not running. err: %s", err)
- Log.Errorf("Run go-cve-dictionary as server mode or specify -cve-dictionary-dbpath option")
- return subcommands.ExitFailure
- }
-
Log.Info("Detecting Server/Contianer OS... ")
if err := scan.InitServers(Log); err != nil {
Log.Errorf("Failed to init servers: %s", err)
@@ -431,21 +220,9 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
}
return subcommands.ExitFailure
}
-
- scanResults, err := scan.GetScanResults()
- if err != nil {
- Log.Fatal(err)
- return subcommands.ExitFailure
- }
-
- Log.Info("Reporting...")
- filtered := scanResults.FilterByCvssOver()
- for _, w := range reports {
- if err := w.Write(filtered); err != nil {
- Log.Fatalf("Failed to report, err: %s", err)
- return subcommands.ExitFailure
- }
- }
+ fmt.Printf("\n\n\n")
+ fmt.Println("To view the detail, vuls tui is useful.")
+ fmt.Println("To send a report, run vuls report -h.")
return subcommands.ExitSuccess
}
diff --git a/commands/tui.go b/commands/tui.go
index 1b9cd53fc7..19a4d74041 100644
--- a/commands/tui.go
+++ b/commands/tui.go
@@ -20,13 +20,12 @@ package commands
import (
"context"
"flag"
- "io/ioutil"
"os"
"path/filepath"
- "strings"
log "github.com/Sirupsen/logrus"
c "github.com/future-architect/vuls/config"
+ "github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/report"
"github.com/google/subcommands"
)
@@ -36,6 +35,11 @@ type TuiCmd struct {
lang string
debugSQL bool
resultsDir string
+
+ refreshCve bool
+ cvedbtype string
+ cvedbpath string
+ cveDictionaryURL string
}
// Name return subcommand name
@@ -47,7 +51,13 @@ func (*TuiCmd) Synopsis() string { return "Run Tui view to anayze vulnerabilites
// Usage return usage
func (*TuiCmd) Usage() string {
return `tui:
- tui [-results-dir=/path/to/results]
+ tui
+ [-cvedb-type=sqlite3|mysql]
+ [-cvedb-path=/path/to/cve.sqlite3]
+ [-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
+ [-results-dir=/path/to/results]
+ [-refresh-cve]
+ [-debug-sql]
`
}
@@ -58,9 +68,33 @@ func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&p.debugSQL, "debug-sql", false, "debug SQL")
wd, _ := os.Getwd()
-
defaultResultsDir := filepath.Join(wd, "results")
f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
+
+ f.BoolVar(
+ &p.refreshCve,
+ "refresh-cve",
+ false,
+ "Refresh CVE information in JSON file under results dir")
+
+ f.StringVar(
+ &p.cvedbtype,
+ "cvedb-type",
+ "sqlite3",
+ "DB type for fetching CVE dictionary (sqlite3 or mysql)")
+
+ defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
+ f.StringVar(
+ &p.cvedbpath,
+ "cvedb-path",
+ defaultCveDBPath,
+ "/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
+
+ f.StringVar(
+ &p.cveDictionaryURL,
+ "cvedb-url",
+ "",
+ "http://cve-dictionary.com:8080 or mysql connection string")
}
// Execute execute
@@ -68,38 +102,53 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
c.Conf.Lang = "en"
c.Conf.DebugSQL = p.debugSQL
c.Conf.ResultsDir = p.resultsDir
+ c.Conf.CveDBType = p.cvedbtype
+ c.Conf.CveDBPath = p.cvedbpath
+ c.Conf.CveDictionaryURL = p.cveDictionaryURL
- var jsonDirName string
- var err error
- if 0 < len(f.Args()) {
- var jsonDirs report.JSONDirs
- if jsonDirs, err = report.GetValidJSONDirs(); err != nil {
- return subcommands.ExitFailure
- }
- for _, d := range jsonDirs {
- splitPath := strings.Split(d, string(os.PathSeparator))
- if splitPath[len(splitPath)-1] == f.Args()[0] {
- jsonDirName = f.Args()[0]
- break
+ log.Info("Validating Config...")
+ if !c.Conf.ValidateOnTui() {
+ return subcommands.ExitUsageError
+ }
+
+ jsonDir, err := jsonDir(f.Args())
+ if err != nil {
+ log.Errorf("Failed to read json dir under results: %s", err)
+ return subcommands.ExitFailure
+ }
+
+ history, err := loadOneScanHistory(jsonDir)
+ if err != nil {
+ log.Errorf("Failed to read from JSON: %s", err)
+ return subcommands.ExitFailure
+ }
+
+ var results []models.ScanResult
+ for _, r := range history.ScanResults {
+ if p.refreshCve || needToRefreshCve(r) {
+ if c.Conf.CveDBType == "sqlite3" {
+ if _, err := os.Stat(c.Conf.CveDBPath); os.IsNotExist(err) {
+ log.Errorf("SQLite3 DB(CVE-Dictionary) is not exist: %s",
+ c.Conf.CveDBPath)
+ return subcommands.ExitFailure
+ }
}
- }
- if len(jsonDirName) == 0 {
- log.Errorf("First Argument have to be JSON directory name : %s", err)
- return subcommands.ExitFailure
- }
- } else {
- stat, _ := os.Stdin.Stat()
- if (stat.Mode() & os.ModeCharDevice) == 0 {
- bytes, err := ioutil.ReadAll(os.Stdin)
+
+ filled, err := fillCveInfoFromCveDB(r)
if err != nil {
- log.Errorf("Failed to read stdin: %s", err)
+ log.Errorf("Failed to fill CVE information: %s", err)
return subcommands.ExitFailure
}
- fields := strings.Fields(string(bytes))
- if 0 < len(fields) {
- jsonDirName = fields[0]
+
+ if err := overwriteJSONFile(jsonDir, filled); err != nil {
+ log.Errorf("Failed to write JSON: %s", err)
+ return subcommands.ExitFailure
}
+ results = append(results, filled)
+ } else {
+ results = append(results, r)
}
}
- return report.RunTui(jsonDirName)
+ history.ScanResults = results
+ return report.RunTui(history)
}
diff --git a/commands/util.go b/commands/util.go
new file mode 100644
index 0000000000..6a7f345108
--- /dev/null
+++ b/commands/util.go
@@ -0,0 +1,225 @@
+/* Vuls - Vulnerability Scanner
+Copyright (C) 2016 Future Architect, Inc. Japan.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+package commands
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strings"
+
+ c "github.com/future-architect/vuls/config"
+ "github.com/future-architect/vuls/cveapi"
+ "github.com/future-architect/vuls/models"
+ "github.com/future-architect/vuls/report"
+ "github.com/future-architect/vuls/util"
+)
+
+// jsonDirPattern is file name pattern of JSON directory
+// 2016-11-16T10:43:28+09:00
+// 2016-11-16T10:43:28Z
+var jsonDirPattern = regexp.MustCompile(
+ `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$`)
+
+// JSONDirs is array of json files path.
+type jsonDirs []string
+
+// sort as recent directories are at the head
+func (d jsonDirs) Len() int {
+ return len(d)
+}
+func (d jsonDirs) Swap(i, j int) {
+ d[i], d[j] = d[j], d[i]
+}
+func (d jsonDirs) Less(i, j int) bool {
+ return d[j] < d[i]
+}
+
+// getValidJSONDirs return valid json directory as array
+// Returned array is sorted so that recent directories are at the head
+func lsValidJSONDirs() (dirs jsonDirs, err error) {
+ var dirInfo []os.FileInfo
+ if dirInfo, err = ioutil.ReadDir(c.Conf.ResultsDir); err != nil {
+ err = fmt.Errorf("Failed to read %s: %s", c.Conf.ResultsDir, err)
+ return
+ }
+ for _, d := range dirInfo {
+ if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
+ jsonDir := filepath.Join(c.Conf.ResultsDir, d.Name())
+ dirs = append(dirs, jsonDir)
+ }
+ }
+ sort.Sort(dirs)
+ return
+}
+
+// jsonDir returns
+// If there is an arg, check if it is a valid format and return the corresponding path under results.
+// If passed via PIPE (such as history subcommand), return that path.
+// Otherwise, returns the path of the latest directory
+func jsonDir(args []string) (string, error) {
+ var err error
+ var dirs jsonDirs
+
+ if 0 < len(args) {
+ if dirs, err = lsValidJSONDirs(); err != nil {
+ return "", err
+ }
+
+ path := filepath.Join(c.Conf.ResultsDir, args[0])
+ for _, d := range dirs {
+ ss := strings.Split(d, string(os.PathSeparator))
+ timedir := ss[len(ss)-1]
+ if timedir == args[0] {
+ return path, nil
+ }
+ }
+
+ return "", fmt.Errorf("Invalid path: %s", path)
+ }
+
+ // PIPE
+ stat, _ := os.Stdin.Stat()
+ if (stat.Mode() & os.ModeCharDevice) == 0 {
+ bytes, err := ioutil.ReadAll(os.Stdin)
+ if err != nil {
+ return "", fmt.Errorf("Failed to read stdin: %s", err)
+ }
+ fields := strings.Fields(string(bytes))
+ if 0 < len(fields) {
+ return filepath.Join(c.Conf.ResultsDir, fields[0]), nil
+ }
+ return "", fmt.Errorf("Stdin is invalid: %s", string(bytes))
+ }
+
+ // returns latest dir when no args or no PIPE
+ if dirs, err = lsValidJSONDirs(); err != nil {
+ return "", err
+ }
+ if len(dirs) == 0 {
+ return "", fmt.Errorf("No results under %s",
+ c.Conf.ResultsDir)
+ }
+ return dirs[0], nil
+}
+
+// loadOneScanHistory read JSON data
+func loadOneScanHistory(jsonDir string) (scanHistory models.ScanHistory, err error) {
+ var results []models.ScanResult
+ var files []os.FileInfo
+ if files, err = ioutil.ReadDir(jsonDir); err != nil {
+ err = fmt.Errorf("Failed to read %s: %s", jsonDir, err)
+ return
+ }
+ for _, f := range files {
+ if filepath.Ext(f.Name()) != ".json" {
+ continue
+ }
+ var r models.ScanResult
+ var data []byte
+ path := filepath.Join(jsonDir, f.Name())
+ if data, err = ioutil.ReadFile(path); err != nil {
+ err = fmt.Errorf("Failed to read %s: %s", path, err)
+ return
+ }
+ if json.Unmarshal(data, &r) != nil {
+ err = fmt.Errorf("Failed to parse %s: %s", path, err)
+ return
+ }
+ results = append(results, r)
+ }
+ if len(results) == 0 {
+ err = fmt.Errorf("There is no json file under %s", jsonDir)
+ return
+ }
+
+ scanHistory = models.ScanHistory{
+ ScanResults: results,
+ }
+ return
+}
+
+func fillCveInfoFromCveDB(r models.ScanResult) (filled models.ScanResult, err error) {
+ sInfo := c.Conf.Servers[r.ServerName]
+ vs, err := scanVulnByCpeNames(sInfo.CpeNames, r.ScannedCves)
+ if err != nil {
+ return
+ }
+ r.ScannedCves = vs
+ filled, err = r.FillCveDetail()
+ if err != nil {
+ return
+ }
+ return
+}
+
+func overwriteJSONFile(dir string, r models.ScanResult) error {
+ before := c.Conf.FormatJSON
+ c.Conf.FormatJSON = true
+ w := report.LocalFileWriter{CurrentDir: dir}
+ if err := w.Write(r); err != nil {
+ return fmt.Errorf("Failed to write summary report: %s", err)
+ }
+ c.Conf.FormatJSON = before
+ return nil
+}
+
+func scanVulnByCpeNames(cpeNames []string, scannedVulns []models.VulnInfo) ([]models.VulnInfo,
+ error) {
+ // To remove duplicate
+ set := map[string]models.VulnInfo{}
+ for _, v := range scannedVulns {
+ set[v.CveID] = v
+ }
+
+ for _, name := range cpeNames {
+ details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
+ if err != nil {
+ return nil, err
+ }
+ for _, detail := range details {
+ if val, ok := set[detail.CveID]; ok {
+ names := val.CpeNames
+ names = util.AppendIfMissing(names, name)
+ val.CpeNames = names
+ set[detail.CveID] = val
+ } else {
+ set[detail.CveID] = models.VulnInfo{
+ CveID: detail.CveID,
+ CpeNames: []string{name},
+ }
+ }
+ }
+ }
+
+ vinfos := []models.VulnInfo{}
+ for key := range set {
+ vinfos = append(vinfos, set[key])
+ }
+ return vinfos, nil
+}
+
+func needToRefreshCve(r models.ScanResult) bool {
+ return r.Lang != c.Conf.Lang || len(r.KnownCves) == 0 &&
+ len(r.UnknownCves) == 0 &&
+ len(r.IgnoredCves) == 0
+}
diff --git a/config/config.go b/config/config.go
index c21c6d22d7..f50d861c07 100644
--- a/config/config.go
+++ b/config/config.go
@@ -35,7 +35,7 @@ type Config struct {
DebugSQL bool
Lang string
- Mail smtpConf
+ EMail smtpConf
Slack SlackConf
Default ServerInfo
Servers map[string]ServerInfo
@@ -56,6 +56,14 @@ type Config struct {
CveDBPath string
CacheDBPath string
+ FormatXML bool
+ FormatJSON bool
+ FormatOneLineText bool
+ FormatShortText bool
+ FormatFullText bool
+
+ GZIP bool
+
AwsProfile string
AwsRegion string
S3Bucket string
@@ -63,15 +71,44 @@ type Config struct {
AzureAccount string
AzureKey string
AzureContainer string
+}
+
+// ValidateOnConfigtest validates
+func (c Config) ValidateOnConfigtest() bool {
+ errs := []error{}
+
+ if runtime.GOOS == "windows" && c.SSHExternal {
+ errs = append(errs, fmt.Errorf("-ssh-external cannot be used on windows"))
+ }
+
+ _, err := valid.ValidateStruct(c)
+ if err != nil {
+ errs = append(errs, err)
+ }
- // CpeNames []string
- // SummaryMode bool
+ for _, err := range errs {
+ log.Error(err)
+ }
+
+ return len(errs) == 0
}
-// Validate configuration
-func (c Config) Validate() bool {
+// ValidateOnPrepare validates configuration
+func (c Config) ValidateOnPrepare() bool {
+ return c.ValidateOnConfigtest()
+}
+
+// ValidateOnScan validates configuration
+func (c Config) ValidateOnScan() bool {
errs := []error{}
+ if len(c.ResultsDir) != 0 {
+ if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
+ errs = append(errs, fmt.Errorf(
+ "JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
+ }
+ }
+
if runtime.GOOS == "windows" && c.SSHExternal {
errs = append(errs, fmt.Errorf("-ssh-external cannot be used on windows"))
}
@@ -83,9 +120,34 @@ func (c Config) Validate() bool {
}
}
- // If no valid DB type is set, default to sqlite3
- if c.CveDBType == "" {
- c.CveDBType = "sqlite3"
+ if len(c.CacheDBPath) != 0 {
+ if ok, _ := valid.IsFilePath(c.CacheDBPath); !ok {
+ errs = append(errs, fmt.Errorf(
+ "Cache DB path must be a *Absolute* file path. -cache-dbpath: %s", c.CacheDBPath))
+ }
+ }
+
+ _, err := valid.ValidateStruct(c)
+ if err != nil {
+ errs = append(errs, err)
+ }
+
+ for _, err := range errs {
+ log.Error(err)
+ }
+
+ return len(errs) == 0
+}
+
+// ValidateOnReport validates configuration
+func (c Config) ValidateOnReport() bool {
+ errs := []error{}
+
+ if len(c.ResultsDir) != 0 {
+ if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
+ errs = append(errs, fmt.Errorf(
+ "JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
+ }
}
if c.CveDBType != "sqlite3" && c.CveDBType != "mysql" {
@@ -94,18 +156,9 @@ func (c Config) Validate() bool {
}
if c.CveDBType == "sqlite3" {
- if len(c.CveDBPath) != 0 {
- if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
- errs = append(errs, fmt.Errorf(
- "SQLite3 DB(Cve Dictionary) path must be a *Absolute* file path. -cve-dictionary-dbpath: %s", c.CveDBPath))
- }
- }
- }
-
- if len(c.CacheDBPath) != 0 {
- if ok, _ := valid.IsFilePath(c.CacheDBPath); !ok {
+ if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
errs = append(errs, fmt.Errorf(
- "Cache DB path must be a *Absolute* file path. -cache-dbpath: %s", c.CacheDBPath))
+ "SQLite3 DB(CVE-Dictionary) path must be a *Absolute* file path. -cve-dictionary-dbpath: %s", c.CveDBPath))
}
}
@@ -114,7 +167,7 @@ func (c Config) Validate() bool {
errs = append(errs, err)
}
- if mailerrs := c.Mail.Validate(); 0 < len(mailerrs) {
+ if mailerrs := c.EMail.Validate(); 0 < len(mailerrs) {
errs = append(errs, mailerrs...)
}
@@ -129,6 +182,36 @@ func (c Config) Validate() bool {
return len(errs) == 0
}
+// ValidateOnTui validates configuration
+func (c Config) ValidateOnTui() bool {
+ errs := []error{}
+
+ if len(c.ResultsDir) != 0 {
+ if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
+ errs = append(errs, fmt.Errorf(
+ "JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
+ }
+ }
+
+ if c.CveDBType != "sqlite3" && c.CveDBType != "mysql" {
+ errs = append(errs, fmt.Errorf(
+ "CVE DB type must be either 'sqlite3' or 'mysql'. -cve-dictionary-dbtype: %s", c.CveDBType))
+ }
+
+ if c.CveDBType == "sqlite3" {
+ if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
+ errs = append(errs, fmt.Errorf(
+ "SQLite3 DB(CVE-Dictionary) path must be a *Absolute* file path. -cve-dictionary-dbpath: %s", c.CveDBPath))
+ }
+ }
+
+ for _, err := range errs {
+ log.Error(err)
+ }
+
+ return len(errs) == 0
+}
+
// smtpConf is smtp config
type smtpConf struct {
SMTPAddr string
diff --git a/config/tomlloader.go b/config/tomlloader.go
index abb7a9ede6..f4c4f6af80 100644
--- a/config/tomlloader.go
+++ b/config/tomlloader.go
@@ -43,7 +43,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
return err
}
- Conf.Mail = conf.Mail
+ Conf.EMail = conf.EMail
Conf.Slack = conf.Slack
d := conf.Default
@@ -119,7 +119,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
return fmt.Errorf(
"Failed to read OWASP Dependency Check XML: %s", err)
}
- log.Infof("Loaded from OWASP Dependency Check XML: %s",
+ log.Debugf("Loaded from OWASP Dependency Check XML: %s",
s.ServerName)
s.CpeNames = append(s.CpeNames, cpes...)
}
diff --git a/cveapi/cve_client.go b/cveapi/cve_client.go
index 44f5f282b6..4560c43a18 100644
--- a/cveapi/cve_client.go
+++ b/cveapi/cve_client.go
@@ -48,7 +48,7 @@ func (api *cvedictClient) initialize() {
}
func (api cvedictClient) CheckHealth() (ok bool, err error) {
- if config.Conf.CveDBPath != "" {
+ if config.Conf.CveDictionaryURL == "" {
log.Debugf("get cve-dictionary from %s", config.Conf.CveDBType)
return true, nil
}
@@ -71,7 +71,7 @@ type response struct {
}
func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDetails, err error) {
- if config.Conf.CveDBPath != "" {
+ if config.Conf.CveDictionaryURL == "" {
return api.FetchCveDetailsFromCveDB(cveIDs)
}
@@ -129,7 +129,6 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDet
fmt.Errorf("Failed to fetch CVE. err: %v", errs)
}
- // order by CVE ID desc
sort.Sort(cveDetails)
return
}
@@ -137,7 +136,11 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDet
func (api cvedictClient) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails cve.CveDetails, err error) {
log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
cveconfig.Conf.DBType = config.Conf.CveDBType
- cveconfig.Conf.DBPath = config.Conf.CveDBPath
+ if config.Conf.CveDBType == "sqlite3" {
+ cveconfig.Conf.DBPath = config.Conf.CveDBPath
+ } else {
+ cveconfig.Conf.DBPath = config.Conf.CveDictionaryURL
+ }
cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
if err := cvedb.OpenDB(); err != nil {
return []cve.CveDetail{},
@@ -194,7 +197,7 @@ type responseGetCveDetailByCpeName struct {
}
func (api cvedictClient) FetchCveDetailsByCpeName(cpeName string) ([]cve.CveDetail, error) {
- if config.Conf.CveDBPath != "" {
+ if config.Conf.CveDictionaryURL == "" {
return api.FetchCveDetailsByCpeNameFromDB(cpeName)
}
diff --git a/glide.lock b/glide.lock
index ccbff0ca69..3f2cee6efa 100644
--- a/glide.lock
+++ b/glide.lock
@@ -1,5 +1,5 @@
-hash: ca64aef6e9e94c7be91f79b88edb847363c8a5bd48da4ad27784e9342c8db6e2
-updated: 2016-11-14T17:14:19.692072231Z
+hash: c3167d83e68562cd7ef73f138ce60cb9e60b72b50394e8615388d1f3ba9fbef2
+updated: 2017-01-02T09:37:09.437363123+09:00
imports:
- name: github.com/asaskevich/govalidator
version: 7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877
@@ -46,7 +46,7 @@ imports:
- name: github.com/go-ini/ini
version: 6e4869b434bd001f6983749881c7ead3545887d8
- name: github.com/go-sql-driver/mysql
- version: 2a6c6079c7eff49a7e9d641e109d922f124a3e4c
+ version: d512f204a577a4ab037a1816604c48c9c13210be
- name: github.com/google/subcommands
version: a71b91e238406bd68766ee52db63bebedce0e9f6
- name: github.com/gosuri/uitable
@@ -60,6 +60,7 @@ imports:
version: 39165d498058a823126af3cbf4d2a3b0e1acf11e
subpackages:
- dialects/mysql
+ - dialects/sqlite
- name: github.com/jinzhu/inflection
version: 74387dc39a75e970e7a3ae6a3386b5bd2e5c5cff
- name: github.com/jmespath/go-jmespath
@@ -69,7 +70,7 @@ imports:
- name: github.com/k0kubun/pp
version: f5dce6ed0ccf6c350f1679964ff6b61f3d6d2033
- name: github.com/kotakanbe/go-cve-dictionary
- version: 2dd369d26145eab2178f6d509821fcabc9391627
+ version: 7eb1f1a2e7e436177570bf234e21c2ed9489d3fb
subpackages:
- config
- db
@@ -89,7 +90,7 @@ imports:
- name: github.com/mattn/go-runewidth
version: 737072b4e32b7a5018b4a7125da8d12de90e8045
- name: github.com/mattn/go-sqlite3
- version: e5a3c16c5c1d80b24f633e68aecd6b0702786d3d
+ version: 5510da399572b4962c020184bb291120c0a412e2
- name: github.com/mgutz/ansi
version: c286dcecd19ff979eeb73ea444e479b903f2cfcb
- name: github.com/moul/http2curl
@@ -112,9 +113,8 @@ imports:
- ssh/agent
- ssh/terminal
- name: golang.org/x/net
- version: cf4effbb9db1f3ef07f7e1891402991b6afbb276
+ version: 1d7a0b2100da090d8b02afcfb42f97e2c77e71a4
subpackages:
- - context
- publicsuffix
- name: golang.org/x/sys
version: 9bb9f0998d48b31547d975974935ae9b48c7a03c
diff --git a/glide.yaml b/glide.yaml
index 736d995a70..ce42b29f1f 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -15,16 +15,15 @@ import:
- package: github.com/boltdb/bolt
- package: github.com/cenkalti/backoff
- package: github.com/google/subcommands
- branch: context
- package: github.com/gosuri/uitable
- package: github.com/howeyc/gopass
-- package: github.com/jinzhu/gorm
- package: github.com/jroimartin/gocui
- package: github.com/k0kubun/pp
- package: github.com/kotakanbe/go-cve-dictionary
subpackages:
- config
- db
+ - log
- models
- package: github.com/kotakanbe/go-pingscanner
- package: github.com/kotakanbe/logrus-prefixed-formatter
@@ -35,6 +34,3 @@ import:
subpackages:
- ssh
- ssh/agent
-- package: golang.org/x/net
- subpackages:
- - context
diff --git a/img/vuls-architecture.graphml b/img/vuls-architecture.graphml
index 6b6f723742..0639fa6c7d 100644
--- a/img/vuls-architecture.graphml
+++ b/img/vuls-architecture.graphml
@@ -37,7 +37,7 @@
-
+
Vulnerbility Database
@@ -63,7 +63,7 @@
-
+
JVN
@@ -81,7 +81,7 @@
-
+
NVD
@@ -103,7 +103,7 @@
-
+
Distribution Support
@@ -129,7 +129,7 @@
-
+
apptitude
@@ -147,7 +147,7 @@ changelog
-
+
yum
@@ -165,7 +165,7 @@ changelog
-
+
RHSA (RedHat)
@@ -183,7 +183,7 @@ ALAS (Amazon)
-
+
FreeBSD Support
@@ -202,7 +202,7 @@ ALAS (Amazon)
-
+
@@ -222,14 +222,14 @@ ALAS (Amazon)
-
+
- Vuls
+ Vuls
-
+
@@ -248,10 +248,10 @@ ALAS (Amazon)
-
-
+
+
- Report
+ Scan
@@ -265,10 +265,10 @@ ALAS (Amazon)
-
-
+
+
- TUI View
+ Report
@@ -282,10 +282,11 @@ ALAS (Amazon)
-
+
- Scan
+ VulsRepo
+(WebUI)
@@ -299,11 +300,10 @@ ALAS (Amazon)
-
-
+
+
- Web View
-(Vulsrepo)
+ TUI
@@ -319,7 +319,7 @@ ALAS (Amazon)
-
+
System Operator
@@ -339,7 +339,7 @@ ALAS (Amazon)
-
+
@@ -353,7 +353,7 @@ ALAS (Amazon)
-
+
@@ -468,14 +468,14 @@ ALAS (Amazon)
-
+
- Docker Containers
+ Docker Containers
-
+
@@ -534,14 +534,14 @@ Container
-
+
- Linux/FreeBSD Servers
+ Linux/FreeBSD
-
+
@@ -599,7 +599,7 @@ Container
-
+
results dir
@@ -625,7 +625,7 @@ Container
-
+
JSON
@@ -642,7 +642,7 @@ Container
-
+
JSON
@@ -659,7 +659,7 @@ Container
-
+
JSON
@@ -675,6 +675,104 @@ Container
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Azure
+BLOB
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .txt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .json
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .gz
+
+
+
+
+
+
+
+
+
@@ -700,7 +798,7 @@ Vulnerability data
- HTTP
+ HTTP
@@ -712,17 +810,17 @@ Vulnerability data
-
+
- HTTP or --cve-dictoianry-dbpath option
+ HTTP
-
+
@@ -730,17 +828,17 @@ Vulnerability data
-
+
-
+
- HTTP
+ WebUI
-
+
@@ -748,17 +846,17 @@ Vulnerability data
-
+
-
+
- send
+ SSH
-
+
@@ -766,17 +864,17 @@ Vulnerability data
-
+
- Generate
+ SSH
-
+
@@ -784,17 +882,17 @@ Vulnerability data
-
+
- View Detail Information
+ docker exec
-
+
@@ -802,17 +900,37 @@ Vulnerability data
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
- SSH
+ Insert
-
+
@@ -820,17 +938,17 @@ Vulnerability data
-
+
- SSH
+ Notify
-
+
@@ -838,17 +956,17 @@ Vulnerability data
-
+
- docker exec
+ Select
-
+
@@ -856,7 +974,7 @@ Vulnerability data
-
+
@@ -866,7 +984,8 @@ Vulnerability data
-
+
+
@@ -876,25 +995,19 @@ Vulnerability data
-
+
+
- Insert
-
-
-
-
-
-
-
-
+
+
@@ -904,50 +1017,36 @@ Vulnerability data
-
+
+
- Notify
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
+
- Select
+ Put
@@ -959,17 +1058,8 @@ Vulnerability data
-
-
-
-
-
-
-
-
-
-
-
+
+
@@ -979,7 +1069,7 @@ Vulnerability data
-
+
@@ -990,13 +1080,22 @@ Vulnerability data
-
+
+ View Results
+ on Terminal
+
+
+
+
+
+
+
@@ -1185,6 +1284,180 @@ Vulnerability data
style="fill:#373d47;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path46-3"
inkscape:connector-curvature="0" /></g></svg>
+ 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
diff --git a/img/vuls-architecture.png b/img/vuls-architecture.png
index eeeb7d2e01..08a81457c7 100644
Binary files a/img/vuls-architecture.png and b/img/vuls-architecture.png differ
diff --git a/img/vuls-scan-flow.png b/img/vuls-scan-flow.png
index a82b9403b8..f4959b35a3 100644
Binary files a/img/vuls-scan-flow.png and b/img/vuls-scan-flow.png differ
diff --git a/main.go b/main.go
index 8fef988c87..cad1336ee3 100644
--- a/main.go
+++ b/main.go
@@ -31,7 +31,7 @@ import (
)
// Version of Vuls
-var version = "0.1.7"
+var version = "0.2.0"
// Revision of Git
var revision string
@@ -45,6 +45,7 @@ func main() {
subcommands.Register(&commands.ScanCmd{}, "scan")
subcommands.Register(&commands.PrepareCmd{}, "prepare")
subcommands.Register(&commands.HistoryCmd{}, "history")
+ subcommands.Register(&commands.ReportCmd{}, "report")
subcommands.Register(&commands.ConfigtestCmd{}, "configtest")
var v = flag.Bool("v", false, "Show version")
diff --git a/models/models.go b/models/models.go
index d5e75bf0be..1ad9976863 100644
--- a/models/models.go
+++ b/models/models.go
@@ -23,15 +23,13 @@ import (
"time"
"github.com/future-architect/vuls/config"
- "github.com/jinzhu/gorm"
+ "github.com/future-architect/vuls/cveapi"
cve "github.com/kotakanbe/go-cve-dictionary/models"
)
// ScanHistory is the history of Scanning.
type ScanHistory struct {
- gorm.Model
ScanResults ScanResults
- ScannedAt time.Time
}
// ScanResults is slice of ScanResult.
@@ -55,43 +53,111 @@ func (s ScanResults) Less(i, j int) bool {
return s[i].ServerName < s[j].ServerName
}
-// FilterByCvssOver is filter function.
-func (s ScanResults) FilterByCvssOver() (filtered ScanResults) {
- for _, result := range s {
- cveInfos := []CveInfo{}
- for _, cveInfo := range result.KnownCves {
- if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
- cveInfos = append(cveInfos, cveInfo)
- }
- }
- result.KnownCves = cveInfos
- filtered = append(filtered, result)
- }
- return
-}
-
// ScanResult has the result of scanned CVE information.
type ScanResult struct {
- gorm.Model `json:"-" xml:"-"`
- ScanHistoryID uint `json:"-" xml:"-"`
- ScannedAt time.Time
+ ScannedAt time.Time
+ Lang string
ServerName string // TOML Section key
- // Hostname string
- Family string
- Release string
-
- Container Container
+ Family string
+ Release string
+ Container Container
+ Platform Platform
- Platform Platform
+ // Scanned Vulns via SSH + CPE Vulns
+ ScannedCves []VulnInfo
- // Fqdn string
- // NWLinks []NWLink
KnownCves []CveInfo
UnknownCves []CveInfo
IgnoredCves []CveInfo
- Optional [][]interface{} `gorm:"-"`
+ Packages PackageInfoList
+
+ Optional [][]interface{}
+}
+
+// FillCveDetail fetches CVE detailed information from
+// CVE Database, and then set to fields.
+func (r ScanResult) FillCveDetail() (ScanResult, error) {
+ set := map[string]VulnInfo{}
+ var cveIDs []string
+ for _, v := range r.ScannedCves {
+ set[v.CveID] = v
+ cveIDs = append(cveIDs, v.CveID)
+ }
+
+ ds, err := cveapi.CveClient.FetchCveDetails(cveIDs)
+ if err != nil {
+ return r, err
+ }
+
+ icves := config.Conf.Servers[r.ServerName].IgnoreCves
+
+ var known, unknown, ignored CveInfos
+ for _, d := range ds {
+ cinfo := CveInfo{
+ CveDetail: d,
+ VulnInfo: set[d.CveID],
+ }
+
+ // ignored
+ found := false
+ for _, icve := range icves {
+ if icve == d.CveID {
+ ignored = append(ignored, cinfo)
+ found = true
+ break
+ }
+ }
+ if found {
+ continue
+ }
+
+ // unknown
+ if d.CvssScore(config.Conf.Lang) <= 0 {
+ unknown = append(unknown, cinfo)
+ continue
+ }
+
+ // known
+ known = append(known, cinfo)
+ }
+ sort.Sort(known)
+ sort.Sort(unknown)
+ sort.Sort(ignored)
+ r.KnownCves = known
+ r.UnknownCves = unknown
+ r.IgnoredCves = ignored
+ return r, nil
+}
+
+// FilterByCvssOver is filter function.
+func (r ScanResult) FilterByCvssOver() ScanResult {
+ cveInfos := []CveInfo{}
+ for _, cveInfo := range r.KnownCves {
+ if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
+ cveInfos = append(cveInfos, cveInfo)
+ }
+ }
+ r.KnownCves = cveInfos
+ return r
+}
+
+// ReportFileName returns the filename on localhost without extention
+func (r ScanResult) ReportFileName() (name string) {
+ if len(r.Container.ContainerID) == 0 {
+ return fmt.Sprintf("%s", r.ServerName)
+ }
+ return fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName)
+}
+
+// ReportKeyName returns the name of key on S3, Azure-Blob without extention
+func (r ScanResult) ReportKeyName() (name string) {
+ timestr := r.ScannedAt.Format(time.RFC3339)
+ if len(r.Container.ContainerID) == 0 {
+ return fmt.Sprintf("%s/%s", timestr, r.ServerName)
+ }
+ return fmt.Sprintf("%s/%s@%s", timestr, r.Container.Name, r.ServerName)
}
// ServerInfo returns server name one line
@@ -141,15 +207,15 @@ func (r ScanResult) ServerInfoTui() string {
// CveSummary summarize the number of CVEs group by CVSSv2 Severity
func (r ScanResult) CveSummary() string {
- var high, middle, low, unknown int
+ var high, medium, low, unknown int
cves := append(r.KnownCves, r.UnknownCves...)
for _, cveInfo := range cves {
score := cveInfo.CveDetail.CvssScore(config.Conf.Lang)
switch {
- case 7.0 < score:
+ case 7.0 <= score:
high++
- case 4.0 < score:
- middle++
+ case 4.0 <= score:
+ medium++
case 0 < score:
low++
default:
@@ -158,11 +224,11 @@ func (r ScanResult) CveSummary() string {
}
if config.Conf.IgnoreUnscoredCves {
- return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d)",
- high+middle+low, high, middle, low)
+ return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)",
+ high+medium+low, high, medium, low)
}
- return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d ?:%d)",
- high+middle+low+unknown, high, middle, low, unknown)
+ return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)",
+ high+medium+low+unknown, high, medium, low, unknown)
}
// AllCves returns Known and Unknown CVEs
@@ -172,15 +238,59 @@ func (r ScanResult) AllCves() []CveInfo {
// NWLink has network link information.
type NWLink struct {
- gorm.Model `json:"-" xml:"-"`
- ScanResultID uint `json:"-" xml:"-"`
-
IPAddress string
Netmask string
DevName string
LinkState string
}
+// VulnInfos is VulnInfo list, getter/setter, sortable methods.
+type VulnInfos []VulnInfo
+
+// VulnInfo holds a vulnerability information and unsecure packages
+type VulnInfo struct {
+ CveID string
+ Packages PackageInfoList
+ DistroAdvisories []DistroAdvisory // for Aamazon, RHEL, FreeBSD
+ CpeNames []string
+}
+
+// FindByCveID find by CVEID
+func (s VulnInfos) FindByCveID(cveID string) (VulnInfo, bool) {
+ for _, p := range s {
+ if cveID == p.CveID {
+ return p, true
+ }
+ }
+ return VulnInfo{CveID: cveID}, false
+}
+
+// immutable
+func (s VulnInfos) set(cveID string, v VulnInfo) VulnInfos {
+ for i, p := range s {
+ if cveID == p.CveID {
+ s[i] = v
+ return s
+ }
+ }
+ return append(s, v)
+}
+
+// Len implement Sort Interface
+func (s VulnInfos) Len() int {
+ return len(s)
+}
+
+// Swap implement Sort Interface
+func (s VulnInfos) Swap(i, j int) {
+ s[i], s[j] = s[j], s[i]
+}
+
+// Less implement Sort Interface
+func (s VulnInfos) Less(i, j int) bool {
+ return s[i].CveID < s[j].CveID
+}
+
// CveInfos is for sorting
type CveInfos []CveInfo
@@ -202,21 +312,8 @@ func (c CveInfos) Less(i, j int) bool {
// CveInfo has Cve Information.
type CveInfo struct {
- gorm.Model `json:"-" xml:"-"`
- ScanResultID uint `json:"-" xml:"-"`
-
- CveDetail cve.CveDetail
- Packages []PackageInfo
- DistroAdvisories []DistroAdvisory
- CpeNames []CpeName
-}
-
-// CpeName has CPE name
-type CpeName struct {
- gorm.Model `json:"-" xml:"-"`
- CveInfoID uint `json:"-" xml:"-"`
-
- Name string
+ CveDetail cve.CveDetail
+ VulnInfo
}
// PackageInfoList is slice of PackageInfo
@@ -260,6 +357,34 @@ func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found boo
return PackageInfo{}, false
}
+// MergeNewVersion merges candidate version information to the receiver struct
+func (ps PackageInfoList) MergeNewVersion(as PackageInfoList) {
+ for _, a := range as {
+ for i, p := range ps {
+ if p.Name == a.Name {
+ ps[i].NewVersion = a.NewVersion
+ ps[i].NewRelease = a.NewRelease
+ }
+ }
+ }
+}
+
+func (ps PackageInfoList) countUpdatablePacks() int {
+ count := 0
+ for _, p := range ps {
+ if len(p.NewVersion) != 0 {
+ count++
+ }
+ }
+ return count
+}
+
+// ToUpdatablePacksSummary returns a summary of updatable packages
+func (ps PackageInfoList) ToUpdatablePacksSummary() string {
+ return fmt.Sprintf("%d updatable packages",
+ ps.countUpdatablePacks())
+}
+
// Find search PackageInfo by name-version-release
// func (ps PackageInfoList) find(nameVersionRelease string) (PackageInfo, bool) {
// for _, p := range ps {
@@ -287,9 +412,6 @@ func (a PackageInfosByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
// PackageInfo has installed packages.
type PackageInfo struct {
- gorm.Model `json:"-" xml:"-"`
- CveInfoID uint `json:"-" xml:"-"`
-
Name string
Version string
Release string
@@ -324,9 +446,6 @@ func (p PackageInfo) ToStringNewVersion() string {
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
type DistroAdvisory struct {
- gorm.Model `json:"-" xml:"-"`
- CveInfoID uint `json:"-" xml:"-"`
-
AdvisoryID string
Severity string
Issued time.Time
@@ -335,18 +454,12 @@ type DistroAdvisory struct {
// Container has Container information
type Container struct {
- gorm.Model `json:"-" xml:"-"`
- ScanResultID uint `json:"-" xml:"-"`
-
ContainerID string
Name string
}
// Platform has platform information
type Platform struct {
- gorm.Model `json:"-" xml:"-"`
- ScanResultID uint `json:"-" xml:"-"`
-
Name string // aws or azure or gcp or other...
InstanceID string
}
diff --git a/models/models_test.go b/models/models_test.go
index a6fa25e677..0ef1d40e44 100644
--- a/models/models_test.go
+++ b/models/models_test.go
@@ -17,9 +17,14 @@ along with this program. If not, see .
package models
-import "testing"
+import (
+ "reflect"
+ "testing"
-func TestPackageInfosUniqByName(t *testing.T) {
+ "github.com/k0kubun/pp"
+)
+
+func TestPackageInfoListUniqByName(t *testing.T) {
var test = struct {
in PackageInfoList
out PackageInfoList
@@ -52,3 +57,81 @@ func TestPackageInfosUniqByName(t *testing.T) {
}
}
}
+
+func TestMergeNewVersion(t *testing.T) {
+ var test = struct {
+ a PackageInfoList
+ b PackageInfoList
+ expected PackageInfoList
+ }{
+ PackageInfoList{
+ {
+ Name: "hoge",
+ },
+ },
+ PackageInfoList{
+ {
+ Name: "hoge",
+ NewVersion: "1.0.0",
+ NewRelease: "release1",
+ },
+ },
+ PackageInfoList{
+ {
+ Name: "hoge",
+ NewVersion: "1.0.0",
+ NewRelease: "release1",
+ },
+ },
+ }
+
+ test.a.MergeNewVersion(test.b)
+ if !reflect.DeepEqual(test.a, test.expected) {
+ e := pp.Sprintf("%v", test.a)
+ a := pp.Sprintf("%v", test.expected)
+ t.Errorf("expected %s, actual %s", e, a)
+ }
+}
+func TestVulnInfosSetGet(t *testing.T) {
+ var test = struct {
+ in []string
+ out []string
+ }{
+ []string{
+ "CVE1",
+ "CVE2",
+ "CVE3",
+ "CVE1",
+ "CVE1",
+ "CVE2",
+ "CVE3",
+ },
+ []string{
+ "CVE1",
+ "CVE2",
+ "CVE3",
+ },
+ }
+
+ // var ps packageCveInfos
+ var ps VulnInfos
+ for _, cid := range test.in {
+ ps = ps.set(cid, VulnInfo{CveID: cid})
+ }
+
+ if len(test.out) != len(ps) {
+ t.Errorf("length: expected %d, actual %d", len(test.out), len(ps))
+ }
+
+ for i, expectedCid := range test.out {
+ if expectedCid != ps[i].CveID {
+ t.Errorf("expected %s, actual %s", expectedCid, ps[i].CveID)
+ }
+ }
+ for _, cid := range test.in {
+ p, _ := ps.FindByCveID(cid)
+ if p.CveID != cid {
+ t.Errorf("expected %s, actual %s", cid, p.CveID)
+ }
+ }
+}
diff --git a/report/azureblob.go b/report/azureblob.go
index bf2ef35bf7..2d84a8209a 100644
--- a/report/azureblob.go
+++ b/report/azureblob.go
@@ -20,6 +20,7 @@ package report
import (
"bytes"
"encoding/json"
+ "encoding/xml"
"fmt"
"time"
@@ -27,12 +28,76 @@ import (
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
- "github.com/future-architect/vuls/util"
)
// AzureBlobWriter writes results to AzureBlob
type AzureBlobWriter struct{}
+// Write results to Azure Blob storage
+func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) {
+ if len(rs) == 0 {
+ return nil
+ }
+
+ cli, err := getBlobClient()
+ if err != nil {
+ return err
+ }
+
+ if c.Conf.FormatOneLineText {
+ timestr := rs[0].ScannedAt.Format(time.RFC3339)
+ k := fmt.Sprintf(timestr + "/summary.txt")
+ text := toOneLineSummary(rs...)
+ b := []byte(text)
+ if err := createBlockBlob(cli, k, b); err != nil {
+ return err
+ }
+ }
+
+ for _, r := range rs {
+ key := r.ReportKeyName()
+ if c.Conf.FormatJSON {
+ k := key + ".json"
+ var b []byte
+ if b, err = json.Marshal(r); err != nil {
+ return fmt.Errorf("Failed to Marshal to JSON: %s", err)
+ }
+ if err := createBlockBlob(cli, k, b); err != nil {
+ return err
+ }
+ }
+
+ if c.Conf.FormatShortText {
+ k := key + "_short.txt"
+ b := []byte(toShortPlainText(r))
+ if err := createBlockBlob(cli, k, b); err != nil {
+ return err
+ }
+ }
+
+ if c.Conf.FormatFullText {
+ k := key + "_full.txt"
+ b := []byte(toFullPlainText(r))
+ if err := createBlockBlob(cli, k, b); err != nil {
+ return err
+ }
+ }
+
+ if c.Conf.FormatXML {
+ k := key + ".xml"
+ var b []byte
+ if b, err = xml.Marshal(r); err != nil {
+ return fmt.Errorf("Failed to Marshal to XML: %s", err)
+ }
+ allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
+ if err := createBlockBlob(cli, k, allBytes); err != nil {
+ return err
+ }
+ }
+ }
+ return
+}
+
// CheckIfAzureContainerExists check the existence of Azure storage container
func CheckIfAzureContainerExists() error {
cli, err := getBlobClient()
@@ -57,84 +122,24 @@ func getBlobClient() (storage.BlobStorageClient, error) {
return api.GetBlobService(), nil
}
-// Write results to Azure Blob storage
-func (w AzureBlobWriter) Write(scanResults []models.ScanResult) (err error) {
- reqChan := make(chan models.ScanResult, len(scanResults))
- resChan := make(chan bool)
- errChan := make(chan error, len(scanResults))
- defer close(resChan)
- defer close(errChan)
- defer close(reqChan)
-
- timeout := time.After(10 * 60 * time.Second)
- concurrency := 10
- tasks := util.GenWorkers(concurrency)
-
- go func() {
- for _, r := range scanResults {
- reqChan <- r
- }
- }()
-
- for range scanResults {
- tasks <- func() {
- select {
- case sresult := <-reqChan:
- func(r models.ScanResult) {
- err := w.upload(r)
- if err != nil {
- errChan <- err
- }
- resChan <- true
- }(sresult)
- }
- }
- }
-
- errs := []error{}
- for i := 0; i < len(scanResults); i++ {
- select {
- case <-resChan:
- case err := <-errChan:
- errs = append(errs, err)
- case <-timeout:
- errs = append(errs, fmt.Errorf("Timeout while uploading to azure Blob"))
+func createBlockBlob(cli storage.BlobStorageClient, k string, b []byte) error {
+ var err error
+ if c.Conf.GZIP {
+ if b, err = gz(b); err != nil {
+ return err
}
+ k = k + ".gz"
}
- if 0 < len(errs) {
- return fmt.Errorf("Failed to upload json to Azure Blob: %v", errs)
- }
- return nil
-}
-
-func (w AzureBlobWriter) upload(res models.ScanResult) (err error) {
- cli, err := getBlobClient()
- if err != nil {
- return err
- }
- timestr := time.Now().Format(time.RFC3339)
- name := ""
- if len(res.Container.ContainerID) == 0 {
- name = fmt.Sprintf("%s/%s.json", timestr, res.ServerName)
- } else {
- name = fmt.Sprintf("%s/%s_%s.json", timestr, res.ServerName, res.Container.Name)
- }
-
- jsonBytes, err := json.Marshal(res)
- if err != nil {
- return fmt.Errorf("Failed to Marshal to JSON: %s", err)
- }
-
- if err = cli.CreateBlockBlobFromReader(
+ if err := cli.CreateBlockBlobFromReader(
c.Conf.AzureContainer,
- name,
- uint64(len(jsonBytes)),
- bytes.NewReader(jsonBytes),
+ k,
+ uint64(len(b)),
+ bytes.NewReader(b),
map[string]string{},
); err != nil {
- return fmt.Errorf("%s/%s, %s",
- c.Conf.AzureContainer, name, err)
+ return fmt.Errorf("Failed to upload data to %s/%s, %s",
+ c.Conf.AzureContainer, k, err)
}
- return
+ return nil
}
diff --git a/report/mail.go b/report/email.go
similarity index 62%
rename from report/mail.go
rename to report/email.go
index a8ba42718f..e2b7c71ad4 100644
--- a/report/mail.go
+++ b/report/email.go
@@ -29,27 +29,27 @@ import (
"github.com/future-architect/vuls/models"
)
-// MailWriter send mail
-type MailWriter struct{}
+// EMailWriter send mail
+type EMailWriter struct{}
-func (w MailWriter) Write(scanResults []models.ScanResult) (err error) {
+func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
conf := config.Conf
- for _, s := range scanResults {
- to := strings.Join(conf.Mail.To[:], ", ")
- cc := strings.Join(conf.Mail.Cc[:], ", ")
- mailAddresses := append(conf.Mail.To, conf.Mail.Cc...)
- if _, err := mail.ParseAddressList(strings.Join(mailAddresses[:], ", ")); err != nil {
- return fmt.Errorf("Failed to parse email addresses: %s", err)
- }
+ to := strings.Join(conf.EMail.To[:], ", ")
+ cc := strings.Join(conf.EMail.Cc[:], ", ")
+ mailAddresses := append(conf.EMail.To, conf.EMail.Cc...)
+ if _, err := mail.ParseAddressList(strings.Join(mailAddresses[:], ", ")); err != nil {
+ return fmt.Errorf("Failed to parse email addresses: %s", err)
+ }
+ for _, r := range rs {
subject := fmt.Sprintf("%s%s %s",
- conf.Mail.SubjectPrefix,
- s.ServerInfo(),
- s.CveSummary(),
+ conf.EMail.SubjectPrefix,
+ r.ServerInfo(),
+ r.CveSummary(),
)
headers := make(map[string]string)
- headers["From"] = conf.Mail.From
+ headers["From"] = conf.EMail.From
headers["To"] = to
headers["Cc"] = cc
headers["Subject"] = subject
@@ -60,25 +60,19 @@ func (w MailWriter) Write(scanResults []models.ScanResult) (err error) {
for k, v := range headers {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
+ message += "\r\n" + toFullPlainText(r)
- var body string
- if body, err = toPlainText(s); err != nil {
- return err
- }
- message += "\r\n" + body
-
- smtpServer := net.JoinHostPort(conf.Mail.SMTPAddr, conf.Mail.SMTPPort)
-
- err := smtp.SendMail(
+ smtpServer := net.JoinHostPort(conf.EMail.SMTPAddr, conf.EMail.SMTPPort)
+ err = smtp.SendMail(
smtpServer,
smtp.PlainAuth(
"",
- conf.Mail.User,
- conf.Mail.Password,
- conf.Mail.SMTPAddr,
+ conf.EMail.User,
+ conf.EMail.Password,
+ conf.EMail.SMTPAddr,
),
- conf.Mail.From,
- conf.Mail.To,
+ conf.EMail.From,
+ conf.EMail.To,
[]byte(message),
)
diff --git a/report/json.go b/report/json.go
deleted file mode 100644
index 728239efab..0000000000
--- a/report/json.go
+++ /dev/null
@@ -1,150 +0,0 @@
-/* Vuls - Vulnerability Scanner
-Copyright (C) 2016 Future Architect, Inc. Japan.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*/
-
-package report
-
-import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "regexp"
- "sort"
- "strings"
- "time"
-
- c "github.com/future-architect/vuls/config"
- "github.com/future-architect/vuls/models"
-)
-
-// JSONDirs array of json files path.
-type JSONDirs []string
-
-func (d JSONDirs) Len() int {
- return len(d)
-}
-func (d JSONDirs) Swap(i, j int) {
- d[i], d[j] = d[j], d[i]
-}
-func (d JSONDirs) Less(i, j int) bool {
- return d[j] < d[i]
-}
-
-// JSONWriter writes results to file.
-type JSONWriter struct {
- ScannedAt time.Time
-}
-
-func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
- var path string
- if path, err = ensureResultDir(w.ScannedAt); err != nil {
- return fmt.Errorf("Failed to make direcotory/symlink : %s", err)
- }
-
- for _, scanResult := range scanResults {
- scanResult.ScannedAt = w.ScannedAt
- }
-
- var jsonBytes []byte
- for _, r := range scanResults {
- jsonPath := ""
- if len(r.Container.ContainerID) == 0 {
- jsonPath = filepath.Join(path, fmt.Sprintf("%s.json", r.ServerName))
- } else {
- jsonPath = filepath.Join(path,
- fmt.Sprintf("%s_%s.json", r.ServerName, r.Container.Name))
- }
-
- if jsonBytes, err = json.Marshal(r); err != nil {
- return fmt.Errorf("Failed to Marshal to JSON: %s", err)
- }
- if err := ioutil.WriteFile(jsonPath, jsonBytes, 0600); err != nil {
- return fmt.Errorf("Failed to write JSON. path: %s, err: %s", jsonPath, err)
- }
- }
- return nil
-}
-
-// JSONDirPattern is file name pattern of JSON directory
-var JSONDirPattern = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$`)
-
-// GetValidJSONDirs return valid json directory as array
-func GetValidJSONDirs() (jsonDirs JSONDirs, err error) {
- var dirInfo []os.FileInfo
- if dirInfo, err = ioutil.ReadDir(c.Conf.ResultsDir); err != nil {
- err = fmt.Errorf("Failed to read %s: %s", c.Conf.ResultsDir, err)
- return
- }
- for _, d := range dirInfo {
- if d.IsDir() && JSONDirPattern.MatchString(d.Name()) {
- jsonDir := filepath.Join(c.Conf.ResultsDir, d.Name())
- jsonDirs = append(jsonDirs, jsonDir)
- }
- }
- sort.Sort(jsonDirs)
- return
-}
-
-// LoadOneScanHistory read JSON data
-func LoadOneScanHistory(jsonDir string) (scanHistory models.ScanHistory, err error) {
- var scanResults []models.ScanResult
- var files []os.FileInfo
- if files, err = ioutil.ReadDir(jsonDir); err != nil {
- err = fmt.Errorf("Failed to read %s: %s", jsonDir, err)
- return
- }
- for _, file := range files {
- if filepath.Ext(file.Name()) != ".json" {
- continue
- }
- var scanResult models.ScanResult
- var data []byte
- jsonPath := filepath.Join(jsonDir, file.Name())
- if data, err = ioutil.ReadFile(jsonPath); err != nil {
- err = fmt.Errorf("Failed to read %s: %s", jsonPath, err)
- return
- }
- if json.Unmarshal(data, &scanResult) != nil {
- err = fmt.Errorf("Failed to parse %s: %s", jsonPath, err)
- return
- }
- scanResults = append(scanResults, scanResult)
- }
- if len(scanResults) == 0 {
- err = fmt.Errorf("There is no json file under %s", jsonDir)
- return
- }
-
- var scannedAt time.Time
- if scanResults[0].ScannedAt.IsZero() {
- splitPath := strings.Split(jsonDir, string(os.PathSeparator))
- timeStr := splitPath[len(splitPath)-1]
- if scannedAt, err = time.Parse(time.RFC3339, timeStr); err != nil {
- err = fmt.Errorf("Failed to parse %s: %s", timeStr, err)
- return
- }
- } else {
- scannedAt = scanResults[0].ScannedAt
- }
-
- scanHistory = models.ScanHistory{
- ScanResults: scanResults,
- ScannedAt: scannedAt,
- }
- return
-}
diff --git a/report/localfile.go b/report/localfile.go
new file mode 100644
index 0000000000..f8d6e20226
--- /dev/null
+++ b/report/localfile.go
@@ -0,0 +1,111 @@
+/* Vuls - Vulnerability Scanner
+Copyright (C) 2016 Future Architect, Inc. Japan.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+package report
+
+import (
+ "bytes"
+ "encoding/json"
+ "encoding/xml"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ c "github.com/future-architect/vuls/config"
+ "github.com/future-architect/vuls/models"
+)
+
+// LocalFileWriter writes results to a local file.
+type LocalFileWriter struct {
+ CurrentDir string
+}
+
+func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
+ if c.Conf.FormatOneLineText {
+ path := filepath.Join(w.CurrentDir, "summary.txt")
+ text := toOneLineSummary(rs...)
+ if err := writeFile(path, []byte(text), 0600); err != nil {
+ return fmt.Errorf(
+ "Failed to write to file. path: %s, err: %s",
+ path, err)
+ }
+ }
+
+ for _, r := range rs {
+ path := filepath.Join(w.CurrentDir, r.ReportFileName())
+
+ if c.Conf.FormatJSON {
+ p := path + ".json"
+ var b []byte
+ if b, err = json.Marshal(r); err != nil {
+ return fmt.Errorf("Failed to Marshal to JSON: %s", err)
+ }
+ if err := writeFile(p, b, 0600); err != nil {
+ return fmt.Errorf("Failed to write JSON. path: %s, err: %s", p, err)
+ }
+ }
+
+ if c.Conf.FormatShortText {
+ p := path + "_short.txt"
+ if err := writeFile(
+ p, []byte(toShortPlainText(r)), 0600); err != nil {
+ return fmt.Errorf(
+ "Failed to write text files. path: %s, err: %s", p, err)
+ }
+ }
+
+ if c.Conf.FormatFullText {
+ p := path + "_full.txt"
+ if err := writeFile(
+ p, []byte(toFullPlainText(r)), 0600); err != nil {
+ return fmt.Errorf(
+ "Failed to write text files. path: %s, err: %s", p, err)
+ }
+ }
+
+ if c.Conf.FormatXML {
+ p := path + ".xml"
+ var b []byte
+ if b, err = xml.Marshal(r); err != nil {
+ return fmt.Errorf("Failed to Marshal to XML: %s", err)
+ }
+ allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
+ if err := writeFile(p, allBytes, 0600); err != nil {
+ return fmt.Errorf("Failed to write XML. path: %s, err: %s", p, err)
+ }
+ }
+ }
+ return nil
+}
+
+func writeFile(path string, data []byte, perm os.FileMode) error {
+ var err error
+ if c.Conf.GZIP {
+ if data, err = gz(data); err != nil {
+ return err
+ }
+ path = path + ".gz"
+ }
+
+ if err := ioutil.WriteFile(
+ path, []byte(data), perm); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/report/logrus.go b/report/logrus.go
deleted file mode 100644
index c48b2a5fae..0000000000
--- a/report/logrus.go
+++ /dev/null
@@ -1,56 +0,0 @@
-/* Vuls - Vulnerability Scanner
-Copyright (C) 2016 Future Architect, Inc. Japan.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*/
-
-package report
-
-import (
- "os"
- "path/filepath"
- "runtime"
-
- "github.com/Sirupsen/logrus"
- "github.com/future-architect/vuls/models"
- formatter "github.com/kotakanbe/logrus-prefixed-formatter"
-)
-
-// LogrusWriter write to logfile
-type LogrusWriter struct {
-}
-
-func (w LogrusWriter) Write(scanResults []models.ScanResult) error {
- path := "/var/log/vuls/report.log"
- if runtime.GOOS == "windows" {
- path = filepath.Join(os.Getenv("APPDATA"), "vuls", "report.log")
- }
- f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
- if err != nil {
- return err
- }
- log := logrus.New()
- log.Formatter = &formatter.TextFormatter{}
- log.Out = f
- log.Level = logrus.InfoLevel
-
- for _, s := range scanResults {
- text, err := toPlainText(s)
- if err != nil {
- return err
- }
- log.Infof(text)
- }
- return nil
-}
diff --git a/report/s3.go b/report/s3.go
index 062623038c..754d757787 100644
--- a/report/s3.go
+++ b/report/s3.go
@@ -20,6 +20,7 @@ package report
import (
"bytes"
"encoding/json"
+ "encoding/xml"
"fmt"
"time"
@@ -32,6 +33,78 @@ import (
"github.com/future-architect/vuls/models"
)
+// S3Writer writes results to S3
+type S3Writer struct{}
+
+func getS3() *s3.S3 {
+ return s3.New(session.New(&aws.Config{
+ Region: aws.String(c.Conf.AwsRegion),
+ Credentials: credentials.NewSharedCredentials("", c.Conf.AwsProfile),
+ }))
+}
+
+// Write results to S3
+// http://docs.aws.amazon.com/sdk-for-go/latest/v1/developerguide/common-examples.title.html
+func (w S3Writer) Write(rs ...models.ScanResult) (err error) {
+ if len(rs) == 0 {
+ return nil
+ }
+
+ svc := getS3()
+
+ if c.Conf.FormatOneLineText {
+ timestr := rs[0].ScannedAt.Format(time.RFC3339)
+ k := fmt.Sprintf(timestr + "/summary.txt")
+ text := toOneLineSummary(rs...)
+ if err := putObject(svc, k, []byte(text)); err != nil {
+ return err
+ }
+ }
+
+ for _, r := range rs {
+ key := r.ReportKeyName()
+ if c.Conf.FormatJSON {
+ k := key + ".json"
+ var b []byte
+ if b, err = json.Marshal(r); err != nil {
+ return fmt.Errorf("Failed to Marshal to JSON: %s", err)
+ }
+ if err := putObject(svc, k, b); err != nil {
+ return err
+ }
+ }
+
+ if c.Conf.FormatShortText {
+ k := key + "_short.txt"
+ text := toShortPlainText(r)
+ if err := putObject(svc, k, []byte(text)); err != nil {
+ return err
+ }
+ }
+
+ if c.Conf.FormatFullText {
+ k := key + "_full.txt"
+ text := toFullPlainText(r)
+ if err := putObject(svc, k, []byte(text)); err != nil {
+ return err
+ }
+ }
+
+ if c.Conf.FormatXML {
+ k := key + ".xml"
+ var b []byte
+ if b, err = xml.Marshal(r); err != nil {
+ return fmt.Errorf("Failed to Marshal to XML: %s", err)
+ }
+ allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
+ if err := putObject(svc, k, allBytes); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
// CheckIfBucketExists check the existence of S3 bucket
func CheckIfBucketExists() error {
svc := getS3()
@@ -57,46 +130,22 @@ func CheckIfBucketExists() error {
return nil
}
-// S3Writer writes results to S3
-type S3Writer struct{}
-
-func getS3() *s3.S3 {
- return s3.New(session.New(&aws.Config{
- Region: aws.String(c.Conf.AwsRegion),
- Credentials: credentials.NewSharedCredentials("", c.Conf.AwsProfile),
- }))
-}
-
-// Write results to S3
-func (w S3Writer) Write(scanResults []models.ScanResult) (err error) {
-
- var jsonBytes []byte
- if jsonBytes, err = json.Marshal(scanResults); err != nil {
- return fmt.Errorf("Failed to Marshal to JSON: %s", err)
- }
-
- // http://docs.aws.amazon.com/sdk-for-go/latest/v1/developerguide/common-examples.title.html
- svc := getS3()
- timestr := time.Now().Format(time.RFC3339)
- for _, r := range scanResults {
- key := ""
- if len(r.Container.ContainerID) == 0 {
- key = fmt.Sprintf("%s/%s.json", timestr, r.ServerName)
- } else {
- key = fmt.Sprintf("%s/%s_%s.json", timestr, r.ServerName, r.Container.Name)
+func putObject(svc *s3.S3, k string, b []byte) error {
+ var err error
+ if c.Conf.GZIP {
+ if b, err = gz(b); err != nil {
+ return err
}
+ k = k + ".gz"
+ }
- if jsonBytes, err = json.Marshal(r); err != nil {
- return fmt.Errorf("Failed to Marshal to JSON: %s", err)
- }
- _, err = svc.PutObject(&s3.PutObjectInput{
- Bucket: &c.Conf.S3Bucket,
- Key: &key,
- Body: bytes.NewReader(jsonBytes),
- })
- if err != nil {
- return fmt.Errorf("Failed to upload data to %s/%s, %s", c.Conf.S3Bucket, key, err)
- }
+ if _, err := svc.PutObject(&s3.PutObjectInput{
+ Bucket: &c.Conf.S3Bucket,
+ Key: &k,
+ Body: bytes.NewReader(b),
+ }); err != nil {
+ return fmt.Errorf("Failed to upload data to %s/%s, %s",
+ c.Conf.S3Bucket, k, err)
}
return nil
}
diff --git a/report/slack.go b/report/slack.go
index 655445e67f..d38e49ca22 100644
--- a/report/slack.go
+++ b/report/slack.go
@@ -56,36 +56,36 @@ type message struct {
// SlackWriter send report to slack
type SlackWriter struct{}
-func (w SlackWriter) Write(scanResults []models.ScanResult) error {
+func (w SlackWriter) Write(rs ...models.ScanResult) error {
conf := config.Conf.Slack
- for _, s := range scanResults {
- channel := conf.Channel
+ channel := conf.Channel
+
+ for _, r := range rs {
if channel == "${servername}" {
- channel = fmt.Sprintf("#%s", s.ServerName)
+ channel = fmt.Sprintf("#%s", r.ServerName)
}
msg := message{
- Text: msgText(s),
+ Text: msgText(r),
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
Channel: channel,
- Attachments: toSlackAttachments(s),
+ Attachments: toSlackAttachments(r),
}
bytes, _ := json.Marshal(msg)
jsonBody := string(bytes)
f := func() (err error) {
- resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).
- Send(string(jsonBody)).End()
- if resp.StatusCode != 200 {
- log.Errorf("Resonse body: %s", body)
- if 0 < len(errs) {
- return errs[0]
- }
+ resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).Send(string(jsonBody)).End()
+ if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
+ return fmt.Errorf(
+ "HTTP POST error: %v, url: %s, resp: %v, body: %s",
+ errs, conf.HookURL, resp, body)
}
return nil
}
notify := func(err error, t time.Duration) {
+ log.Warn("Error %s", err)
log.Warn("Retrying in ", t)
}
if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
@@ -118,8 +118,8 @@ func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
for _, p := range cveInfo.Packages {
curentPackages = append(curentPackages, p.ToStringCurrentVersion())
}
- for _, cpename := range cveInfo.CpeNames {
- curentPackages = append(curentPackages, cpename.Name)
+ for _, n := range cveInfo.CpeNames {
+ curentPackages = append(curentPackages, n)
}
newPackages := []string{}
diff --git a/report/stdout.go b/report/stdout.go
index eb7dbaa5cc..ef963c58c6 100644
--- a/report/stdout.go
+++ b/report/stdout.go
@@ -20,19 +20,40 @@ package report
import (
"fmt"
+ c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
)
// StdoutWriter write to stdout
type StdoutWriter struct{}
-func (w StdoutWriter) Write(scanResults []models.ScanResult) error {
- for _, s := range scanResults {
- text, err := toPlainText(s)
- if err != nil {
- return err
+// WriteScanSummary prints Scan summary at the end of scan
+func (w StdoutWriter) WriteScanSummary(rs ...models.ScanResult) {
+ fmt.Printf("\n\n")
+ fmt.Printf("Scan Summary\n")
+ fmt.Printf("============\n")
+ fmt.Printf("%s\n", toScanSummary(rs...))
+}
+
+func (w StdoutWriter) Write(rs ...models.ScanResult) error {
+ if c.Conf.FormatOneLineText {
+ fmt.Print("\n\n")
+ fmt.Println("One Line Summary")
+ fmt.Println("================")
+ fmt.Println(toOneLineSummary(rs...))
+ fmt.Print("\n")
+ }
+
+ if c.Conf.FormatShortText {
+ for _, r := range rs {
+ fmt.Println(toShortPlainText(r))
+ }
+ }
+
+ if c.Conf.FormatFullText {
+ for _, r := range rs {
+ fmt.Println(toFullPlainText(r))
}
- fmt.Println(text)
}
return nil
}
diff --git a/report/textfile.go b/report/textfile.go
deleted file mode 100644
index d93a7e66ea..0000000000
--- a/report/textfile.go
+++ /dev/null
@@ -1,64 +0,0 @@
-/* Vuls - Vulnerability Scanner
-Copyright (C) 2016 Future Architect, Inc. Japan.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*/
-
-package report
-
-import (
- "fmt"
- "io/ioutil"
- "path/filepath"
- "strings"
- "time"
-
- "github.com/future-architect/vuls/models"
-)
-
-// TextFileWriter writes results to file.
-type TextFileWriter struct {
- ScannedAt time.Time
-}
-
-func (w TextFileWriter) Write(scanResults []models.ScanResult) (err error) {
- path, err := ensureResultDir(w.ScannedAt)
- all := []string{}
- for _, r := range scanResults {
- textFilePath := ""
- if len(r.Container.ContainerID) == 0 {
- textFilePath = filepath.Join(path, fmt.Sprintf("%s.txt", r.ServerName))
- } else {
- textFilePath = filepath.Join(path,
- fmt.Sprintf("%s_%s.txt", r.ServerName, r.Container.Name))
- }
- text, err := toPlainText(r)
- if err != nil {
- return err
- }
- all = append(all, text)
- b := []byte(text)
- if err := ioutil.WriteFile(textFilePath, b, 0600); err != nil {
- return fmt.Errorf("Failed to write text files. path: %s, err: %s", textFilePath, err)
- }
- }
-
- text := strings.Join(all, "\n\n")
- b := []byte(text)
- allPath := filepath.Join(path, "all.txt")
- if err := ioutil.WriteFile(allPath, b, 0600); err != nil {
- return fmt.Errorf("Failed to write text files. path: %s, err: %s", allPath, err)
- }
- return nil
-}
diff --git a/report/tui.go b/report/tui.go
index bf7ad6d508..ce5c9965ef 100644
--- a/report/tui.go
+++ b/report/tui.go
@@ -20,7 +20,7 @@ package report
import (
"bytes"
"fmt"
- "path/filepath"
+ "os"
"strings"
"text/template"
"time"
@@ -40,13 +40,8 @@ var currentCveInfo int
var currentDetailLimitY int
// RunTui execute main logic
-func RunTui(jsonDirName string) subcommands.ExitStatus {
- var err error
- scanHistory, err = selectScanHistory(jsonDirName)
- if err != nil {
- log.Errorf("%s", err)
- return subcommands.ExitFailure
- }
+func RunTui(history models.ScanHistory) subcommands.ExitStatus {
+ scanHistory = history
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
@@ -64,34 +59,14 @@ func RunTui(jsonDirName string) subcommands.ExitStatus {
g.SelFgColor = gocui.ColorBlack
g.Cursor = true
- if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
+ if err := g.MainLoop(); err != nil {
+ g.Close()
log.Errorf("%s", err)
- return subcommands.ExitFailure
+ os.Exit(1)
}
-
return subcommands.ExitSuccess
}
-func selectScanHistory(jsonDirName string) (latest models.ScanHistory, err error) {
- var jsonDir string
- if 0 < len(jsonDirName) {
- jsonDir = filepath.Join(config.Conf.ResultsDir, jsonDirName)
- } else {
- var jsonDirs JSONDirs
- if jsonDirs, err = GetValidJSONDirs(); err != nil {
- return
- }
- if len(jsonDirs) == 0 {
- return latest, fmt.Errorf("No scan results are found in %s", config.Conf.ResultsDir)
- }
- jsonDir = jsonDirs[0]
- }
- if latest, err = LoadOneScanHistory(jsonDir); err != nil {
- return
- }
- return
-}
-
func keybindings(g *gocui.Gui) (err error) {
errs := []error{}
@@ -537,6 +512,9 @@ func setSideLayout(g *gocui.Gui) error {
for _, result := range scanHistory.ScanResults {
fmt.Fprintln(v, result.ServerInfoTui())
}
+ if len(scanHistory.ScanResults) == 0 {
+ return fmt.Errorf("No scan results")
+ }
currentScanResult = scanHistory.ScanResults[0]
if _, err := g.SetCurrentView("side"); err != nil {
return err
@@ -666,7 +644,7 @@ type dataForTmpl struct {
VulnSiteLinks []string
References []cve.Reference
Packages []string
- CpeNames []models.CpeName
+ CpeNames []string
PublishedDate time.Time
LastModifiedDate time.Time
}
@@ -780,8 +758,8 @@ Package/CPE
{{range $pack := .Packages -}}
* {{$pack}}
{{end -}}
-{{range .CpeNames -}}
-* {{.Name}}
+{{range $name := .CpeNames -}}
+* {{$name}}
{{end}}
Links
--------------
diff --git a/report/util.go b/report/util.go
index e236c7502c..667abe210f 100644
--- a/report/util.go
+++ b/report/util.go
@@ -20,89 +20,49 @@ package report
import (
"bytes"
"fmt"
- "os"
- "path/filepath"
"strings"
- "time"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/gosuri/uitable"
)
-func ensureResultDir(scannedAt time.Time) (path string, err error) {
- jsonDirName := scannedAt.Format(time.RFC3339)
-
- resultsDir := config.Conf.ResultsDir
- if len(resultsDir) == 0 {
- wd, _ := os.Getwd()
- resultsDir = filepath.Join(wd, "results")
- }
- jsonDir := filepath.Join(resultsDir, jsonDirName)
-
- if err := os.MkdirAll(jsonDir, 0700); err != nil {
- return "", fmt.Errorf("Failed to create dir: %s", err)
- }
-
- symlinkPath := filepath.Join(resultsDir, "current")
- if _, err := os.Lstat(symlinkPath); err == nil {
- if err := os.Remove(symlinkPath); err != nil {
- return "", fmt.Errorf(
- "Failed to remove symlink. path: %s, err: %s", symlinkPath, err)
+const maxColWidth = 80
+
+func toScanSummary(rs ...models.ScanResult) string {
+ table := uitable.New()
+ table.MaxColWidth = maxColWidth
+ table.Wrap = true
+ for _, r := range rs {
+ cols := []interface{}{
+ r.ServerName,
+ fmt.Sprintf("%s%s", r.Family, r.Release),
+ fmt.Sprintf("%d CVEs", len(r.ScannedCves)),
+ r.Packages.ToUpdatablePacksSummary(),
}
+ table.AddRow(cols...)
}
-
- if err := os.Symlink(jsonDir, symlinkPath); err != nil {
- return "", fmt.Errorf(
- "Failed to create symlink: path: %s, err: %s", symlinkPath, err)
- }
- return jsonDir, nil
+ return fmt.Sprintf("%s\n", table)
}
-func toPlainText(scanResult models.ScanResult) (string, error) {
- serverInfo := scanResult.ServerInfo()
-
- var buffer bytes.Buffer
- for i := 0; i < len(serverInfo); i++ {
- buffer.WriteString("=")
- }
- header := fmt.Sprintf("%s\n%s", serverInfo, buffer.String())
-
- if len(scanResult.KnownCves) == 0 && len(scanResult.UnknownCves) == 0 {
- return fmt.Sprintf(`
-%s
-No unsecure packages.
-`, header), nil
- }
-
- summary := ToPlainTextSummary(scanResult)
- scoredReport, unscoredReport := []string{}, []string{}
- scoredReport, unscoredReport = toPlainTextDetails(scanResult, scanResult.Family)
-
- scored := strings.Join(scoredReport, "\n\n")
-
- unscored := ""
- if !config.Conf.IgnoreUnscoredCves {
- unscored = strings.Join(unscoredReport, "\n\n")
+func toOneLineSummary(rs ...models.ScanResult) string {
+ table := uitable.New()
+ table.MaxColWidth = maxColWidth
+ table.Wrap = true
+ for _, r := range rs {
+ cols := []interface{}{
+ r.ServerName,
+ r.CveSummary(),
+ r.Packages.ToUpdatablePacksSummary(),
+ }
+ table.AddRow(cols...)
}
-
- detail := fmt.Sprintf(`
-%s
-
-%s
-`,
- scored,
- unscored,
- )
- text := fmt.Sprintf("%s\n%s\n%s\n", header, summary, detail)
-
- return text, nil
+ return fmt.Sprintf("%s\n", table)
}
-// ToPlainTextSummary format summary for plain text.
-func ToPlainTextSummary(r models.ScanResult) string {
+func toShortPlainText(r models.ScanResult) string {
stable := uitable.New()
- stable.MaxColWidth = 84
+ stable.MaxColWidth = maxColWidth
stable.Wrap = true
cves := r.KnownCves
@@ -110,14 +70,45 @@ func ToPlainTextSummary(r models.ScanResult) string {
cves = append(cves, r.UnknownCves...)
}
+ var buf bytes.Buffer
+ for i := 0; i < len(r.ServerInfo()); i++ {
+ buf.WriteString("=")
+ }
+ header := fmt.Sprintf("%s\n%s\n%s\t%s\n\n",
+ r.ServerInfo(),
+ buf.String(),
+ r.CveSummary(),
+ r.Packages.ToUpdatablePacksSummary(),
+ )
+
+ if len(cves) == 0 {
+ return fmt.Sprintf(`
+%s
+No CVE-IDs are found in updatable packages.
+%s
+`, header, r.Packages.ToUpdatablePacksSummary())
+ }
+
for _, d := range cves {
- var scols []string
+ var packsVer string
+ for _, p := range d.Packages {
+ packsVer += fmt.Sprintf(
+ "%s -> %s\n", p.ToStringCurrentVersion(), p.ToStringNewVersion())
+ }
+ for _, n := range d.CpeNames {
+ packsVer += n
+ }
+ var scols []string
switch {
case config.Conf.Lang == "ja" &&
0 < d.CveDetail.Jvn.CvssScore():
-
- summary := d.CveDetail.Jvn.CveTitle()
+ summary := fmt.Sprintf("%s\n%s\n%s\n%s",
+ d.CveDetail.Jvn.CveTitle(),
+ d.CveDetail.Jvn.Link(),
+ distroLinks(d, r.Family)[0].url,
+ packsVer,
+ )
scols = []string{
d.CveDetail.CveID,
fmt.Sprintf("%-4.1f (%s)",
@@ -126,8 +117,15 @@ func ToPlainTextSummary(r models.ScanResult) string {
),
summary,
}
+
case 0 < d.CveDetail.CvssScore("en"):
- summary := d.CveDetail.Nvd.CveSummary()
+ summary := fmt.Sprintf("%s\n%s/%s\n%s\n%s",
+ d.CveDetail.Nvd.CveSummary(),
+ cveDetailsBaseURL,
+ d.CveDetail.CveID,
+ distroLinks(d, r.Family)[0].url,
+ packsVer,
+ )
scols = []string{
d.CveDetail.CveID,
fmt.Sprintf("%-4.1f (%s)",
@@ -137,10 +135,12 @@ func ToPlainTextSummary(r models.ScanResult) string {
summary,
}
default:
+ summary := fmt.Sprintf("%s\n%s",
+ distroLinks(d, r.Family)[0].url, packsVer)
scols = []string{
d.CveDetail.CveID,
"?",
- d.CveDetail.Nvd.CveSummary(),
+ summary,
}
}
@@ -149,12 +149,55 @@ func ToPlainTextSummary(r models.ScanResult) string {
cols[i] = scols[i]
}
stable.AddRow(cols...)
+ stable.AddRow("")
+ }
+ return fmt.Sprintf("%s\n%s\n", header, stable)
+}
+
+func toFullPlainText(r models.ScanResult) string {
+ serverInfo := r.ServerInfo()
+
+ var buf bytes.Buffer
+ for i := 0; i < len(serverInfo); i++ {
+ buf.WriteString("=")
+ }
+ header := fmt.Sprintf("%s\n%s\n%s\t%s\n",
+ r.ServerInfo(),
+ buf.String(),
+ r.CveSummary(),
+ r.Packages.ToUpdatablePacksSummary(),
+ )
+
+ if len(r.KnownCves) == 0 && len(r.UnknownCves) == 0 {
+ return fmt.Sprintf(`
+%s
+No CVE-IDs are found in updatable packages.
+%s
+`, header, r.Packages.ToUpdatablePacksSummary())
+ }
+
+ scoredReport, unscoredReport := []string{}, []string{}
+ scoredReport, unscoredReport = toPlainTextDetails(r, r.Family)
+
+ unscored := ""
+ if !config.Conf.IgnoreUnscoredCves {
+ unscored = strings.Join(unscoredReport, "\n\n")
}
- return fmt.Sprintf("%s", stable)
+
+ scored := strings.Join(scoredReport, "\n\n")
+ detail := fmt.Sprintf(`
+%s
+
+%s
+`,
+ scored,
+ unscored,
+ )
+ return fmt.Sprintf("%s\n%s\n", header, detail)
}
-func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
- for _, cve := range data.KnownCves {
+func toPlainTextDetails(r models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
+ for _, cve := range r.KnownCves {
switch config.Conf.Lang {
case "en":
if 0 < cve.CveDetail.Nvd.CvssScore() {
@@ -177,7 +220,7 @@ func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport,
}
}
}
- for _, cve := range data.UnknownCves {
+ for _, cve := range r.UnknownCves {
unscoredReport = append(
unscoredReport, toPlainTextUnknownCve(cve, osFamily))
}
@@ -187,7 +230,7 @@ func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport,
func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
cveID := cveInfo.CveDetail.CveID
dtable := uitable.New()
- dtable.MaxColWidth = 100
+ dtable.MaxColWidth = maxColWidth
dtable.Wrap = true
dtable.AddRow(cveID)
dtable.AddRow("-------------")
@@ -201,6 +244,8 @@ func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
for _, link := range dlinks {
dtable.AddRow(link.title, link.url)
}
+ dtable = addPackageInfos(dtable, cveInfo.Packages)
+ dtable = addCpeNames(dtable, cveInfo.CpeNames)
return fmt.Sprintf("%s", dtable)
}
@@ -211,7 +256,7 @@ func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
jvn := cveDetail.Jvn
dtable := uitable.New()
- dtable.MaxColWidth = 100
+ dtable.MaxColWidth = maxColWidth
dtable.Wrap = true
dtable.AddRow(cveID)
dtable.AddRow("-------------")
@@ -253,7 +298,7 @@ func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
nvd := cveDetail.Nvd
dtable := uitable.New()
- dtable.MaxColWidth = 100
+ dtable.MaxColWidth = maxColWidth
dtable.Wrap = true
dtable.AddRow(cveID)
dtable.AddRow("-------------")
@@ -357,13 +402,12 @@ func distroLinks(cveInfo models.CveInfo, osFamily string) []distroLink {
}
}
-//TODO
// addPackageInfos add package information related the CVE to table
func addPackageInfos(table *uitable.Table, packs []models.PackageInfo) *uitable.Table {
for i, p := range packs {
var title string
if i == 0 {
- title = "Package/CPE"
+ title = "Package"
}
ver := fmt.Sprintf(
"%s -> %s", p.ToStringCurrentVersion(), p.ToStringNewVersion())
@@ -372,9 +416,9 @@ func addPackageInfos(table *uitable.Table, packs []models.PackageInfo) *uitable.
return table
}
-func addCpeNames(table *uitable.Table, names []models.CpeName) *uitable.Table {
- for _, p := range names {
- table.AddRow("CPE", fmt.Sprintf("%s", p.Name))
+func addCpeNames(table *uitable.Table, names []string) *uitable.Table {
+ for _, n := range names {
+ table.AddRow("CPE", fmt.Sprintf("%s", n))
}
return table
}
diff --git a/report/writer.go b/report/writer.go
index 13fd22f2f3..9f7e1dce13 100644
--- a/report/writer.go
+++ b/report/writer.go
@@ -17,7 +17,12 @@ along with this program. If not, see .
package report
-import "github.com/future-architect/vuls/models"
+import (
+ "bytes"
+ "compress/gzip"
+
+ "github.com/future-architect/vuls/models"
+)
const (
nvdBaseURL = "https://web.nvd.nist.gov/view/vuln/detail"
@@ -33,9 +38,27 @@ const (
debianTrackerBaseURL = "https://security-tracker.debian.org/tracker"
freeBSDVuXMLBaseURL = "https://vuxml.freebsd.org/freebsd/%s.html"
+
+ vulsOpenTag = ""
+ vulsCloseTag = ""
)
// ResultWriter Interface
type ResultWriter interface {
- Write([]models.ScanResult) error
+ Write(...models.ScanResult) error
+}
+
+func gz(data []byte) ([]byte, error) {
+ var b bytes.Buffer
+ gz := gzip.NewWriter(&b)
+ if _, err := gz.Write(data); err != nil {
+ return nil, err
+ }
+ if err := gz.Flush(); err != nil {
+ return nil, err
+ }
+ if err := gz.Close(); err != nil {
+ return nil, err
+ }
+ return b.Bytes(), nil
}
diff --git a/report/xml.go b/report/xml.go
deleted file mode 100644
index 5699e1714a..0000000000
--- a/report/xml.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package report
-
-import (
- "bytes"
- "encoding/xml"
- "fmt"
- "io/ioutil"
- "path/filepath"
- "time"
-
- "github.com/future-architect/vuls/models"
-)
-
-const (
- vulsOpenTag = ""
- vulsCloseTag = ""
-)
-
-// XMLWriter writes results to file.
-type XMLWriter struct {
- ScannedAt time.Time
-}
-
-func (w XMLWriter) Write(scanResults []models.ScanResult) (err error) {
- var path string
- if path, err = ensureResultDir(w.ScannedAt); err != nil {
- return fmt.Errorf("Failed to make direcotory/symlink : %s", err)
- }
-
- for _, scanResult := range scanResults {
- scanResult.ScannedAt = w.ScannedAt
- }
-
- var xmlBytes []byte
- for _, r := range scanResults {
- xmlPath := ""
- if len(r.Container.ContainerID) == 0 {
- xmlPath = filepath.Join(path, fmt.Sprintf("%s.xml", r.ServerName))
- } else {
- xmlPath = filepath.Join(path,
- fmt.Sprintf("%s_%s.xml", r.ServerName, r.Container.Name))
- }
-
- if xmlBytes, err = xml.Marshal(r); err != nil {
- return fmt.Errorf("Failed to Marshal to XML: %s", err)
- }
-
- allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), xmlBytes, []byte(vulsCloseTag)}, []byte{})
- if err := ioutil.WriteFile(xmlPath, allBytes, 0600); err != nil {
- return fmt.Errorf("Failed to write XML. path: %s, err: %s", xmlPath, err)
- }
- }
- return nil
-}
diff --git a/scan/base.go b/scan/base.go
index 04485ea44e..70c0570983 100644
--- a/scan/base.go
+++ b/scan/base.go
@@ -26,7 +26,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/future-architect/vuls/config"
- "github.com/future-architect/vuls/cveapi"
"github.com/future-architect/vuls/models"
)
@@ -224,62 +223,16 @@ func (l base) isAwsInstanceID(str string) bool {
}
func (l *base) convertToModel() (models.ScanResult, error) {
- var scoredCves, unscoredCves, ignoredCves models.CveInfos
- for _, p := range l.UnsecurePackages {
- sort.Sort(models.PackageInfosByName(p.Packs))
-
- // ignoreCves
- found := false
- for _, icve := range l.getServerInfo().IgnoreCves {
- if icve == p.CveDetail.CveID {
- ignoredCves = append(ignoredCves, models.CveInfo{
- CveDetail: p.CveDetail,
- Packages: p.Packs,
- DistroAdvisories: p.DistroAdvisories,
- })
- found = true
- break
- }
- }
- if found {
- continue
- }
-
- // unscoredCves
- if p.CveDetail.CvssScore(config.Conf.Lang) <= 0 {
- unscoredCves = append(unscoredCves, models.CveInfo{
- CveDetail: p.CveDetail,
- Packages: p.Packs,
- DistroAdvisories: p.DistroAdvisories,
- })
- continue
- }
-
- cpenames := []models.CpeName{}
- for _, cpename := range p.CpeNames {
- cpenames = append(cpenames,
- models.CpeName{Name: cpename})
- }
-
- // scoredCves
- cve := models.CveInfo{
- CveDetail: p.CveDetail,
- Packages: p.Packs,
- DistroAdvisories: p.DistroAdvisories,
- CpeNames: cpenames,
- }
- scoredCves = append(scoredCves, cve)
+ for _, p := range l.VulnInfos {
+ sort.Sort(models.PackageInfosByName(p.Packages))
}
+ sort.Sort(l.VulnInfos)
container := models.Container{
ContainerID: l.ServerInfo.Container.ContainerID,
Name: l.ServerInfo.Container.Name,
}
- sort.Sort(scoredCves)
- sort.Sort(unscoredCves)
- sort.Sort(ignoredCves)
-
return models.ScanResult{
ServerName: l.ServerInfo.ServerName,
ScannedAt: time.Now(),
@@ -287,52 +240,12 @@ func (l *base) convertToModel() (models.ScanResult, error) {
Release: l.Distro.Release,
Container: container,
Platform: l.Platform,
- KnownCves: scoredCves,
- UnknownCves: unscoredCves,
- IgnoredCves: ignoredCves,
+ ScannedCves: l.VulnInfos,
+ Packages: l.Packages,
Optional: l.ServerInfo.Optional,
}, nil
}
-// scanVulnByCpeName search vulnerabilities that specified in config file.
-func (l *base) scanVulnByCpeName() error {
- unsecurePacks := CvePacksList{}
-
- serverInfo := l.getServerInfo()
- cpeNames := serverInfo.CpeNames
-
- // remove duplicate
- set := map[string]CvePacksInfo{}
-
- for _, name := range cpeNames {
- details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
- if err != nil {
- return err
- }
- for _, detail := range details {
- if val, ok := set[detail.CveID]; ok {
- names := val.CpeNames
- names = append(names, name)
- val.CpeNames = names
- set[detail.CveID] = val
- } else {
- set[detail.CveID] = CvePacksInfo{
- CveID: detail.CveID,
- CveDetail: detail,
- CpeNames: []string{name},
- }
- }
- }
- }
-
- for key := range set {
- unsecurePacks = append(unsecurePacks, set[key])
- }
- unsecurePacks = append(unsecurePacks, l.UnsecurePackages...)
- l.setUnsecurePackages(unsecurePacks)
- return nil
-}
-
func (l *base) setErrs(errs []error) {
l.errs = errs
}
diff --git a/scan/debian.go b/scan/debian.go
index 32b7b5ad85..22f4248d1a 100644
--- a/scan/debian.go
+++ b/scan/debian.go
@@ -26,7 +26,6 @@ import (
"github.com/future-architect/vuls/cache"
"github.com/future-architect/vuls/config"
- "github.com/future-architect/vuls/cveapi"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
)
@@ -179,12 +178,12 @@ func (o *debian) scanPackages() error {
}
o.setPackages(packs)
- var unsecurePacks []CvePacksInfo
+ var unsecurePacks []models.VulnInfo
if unsecurePacks, err = o.scanUnsecurePackages(packs); err != nil {
o.log.Errorf("Failed to scan vulnerable packages")
return err
}
- o.setUnsecurePackages(unsecurePacks)
+ o.setVulnInfos(unsecurePacks)
return nil
}
@@ -242,37 +241,41 @@ func (o *debian) checkRequiredPackagesInstalled() error {
return nil
}
-func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInfo, error) {
+func (o *debian) scanUnsecurePackages(installed []models.PackageInfo) ([]models.VulnInfo, error) {
o.log.Infof("apt-get update...")
cmd := util.PrependProxyEnv("apt-get update")
if r := o.ssh(cmd, sudo); !r.isSuccess() {
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
- upgradablePackNames, err := o.GetUpgradablePackNames()
+ // Convert the name of upgradable packages to PackageInfo struct
+ upgradableNames, err := o.GetUpgradablePackNames()
if err != nil {
- return []CvePacksInfo{}, err
+ return nil, err
}
-
- // Convert package name to PackageInfo struct
- var unsecurePacks []models.PackageInfo
- for _, name := range upgradablePackNames {
- for _, pack := range packs {
+ var upgradablePacks []models.PackageInfo
+ for _, name := range upgradableNames {
+ for _, pack := range installed {
if pack.Name == name {
- unsecurePacks = append(unsecurePacks, pack)
+ upgradablePacks = append(upgradablePacks, pack)
break
}
}
}
- unsecurePacks, err = o.fillCandidateVersion(unsecurePacks)
+
+ // Fill the candidate versions of upgradable packages
+ upgradablePacks, err = o.fillCandidateVersion(upgradablePacks)
if err != nil {
return nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
}
+ o.Packages.MergeNewVersion(upgradablePacks)
+
+ // Setup changelog cache
current := cache.Meta{
Name: o.getServerInfo().GetServerName(),
Distro: o.getServerInfo().Distro,
- Packs: unsecurePacks,
+ Packs: upgradablePacks,
}
o.log.Debugf("Ensure changelog cache: %s", current.Name)
if err := o.ensureChangelogCache(current); err != nil {
@@ -280,12 +283,12 @@ func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInf
}
// Collect CVE information of upgradable packages
- cvePacksInfos, err := o.scanPackageCveInfos(unsecurePacks)
+ vulnInfos, err := o.scanVulnInfos(upgradablePacks)
if err != nil {
return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
}
- return cvePacksInfos, nil
+ return vulnInfos, nil
}
func (o *debian) ensureChangelogCache(current cache.Meta) error {
@@ -327,7 +330,7 @@ func (o *debian) fillCandidateVersion(before models.PackageInfoList) (filled []m
cmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 apt-cache policy %s", strings.Join(names, " "))
r := o.ssh(cmd, sudo)
if !r.isSuccess() {
- return nil, fmt.Errorf("Failed to SSH: %s.", r)
+ return nil, fmt.Errorf("Failed to SSH: %s", r)
}
packChangelog := o.splitAptCachePolicy(r.Stdout)
for k, v := range packChangelog {
@@ -398,26 +401,26 @@ func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, er
return
}
-func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePacksList CvePacksList, err error) {
+func (o *debian) scanVulnInfos(upgradablePacks []models.PackageInfo) (models.VulnInfos, error) {
meta := cache.Meta{
Name: o.getServerInfo().GetServerName(),
Distro: o.getServerInfo().Distro,
- Packs: unsecurePacks,
+ Packs: upgradablePacks,
}
type strarray []string
resChan := make(chan struct {
models.PackageInfo
strarray
- }, len(unsecurePacks))
- errChan := make(chan error, len(unsecurePacks))
- reqChan := make(chan models.PackageInfo, len(unsecurePacks))
+ }, len(upgradablePacks))
+ errChan := make(chan error, len(upgradablePacks))
+ reqChan := make(chan models.PackageInfo, len(upgradablePacks))
defer close(resChan)
defer close(errChan)
defer close(reqChan)
go func() {
- for _, pack := range unsecurePacks {
+ for _, pack := range upgradablePacks {
reqChan <- pack
}
}()
@@ -425,7 +428,7 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
timeout := time.After(30 * 60 * time.Second)
concurrency := 10
tasks := util.GenWorkers(concurrency)
- for range unsecurePacks {
+ for range upgradablePacks {
tasks <- func() {
select {
case pack := <-reqChan:
@@ -458,7 +461,7 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
// { CVE ID: [packageInfo] }
cvePackages := make(map[string][]models.PackageInfo)
errs := []error{}
- for i := 0; i < len(unsecurePacks); i++ {
+ for i := 0; i < len(upgradablePacks); i++ {
select {
case pair := <-resChan:
pack := pair.PackageInfo
@@ -467,12 +470,11 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
cvePackages[cveID] = appendPackIfMissing(cvePackages[cveID], pack)
}
o.log.Infof("(%d/%d) Scanned %s-%s : %s",
- i+1, len(unsecurePacks), pair.Name, pair.PackageInfo.Version, cveIDs)
+ i+1, len(upgradablePacks), pair.Name, pair.PackageInfo.Version, cveIDs)
case err := <-errChan:
errs = append(errs, err)
case <-timeout:
- //TODO append to errs
- return nil, fmt.Errorf("Timeout scanPackageCveIDs")
+ errs = append(errs, fmt.Errorf("Timeout scanPackageCveIDs"))
}
}
if 0 < len(errs) {
@@ -484,23 +486,14 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
cveIDs = append(cveIDs, k)
}
o.log.Debugf("%d Cves are found. cves: %v", len(cveIDs), cveIDs)
-
- o.log.Info("Fetching CVE details...")
- cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIDs)
- if err != nil {
- return nil, err
- }
- o.log.Info("Done")
-
- for _, detail := range cveDetails {
- cvePacksList = append(cvePacksList, CvePacksInfo{
- CveID: detail.CveID,
- CveDetail: detail,
- Packs: cvePackages[detail.CveID],
- // CvssScore: cinfo.CvssScore(conf.Lang),
+ var vinfos models.VulnInfos
+ for k, v := range cvePackages {
+ vinfos = append(vinfos, models.VulnInfo{
+ CveID: k,
+ Packages: v,
})
}
- return
+ return vinfos, nil
}
func (o *debian) getChangelogCache(meta cache.Meta, pack models.PackageInfo) string {
diff --git a/scan/freebsd.go b/scan/freebsd.go
index 2d904f17ed..0abee79e6c 100644
--- a/scan/freebsd.go
+++ b/scan/freebsd.go
@@ -22,7 +22,6 @@ import (
"strings"
"github.com/future-architect/vuls/config"
- "github.com/future-architect/vuls/cveapi"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
)
@@ -87,12 +86,12 @@ func (o *bsd) scanPackages() error {
}
o.setPackages(packs)
- var unsecurePacks []CvePacksInfo
- if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
+ var vinfos []models.VulnInfo
+ if vinfos, err = o.scanUnsecurePackages(); err != nil {
o.log.Errorf("Failed to scan vulnerable packages")
return err
}
- o.setUnsecurePackages(unsecurePacks)
+ o.setVulnInfos(vinfos)
return nil
}
@@ -105,7 +104,7 @@ func (o *bsd) scanInstalledPackages() ([]models.PackageInfo, error) {
return o.parsePkgVersion(r.Stdout), nil
}
-func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
+func (o *bsd) scanUnsecurePackages() (vulnInfos []models.VulnInfo, err error) {
const vulndbPath = "/tmp/vuln.db"
cmd := "rm -f " + vulndbPath
r := o.ssh(cmd, noSudo)
@@ -120,7 +119,7 @@ func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
}
if r.ExitStatus == 0 {
// no vulnerabilities
- return []CvePacksInfo{}, nil
+ return []models.VulnInfo{}, nil
}
var packAdtRslt []pkgAuditResult
@@ -151,34 +150,22 @@ func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
}
}
- cveIDs := []string{}
for k := range cveIDAdtMap {
- cveIDs = append(cveIDs, k)
- }
-
- cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIDs)
- if err != nil {
- return nil, err
- }
- o.log.Info("Done")
-
- for _, d := range cveDetails {
packs := []models.PackageInfo{}
- for _, r := range cveIDAdtMap[d.CveID] {
+ for _, r := range cveIDAdtMap[k] {
packs = append(packs, r.pack)
}
disAdvs := []models.DistroAdvisory{}
- for _, r := range cveIDAdtMap[d.CveID] {
+ for _, r := range cveIDAdtMap[k] {
disAdvs = append(disAdvs, models.DistroAdvisory{
AdvisoryID: r.vulnIDCveIDs.vulnID,
})
}
- cvePacksList = append(cvePacksList, CvePacksInfo{
- CveID: d.CveID,
- CveDetail: d,
- Packs: packs,
+ vulnInfos = append(vulnInfos, models.VulnInfo{
+ CveID: k,
+ Packages: packs,
DistroAdvisories: disAdvs,
})
}
diff --git a/scan/redhat.go b/scan/redhat.go
index 3dbfc585a3..7091b24d26 100644
--- a/scan/redhat.go
+++ b/scan/redhat.go
@@ -26,7 +26,6 @@ import (
"time"
"github.com/future-architect/vuls/config"
- "github.com/future-architect/vuls/cveapi"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
@@ -189,12 +188,12 @@ func (o *redhat) scanPackages() error {
}
o.setPackages(packs)
- var unsecurePacks []CvePacksInfo
- if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
+ var vinfos []models.VulnInfo
+ if vinfos, err = o.scanVulnInfos(); err != nil {
o.log.Errorf("Failed to scan vulnerable packages")
return err
}
- o.setUnsecurePackages(unsecurePacks)
+ o.setVulnInfos(vinfos)
return nil
}
@@ -235,7 +234,7 @@ func (o *redhat) parseScannedPackagesLine(line string) (models.PackageInfo, erro
}, nil
}
-func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
+func (o *redhat) scanVulnInfos() ([]models.VulnInfo, error) {
if o.Distro.Family != "centos" {
// Amazon, RHEL has yum updateinfo as default
// yum updateinfo can collenct vendor advisory information.
@@ -247,7 +246,7 @@ func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
}
// For CentOS
-func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error) {
+func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (models.VulnInfos, error) {
cmd := "LANGUAGE=en_US.UTF-8 yum --color=never %s check-update"
if o.getServerInfo().Enablerepo != "" {
cmd = fmt.Sprintf(cmd, "--enablerepo="+o.getServerInfo().Enablerepo)
@@ -268,19 +267,23 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error)
}
o.log.Debugf("%s", pp.Sprintf("%v", packInfoList))
+ // set candidate version info
+ o.Packages.MergeNewVersion(packInfoList)
+
// Collect CVE-IDs in changelog
type PackInfoCveIDs struct {
PackInfo models.PackageInfo
CveIDs []string
}
- // { packageName: changelog-lines }
- var rpm2changelog map[string]*string
allChangelog, err := o.getAllChangelog(packInfoList)
if err != nil {
o.log.Errorf("Failed to getAllchangelog. err: %s", err)
return nil, err
}
+
+ // { packageName: changelog-lines }
+ var rpm2changelog map[string]*string
rpm2changelog, err = o.parseAllChangelog(allChangelog)
if err != nil {
return nil, fmt.Errorf("Failed to parseAllChangelog. err: %s", err)
@@ -337,39 +340,20 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error)
cveIDPackInfoMap := make(map[string][]models.PackageInfo)
for _, res := range results {
for _, cveID := range res.CveIDs {
- // packInfo, found := o.Packages.FindByName(res.Packname)
- // if !found {
- // return CvePacksList{}, fmt.Errorf(
- // "Faild to transform data structure: %v", res.Packname)
- // }
- cveIDPackInfoMap[cveID] = append(cveIDPackInfoMap[cveID], res.PackInfo)
+ cveIDPackInfoMap[cveID] = append(
+ cveIDPackInfoMap[cveID], res.PackInfo)
}
}
- var uniqueCveIDs []string
- for cveID := range cveIDPackInfoMap {
- uniqueCveIDs = append(uniqueCveIDs, cveID)
- }
-
- // cveIDs => []cve.CveInfo
- o.log.Info("Fetching CVE details...")
- cveDetails, err := cveapi.CveClient.FetchCveDetails(uniqueCveIDs)
- if err != nil {
- return nil, err
- }
- o.log.Info("Done")
-
- cvePacksList := []CvePacksInfo{}
- for _, detail := range cveDetails {
+ vinfos := []models.VulnInfo{}
+ for k, v := range cveIDPackInfoMap {
// Amazon, RHEL do not use this method, so VendorAdvisory do not set.
- cvePacksList = append(cvePacksList, CvePacksInfo{
- CveID: detail.CveID,
- CveDetail: detail,
- Packs: cveIDPackInfoMap[detail.CveID],
- // CvssScore: cinfo.CvssScore(conf.Lang),
+ vinfos = append(vinfos, models.VulnInfo{
+ CveID: k,
+ Packages: v,
})
}
- return cvePacksList, nil
+ return vinfos, nil
}
// parseYumCheckUpdateLines parse yum check-update to get package name, candidate version
@@ -579,11 +563,11 @@ type distroAdvisoryCveIDs struct {
// Scaning unsecure packages using yum-plugin-security.
// Amazon, RHEL
-func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, error) {
+func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (models.VulnInfos, error) {
if o.Distro.Family == "centos" {
// CentOS has no security channel.
// So use yum check-update && parse changelog
- return CvePacksList{}, fmt.Errorf(
+ return nil, fmt.Errorf(
"yum updateinfo is not suppported on CentOS")
}
@@ -615,6 +599,9 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
}
o.log.Debugf("%s", pp.Sprintf("%v", updatable))
+ // set candidate version info
+ o.Packages.MergeNewVersion(updatable)
+
dict := map[string][]models.PackageInfo{}
for _, advIDPackNames := range advIDPackNamesList {
packInfoList := models.PackageInfoList{}
@@ -638,48 +625,41 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
}
advisoryCveIDsList, err := o.parseYumUpdateinfo(r.Stdout)
if err != nil {
- return CvePacksList{}, err
+ return nil, err
}
// pp.Println(advisoryCveIDsList)
// All information collected.
- // Convert to CvePacksList.
+ // Convert to VulnInfos.
o.log.Info("Fetching CVE details...")
- result := CvePacksList{}
+ vinfos := models.VulnInfos{}
for _, advIDCveIDs := range advisoryCveIDsList {
- cveDetails, err :=
- cveapi.CveClient.FetchCveDetails(advIDCveIDs.CveIDs)
- if err != nil {
- return nil, err
- }
-
- for _, cveDetail := range cveDetails {
+ for _, cveID := range advIDCveIDs.CveIDs {
found := false
- for i, p := range result {
- if cveDetail.CveID == p.CveID {
+ for i, p := range vinfos {
+ if cveID == p.CveID {
advAppended := append(p.DistroAdvisories, advIDCveIDs.DistroAdvisory)
- result[i].DistroAdvisories = advAppended
+ vinfos[i].DistroAdvisories = advAppended
packs := dict[advIDCveIDs.DistroAdvisory.AdvisoryID]
- result[i].Packs = append(result[i].Packs, packs...)
+ vinfos[i].Packages = append(vinfos[i].Packages, packs...)
found = true
break
}
}
if !found {
- cpinfo := CvePacksInfo{
- CveID: cveDetail.CveID,
- CveDetail: cveDetail,
+ cpinfo := models.VulnInfo{
+ CveID: cveID,
DistroAdvisories: []models.DistroAdvisory{advIDCveIDs.DistroAdvisory},
- Packs: dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
+ Packages: dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
}
- result = append(result, cpinfo)
+ vinfos = append(vinfos, cpinfo)
}
+
}
}
- o.log.Info("Done")
- return result, nil
+ return vinfos, nil
}
var horizontalRulePattern = regexp.MustCompile(`^=+$`)
@@ -929,7 +909,6 @@ func (o *redhat) extractPackNameVerRel(nameVerRel string) (name, ver, rel string
// parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS), packages
func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacksList, error) {
-
result := []advisoryIDPacks{}
lines := strings.Split(stdout, "\n")
for _, line := range lines {
diff --git a/scan/serverapi.go b/scan/serverapi.go
index eb8dbf3259..d011169275 100644
--- a/scan/serverapi.go
+++ b/scan/serverapi.go
@@ -21,6 +21,7 @@ import (
"bufio"
"fmt"
"os"
+ "path/filepath"
"strings"
"time"
@@ -28,7 +29,7 @@ import (
"github.com/future-architect/vuls/cache"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
- cve "github.com/kotakanbe/go-cve-dictionary/models"
+ "github.com/future-architect/vuls/report"
)
// Log for localhsot
@@ -54,7 +55,6 @@ type osTypeInterface interface {
checkRequiredPackagesInstalled() error
scanPackages() error
- scanVulnByCpeName() error
install() error
convertToModel() (models.ScanResult, error)
@@ -72,64 +72,15 @@ type osPackages struct {
Packages models.PackageInfoList
// unsecure packages
- UnsecurePackages CvePacksList
+ VulnInfos models.VulnInfos
}
func (p *osPackages) setPackages(pi models.PackageInfoList) {
p.Packages = pi
}
-func (p *osPackages) setUnsecurePackages(pi []CvePacksInfo) {
- p.UnsecurePackages = pi
-}
-
-// CvePacksList have CvePacksInfo list, getter/setter, sortable methods.
-type CvePacksList []CvePacksInfo
-
-// CvePacksInfo hold the CVE information.
-type CvePacksInfo struct {
- CveID string
- CveDetail cve.CveDetail
- Packs models.PackageInfoList
- DistroAdvisories []models.DistroAdvisory // for Aamazon, RHEL, FreeBSD
- CpeNames []string
-}
-
-// FindByCveID find by CVEID
-func (s CvePacksList) FindByCveID(cveID string) (pi CvePacksInfo, found bool) {
- for _, p := range s {
- if cveID == p.CveID {
- return p, true
- }
- }
- return CvePacksInfo{CveID: cveID}, false
-}
-
-// immutable
-func (s CvePacksList) set(cveID string, cvePacksInfo CvePacksInfo) CvePacksList {
- for i, p := range s {
- if cveID == p.CveID {
- s[i] = cvePacksInfo
- return s
- }
- }
- return append(s, cvePacksInfo)
-}
-
-// Len implement Sort Interface
-func (s CvePacksList) Len() int {
- return len(s)
-}
-
-// Swap implement Sort Interface
-func (s CvePacksList) Swap(i, j int) {
- s[i], s[j] = s[j], s[i]
-}
-
-// Less implement Sort Interface
-func (s CvePacksList) Less(i, j int) bool {
- return s[i].CveDetail.CvssScore(config.Conf.Lang) >
- s[j].CveDetail.CvssScore(config.Conf.Lang)
+func (p *osPackages) setVulnInfos(vi []models.VulnInfo) {
+ p.VulnInfos = vi
}
func detectOS(c config.ServerInfo) (osType osTypeInterface) {
@@ -518,14 +469,15 @@ func Scan() []error {
}()
Log.Info("Scanning vulnerable OS packages...")
- if errs := scanPackages(); errs != nil {
- return errs
+ scannedAt := time.Now()
+ dir, err := ensureResultDir(scannedAt)
+ if err != nil {
+ return []error{err}
}
-
- Log.Info("Scanning vulnerable software specified in the CPE...")
- if errs := scanVulnByCpeName(); errs != nil {
+ if errs := scanVulns(dir, scannedAt); errs != nil {
return errs
}
+
return nil
}
@@ -553,31 +505,67 @@ func checkRequiredPackagesInstalled() []error {
}, timeoutSec)
}
-func scanPackages() []error {
+func scanVulns(jsonDir string, scannedAt time.Time) []error {
+ var results models.ScanResults
timeoutSec := 120 * 60
- return parallelSSHExec(func(o osTypeInterface) error {
- return o.scanPackages()
- }, timeoutSec)
+ errs := parallelSSHExec(func(o osTypeInterface) error {
+ if err := o.scanPackages(); err != nil {
+ return err
+ }
-}
+ r, err := o.convertToModel()
+ if err != nil {
+ return err
+ }
+ r.ScannedAt = scannedAt
+ results = append(results, r)
-// scanVulnByCpeName search vulnerabilities that specified in config file.
-func scanVulnByCpeName() []error {
- timeoutSec := 30 * 60
- return parallelSSHExec(func(o osTypeInterface) error {
- return o.scanVulnByCpeName()
+ return nil
}, timeoutSec)
+ config.Conf.FormatJSON = true
+ ws := []report.ResultWriter{
+ report.LocalFileWriter{CurrentDir: jsonDir},
+ }
+ for _, w := range ws {
+ if err := w.Write(results...); err != nil {
+ return []error{
+ fmt.Errorf("Failed to write summary report: %s", err),
+ }
+ }
+ }
+ if errs != nil {
+ return errs
+ }
+
+ report.StdoutWriter{}.WriteScanSummary(results...)
+ return nil
}
-// GetScanResults returns Scan Resutls
-func GetScanResults() (results models.ScanResults, err error) {
- for _, s := range servers {
- r, err := s.convertToModel()
- if err != nil {
- return results, fmt.Errorf("Failed converting to model: %s", err)
+func ensureResultDir(scannedAt time.Time) (currentDir string, err error) {
+ jsonDirName := scannedAt.Format(time.RFC3339)
+
+ resultsDir := config.Conf.ResultsDir
+ if len(resultsDir) == 0 {
+ wd, _ := os.Getwd()
+ resultsDir = filepath.Join(wd, "results")
+ }
+ jsonDir := filepath.Join(resultsDir, jsonDirName)
+ if err := os.MkdirAll(jsonDir, 0700); err != nil {
+ return "", fmt.Errorf("Failed to create dir: %s", err)
+ }
+
+ symlinkPath := filepath.Join(resultsDir, "current")
+ if _, err := os.Lstat(symlinkPath); err == nil {
+ if err := os.Remove(symlinkPath); err != nil {
+ return "", fmt.Errorf(
+ "Failed to remove symlink. path: %s, err: %s", symlinkPath, err)
}
- results = append(results, r)
}
- return
+
+ if err := os.Symlink(jsonDir, symlinkPath); err != nil {
+ return "", fmt.Errorf(
+ "Failed to create symlink: path: %s, err: %s", symlinkPath, err)
+ }
+ return jsonDir, nil
}
diff --git a/scan/serverapi_test.go b/scan/serverapi_test.go
index a9b5b64e41..040485ba11 100644
--- a/scan/serverapi_test.go
+++ b/scan/serverapi_test.go
@@ -1,158 +1 @@
package scan
-
-import (
- "github.com/future-architect/vuls/config"
- "github.com/future-architect/vuls/models"
- cve "github.com/kotakanbe/go-cve-dictionary/models"
- "reflect"
- "testing"
-)
-
-func TestPackageCveInfosSetGet(t *testing.T) {
- var test = struct {
- in []string
- out []string
- }{
- []string{
- "CVE1",
- "CVE2",
- "CVE3",
- "CVE1",
- "CVE1",
- "CVE2",
- "CVE3",
- },
- []string{
- "CVE1",
- "CVE2",
- "CVE3",
- },
- }
-
- // var ps packageCveInfos
- var ps CvePacksList
- for _, cid := range test.in {
- ps = ps.set(cid, CvePacksInfo{CveID: cid})
- }
-
- if len(test.out) != len(ps) {
- t.Errorf("length: expected %d, actual %d", len(test.out), len(ps))
- }
-
- for i, expectedCid := range test.out {
- if expectedCid != ps[i].CveID {
- t.Errorf("expected %s, actual %s", expectedCid, ps[i].CveID)
- }
- }
- for _, cid := range test.in {
- p, _ := ps.FindByCveID(cid)
- if p.CveID != cid {
- t.Errorf("expected %s, actual %s", cid, p.CveID)
- }
- }
-}
-
-func TestGetScanResults(t *testing.T) {
- // setup servers
- c := config.ServerInfo{
- ServerName: "ubuntu",
- }
- deb1 := newDebian(c)
- deb2 := newDebian(c)
-
- cpis1 := []CvePacksInfo{
- {
- CveID: "CVE1",
- CveDetail: cve.CveDetail{CveID: "CVE1"},
- Packs: []models.PackageInfo{
- {Name: "mysql-client-5.5"},
- {Name: "mysql-server-5.5"},
- {Name: "mysql-common-5.5"},
- },
- },
- {
- CveID: "CVE2",
- CveDetail: cve.CveDetail{CveID: "CVE2"},
- Packs: []models.PackageInfo{
- {Name: "mysql-common-5.5"},
- {Name: "mysql-server-5.5"},
- {Name: "mysql-client-5.5"},
- },
- },
- }
- cpis2 := []CvePacksInfo{
- {
- CveID: "CVE3",
- CveDetail: cve.CveDetail{CveID: "CVE3"},
- Packs: []models.PackageInfo{
- {Name: "libcurl3"},
- {Name: "curl"},
- },
- },
- {
- CveID: "CVE4",
- CveDetail: cve.CveDetail{CveID: "CVE4"},
- Packs: []models.PackageInfo{
- {Name: "bind9"},
- {Name: "libdns100"},
- },
- },
- }
- deb1.setUnsecurePackages(cpis1)
- servers = append(servers, deb1)
-
- deb2.setUnsecurePackages(cpis2)
- servers = append(servers, deb2)
-
- // prepare expected data
- expectedUnKnownPackages := []map[string][]models.PackageInfo{
- {
- "CVE1": {
- {Name: "mysql-client-5.5"},
- {Name: "mysql-common-5.5"},
- {Name: "mysql-server-5.5"},
- },
- },
- {
- "CVE2": {
- {Name: "mysql-client-5.5"},
- {Name: "mysql-common-5.5"},
- {Name: "mysql-server-5.5"},
- },
- },
- {
- "CVE3": {
- {Name: "curl"},
- {Name: "libcurl3"},
- },
- },
- {
- "CVE4": {
- {Name: "bind9"},
- {Name: "libdns100"},
- },
- },
- }
-
- // check scanResults
- scanResults, _ := GetScanResults()
- if len(scanResults) != 2 {
- t.Errorf("length of scanResults should be 2")
- }
- for i, result := range scanResults {
- if result.ServerName != "ubuntu" {
- t.Errorf("expected ubuntu, actual %s", result.ServerName)
- }
-
- unKnownCves := result.UnknownCves
- if len(unKnownCves) != 2 {
- t.Errorf("length of unKnownCves should be 2")
- }
- for j, unKnownCve := range unKnownCves {
- expected := expectedUnKnownPackages[i*2+j][unKnownCve.CveDetail.CveID]
- if !reflect.DeepEqual(expected, unKnownCve.Packages) {
- t.Errorf("expected %v, actual %v", expected, unKnownCve.Packages)
- }
- }
- }
-}
diff --git a/setup/docker/README.md b/setup/docker/README.md
index d72aae70c2..1914f188ea 100644
--- a/setup/docker/README.md
+++ b/setup/docker/README.md
@@ -149,12 +149,24 @@ $ docker run --rm -it \
-v /etc/localtime:/etc/localtime:ro \
-e "TZ=Asia/Tokyo" \
vuls/vuls scan \
- -cve-dictionary-dbpath=/vuls/cve.sqlite3 \
- -report-json \
-config=./config.toml # path to config.toml in docker
```
-## Step5. vulsrepo
+## Step5. Report
+
+```console
+$ docker run --rm -it \
+ -v ~/.ssh:/root/.ssh:ro \
+ -v $PWD:/vuls \
+ -v $PWD/vuls-log:/var/log/vuls \
+ -v /etc/localtime:/etc/localtime:ro \
+ vuls/vuls report \
+ -cvedb-path=/vuls/cve.sqlite3 \
+ -config=./config.toml \ # path to config.toml in docker
+ -format-short-text
+```
+
+## Step6. vulsrepo
```console
$docker run -dt \
diff --git a/setup/docker/vuls/latest/README.md b/setup/docker/vuls/latest/README.md
index 0fe862850e..897f8d3941 100644
--- a/setup/docker/vuls/latest/README.md
+++ b/setup/docker/vuls/latest/README.md
@@ -83,9 +83,21 @@ $ docker run --rm -it \
-v $PWD/vuls-log:/var/log/vuls \
-v /etc/localtime:/etc/localtime:ro \
vuls/vuls scan \
- -cve-dictionary-dbpath=/vuls/cve.sqlite3 \
+ -config=./config.toml # path to config.toml in docker
+```
+
+## Report
+
+```console
+$ docker run --rm -it \
+ -v ~/.ssh:/root/.ssh:ro \
+ -v $PWD:/vuls \
+ -v $PWD/vuls-log:/var/log/vuls \
+ -v /etc/localtime:/etc/localtime:ro \
+ vuls/vuls report \
+ -cvedb-path=/vuls/cve.sqlite3 \
-config=./config.toml \ # path to config.toml in docker
- -report-json
+ -format-short-text
```
## tui
@@ -94,7 +106,8 @@ $ docker run --rm -it \
$ docker run --rm -it \
-v $PWD:/vuls \
-v $PWD/vuls-log:/var/log/vuls \
- vuls/vuls tui
+ vuls/vuls tui \
+ -cvedb-path=/vuls/cve.sqlite3
```
## vulsrepo