Add report subcommand, change scan options. Bump up ver #239
This commit is contained in:
2
Makefile
2
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);)
|
||||
|
||||
414
README.ja.md
414
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 -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 -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
|
||||
|
||||

|
||||
|
||||
## 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]...
|
||||
|
||||
|
||||
-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.
|
||||
|
||||
480
README.md
480
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 ...
|
||||
|
||||
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 ...
|
||||
|
||||
CVE-2016-0494
|
||||
-------------
|
||||
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
|
||||
Scan Summary
|
||||
============
|
||||
172-31-4-82 amazon 2015.09 94 CVEs 103 updatable packages
|
||||
|
||||
```
|
||||
|
||||
## Step9. TUI
|
||||
## 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)
|
||||
============================
|
||||
Total: 94 (High:19 Medium:54 Low:7 ?:14) 103 updatable packages
|
||||
|
||||
|
||||
CVE-2016-0705
|
||||
-------------
|
||||
Score 10.0 (High)
|
||||
Vector (AV:N/AC:L/Au:N/C:C/I:C/A:C)
|
||||
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 ...
|
||||
```
|
||||
|
||||
## Step10. TUI
|
||||
|
||||
Vuls has Terminal-Based User Interface to display the scan result.
|
||||
|
||||
```
|
||||
$ vuls tui
|
||||
$ vuls tui
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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
|
||||

|
||||
- 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
|
||||
|
||||

|
||||
|
||||
## Scan a single server
|
||||
## Scan a Single Server
|
||||
|
||||
web/app server in the same configuration under the load balancer
|
||||
|
||||

|
||||
|
||||
## 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]...
|
||||
|
||||
|
||||
-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
|
||||
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_1949
|
||||
- Display the result of scan 2016-12-30T10:34:38+09:00
|
||||
```
|
||||
$ vuls tui 20160524_1950
|
||||
```
|
||||
|
||||
- Display the result of scan 20160524_1948
|
||||
```
|
||||
$ 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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ iconEmoji = ":ghost:"
|
||||
authUser = "username"
|
||||
notifyUsers = ["@username"]
|
||||
|
||||
[mail]
|
||||
[email]
|
||||
smtpAddr = "smtp.gmail.com"
|
||||
smtpPort = "587"
|
||||
user = "username"
|
||||
|
||||
@@ -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, ", "),
|
||||
|
||||
@@ -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
|
||||
|
||||
386
commands/report.go
Normal file
386
commands/report.go
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
249
commands/scan.go
249
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
|
||||
}
|
||||
|
||||
109
commands/tui.go
109
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)
|
||||
}
|
||||
|
||||
225
commands/util.go
Normal file
225
commands/util.go
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
135
config/config.go
135
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,19 +71,48 @@ type Config struct {
|
||||
AzureAccount string
|
||||
AzureKey string
|
||||
AzureContainer string
|
||||
|
||||
// CpeNames []string
|
||||
// SummaryMode bool
|
||||
}
|
||||
|
||||
// Validate configuration
|
||||
func (c Config) Validate() bool {
|
||||
// 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)
|
||||
}
|
||||
|
||||
for _, err := range errs {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
return len(errs) == 0
|
||||
}
|
||||
|
||||
// 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"))
|
||||
}
|
||||
|
||||
if len(c.ResultsDir) != 0 {
|
||||
if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
@@ -83,25 +120,6 @@ func (c Config) Validate() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid DB type is set, default to sqlite3
|
||||
if c.CveDBType == "" {
|
||||
c.CveDBType = "sqlite3"
|
||||
}
|
||||
|
||||
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 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 {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
@@ -114,7 +132,42 @@ func (c Config) Validate() bool {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if mailerrs := c.Mail.Validate(); 0 < len(mailerrs) {
|
||||
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" {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
_, err := valid.ValidateStruct(c)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
14
glide.lock
generated
14
glide.lock
generated
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<y:ProxyAutoBoundsNode>
|
||||
<y:Realizers active="0">
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="289.5891316731771" width="171.0" x="1274.5282340049735" y="-47.136413574218864"/>
|
||||
<y:Geometry height="289.5891316731771" width="171.0" x="1230.5282340049735" y="-28.09765625"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="171.0" x="0.0" y="0.0">Vulnerbility Database</y:NodeLabel>
|
||||
@@ -63,7 +63,7 @@
|
||||
<node id="n1::n0">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="70.0" width="85.0" x="1318.8823466300955" y="128.4841308593749"/>
|
||||
<y:Geometry height="70.0" width="85.0" x="1274.8823466300955" y="147.52288818359375"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="63.279296875" x="10.8603515625" y="18.8671875">JVN
|
||||
@@ -81,7 +81,7 @@
|
||||
<node id="n1::n1">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="70.0" width="85.0" x="1318.8823466300955" y="16.771667480468693"/>
|
||||
<y:Geometry height="70.0" width="85.0" x="1274.8823466300955" y="35.81042480468756"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="29.69921875" x="27.650390625" y="25.93359375">NVD<y:LabelModel>
|
||||
@@ -103,7 +103,7 @@
|
||||
<y:ProxyAutoBoundsNode>
|
||||
<y:Realizers active="0">
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="336.1141560872396" width="171.61765336990447" x="1271.3823466300955" y="296.7261962890625"/>
|
||||
<y:Geometry height="336.1141560872396" width="171.61765336990447" x="1227.3823466300955" y="315.76495361328136"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="171.61765336990447" x="0.0" y="0.0">Distribution Support</y:NodeLabel>
|
||||
@@ -129,7 +129,7 @@
|
||||
<node id="n2::n0">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="100.0" x="1313.0" y="416.42173258463527"/>
|
||||
<y:Geometry height="50.0" width="100.0" x="1269.0" y="435.46048990885413"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="64.158203125" x="17.9208984375" y="8.8671875">apptitude
|
||||
@@ -147,7 +147,7 @@ changelog<y:LabelModel>
|
||||
<node id="n2::n1">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="100.0" x="1314.0" y="345.96124267578114"/>
|
||||
<y:Geometry height="50.0" width="100.0" x="1270.0" y="365.0"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="64.158203125" x="17.9208984375" y="8.8671875">yum
|
||||
@@ -165,7 +165,7 @@ changelog<y:LabelModel>
|
||||
<node id="n2::n2">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="100.0" x="1315.0" y="485.0"/>
|
||||
<y:Geometry height="50.0" width="100.0" x="1271.0" y="504.03875732421886"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="92.828125" x="3.5859375" y="8.8671875">RHSA (RedHat)
|
||||
@@ -183,7 +183,7 @@ ALAS (Amazon)<y:LabelModel>
|
||||
<node id="n2::n3">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="100.0" x="1314.3823466300955" y="555.9612426757811"/>
|
||||
<y:Geometry height="50.0" width="100.0" x="1270.3823466300955" y="575.0"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="100.650390625" x="-0.3251953125" y="15.93359375">FreeBSD Support<y:LabelModel>
|
||||
@@ -202,7 +202,7 @@ ALAS (Amazon)<y:LabelModel>
|
||||
<node id="n3">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.cloud">
|
||||
<y:Geometry height="50.0" width="56.554100036621094" x="1180.4405970573423" y="439.7832743326823"/>
|
||||
<y:Geometry height="50.0" width="56.554100036621094" x="1141.7229499816895" y="425.0"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="26.277050018310547" y="23.0">
|
||||
@@ -222,14 +222,14 @@ ALAS (Amazon)<y:LabelModel>
|
||||
<y:ProxyAutoBoundsNode>
|
||||
<y:Realizers active="0">
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="382.650146484375" width="320.0" x="575.0" y="285.984130859375"/>
|
||||
<y:Geometry height="371.666015625" width="341.1769911504424" x="575.0" y="298.333984375"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="320.0" x="0.0" y="0.0">Vuls</y:NodeLabel>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="341.1769911504424" x="0.0" y="0.0">Vuls</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
||||
<y:BorderInsets bottom="29" bottomF="28.63427734375" left="0" leftF="0.0" right="0" rightF="0.0" top="31" topF="30.57621256510413"/>
|
||||
<y:BorderInsets bottom="30" bottomF="30.0" left="9" leftF="8.695480404551176" right="0" rightF="0.0" top="14" topF="14.179861277351222"/>
|
||||
</y:GroupNode>
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
||||
@@ -248,10 +248,10 @@ ALAS (Amazon)<y:LabelModel>
|
||||
<node id="n4::n0">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="80.0" x="800.0" y="575.0"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:Geometry height="50.0" width="90.96302149178246" x="749.634165613148" y="349.1798612773512"/>
|
||||
<y:Fill color="#FFCC00" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="42.595703125" x="18.7021484375" y="15.93359375">Report<y:LabelModel>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="30.68359375" x="30.139713870891228" y="15.93359375">Scan<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
@@ -265,10 +265,10 @@ ALAS (Amazon)<y:LabelModel>
|
||||
<node id="n4::n1">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="80.0" x="690.2499999999993" y="572.817138671875"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:Geometry height="50.0" width="90.96302149178268" x="598.6954804045512" y="455.0"/>
|
||||
<y:Fill color="#FFCC00" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="54.40234375" x="12.798828125" y="15.93359375">TUI View<y:LabelModel>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="42.595703125" x="24.18365918339134" y="15.93359375">Report<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
@@ -282,10 +282,11 @@ ALAS (Amazon)<y:LabelModel>
|
||||
<node id="n4::n2">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="180.25" x="681.9999999999993" y="353.22635904947913"/>
|
||||
<y:Geometry height="50.0" width="90.96302149178268" x="715.9609671302148" y="575.0"/>
|
||||
<y:Fill color="#FFCC00" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="30.68359375" x="74.783203125" y="15.93359375">Scan<y:LabelModel>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="58.076171875" x="16.44342480839134" y="8.8671875">VulsRepo
|
||||
(WebUI)<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
@@ -299,11 +300,10 @@ ALAS (Amazon)<y:LabelModel>
|
||||
<node id="n4::n3">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="80.0" x="590.0" y="575.0"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:Geometry height="50.0" width="90.96302149178268" x="810.2139696586597" y="575.0"/>
|
||||
<y:Fill color="#FFCC00" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="63.203125" x="8.3984375" y="8.8671875">Web View
|
||||
(Vulsrepo)<y:LabelModel>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="23.359375" x="33.80182324589134" y="15.93359375">TUI<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
@@ -319,7 +319,7 @@ ALAS (Amazon)<y:LabelModel>
|
||||
<node id="n5">
|
||||
<data key="d6">
|
||||
<y:SVGNode>
|
||||
<y:Geometry height="64.96826171875" width="56.554100036621094" x="743.8479499816888" y="734.5277913411459"/>
|
||||
<y:Geometry height="64.96826171875" width="56.554100036621094" x="691.7229499816895" y="717.515869140625"/>
|
||||
<y:Fill color="#CCCCFF" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="100.890625" x="-22.168262481689453" y="68.96826171875">System Operator<y:LabelModel>
|
||||
@@ -339,7 +339,7 @@ ALAS (Amazon)<y:LabelModel>
|
||||
<node id="n6">
|
||||
<data key="d6">
|
||||
<y:SVGNode>
|
||||
<y:Geometry height="37.0" width="109.57881927490234" x="889.737640380859" y="748.5119222005209"/>
|
||||
<y:Geometry height="37.0" width="109.57881927490234" x="575.2105903625488" y="701.5"/>
|
||||
<y:Fill color="#CCCCFF" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="sandwich" modelPosition="s" textColor="#000000" visible="true" width="4.0" x="52.78940963745117" y="41.0"/>
|
||||
@@ -353,7 +353,7 @@ ALAS (Amazon)<y:LabelModel>
|
||||
<node id="n7">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.bpmn.Artifact.withShadow">
|
||||
<y:Geometry height="24.0" width="35.0" x="811.8264548778523" y="681.5277913411459"/>
|
||||
<y:Geometry height="24.0" width="35.0" x="689.5518331226297" y="663.3072060682681"/>
|
||||
<y:Fill color="#FFFFFFE6" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="15.5" y="28.0">
|
||||
@@ -468,14 +468,14 @@ ALAS (Amazon)<y:LabelModel>
|
||||
<y:ProxyAutoBoundsNode>
|
||||
<y:Realizers active="0">
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="201.49428304036473" width="192.109991788863" x="964.6223485469814" y="296.7320760091144"/>
|
||||
<y:Geometry height="201.49428304036473" width="161.48764324188164" x="964.6223485469814" y="296.7320760091144"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="192.109991788863" x="0.0" y="0.0">Docker Containers</y:NodeLabel>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="161.48764324188164" x="0.0" y="0.0">Docker Containers</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
||||
<y:BorderInsets bottom="0" bottomF="0.0" left="32" leftF="32.0" right="45" rightF="45.109991788863" top="0" topF="0.0"/>
|
||||
<y:BorderInsets bottom="0" bottomF="0.0" left="32" leftF="32.0" right="14" rightF="14.487643241881642" top="0" topF="0.0"/>
|
||||
</y:GroupNode>
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
||||
@@ -534,14 +534,14 @@ Container<y:LabelModel>
|
||||
<y:ProxyAutoBoundsNode>
|
||||
<y:Realizers active="0">
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="131.666015625" width="192.109991788863" x="964.6223485469814" y="516.3727416992189"/>
|
||||
<y:Geometry height="131.666015625" width="168.0" x="964.6223485469814" y="516.3727416992189"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="192.109991788863" x="0.0" y="0.0">Linux/FreeBSD Servers</y:NodeLabel>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="168.0" x="0.0" y="0.0">Linux/FreeBSD</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
||||
<y:BorderInsets bottom="17" bottomF="16.607625325520758" left="32" leftF="32.0" right="24" rightF="24.109991788863" top="0" topF="0.0"/>
|
||||
<y:BorderInsets bottom="17" bottomF="16.607625325520758" left="32" leftF="32.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
||||
</y:GroupNode>
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
||||
@@ -599,7 +599,7 @@ Container<y:LabelModel>
|
||||
<y:ProxyAutoBoundsNode>
|
||||
<y:Realizers active="0">
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="108.94242662355293" width="124.0" x="649.0" y="434.05757337644707"/>
|
||||
<y:Geometry height="108.94242662355293" width="124.0" x="739.0" y="434.05757337644707"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="124.0" x="0.0" y="0.0">results dir</y:NodeLabel>
|
||||
@@ -625,7 +625,7 @@ Container<y:LabelModel>
|
||||
<node id="n11::n0">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="36.0" width="76.0" x="664.0" y="470.72358900144707"/>
|
||||
<y:Geometry height="36.0" width="76.0" x="754.0" y="470.72358900144707"/>
|
||||
<y:Fill color="#99CCFF" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
|
||||
@@ -642,7 +642,7 @@ Container<y:LabelModel>
|
||||
<node id="n11::n1">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="36.0" width="76.0" x="671.0" y="480.2981186685961"/>
|
||||
<y:Geometry height="36.0" width="76.0" x="761.0" y="480.2981186685961"/>
|
||||
<y:Fill color="#99CCFF" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
|
||||
@@ -659,7 +659,7 @@ Container<y:LabelModel>
|
||||
<node id="n11::n2">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="36.0" width="76.0" x="682.0" y="492.0"/>
|
||||
<y:Geometry height="36.0" width="76.0" x="772.0" y="492.0"/>
|
||||
<y:Fill color="#99CCFF" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
|
||||
@@ -675,6 +675,104 @@ Container<y:LabelModel>
|
||||
</node>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="n12">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ImageNode>
|
||||
<y:Geometry height="116.06321112515802" width="97.39570164348925" x="445.4298356510746" y="571.968394437421"/>
|
||||
<y:Fill color="#CCCCFF" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="sandwich" modelPosition="s" textColor="#000000" visible="true" width="4.0" x="46.69785082174462" y="120.06321112515798"/>
|
||||
<y:Image alphaImage="true" refid="3"/>
|
||||
</y:ImageNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n13">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="85.0" x="451.6276864728192" y="485.0"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="38.201171875" x="23.3994140625" y="8.8671875">Azure
|
||||
BLOB<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n14">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.card">
|
||||
<y:Geometry height="40.0" width="39.02970922882429" x="390.97029077117577" y="490.0"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="29.828125" x="4.600792114412172" y="10.93359375">.xml<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n15">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.card">
|
||||
<y:Geometry height="40.0" width="39.02970922882429" x="389.4630622503162" y="587.4087217193426"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="24.1328125" x="7.4484483644121156" y="10.93359375">.txt<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n16">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.card">
|
||||
<y:Geometry height="40.0" width="39.02970922882429" x="389.9353177243998" y="538.8895352718077"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="32.3828125" x="3.3234483644121156" y="10.93359375">.json<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n17">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.card">
|
||||
<y:Geometry height="40.0" width="39.02970922882429" x="389.7450294816688" y="640.0"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="22.158203125" x="8.435753051912116" y="10.93359375">.gz<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<edge id="e0" source="n8::n2" target="n0">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
@@ -700,7 +798,7 @@ Vulnerability data<y:LabelModel>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="34.626953125" x="6.7270960807795745" y="20.93360392252606">HTTP<y:LabelModel>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="34.626953125" x="-4.560574684247513" y="24.771374369551154">HTTP<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
@@ -712,31 +810,13 @@ Vulnerability data<y:LabelModel>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e2" source="n4::n2" target="n8::n1">
|
||||
<edge id="e2" source="n0" target="n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="241.78515625" x="-130.8343747008911" y="-104.32744691064147">HTTP or --cve-dictoianry-dbpath option<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.6111993328569665" segment="-1"/>
|
||||
</y:ModelParameter>
|
||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||
</y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e3" source="n0" target="n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="34.626953125" x="65.80254757404236" y="21.547962712535252">HTTP<y:LabelModel>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="34.626953125" x="42.345150113104864" y="26.521800272057305">HTTP<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
@@ -748,17 +828,17 @@ Vulnerability data<y:LabelModel>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e4" source="n4::n0" target="n5">
|
||||
<edge id="e3" source="n5" target="n4::n2">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:Path sx="0.0" sy="0.0" tx="2.1630847029074403" ty="11.890644753476522"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="31.802734375" x="-24.439868927002635" y="91.6240450195761">send<y:LabelModel>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="40.275390625" x="-14.861825373422448" y="-25.77642822265625">WebUI<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="left" ratio="1.0" segment="-1"/>
|
||||
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="124.43524707167697" distanceToCenter="true" position="center" ratio="0.0" segment="0"/>
|
||||
</y:ModelParameter>
|
||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||
</y:EdgeLabel>
|
||||
@@ -766,49 +846,13 @@ Vulnerability data<y:LabelModel>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="n4::e0" source="n4::n2" target="n4::n0">
|
||||
<edge id="e4" source="n4::n0" target="n10::n0">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:Path sx="44.363642405794096" sy="24.54915453361525" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="56.201171875" x="41.04640068840672" y="114.35365985463022">Generate<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="left" ratio="0.7612118931433132" segment="-1"/>
|
||||
</y:ModelParameter>
|
||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||
</y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e5" source="n5" target="n4::n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="140.76953125" x="-148.83947500014722" y="-53.63345557215143">View Detail Information<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="65.44983715439666" distanceToCenter="true" position="left" ratio="0.3605218238342404" segment="-1"/>
|
||||
</y:ModelParameter>
|
||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||
</y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e6" source="n4::n2" target="n10::n0">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="87.9098606499872" sy="24.54915453361525" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="27.07421875" x="63.50980239529861" y="100.92919596059016">SSH<y:LabelModel>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="27.07421875" x="76.88603771593068" y="105.56250755816848">SSH<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
@@ -820,17 +864,17 @@ Vulnerability data<y:LabelModel>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e7" source="n4::n2" target="n9::n0">
|
||||
<edge id="e5" source="n4::n0" target="n9::n0">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="27.07421875" x="47.789244589009286" y="-10.21369683159321">SSH<y:LabelModel>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="27.07421875" x="71.96010962283094" y="-0.07972829529296632">SSH<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="17.840986205821807" distanceToCenter="true" position="left" ratio="0.3567784326754698" segment="-1"/>
|
||||
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="17.840986205821807" distanceToCenter="true" position="left" ratio="0.5" segment="0"/>
|
||||
</y:ModelParameter>
|
||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||
</y:EdgeLabel>
|
||||
@@ -856,7 +900,7 @@ Vulnerability data<y:LabelModel>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e8" source="n9::n1" target="n3">
|
||||
<edge id="e6" source="n9::n1" target="n3">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
@@ -866,7 +910,7 @@ Vulnerability data<y:LabelModel>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e9" source="n10::n0" target="n3">
|
||||
<edge id="e7" source="n10::n0" target="n3">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
@@ -894,46 +938,17 @@ Vulnerability data<y:LabelModel>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e10" source="n4::n0" target="n6">
|
||||
<edge id="e8" source="n6" target="n5">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e11" source="n6" target="n5">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="38.875" x="-43.904876708984716" y="20.93361409505212">Notify<y:LabelModel>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="38.875" x="3.063580934131096" y="-107.12398849561202">Notify<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="left" ratio="0.0" segment="0"/>
|
||||
</y:ModelParameter>
|
||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||
</y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e12" source="n4::n2" target="n8::n0">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="4.0" x="116.345248769779" y="-97.77053302962645">
|
||||
<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="99.7428628309468" distanceToCenter="true" position="right" ratio="0.7403938917830506" segment="-1"/>
|
||||
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="100.14105708039112" distanceToCenter="true" position="left" ratio="-56.992693208601096" segment="-1"/>
|
||||
</y:ModelParameter>
|
||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||
</y:EdgeLabel>
|
||||
@@ -959,7 +974,7 @@ Vulnerability data<y:LabelModel>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e13" source="n4::n2" target="n11">
|
||||
<edge id="e9" source="n4::n0" target="n11">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
@@ -969,17 +984,7 @@ Vulnerability data<y:LabelModel>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e14" source="n11" target="n4::n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e15" source="n5" target="n4::n3">
|
||||
<edge id="e10" source="n4::n1" target="n11">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
@@ -990,7 +995,7 @@ Vulnerability data<y:LabelModel>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e16" source="n11" target="n4::n3">
|
||||
<edge id="e11" source="n4::n1" target="n5">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
@@ -1001,6 +1006,100 @@ Vulnerability data<y:LabelModel>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e12" source="n4::n1" target="n6">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e13" source="n4::n1" target="n8">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e14" source="n4::n2" target="n11">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e15" source="n4::n1" target="n12">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="22.568359375" x="-77.34538676739476" y="14.553670286396482">Put<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
|
||||
</y:ModelParameter>
|
||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||
</y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e16" source="n4::n1" target="n13">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e17" source="n4::n3" target="n11">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e18" source="n5" target="n4::n3">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="95.939453125" x="30.74393308155709" y="-58.42560122139275"> View Results
|
||||
on Terminal<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.2607824010854994" segment="-1"/>
|
||||
</y:ModelParameter>
|
||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||
</y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
</graph>
|
||||
<data key="d7">
|
||||
<y:Resources>
|
||||
@@ -1185,6 +1284,180 @@ Vulnerability data<y:LabelModel>
|
||||
style="fill:#373d47;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path46-3"
|
||||
inkscape:connector-curvature="0" /></g></svg></y:Resource>
|
||||
<y:Resource id="3" type="java.awt.image.BufferedImage">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</y:Resource>
|
||||
</y:Resources>
|
||||
</data>
|
||||
</graphml>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 91 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 72 KiB |
3
main.go
3
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")
|
||||
|
||||
249
models/models.go
249
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
|
||||
Family string
|
||||
Release string
|
||||
Container Container
|
||||
Platform Platform
|
||||
|
||||
Container Container
|
||||
// Scanned Vulns via SSH + CPE Vulns
|
||||
ScannedCves []VulnInfo
|
||||
|
||||
Platform Platform
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -17,9 +17,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
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"
|
||||
}
|
||||
|
||||
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"))
|
||||
}
|
||||
}
|
||||
|
||||
if 0 < len(errs) {
|
||||
return fmt.Errorf("Failed to upload json to Azure Blob: %v", errs)
|
||||
if err := cli.CreateBlockBlobFromReader(
|
||||
c.Conf.AzureContainer,
|
||||
k,
|
||||
uint64(len(b)),
|
||||
bytes.NewReader(b),
|
||||
map[string]string{},
|
||||
); err != nil {
|
||||
return fmt.Errorf("Failed to upload data to %s/%s, %s",
|
||||
c.Conf.AzureContainer, k, err)
|
||||
}
|
||||
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(
|
||||
c.Conf.AzureContainer,
|
||||
name,
|
||||
uint64(len(jsonBytes)),
|
||||
bytes.NewReader(jsonBytes),
|
||||
map[string]string{},
|
||||
); err != nil {
|
||||
return fmt.Errorf("%s/%s, %s",
|
||||
c.Conf.AzureContainer, name, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
150
report/json.go
150
report/json.go
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
111
report/localfile.go
Normal file
111
report/localfile.go
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
125
report/s3.go
125
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)
|
||||
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"
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
--------------
|
||||
|
||||
214
report/util.go
214
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)
|
||||
const maxColWidth = 80
|
||||
|
||||
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)
|
||||
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("=")
|
||||
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...)
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
for _, d := range cves {
|
||||
var scols []string
|
||||
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 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", stable)
|
||||
return fmt.Sprintf("%s\n%s\n", header, stable)
|
||||
}
|
||||
|
||||
func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
|
||||
for _, cve := range data.KnownCves {
|
||||
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")
|
||||
}
|
||||
|
||||
scored := strings.Join(scoredReport, "\n\n")
|
||||
detail := fmt.Sprintf(`
|
||||
%s
|
||||
|
||||
%s
|
||||
`,
|
||||
scored,
|
||||
unscored,
|
||||
)
|
||||
return fmt.Sprintf("%s\n%s\n", header, detail)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -17,7 +17,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 = "<vulsreport>"
|
||||
vulsCloseTag = "</vulsreport>"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
const (
|
||||
vulsOpenTag = "<vulsreport>"
|
||||
vulsCloseTag = "</vulsreport>"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
97
scan/base.go
97
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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
scannedAt := time.Now()
|
||||
dir, err := ensureResultDir(scannedAt)
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
if errs := scanVulns(dir, scannedAt); errs != nil {
|
||||
return errs
|
||||
}
|
||||
|
||||
Log.Info("Scanning vulnerable software specified in the CPE...")
|
||||
if errs := scanVulnByCpeName(); 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)
|
||||
|
||||
}
|
||||
|
||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
|
||||
func scanVulnByCpeName() []error {
|
||||
timeoutSec := 30 * 60
|
||||
return parallelSSHExec(func(o osTypeInterface) error {
|
||||
return o.scanVulnByCpeName()
|
||||
}, timeoutSec)
|
||||
|
||||
}
|
||||
|
||||
// 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)
|
||||
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)
|
||||
|
||||
return nil
|
||||
}, timeoutSec)
|
||||
|
||||
config.Conf.FormatJSON = true
|
||||
ws := []report.ResultWriter{
|
||||
report.LocalFileWriter{CurrentDir: jsonDir},
|
||||
}
|
||||
return
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Symlink(jsonDir, symlinkPath); err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"Failed to create symlink: path: %s, err: %s", symlinkPath, err)
|
||||
}
|
||||
return jsonDir, nil
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 \
|
||||
-format-short-text \
|
||||
-config=./config.toml # path to config.toml in docker
|
||||
```
|
||||
|
||||
## Step6. vulsrepo
|
||||
|
||||
```console
|
||||
$docker run -dt \
|
||||
|
||||
@@ -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-json
|
||||
-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 \
|
||||
-format-short-text \
|
||||
-config=./config.toml # path to config.toml in docker
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
Reference in New Issue
Block a user