Compare commits

...

303 Commits

Author SHA1 Message Date
Kota Kanbe
b0d9c0b550 Update Changelog 2017-03-24 14:55:28 +09:00
Kota Kanbe
9255132f9b Bump up version 2017-03-24 14:37:48 +09:00
大沼
d5c0092fa3 fix typo (#394) 2017-03-24 00:25:23 +09:00
Teppei Fukuda
c7019debb9 Notify the difference from the previous scan result (#392)
add diff option
2017-03-23 23:58:05 +09:00
Kota Kanbe
7131270cad Add timeout option to configtest (#400) 2017-03-23 20:52:25 +09:00
Kota Kanbe
af5a1204bc Update README (#387)
Update Tutorial in README
2017-03-21 10:47:19 +09:00
Kota Kanbe
58afcfc49a Fix nil-ponter in TUI (#388) 2017-03-17 16:46:42 +09:00
Avi Miller
986762ca85 Add Oracle Linux support (#386)
Adding support for Oracle Linux
2017-03-16 17:07:43 +09:00
Kota Kanbe
6342cf79f5 Merge pull request #383 from usiusi360/Fix_README
Fix README
2017-03-15 17:47:36 +09:00
Kota Kanbe
5fbf67f971 Merge pull request #384 from future-architect/mysql
Fix Bug of Mysql Backend
2017-03-15 16:51:25 +09:00
Kota Kanbe
e441e5a696 Fix Bug of Mysql Backend 2017-03-15 16:44:49 +09:00
usiusi360
d201efb029 Fix README 2017-03-15 13:53:42 +09:00
Kota Kanbe
25960126c7 Fix README 2017-03-15 12:35:50 +09:00
Kota Kanbe
63d5a6f584 Merge pull request #382 from beuno/patch-1
s/dictinary/dictionary typo
2017-03-15 10:32:36 +09:00
Martin Albisetti
2030951a8f s/dictinary/dictionary typo 2017-03-14 16:50:36 -03:00
Kota Kanbe
cd841462cd Merge pull request #381 from future-architect/container-excluded
Change container scan format in config.toml
2017-03-14 20:32:22 +09:00
Kota Kanbe
735aa835a6 Change container scan setting in config.toml 2017-03-14 20:07:51 +09:00
Kota Kanbe
92e213ca32 Merge pull request #379 from future-architect/fix-scan-confidence-on-debian
Fix scan confidence on Ubuntu/Debian/Raspbian #362
2017-03-13 21:03:12 +09:00
Kota Kanbe
d077c29716 Fix scan confidence on Ubuntu/Debian/Raspbian #362 2017-03-13 20:55:23 +09:00
Kota Kanbe
d6eba48a50 Merge pull request #377 from IMAI-Yuji/IMAI-Yuji-patch-1
Fix Japanese typo
2017-03-13 17:27:11 +09:00
Kota Kanbe
2a1608d1d2 Merge pull request #378 from future-architect/obsolete-centos5
Obsolete CentOS5 support
2017-03-13 17:04:36 +09:00
Kota Kanbe
cc7d3dc2aa Obsolete CentOS5 2017-03-13 16:57:43 +09:00
Kota Kanbe
a5c4c682f5 Merge pull request #375 from future-architect/deprecate-prepare
Deprecate prepare subcommand to minimize the root authority defined by /etc/sudoers
2017-03-13 15:59:35 +09:00
Kota Kanbe
688cfd6872 Deprecate prepare subcommand to minimize the root authority #375 2017-03-13 13:21:01 +09:00
Yuji IMAI
7e268dbae1 Fix Japanese typo 2017-03-10 11:34:53 +09:00
Kota Kanbe
ce6a4231ef Deprecate prepare subcommand to minimize the root authority defined by /etc/sudoers 2017-03-07 18:09:10 +09:00
Kota Kanbe
e1de8ab626 Merge pull request #370 from ohsawa0515/support_iam_role
Support IAM role for report to S3.
2017-03-07 14:07:32 +09:00
Kota Kanbe
0058eaf357 Merge pull request #374 from future-architect/package-count
Fix updatalbe packages count #373
2017-03-07 14:03:19 +09:00
Kota Kanbe
732d95098a Fix updatalbe packages count #373 2017-03-07 13:49:25 +09:00
Shuichi Ohsawa
52f0943207 Add ec2 roles credentials. 2017-03-07 12:37:31 +09:00
Kota Kanbe
41f99f2b65 Merge pull request #372 from future-architect/sudo-check-update-rhel
sudo yum check-update on RHEL
2017-03-06 15:16:38 +09:00
Kota Kanbe
1f9e5c6263 sudo yum check-update on RHEL 2017-03-06 14:43:02 +09:00
Kota Kanbe
2f3eddd2ab Merge pull request #369 from knqyf263/change_option
Change ssh option from -t to -tt
2017-03-06 14:37:29 +09:00
knqyf263
619a0ee700 Change ssh option from -t to -tt 2017-03-03 11:20:57 +09:00
Kota Kanbe
b1b5c2c9a0 Merge pull request #356 from future-architect/changelog
Output changelog in report, TUI and JSON for Ubuntu/Debian/CentOS
2017-03-02 22:28:29 +09:00
Kota Kanbe
a86035c0bf Output changelog in report, TUI and JSON for Ubuntu/Debian/CentOS 2017-03-02 22:22:35 +09:00
Kota Kanbe
c66b0f4db4 Merge pull request #364 from knqyf263/increase_width
Increase the width of RequestPty
2017-03-01 12:15:23 +09:00
knqyf263
a4cf4bd314 Increase the width of RequestPty 2017-02-28 14:29:12 +09:00
Kota Kanbe
f1cd9383c1 Merge pull request #358 from ymomoi/remove-unused-import
remove unused import line.
2017-02-28 14:23:55 +09:00
Kota Kanbe
6fa57abe10 Merge pull request #363 from knqyf263/support_travis
Add .travis.yml
2017-02-28 13:08:42 +09:00
knqyf263
6e77c714b5 Add .travis.yml 2017-02-27 21:42:22 +09:00
Yasunari Momoi
fbab020e6e remove unused import line. 2017-02-25 04:48:28 +09:00
Kota Kanbe
5581a5cce7 Merge pull request #354 from future-architect/mistook-english
Fix candidate to confidence.
2017-02-23 12:07:44 +09:00
Kota Kanbe
b4be11775e Fix candidate to confidence. 2017-02-23 12:05:13 +09:00
Kota Kanbe
b079f5e52e Update README.ja.md 2017-02-22 21:15:01 +09:00
Kota Kanbe
f9bf470a37 Update README.md 2017-02-22 21:13:54 +09:00
Kota Kanbe
9d783dd2ab Merge pull request #350 from future-architect/show-false-positive
Output confidence score of detection accuracy and detection method to JSON or Reporting
2017-02-22 20:57:39 +09:00
Kota Kanbe
1b9aafbbaf Output confidence ranking of detection accuracy to JSON or Reporting 2017-02-22 20:51:58 +09:00
Kota Kanbe
1d3ee6a241 Merge pull request #328 from federacy/leniant_changelog_parsing_for_debian
Add leniancy to the version matching for debian to account for versio…
2017-02-22 20:43:46 +09:00
Kota Kanbe
2f9c3071a6 Merge pull request #351 from hasegawa-tomoki/patch-1
Improve kanji character
2017-02-21 15:48:24 +09:00
HASEGAWA Tomoki
4b0be4f115 Fix typo(?) 2017-02-21 15:45:17 +09:00
Kota Kanbe
1419c7c8c6 Merge pull request #348 from knqyf263/add_template
Add PULL_REQUEST_TEMPLATE.md
2017-02-20 15:37:44 +09:00
knqyf263
851cecdd73 Add PULL_REQUEST_TEMPLATE.md 2017-02-19 23:36:22 +09:00
Kota Kanbe
753da3aad7 Merge pull request #347 from knqyf263/update_readme
Update README
2017-02-19 09:57:28 +09:00
Kota Kanbe
65c10d6d8e Merge pull request #346 from knqyf263/send_cc
Bug fix: not send e-mail to cc address
2017-02-19 09:56:20 +09:00
Kota Kanbe
1b8b423131 Merge pull request #345 from future-architect/avoid-null
Avoid null slice being null in JSON
2017-02-19 09:37:36 +09:00
Kota Kanbe
55b1264c7d Avoid null slice being null in JSON 2017-02-19 09:34:24 +09:00
knqyf263
902a1888d4 Update README 2017-02-17 18:33:11 +09:00
knqyf263
98151f7d0e Bug fix: not send e-mail to cc address 2017-02-16 22:25:04 +09:00
Kota Kanbe
a6f0c559f8 Merge pull request #332 from kazuminn/add-err-handling
add error handling
2017-02-16 18:06:59 +09:00
kazuminn
e7ec5b841d due to miss error handling
I fixed it according to the review
2017-02-16 12:49:13 +09:00
Kota Kanbe
d6f72ac0f3 Merge pull request #343 from knqyf263/fix_typo
Fix typo
2017-02-16 12:01:03 +09:00
Kota Kanbe
7e3a10025a Merge pull request #344 from future-architect/fix-testcase
Fix test case
2017-02-16 11:33:07 +09:00
Kota Kanbe
e16ec15226 Fix test case 2017-02-16 11:32:18 +09:00
Kota Kanbe
6935b56c9d Merge pull request #308 from lapthorn/update-readme
Update readme
2017-02-16 07:54:51 +09:00
Alan Lapthorn
0e3a0b64e7 Update READMEs
Fix typo

Fix typo in comment
2017-02-15 22:53:03 +00:00
knqyf263
74e6aee236 Fix typo 2017-02-15 23:51:46 +09:00
Kota Kanbe
db0602b7b8 Merge pull request #296 from galigalikun/update-readme
update readme
2017-02-15 22:08:51 +09:00
Kota Kanbe
c9b7c3f179 Merge pull request #331 from knqyf263/add_one-email
Add -format-one-email option
2017-02-15 21:58:14 +09:00
knqyf263
5bd9f4afb4 Add -format-one-email option 2017-02-15 18:31:51 +09:00
Kota Kanbe
9d2ba5912e Merge pull request #340 from future-architect/freebsd-version
Change the command used for os detection from uname to freebsd-version
2017-02-15 14:39:31 +09:00
Kota Kanbe
9986c4a6f3 Change the command used for os detection from uname to freebsd-version 2017-02-15 14:34:53 +09:00
Kota Kanbe
df2c9697ef Merge pull request #339 from future-architect/gnu-makefile
Rename Makefile to GNUmakefile #313
2017-02-15 14:13:45 +09:00
Kota Kanbe
ab0388e882 Rename Makefile to GNUmakefile #313 2017-02-15 14:07:43 +09:00
Kota Kanbe
c05d8a36eb Merge pull request #338 from future-architect/update-readme
Update README
2017-02-14 12:47:33 +09:00
Kota Kanbe
492753d905 Update README 2017-02-14 12:45:28 +09:00
Kota Kanbe
6e08bd23f4 Merge pull request #330 from knqyf263/support_raspbian
Support Raspbian
2017-02-14 12:15:28 +09:00
Kota Kanbe
a687c97808 Merge pull request #337 from future-architect/fix-error-handling
Fix error handling of detectOS
2017-02-14 11:58:43 +09:00
Kota Kanbe
c6864289cb Fix error handling of detectOS 2017-02-14 11:54:06 +09:00
Kota Kanbe
97d85258c5 Merge pull request #309 from future-architect/continue_scan_on_error
Continue scanning even when some hosts have tech issues
2017-02-14 11:10:13 +09:00
knqyf263
bee25f5aa2 Support Raspbian 2017-02-13 22:15:09 +09:00
Kota Kanbe
386b97d2be Continue scanning even when some hosts have tech issues
see #264
2017-02-13 21:55:55 +09:00
Kota Kanbe
00660485b7 Merge pull request #324 from federacy/aptitude_changelog_more_to_cat
aptitude changelog defaults to using more, which is not interactive a…
2017-02-13 14:54:12 +09:00
Kota Kanbe
1e8f24dedb Merge pull request #326 from federacy/add_image_info_for_docker
Add image information for docker containers
2017-02-13 13:48:11 +09:00
Kota Kanbe
2be190f863 Merge pull request #322 from knqyf263/delete_sudo_echo
Do not use sudo when echo
2017-02-13 12:19:16 +09:00
Kota Kanbe
ec7c6e6c85 Merge pull request #317 from federacy/fix_cve_dictionary_url_conditional
Don't check for a CVE DB when CVE Dictionary URL is defined
2017-02-13 10:49:36 +09:00
Kota Kanbe
c52bc53fd8 Merge pull request #314 from justyns/fixcontainertypo
Fix typo contianer -> container
2017-02-13 10:43:47 +09:00
James Sulinski
981631503a Add leniancy to the version matching for debian to account for versions without the "+" when package maintainers aren't using them. 2017-02-10 11:38:46 -08:00
Kota Kanbe
48de3a6a4f Merge pull request #319 from federacy/nosudo_for_debian_scans
Reduce privilege requirements for commands that don't need sudo on Ubuntu/Debian
2017-02-10 19:40:34 +09:00
Kota Kanbe
d1983a6978 Merge pull request #329 from future-architect/retry-exceeded-slack
Fix infinite retry at size overrun error in Slack report
2017-02-10 18:41:22 +09:00
Kota Kanbe
f821a26aec Fix infinite retry at size overrun error in Slack report 2017-02-10 18:40:29 +09:00
James Sulinski
3380e905de Add image information for docker containers 2017-02-09 01:05:12 -08:00
James Sulinski
b5c2718756 aptitude changelog defaults to using more, which is not interactive and breaks docker scans. Set PAGER=cat before running to default to cat. 2017-02-09 00:54:47 -08:00
James Sulinski
a03a803b89 Reduce privilege requirements for commands that don't need sudo 2017-02-09 00:47:08 -08:00
knqyf263
e743177ae6 Do not use sudo when echo 2017-02-09 17:43:15 +09:00
James Sulinski
6e12c69953 Don't check for a CVE DB when CVE Dictionary URL is defined 2017-02-09 00:36:23 -08:00
Justyn Shull
019ab77466 Fix typo contianer -> container 2017-02-08 17:17:12 -06:00
Kota Kanbe
1730caf124 Merge pull request #306 from knqyf263/update_lock
Update glide.lock to fix import error
2017-01-30 17:50:03 +09:00
knqyf263
59d1533795 Update glide.lock to fix import error 2017-01-30 17:49:23 +09:00
Kota Kanbe
a6278ab7ea Merge pull request #305 from future-architect/fix-changelog-cache
Fix the changelog cache logic for ubuntu/debian
2017-01-28 04:16:04 +09:00
Kota Kanbe
42a6004c7d Fix the changelog cache logic for ubuntu/debian 2017-01-28 04:08:57 +09:00
Kota Kanbe
6084c1b1d3 Merge pull request #304 from future-architect/fix-yum-updateinfo-opts
Fix yum updateinfo options
2017-01-27 18:50:17 +09:00
Kota Kanbe
c96fbc1dba Fix yum updateinfo options
see #281
2017-01-27 18:42:14 +09:00
Kota Kanbe
5546a8b093 Merge pull request #303 from future-architect/glide
Update glide.lock to fix create-log-dir error.
2017-01-26 21:37:23 +09:00
Kota Kanbe
6b76b38dcd Update glide.lock to fix create-log-dir error.
see https://github.com/kotakanbe/go-cve-dictionary/pull/40
2017-01-26 21:34:44 +09:00
Kota Kanbe
941e50b460 Merge pull request #302 from future-architect/log-dir
Fix a bug in logging (file output) at scan command
2017-01-26 17:22:45 +09:00
Kota Kanbe
5a10e5c9ff Fix a bug in logging (file output) at scan command
Log of localhost was not output to file. #301
2017-01-26 17:21:03 +09:00
Kota Kanbe
883fe13756 Merge pull request #301 from knqyf263/add_logdir
Add -log-dir option
2017-01-26 16:51:31 +09:00
knqyf263
2e7c34cf9f Add -log-dir option 2017-01-26 15:36:30 +09:00
Kota Kanbe
9216efbd2f Merge pull request #300 from knqyf263/use_assumeno
Use --assumeno option
2017-01-24 15:07:58 +09:00
teppei-fukuda
6c8100e5b6 Use --assumeno option 2017-01-24 12:28:39 +09:00
Kota Kanbe
e7ef50bedf Update README.md 2017-01-24 01:17:05 +09:00
Kota Kanbe
386ca3565a Merge pull request #299 from future-architect/fix-pipe-problem
Add -pipe flag #294
2017-01-24 01:13:48 +09:00
Kota Kanbe
2d854cd64d Add -pipe flag #294
Solved the problem of trying to read from STDIN and stopping on the way when running from CRON or AWS Lambda.
2017-01-24 01:06:22 +09:00
Kota Kanbe
49b4b8be22 Update README.md 2017-01-23 18:47:42 +09:00
Kota Kanbe
db975ebfee Merge pull request #297 from knqyf263/update_readme
Update docker README
2017-01-23 18:36:31 +09:00
Kota Kanbe
d60a41139b Merge pull request #298 from knqyf263/check_echo
Check whether echo is executable with nopasswd
2017-01-23 17:42:17 +09:00
knqyf263
f62d869d27 Check whether echo is executable with nopasswd 2017-01-22 23:15:25 +09:00
knqyf263
6cbe3cdb93 Update docker README 2017-01-21 22:04:57 +09:00
akaishi takeshi
b13e7b9da4 update readme 2017-01-18 14:34:23 +09:00
Kota Kanbe
8fe34c8474 Fix architecture image file 2017-01-17 00:32:53 +09:00
Kota Kanbe
bef29be50f Merge pull request #291 from future-architect/localscan
Add local scan mode(Scan without SSH when target server is localhost)
2017-01-17 00:22:09 +09:00
Kota Kanbe
20275a1063 Add local scan mode.
If the scan target server is localhost, Don't use SSH. #210
2017-01-17 00:16:46 +09:00
Kota Kanbe
910385b084 Merge pull request #288 from jiazio/add-lxd-support
Add LXD support
2017-01-16 16:43:51 +09:00
Kota Kanbe
8e779374a7 Merge pull request #293 from future-architect/fix-rhel5
Fix RHEL5 scan stopped halfway
2017-01-13 06:41:26 +09:00
Kota Kanbe
44fc6f728e Fix RHEL5 scan stopped halfway 2017-01-13 06:40:03 +09:00
Kota Kanbe
1f62dcf22a Merge pull request #292 from future-architect/fix-bug-amazon-linux
Fix amazon linux scan stopped halfway
2017-01-13 04:59:34 +09:00
Kota Kanbe
0416c3b561 Fix amazon linux scan stopped halfway 2017-01-13 04:56:59 +09:00
Kota Kanbe
a6912cae76 Merge pull request #289 from future-architect/rhel5
Support RHEL5
2017-01-10 16:34:37 +09:00
Kota Kanbe
63dfe8a952 Support RHEL5 2017-01-10 16:32:06 +09:00
Kota Kanbe
62d1b761bd Update CHANGELOG 2017-01-10 16:24:02 +09:00
Kota Kanbe
082b10a15b Merge pull request #270 from future-architect/report-subcommand
Add report subcommand, change scan options. #239
2017-01-10 16:15:01 +09:00
Kota Kanbe
1a6bcd82b0 Merge pull request #287 from jiazio/fix-container-os-dectecion
Fix container os detection
2017-01-10 14:35:07 +09:00
jiazio
6ecd70220b Add LXD support 2017-01-06 22:11:13 +09:00
jiazio
e9f55f5772 Fix container os detection 2017-01-06 16:32:42 +09:00
Kota Kanbe
155cadf901 Add report subcommand, change scan options. Bump up ver #239 2017-01-05 13:40:25 +09:00
Kota Kanbe
cb29289167 Merge pull request #283 from ymomoi/add-date-header
Add date header to report mail.
2017-01-02 09:13:33 +09:00
Yasunari Momoi
e4db9d1d91 Add date header to report mail. 2016-12-16 11:22:09 +09:00
Kota Kanbe
7b2e2cb817 Merge pull request #280 from hogehogehugahuga/add-mail-header
Add Content-Type header to report/mail.go .
2016-12-15 10:53:25 +09:00
hogehogehugahuga
c717f8d15d Add Content-Type header to report/mail.go .
(fix pull request, "utf8" to "utf-8".)

I did the following test.
- compile vuls with this fix.
- I executed the following command and confirmed that garbled display is not done.
  + vuls scan -lang=en -report-mail -cve-dictionary...
  + vuls scan -lang=ja -report-mail -cve-dictionary...

Mail header is as follows.
Message-Id: <...>
Subject: <...>
Content-Type: text/plain; charset=utf8
From: <...>
To: <...>
Cc: <...>
2016-12-15 10:27:34 +09:00
Kota Kanbe
8db147acab Merge pull request #272 from yoheimuta/sort-CveInfo-PackageInfo
Keep output of "vuls scan -report-*" to be same every times
2016-11-29 12:15:19 +09:00
yoheimuta
e6de7aa9ca Sorted PackageInfos by Name to keep report texts same every times 2016-11-22 01:11:42 +09:00
Kota Kanbe
46f96740a2 Merge pull request #271 from future-architect/json-dir-regex
Fix JSON-dir regex pattern #265
2016-11-17 22:17:40 +09:00
Kota Kanbe
8f9fb5c262 Fix JSON-dir regex pattern #265 2016-11-17 22:14:41 +09:00
Kota Kanbe
171d6d6684 Merge pull request #263 from Code0x58/ssh-external-tidy
Stop quietly ignoring `--ssh-external` on Windows
2016-11-16 16:31:58 +09:00
Oliver Bristow
f648b5ad0a Refactor SSHExternal flag so it isn't quietly ignored on Windows 2016-11-16 06:42:34 +00:00
Kota Kanbe
ef21376f0a Merge pull request #265 from Code0x58/rfc3339-timestamps
Use RFC3339 timestamps in the results
2016-11-16 11:13:02 +09:00
Kota Kanbe
58958d68d8 Merge pull request #266 from Code0x58/260-prepare-confirm-flag
Add --assume-yes to prepare #260
2016-11-16 10:36:33 +09:00
Kota Kanbe
a06b565ee9 Merge pull request #262 from Code0x58/261-fix-gocui-signature-change
Fix gocui.NewGui after signature change #261
2016-11-16 09:49:24 +09:00
Oliver Bristow
a7db27ce5a Add --assume-yes to prepare #260 2016-11-14 20:44:19 +00:00
Oliver Bristow
cda69dc7f0 Use RFC3339 timestamps in the results 2016-11-14 19:10:58 +00:00
Oliver Bristow
39f9594548 Update glide.lock and fix gocui.NewGui after signature change #261 2016-11-14 18:05:28 +00:00
Kota Kanbe
6d82ad32a9 Merge pull request #254 from Code0x58/patch-2
Replace inconsistent tabs with spaces
2016-11-14 04:53:52 +09:00
Kota Kanbe
cfcd8bf223 Merge pull request #253 from Code0x58/patch-1
Fix non-interactive `apt-get install` #251
2016-11-14 04:49:12 +09:00
Oliver Bristow
8149ad00b5 Replace inconsistent tabs with spaces 2016-11-11 19:26:41 +00:00
Oliver Bristow
2310522806 Fix non-interactive apt-get install #251 2016-11-11 19:13:51 +00:00
Kota Kanbe
e40ef656d6 Merge pull request #249 from usiusi360/Fix-README
Fix README
2016-11-08 22:54:24 +09:00
Takayuki Ushida
e060d40a32 Fix README 2016-11-08 22:27:57 +09:00
Kota Kanbe
a522218c4e Update CHANGELOG.md 2016-11-08 21:15:57 +09:00
Kota Kanbe
820455399c Bump up version 2016-11-08 21:08:03 +09:00
Kota Kanbe
959d612534 Merge pull request #147 from future-architect/enablerepos
Supports yum --enablerepo option (supports only base,updates for now)
2016-11-08 15:56:28 +09:00
kota kanbe
cd81e6eab2 Add enablerepos option 2016-11-08 15:39:30 +09:00
Kota Kanbe
e6ec6920ad Merge pull request #248 from future-architect/skip-broken
Add -skip-broken option [CentOS only] #245
2016-11-07 21:24:33 +09:00
Kota Kanbe
18a92fa1ca Add -skip-broken option [CentOS only] #245 2016-11-07 21:22:38 +09:00
Kota Kanbe
f95af9897b Merge pull request #244 from future-architect/display-unknown-cves-tui
Display unknown CVEs to TUI
2016-11-07 15:03:25 +09:00
Kota Kanbe
b61adcb1fd Display unknown CVEs to TUI 2016-11-07 14:59:50 +09:00
Kota Kanbe
1bbf320755 Merge pull request #243 from yoheimuta/go1.7-context
Moved golang.org/x/net/context to context
2016-11-07 11:16:37 +09:00
Kota Kanbe
159f26171c Merge pull request #240 from gleentea/feature/report-xml
Add the XML output
2016-11-07 10:44:10 +09:00
yoheimuta
8ac00f6c0d Moved golang.org/x/net/context to context 2016-11-04 17:56:42 +09:00
gleentea
ce2daf2493 add xml-report
add struct tag for encoding/xml

update README

update glide.lock
2016-11-04 15:21:32 +09:00
Kota Kanbe
f014f8fd59 Merge pull request #241 from sadayuki-matsuno/fix-docker-readme-cation
fix readme
2016-11-02 13:49:28 +09:00
Kota Kanbe
f50a39a9e2 Merge pull request #242 from future-architect/readme-mysql
Update README #225
2016-11-02 13:46:51 +09:00
Kota Kanbe
e0d8147104 Update README #225 2016-11-02 13:45:37 +09:00
Sadayuki Matsuno
c5cfac62da fix readme 2016-11-01 20:24:37 +09:00
Kota Kanbe
83469ce5cc Update glide.lock 2016-11-01 15:09:53 +09:00
Kota Kanbe
7cd7b4a9a2 Merge pull request #238 from future-architect/debcache
Fix changelog cache bug on Ubuntu and Debian #235
2016-11-01 13:05:18 +09:00
Kota Kanbe
7681b277cf Fix changelog cache bug on Ubuntu and Debian #235 2016-11-01 13:03:44 +09:00
Kota Kanbe
406efa96c0 Merge pull request #237 from future-architect/readme
Fix README #234
2016-11-01 10:57:39 +09:00
Kota Kanbe
9a7a30c0bc Fix README #234 2016-11-01 10:54:59 +09:00
Kota Kanbe
64bdfa0e80 Merge pull request #234 from mykstmhr/master
add '-ssh-external' option to prepare subcommand
2016-10-31 19:26:00 +09:00
Kota Kanbe
067089973c Merge pull request #236 from future-architect/glide
Update glide files
2016-10-31 18:03:43 +09:00
Kota Kanbe
85e6d753c7 Update glide files 2016-10-31 18:02:41 +09:00
Kota Kanbe
4094984642 Merge pull request #225 from oswell/feature/mysql.support
Add support for reading CVE data from MySQL.
2016-10-31 17:07:06 +09:00
Kota Kanbe
85c0009a43 Merge pull request #232 from future-architect/owasp
Integrate OWASP Dependency Check
2016-10-31 15:16:13 +09:00
Tomohiro Miyakoshi
234e312ee2 add '-ssh-external' option to prepare subcommand
modify gofmt

modify gofmt
2016-10-28 19:13:38 +09:00
Kota Kanbe
ce3ca64678 Merge pull request #231 from ymd38/master
Fixed error for the latest version of gocui
2016-10-28 15:54:27 +09:00
Kota Kanbe
b042a600c3 Integrate OWASP Dependency Check 2016-10-27 22:00:53 +09:00
hirokazu yamada
686e9f07a9 Fixed error for the latest version of gocui 2016-10-26 00:51:21 +09:00
Mike Oswell
bb6725372b Add support for reading CVE data from MySQL. 2016-10-24 19:18:11 -07:00
Kota Kanbe
6f012fc9c5 Merge pull request #229 from oswell/feature/fix.tui.errors
Handle the refactored gocui SetCurrentView method.
2016-10-24 11:29:43 +09:00
Mike Oswell
4c82458481 Support recent refactoring of gocui's SetCurrentView method. 2016-10-23 19:16:40 -07:00
Kota Kanbe
a0ac863998 Update README.ja.md 2016-10-19 15:12:04 +09:00
Kota Kanbe
d23ef838f8 Update README.md 2016-10-19 15:08:08 +09:00
Kota Kanbe
f81ac197f5 Merge pull request #226 from usiusi360/fix-README
fix README
2016-10-17 22:55:24 +09:00
Takayuki Ushida
652b37e630 fix README 2016-10-17 22:43:20 +09:00
Kota Kanbe
c57e430393 Merge pull request #223 from sadayuki-matsuno/remove_base_image
remove base docker image
2016-10-17 18:14:52 +09:00
Kota Kanbe
fff6047df9 Merge pull request #222 from future-architect/ignore-cves
Support ignore CveIDs in config
2016-10-17 17:13:34 +09:00
Kota Kanbe
1e2b93d55b Support ignore CveIDs in config 2016-10-17 17:09:44 +09:00
Sadayuki Matsuno
66b27a7795 remove base docker image 2016-10-15 13:59:27 +09:00
Kota Kanbe
63f0a272c4 Update README 2016-10-13 19:30:36 +09:00
Kota Kanbe
8d2180cf5a Update README 2016-10-13 16:14:05 +09:00
Kota Kanbe
1986f7e4dd Merge pull request #219 from future-architect/confirm-before-preparing
Confirm before installing dependencies on prepare
2016-10-13 16:07:32 +09:00
Kota Kanbe
21beb396b4 Confirm before installing dependencies on prepare 2016-10-13 16:06:48 +09:00
Kota Kanbe
cb5a6f38d6 Merge pull request #221 from ymomoi/fix-misspelling
fix some misspelling.
2016-10-13 10:43:28 +09:00
Kota Kanbe
67e4aaede0 Merge pull request #216 from future-architect/makefile
Improve makefile, -version shows git hash, fix README
2016-10-13 10:35:06 +09:00
Yasunari Momoi
b42805d00c fix some misspelling. 2016-10-12 23:57:57 +09:00
Kota Kanbe
95d6888c87 Improve makefile, -version shows git hash, fix README 2016-10-12 20:31:47 +09:00
Kota Kanbe
549b315a65 Merge pull request #218 from future-architect/remove-all-json
Remove all.json
2016-10-12 20:05:48 +09:00
Kota Kanbe
5b80b16684 Remove all.json 2016-10-12 19:57:47 +09:00
Kota Kanbe
0cd0a4bf2b Merge pull request #217 from future-architect/ISSUE_TEMPLATE
Add GitHub issue template
2016-10-12 16:55:22 +09:00
Kota Kanbe
b5cf06cad8 Add GitHub issue template 2016-10-12 16:53:59 +09:00
Kota Kanbe
b964d19d82 Merge pull request #215 from future-architect/lang-to-language
Fix locale env var LANG to LANGUAGE
2016-10-12 09:03:07 +09:00
Kota Kanbe
cf7990d444 Fix locale env var LANG to LANGUAGE 2016-10-12 08:59:05 +09:00
Kota Kanbe
738ccf7dbb Merge pull request #214 from sadayuki-matsuno/fix-docker-readme
fix docker readme
2016-10-11 19:45:47 +09:00
Sadayuki Matsuno
fc2ea48c1d fix docker readme 2016-10-11 19:43:50 +09:00
Kota Kanbe
3af93b93d7 Merge pull request #206 from essentialkaos/master
Fixed bug with parsing update line on CentOS/RHEL
2016-10-11 13:20:53 +09:00
Kota Kanbe
f386c3be92 Merge pull request #213 from shokohara/patch-1
Fix ja document about typo
2016-10-11 13:10:54 +09:00
Sho Kohara
239d910dbe Fix ja document about typo 2016-10-11 13:09:45 +09:00
Kota Kanbe
48929deabd Merge pull request #212 from sadayuki-matsuno/fix-readme-about-mail
fix readme
2016-10-11 12:50:11 +09:00
Sadayuki Matsuno
79523de1db fix readme 2016-10-11 12:37:30 +09:00
Kota Kanbe
fbfc14dfeb Merge pull request #211 from sadayuki-matsuno/fast_mail_package
change e-mail package from gomail to net/smtp
2016-10-11 12:18:13 +09:00
Kota Kanbe
a8dc886f89 Merge pull request #204 from usiusi360/patch-1
fix typo
2016-10-11 11:39:31 +09:00
Sadayuki Matsuno
cfc9e064b9 change e-mail package from gomail to net/smtp 2016-10-11 10:29:18 +09:00
sadayuki-matsuno
e72fa3362a Merge pull request #207 from sadayuki-matsuno/fix-readme
fix README
2016-10-10 10:47:18 +09:00
Sadayuki Matsuno
26364421e8 fix README 2016-10-10 10:46:31 +09:00
Anton Novojilov
4a07974b54 Fixed bug with parsing update line on CentOS/RHEL 2016-10-07 08:26:36 -04:00
Takayuki Ushida
eaddc7f2ba fix typo 2016-10-06 21:05:57 +09:00
Kota Kanbe
85056aaa00 Update README.md 2016-10-01 17:12:58 +09:00
Kota Kanbe
c077c740fa Merge pull request #163 from hikachan/repo01
Improve setup/docker
2016-10-01 17:12:10 +09:00
Sadayuki Matsuno
c2eab87a3f fix docker 2016-10-01 13:21:00 +09:00
Kota Kanbe
ea582d2d2e Merge pull request #201 from future-architect/fix-defer
Fix defer cache.DB.close
2016-10-01 12:43:41 +09:00
Kota Kanbe
2f89a24100 Fix defer cache.DB.close 2016-10-01 12:39:18 +09:00
Kota Kanbe
73ebb94f67 Merge pull request #195 from future-architect/fix-help-msg-azure
Fix a help message of -report-azure-blob option
2016-09-24 20:36:52 +09:00
Kota Kanbe
95bf387ecc Fix a help message of -report-azure-blob option 2016-09-24 20:35:41 +09:00
Kota Kanbe
f17a8452f9 Merge pull request #191 from sadayuki-matsuno/add-gitignore
fix gitignore
2016-09-23 22:00:31 +09:00
Kota Kanbe
920ffe1f33 Merge pull request #193 from future-architect/fix-error-handling-in-tui
Fix error handling in tui
2016-09-23 22:00:07 +09:00
Kota Kanbe
093bcb7477 Fix error handling in tui 2016-09-23 21:59:27 +09:00
Sadayuki Matsuno
c06b3ec9eb fix gitignore 2016-09-21 16:50:30 +09:00
Kota Kanbe
ac6fe6f9fc Merge pull request #190 from future-architect/add-only-containers
Add only-containers option to scan subcommand #122
2016-09-20 21:34:32 +09:00
Kota Kanbe
2dffdaac42 Add only-containers option to scan subcommand #122 2016-09-20 21:32:58 +09:00
Kota Kanbe
cb445c9504 Merge pull request #189 from future-architect/Fix-not-working-changelog-cache-on-docker
Fix not working changelog cache on Container
2016-09-20 20:35:04 +09:00
Kota Kanbe
e3fc3aa9d1 Fix not working changelog cache on Container 2016-09-20 20:29:02 +09:00
Kota Kanbe
97c3f5d642 Update README 2016-09-20 11:51:30 +09:00
Kota Kanbe
0a52fc9a56 Merge pull request #188 from future-architect/update-glide
Update glide.lock
2016-09-20 10:08:41 +09:00
Kota Kanbe
c831339b0d Update glide.lock 2016-09-20 10:07:00 +09:00
Kota Kanbe
058ccf575f Merge pull request #186 from dladuke/master
Fix path in setup/docker/README
2016-09-16 16:37:54 +09:00
dladuke
92be12bc2f Fix config path 2016-09-15 22:29:44 -07:00
dladuke
1aa2f4b5b1 Fixs paths & typos
Fixs paths & typos
2016-09-15 22:27:53 -07:00
Kota Kanbe
bba9431985 Merge pull request #185 from future-architect/fix-results-dir 2016-09-14 21:45:53 +09:00
Kota Kanbe
3c39f1e737 Fix -results-dir option of scan subcommand 2016-09-14 21:45:03 +09:00
Kota Kanbe
e6f4d07a87 Merge pull request #184 from future-architect/fix-release-detection-on-bsd
Fix release version detection on FreeBSD
2016-09-14 20:20:39 +09:00
Kota Kanbe
e43358a0d2 Fix release version detection on FreeBSD 2016-09-14 20:19:32 +09:00
Kota Kanbe
f0644e8a9d Merge pull request #183 from future-architect/fix-defer-close-cache
Fix defer cahce.DB.close()
2016-09-14 18:25:04 +09:00
Kota Kanbe
11b010b281 Fix defer cahce.DB.close() 2016-09-14 18:16:18 +09:00
Kota Kanbe
c751029127 Merge pull request #182 from future-architect/change-output-file-mode
Fix a mode of files/dir (report, log)
2016-09-14 17:50:25 +09:00
Kota Kanbe
fb70d1b2f0 Fix a mode of files/dir (report, log) 2016-09-14 17:47:12 +09:00
Kota Kanbe
3d68783b7f Merge pull request #181 from future-architect/fix-nilpointer-no-json-dir-tui
Fix a error when no json dirs are found under results #180
2016-09-14 12:12:49 +09:00
Kota Kanbe
0d77853912 Fix a error when no json dirs are found under results #180 2016-09-14 12:09:14 +09:00
Kota Kanbe
ea1b5dd8f7 Merge pull request #179 from future-architect/ssh-external-configtest
ssh-external option of configtest is not working #178
2016-09-14 10:53:15 +09:00
Kota Kanbe
2dcb7d5ce1 ssh-external option of configtest is not working #178 2016-09-14 10:46:50 +09:00
Kota Kanbe
99cab34527 Merge pull request #177 from future-architect/erorr-when-no-scannable-servers
Show error when no scannable servers are detected.
2016-09-14 09:39:39 +09:00
Kota Kanbe
f5eeed0bc2 Show error when no scannable servers are detected. 2016-09-14 09:35:15 +09:00
Kota Kanbe
1b85e56961 Merge pull request #176 from future-architect/add_sudo_check_to_prepare
Add sudo check to prepare subcommand
2016-09-14 08:54:55 +09:00
Kota Kanbe
8a8ac5fd22 Add sudo check to prepare subcommand 2016-09-14 08:52:54 +09:00
Kota Kanbe
00c0354a8e Bump up version 2016-09-12 22:03:49 +09:00
Kota Kanbe
a2a6973ba1 Merge pull request #172 from future-architect/ubuntu_bakusoku
High speed scan on Ubuntu/Debian
2016-09-12 21:45:35 +09:00
Kota Kanbe
dd1d3a05fa High speed scan on Ubuntu/Debian 2016-09-12 21:10:21 +09:00
Kota Kanbe
2afe2d2640 Merge pull request #171 from future-architect/update-glide
Update glide.lock #170
2016-09-08 19:41:07 +09:00
Kota Kanbe
29678f9b59 Update glide.lock #170 2016-09-08 19:37:13 +09:00
Kota Kanbe
77edb251bb Merge pull request #169 from future-architect/cwe-support
Support CWE(Common Weakness Enumeration)
2016-09-07 19:45:05 +09:00
Kota Kanbe
29151fa267 Support CWE(Common Weakness Enumeration) 2016-09-07 19:42:46 +09:00
Kota Kanbe
b3f13790bd Merge pull request #168 from future-architect/fix-detect-platform
Fix detecting a platform on Azure
2016-09-07 13:57:21 +09:00
Kota Kanbe
38857c3356 Fix detecting a platform on Azure 2016-09-07 13:56:37 +09:00
Kota Kanbe
d75990d9fd Merge pull request #167 from future-architect/nosudo-amazon
Enable to scan without sudo on amazon linux
2016-09-06 16:28:25 +09:00
Kota Kanbe
ed063f6534 Enable to scan without sudo on amazon linux 2016-09-06 16:26:51 +09:00
Kota Kanbe
c8a9bdc517 Merge pull request #152 from sadayuki-matsuno/delete_sqlite
delete sqlite3
2016-09-06 13:19:07 +09:00
Sadayuki Matsuno
595729cdf8 delete sqlite3 2016-09-06 12:25:47 +09:00
Kota Kanbe
6119f79748 Merge pull request #166 from future-architect/yum-parse-err
Fix parse Error for yum check-update #165
2016-09-06 10:59:46 +09:00
Kota Kanbe
d4fb46c9ba Fix parse Error for yum check-update #165 2016-09-06 10:57:11 +09:00
Kota Kanbe
c41301afca Merge pull request #164 from future-architect/Change_docker_scripts_for_high_speed_jvn_fetch
Change scripts for data fetching from jvn
2016-09-05 10:34:07 +09:00
Kota Kanbe
50fd80830e Change scripts for datafetch from jvn under setup/docker/dockerfile/scripts
see https://github.com/kotakanbe/go-cve-dictionary/pull/21
2016-09-05 10:28:52 +09:00
Kota Kanbe
1c203b4272 Merge pull request #162 from tjinjin/fix_vulsrepo_setup
Fix: setup vulsrepo
2016-08-31 00:43:16 +09:00
tjinjin
c545e9045d Fix: setup vulsrepo 2016-08-31 00:31:35 +09:00
Kota Kanbe
2721dc0647 Merge pull request #160 from usiusi360/Fix-docker-vulsrepo-install
Fix-docker-vulsrepo-install
2016-08-30 14:16:39 +09:00
Kota Kanbe
51d13f4234 Merge pull request #161 from future-architect/remove-deprecated-options
Remove deprecated options -use-unattended-upgrades,-use-yum-plugin-security
2016-08-30 12:40:39 +09:00
Kota Kanbe
a60a5d6eab Remove deprecated options -use-unattended-upgrades,-use-yum-plugin-security 2016-08-30 12:37:03 +09:00
Kota Kanbe
5959235425 Merge pull request #158 from itchyny/regexp-must-compile
Reduce regular expression compilation
2016-08-29 18:00:13 +09:00
Takayuki Ushida
d8e6d4e5fc Fix-docker-vulsrepo-install 2016-08-27 21:56:09 +09:00
itchyny
7dfc9815b3 Reduce regexp compilation
- use regexp.MustCompile instead of regexp.Compile
- use strings.HasPrefix instead of regular expression when it is enough
2016-08-26 20:39:31 +09:00
Kota Kanbe
0c53b187a4 Merge pull request #159 from tjinjin/fix_vulsrepo_path
Fix bug: Vuls on Docker
2016-08-26 11:50:37 +09:00
tanaka masato
42dadfed8f Fix VulRepo path 2016-08-26 11:18:49 +09:00
Kota Kanbe
a46c603c77 Update README.ja.md 2016-08-24 16:00:22 +09:00
Kota Kanbe
ad0020d9a6 Update README.md 2016-08-24 15:46:26 +09:00
Kota Kanbe
a224f0bfd4 Merge pull request #156 from future-architect/add-testcase-153
Add testcases for #153
2016-08-23 19:31:14 +09:00
Kota Kanbe
d8dc3650d3 Add testcases for #153 2016-08-23 19:26:34 +09:00
Kota Kanbe
30f7527f10 Merge pull request #155 from usiusi360/Fix-CVE-ID-is-truncated-to-4-digits
Fix CVE-ID is truncated to 4 digits
2016-08-23 15:58:50 +09:00
Takayuki Ushida
b1f5bdd8b2 Fix CVE-ID is truncated to 4 digits 2016-08-20 21:23:31 +09:00
Kota Kanbe
c8e7c8b9fa Update README.ja.md 2016-08-18 17:39:46 +09:00
Kota Kanbe
30bf3223f8 Update README.md 2016-08-18 17:39:13 +09:00
Kota Kanbe
886710ec30 Update README.md 2016-08-18 17:19:03 +09:00
Kota Kanbe
510dc8d828 Update README.ja.md 2016-08-18 17:17:26 +09:00
Kota Kanbe
5ff7b2aab4 Merge pull request #151 from future-architect/enable-to-scan-on-centos-non-root
Fix yum update --changelog stalled when non-root ssh user on CentOS #150
2016-08-18 16:25:00 +09:00
kota kanbe
1e33536205 Fix yum update --changelog stalled when non-root ssh user on CentOS #150 2016-08-18 16:20:01 +09:00
85 changed files with 10116 additions and 4343 deletions

36
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,36 @@
# Environment
## Vuls
Hash : ____
To check the commit hash of HEAD
$ vuls -v
or
$ cd $GOPATH/src/github.com/future-architect/vuls
$ git rev-parse --short HEAD
## OS
- Target Server: Write here
- Vuls Server: Write here
## Go
- Go version: here
# Current Output
Please re-run the command using ```-debug``` and provide the output below.
# Addition Details
Can you also please fill in each of the remaining sections.
## Expected Behavior
## Actual Behavior
## Steps to reproduce the behaviour

24
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,24 @@
## What did you implement:
Closes #XXXXX
## How did you implement it:
## How can we verify it:
## Todos:
You don't have to satisfy all of the following.
- [ ] Write tests
- [ ] Write documentation
- [ ] Check that there aren't other open pull requests for the same issue/feature
- [ ] Format your source code by `make fmt`
- [ ] Pass the test by `make test`
- [ ] Provide verification config / commands
- [ ] Enable "Allow edits from maintainers" for this PR
- [ ] Update the messages below
***Is this ready for review?:*** NO
***Is it a breaking change?:*** NO

5
.gitignore vendored
View File

@@ -2,7 +2,9 @@ vuls
.vscode
*.txt
*.json
*.sqlite3
*.sqlite3*
*.db
tags
.gitmodules
coverage.out
issues/
@@ -10,3 +12,4 @@ vendor/
log/
results/
*config.toml
!setup/docker/*

6
.travis.yml Normal file
View File

@@ -0,0 +1,6 @@
language: go
go:
- 1.7
- 1.8

View File

@@ -1,5 +1,273 @@
# Change Log
## [v0.3.0](https://github.com/future-architect/vuls/tree/v0.3.0) (2017-03-24)
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.2.0...v0.3.0)
**Implemented enhancements:**
- Changelog parsing fails when package maintainers aren't consistent regarding versions [\#327](https://github.com/future-architect/vuls/issues/327)
- Docker scan doesn't report image name [\#325](https://github.com/future-architect/vuls/issues/325)
- vuls report -to-email only one E-Mail [\#295](https://github.com/future-architect/vuls/issues/295)
- Support RHEL5 [\#286](https://github.com/future-architect/vuls/issues/286)
- Continue scanning even when some hosts have tech issues? [\#264](https://github.com/future-architect/vuls/issues/264)
- Normalization of JSON output [\#259](https://github.com/future-architect/vuls/issues/259)
- Add report subcommand, change scan subcommand options [\#239](https://github.com/future-architect/vuls/issues/239)
- scan localhost? [\#210](https://github.com/future-architect/vuls/issues/210)
- Can Vuls show details about updateable packages [\#341](https://github.com/future-architect/vuls/issues/341)
- Scan all containers except [\#285](https://github.com/future-architect/vuls/issues/285)
- Notify the difference from the previous scan result [\#255](https://github.com/future-architect/vuls/issues/255)
- EC2RoleCreds support? [\#250](https://github.com/future-architect/vuls/issues/250)
- Output confidence score of detection accuracy and detection method to JSON or Reporting [\#350](https://github.com/future-architect/vuls/pull/350) ([kotakanbe](https://github.com/kotakanbe))
- Avoid null slice being null in JSON [\#345](https://github.com/future-architect/vuls/pull/345) ([kotakanbe](https://github.com/kotakanbe))
- Add -format-one-email option [\#331](https://github.com/future-architect/vuls/pull/331) ([knqyf263](https://github.com/knqyf263))
- Support Raspbian [\#330](https://github.com/future-architect/vuls/pull/330) ([knqyf263](https://github.com/knqyf263))
- Add leniancy to the version matching for debian to account for versio… [\#328](https://github.com/future-architect/vuls/pull/328) ([jsulinski](https://github.com/jsulinski))
- Add image information for docker containers [\#326](https://github.com/future-architect/vuls/pull/326) ([jsulinski](https://github.com/jsulinski))
- Continue scanning even when some hosts have tech issues [\#309](https://github.com/future-architect/vuls/pull/309) ([kotakanbe](https://github.com/kotakanbe))
- Add -log-dir option [\#301](https://github.com/future-architect/vuls/pull/301) ([knqyf263](https://github.com/knqyf263))
- Use --assumeno option [\#300](https://github.com/future-architect/vuls/pull/300) ([knqyf263](https://github.com/knqyf263))
- Add local scan mode\(Scan without SSH when target server is localhost\) [\#291](https://github.com/future-architect/vuls/pull/291) ([kotakanbe](https://github.com/kotakanbe))
- Support RHEL5 [\#289](https://github.com/future-architect/vuls/pull/289) ([kotakanbe](https://github.com/kotakanbe))
- Add LXD support [\#288](https://github.com/future-architect/vuls/pull/288) ([jiazio](https://github.com/jiazio))
- Add timeout option to configtest [\#400](https://github.com/future-architect/vuls/pull/400) ([kotakanbe](https://github.com/kotakanbe))
- Notify the difference from the previous scan result [\#392](https://github.com/future-architect/vuls/pull/392) ([knqyf263](https://github.com/knqyf263))
- Add Oracle Linux support [\#386](https://github.com/future-architect/vuls/pull/386) ([Djelibeybi](https://github.com/Djelibeybi))
- Change container scan format in config.toml [\#381](https://github.com/future-architect/vuls/pull/381) ([kotakanbe](https://github.com/kotakanbe))
- Obsolete CentOS5 support [\#378](https://github.com/future-architect/vuls/pull/378) ([kotakanbe](https://github.com/kotakanbe))
- Deprecate prepare subcommand to minimize the root authority defined by /etc/sudoers [\#375](https://github.com/future-architect/vuls/pull/375) ([kotakanbe](https://github.com/kotakanbe))
- Support IAM role for report to S3. [\#370](https://github.com/future-architect/vuls/pull/370) ([ohsawa0515](https://github.com/ohsawa0515))
- Add .travis.yml [\#363](https://github.com/future-architect/vuls/pull/363) ([knqyf263](https://github.com/knqyf263))
- Output changelog in report, TUI and JSON for Ubuntu/Debian/CentOS [\#356](https://github.com/future-architect/vuls/pull/356) ([kotakanbe](https://github.com/kotakanbe))
**Fixed bugs:**
- Debian scans failing in docker [\#323](https://github.com/future-architect/vuls/issues/323)
- Local CVE DB is still checked, even if a CVE Dictionary URL is defined [\#316](https://github.com/future-architect/vuls/issues/316)
- vuls needs gmake. [\#313](https://github.com/future-architect/vuls/issues/313)
- patch request for FreeBSD [\#312](https://github.com/future-architect/vuls/issues/312)
- Report: failed to read from json \(Docker\) [\#294](https://github.com/future-architect/vuls/issues/294)
- -report-mail option does not output required mail header [\#282](https://github.com/future-architect/vuls/issues/282)
- PackInfo not found error when vuls scan. [\#281](https://github.com/future-architect/vuls/issues/281)
- Normalize character set [\#279](https://github.com/future-architect/vuls/issues/279)
- The number of Updatable Packages is different from the number of yum check-update [\#373](https://github.com/future-architect/vuls/issues/373)
- sudo is needed when exec yum check-update on RHEL7 [\#371](https://github.com/future-architect/vuls/issues/371)
- `123-3ubuntu4` should be marked as ChangelogLenientMatch [\#362](https://github.com/future-architect/vuls/issues/362)
- CentOS multi package invalid result [\#360](https://github.com/future-architect/vuls/issues/360)
- Parse error after check-update. \(Unknown format\) [\#359](https://github.com/future-architect/vuls/issues/359)
- Fix candidate to confidence. [\#354](https://github.com/future-architect/vuls/pull/354) ([kotakanbe](https://github.com/kotakanbe))
- Bug fix: not send e-mail to cc address [\#346](https://github.com/future-architect/vuls/pull/346) ([knqyf263](https://github.com/knqyf263))
- Change the command used for os detection from uname to freebsd-version [\#340](https://github.com/future-architect/vuls/pull/340) ([kotakanbe](https://github.com/kotakanbe))
- Fix error handling of detectOS [\#337](https://github.com/future-architect/vuls/pull/337) ([kotakanbe](https://github.com/kotakanbe))
- Fix infinite retry at size overrun error in Slack report [\#329](https://github.com/future-architect/vuls/pull/329) ([kotakanbe](https://github.com/kotakanbe))
- aptitude changelog defaults to using more, which is not interactive a… [\#324](https://github.com/future-architect/vuls/pull/324) ([jsulinski](https://github.com/jsulinski))
- Do not use sudo when echo [\#322](https://github.com/future-architect/vuls/pull/322) ([knqyf263](https://github.com/knqyf263))
- Reduce privilege requirements for commands that don't need sudo on Ubuntu/Debian [\#319](https://github.com/future-architect/vuls/pull/319) ([jsulinski](https://github.com/jsulinski))
- Don't check for a CVE DB when CVE Dictionary URL is defined [\#317](https://github.com/future-architect/vuls/pull/317) ([jsulinski](https://github.com/jsulinski))
- Fix typo contianer -\> container [\#314](https://github.com/future-architect/vuls/pull/314) ([justyns](https://github.com/justyns))
- Fix the changelog cache logic for ubuntu/debian [\#305](https://github.com/future-architect/vuls/pull/305) ([kotakanbe](https://github.com/kotakanbe))
- Fix yum updateinfo options [\#304](https://github.com/future-architect/vuls/pull/304) ([kotakanbe](https://github.com/kotakanbe))
- Update glide.lock to fix create-log-dir error. [\#303](https://github.com/future-architect/vuls/pull/303) ([kotakanbe](https://github.com/kotakanbe))
- Fix a bug in logging \(file output\) at scan command [\#302](https://github.com/future-architect/vuls/pull/302) ([kotakanbe](https://github.com/kotakanbe))
- Add -pipe flag \#294 [\#299](https://github.com/future-architect/vuls/pull/299) ([kotakanbe](https://github.com/kotakanbe))
- Fix RHEL5 scan stopped halfway [\#293](https://github.com/future-architect/vuls/pull/293) ([kotakanbe](https://github.com/kotakanbe))
- Fix amazon linux scan stopped halfway [\#292](https://github.com/future-architect/vuls/pull/292) ([kotakanbe](https://github.com/kotakanbe))
- Fix nil-ponter in TUI [\#388](https://github.com/future-architect/vuls/pull/388) ([kotakanbe](https://github.com/kotakanbe))
- Fix Bug of Mysql Backend [\#384](https://github.com/future-architect/vuls/pull/384) ([kotakanbe](https://github.com/kotakanbe))
- Fix scan confidence on Ubuntu/Debian/Raspbian \#362 [\#379](https://github.com/future-architect/vuls/pull/379) ([kotakanbe](https://github.com/kotakanbe))
- Fix updatalbe packages count \#373 [\#374](https://github.com/future-architect/vuls/pull/374) ([kotakanbe](https://github.com/kotakanbe))
- sudo yum check-update on RHEL [\#372](https://github.com/future-architect/vuls/pull/372) ([kotakanbe](https://github.com/kotakanbe))
- Change ssh option from -t to -tt [\#369](https://github.com/future-architect/vuls/pull/369) ([knqyf263](https://github.com/knqyf263))
- Increase the width of RequestPty [\#364](https://github.com/future-architect/vuls/pull/364) ([knqyf263](https://github.com/knqyf263))
**Closed issues:**
- vuls configtest --debugがsudoのチェックで止まってしまう [\#395](https://github.com/future-architect/vuls/issues/395)
- Add support for Oracle Linux [\#385](https://github.com/future-architect/vuls/issues/385)
- error on install - Ubuntu 16.04 [\#376](https://github.com/future-architect/vuls/issues/376)
- Unknown OS Type [\#335](https://github.com/future-architect/vuls/issues/335)
- mac os 10.12.3 make install error [\#334](https://github.com/future-architect/vuls/issues/334)
- assumeYes doesn't work because there is no else condition [\#320](https://github.com/future-architect/vuls/issues/320)
- Debian scan uses sudo where unnecessary [\#318](https://github.com/future-architect/vuls/issues/318)
- Add FreeBSD 11 to supported OS on documents. [\#311](https://github.com/future-architect/vuls/issues/311)
- docker fetchnvd failing [\#274](https://github.com/future-architect/vuls/issues/274)
- Latest version of labstack echo breaks installation [\#268](https://github.com/future-architect/vuls/issues/268)
- fetchnvd Fails using example loop [\#267](https://github.com/future-architect/vuls/issues/267)
**Merged pull requests:**
- fix typo in README.ja.md [\#394](https://github.com/future-architect/vuls/pull/394) ([lv7777](https://github.com/lv7777))
- Update Tutorial in README [\#387](https://github.com/future-architect/vuls/pull/387) ([kotakanbe](https://github.com/kotakanbe))
- Fix README [\#383](https://github.com/future-architect/vuls/pull/383) ([usiusi360](https://github.com/usiusi360))
- s/dictinary/dictionary typo [\#382](https://github.com/future-architect/vuls/pull/382) ([beuno](https://github.com/beuno))
- Fix Japanese typo [\#377](https://github.com/future-architect/vuls/pull/377) ([IMAI-Yuji](https://github.com/IMAI-Yuji))
- Improve kanji character [\#351](https://github.com/future-architect/vuls/pull/351) ([hasegawa-tomoki](https://github.com/hasegawa-tomoki))
- Add PULL\_REQUEST\_TEMPLATE.md [\#348](https://github.com/future-architect/vuls/pull/348) ([knqyf263](https://github.com/knqyf263))
- Update README [\#347](https://github.com/future-architect/vuls/pull/347) ([knqyf263](https://github.com/knqyf263))
- Fix test case [\#344](https://github.com/future-architect/vuls/pull/344) ([kotakanbe](https://github.com/kotakanbe))
- Fix typo [\#343](https://github.com/future-architect/vuls/pull/343) ([knqyf263](https://github.com/knqyf263))
- Rename Makefile to GNUmakefile \#313 [\#339](https://github.com/future-architect/vuls/pull/339) ([kotakanbe](https://github.com/kotakanbe))
- Update README [\#338](https://github.com/future-architect/vuls/pull/338) ([kotakanbe](https://github.com/kotakanbe))
- add error handling [\#332](https://github.com/future-architect/vuls/pull/332) ([kazuminn](https://github.com/kazuminn))
- Update readme [\#308](https://github.com/future-architect/vuls/pull/308) ([lapthorn](https://github.com/lapthorn))
- Update glide.lock to fix import error [\#306](https://github.com/future-architect/vuls/pull/306) ([knqyf263](https://github.com/knqyf263))
- Check whether echo is executable with nopasswd [\#298](https://github.com/future-architect/vuls/pull/298) ([knqyf263](https://github.com/knqyf263))
- Update docker README [\#297](https://github.com/future-architect/vuls/pull/297) ([knqyf263](https://github.com/knqyf263))
- update readme [\#296](https://github.com/future-architect/vuls/pull/296) ([galigalikun](https://github.com/galigalikun))
- remove unused import line. [\#358](https://github.com/future-architect/vuls/pull/358) ([ymomoi](https://github.com/ymomoi))
## [v0.2.0](https://github.com/future-architect/vuls/tree/v0.2.0) (2017-01-10)
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.7...v0.2.0)
**Implemented enhancements:**
- Add report subcommand, change scan options. \#239 [\#270](https://github.com/future-architect/vuls/pull/270) ([kotakanbe](https://github.com/kotakanbe))
- Add --assume-yes to prepare \#260 [\#266](https://github.com/future-architect/vuls/pull/266) ([Code0x58](https://github.com/Code0x58))
- Use RFC3339 timestamps in the results [\#265](https://github.com/future-architect/vuls/pull/265) ([Code0x58](https://github.com/Code0x58))
**Fixed bugs:**
- vuls prepare failed to centos7 [\#275](https://github.com/future-architect/vuls/issues/275)
- Failed to scan on RHEL5 [\#94](https://github.com/future-architect/vuls/issues/94)
- Fix container os detection [\#287](https://github.com/future-architect/vuls/pull/287) ([jiazio](https://github.com/jiazio))
- Add date header to report mail. [\#283](https://github.com/future-architect/vuls/pull/283) ([ymomoi](https://github.com/ymomoi))
- Add Content-Type header to report/mail.go . [\#280](https://github.com/future-architect/vuls/pull/280) ([hogehogehugahuga](https://github.com/hogehogehugahuga))
- Keep output of "vuls scan -report-\*" to be same every times [\#272](https://github.com/future-architect/vuls/pull/272) ([yoheimuta](https://github.com/yoheimuta))
- Fix JSON-dir regex pattern \#265 [\#271](https://github.com/future-architect/vuls/pull/271) ([kotakanbe](https://github.com/kotakanbe))
- Stop quietly ignoring `--ssh-external` on Windows [\#263](https://github.com/future-architect/vuls/pull/263) ([Code0x58](https://github.com/Code0x58))
- Fix non-interactive `apt-get install` \#251 [\#253](https://github.com/future-architect/vuls/pull/253) ([Code0x58](https://github.com/Code0x58))
**Closed issues:**
- gocui.NewGui now takes a parameter [\#261](https://github.com/future-architect/vuls/issues/261)
- Add a `--yes` flag to bypass interactive prompt for `vuls prepare` [\#260](https://github.com/future-architect/vuls/issues/260)
- `vuls prepare` doesn't work on Debian host due to apt-get confirmation prompt [\#251](https://github.com/future-architect/vuls/issues/251)
**Merged pull requests:**
- Fix gocui.NewGui after signature change \#261 [\#262](https://github.com/future-architect/vuls/pull/262) ([Code0x58](https://github.com/Code0x58))
- Replace inconsistent tabs with spaces [\#254](https://github.com/future-architect/vuls/pull/254) ([Code0x58](https://github.com/Code0x58))
- Fix README [\#249](https://github.com/future-architect/vuls/pull/249) ([usiusi360](https://github.com/usiusi360))
## [v0.1.7](https://github.com/future-architect/vuls/tree/v0.1.7) (2016-11-08)
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.6...v0.1.7)
**Implemented enhancements:**
- Enable to scan only docker container, without docker host [\#122](https://github.com/future-architect/vuls/issues/122)
- Add -skip-broken option \[CentOS only\] \#245 [\#248](https://github.com/future-architect/vuls/pull/248) ([kotakanbe](https://github.com/kotakanbe))
- Display unknown CVEs to TUI [\#244](https://github.com/future-architect/vuls/pull/244) ([kotakanbe](https://github.com/kotakanbe))
- Add the XML output [\#240](https://github.com/future-architect/vuls/pull/240) ([gleentea](https://github.com/gleentea))
- add '-ssh-external' option to prepare subcommand [\#234](https://github.com/future-architect/vuls/pull/234) ([mykstmhr](https://github.com/mykstmhr))
- Integrate OWASP Dependency Check [\#232](https://github.com/future-architect/vuls/pull/232) ([kotakanbe](https://github.com/kotakanbe))
- Add support for reading CVE data from MySQL. [\#225](https://github.com/future-architect/vuls/pull/225) ([oswell](https://github.com/oswell))
- Remove base docker image, -v shows commit hash [\#223](https://github.com/future-architect/vuls/pull/223) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
- Support ignore CveIDs in config [\#222](https://github.com/future-architect/vuls/pull/222) ([kotakanbe](https://github.com/kotakanbe))
- Confirm before installing dependencies on prepare [\#219](https://github.com/future-architect/vuls/pull/219) ([kotakanbe](https://github.com/kotakanbe))
- Remove all.json [\#218](https://github.com/future-architect/vuls/pull/218) ([kotakanbe](https://github.com/kotakanbe))
- Add GitHub issue template [\#217](https://github.com/future-architect/vuls/pull/217) ([kotakanbe](https://github.com/kotakanbe))
- Improve makefile, -version shows git hash, fix README [\#216](https://github.com/future-architect/vuls/pull/216) ([kotakanbe](https://github.com/kotakanbe))
- change e-mail package from gomail to net/smtp [\#211](https://github.com/future-architect/vuls/pull/211) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
- Add only-containers option to scan subcommand \#122 [\#190](https://github.com/future-architect/vuls/pull/190) ([kotakanbe](https://github.com/kotakanbe))
- Fix -results-dir option of scan subcommand [\#185](https://github.com/future-architect/vuls/pull/185) ([kotakanbe](https://github.com/kotakanbe))
- Show error when no scannable servers are detected. [\#177](https://github.com/future-architect/vuls/pull/177) ([kotakanbe](https://github.com/kotakanbe))
- Add sudo check to prepare subcommand [\#176](https://github.com/future-architect/vuls/pull/176) ([kotakanbe](https://github.com/kotakanbe))
- Supports yum --enablerepo option \(supports only base,updates for now\) [\#147](https://github.com/future-architect/vuls/pull/147) ([kotakanbe](https://github.com/kotakanbe))
**Fixed bugs:**
- Debian 8.6 \(jessie\) scan does not show vulnerable packages [\#235](https://github.com/future-architect/vuls/issues/235)
- panic: runtime error: index out of range - ubuntu 16.04 + vuls history [\#180](https://github.com/future-architect/vuls/issues/180)
- Moved golang.org/x/net/context to context [\#243](https://github.com/future-architect/vuls/pull/243) ([yoheimuta](https://github.com/yoheimuta))
- Fix changelog cache bug on Ubuntu and Debian \#235 [\#238](https://github.com/future-architect/vuls/pull/238) ([kotakanbe](https://github.com/kotakanbe))
- add '-ssh-external' option to prepare subcommand [\#234](https://github.com/future-architect/vuls/pull/234) ([mykstmhr](https://github.com/mykstmhr))
- Fixed error for the latest version of gocui [\#231](https://github.com/future-architect/vuls/pull/231) ([ymd38](https://github.com/ymd38))
- Handle the refactored gocui SetCurrentView method. [\#229](https://github.com/future-architect/vuls/pull/229) ([oswell](https://github.com/oswell))
- Fix locale env var LANG to LANGUAGE [\#215](https://github.com/future-architect/vuls/pull/215) ([kotakanbe](https://github.com/kotakanbe))
- Fixed bug with parsing update line on CentOS/RHEL [\#206](https://github.com/future-architect/vuls/pull/206) ([andyone](https://github.com/andyone))
- Fix defer cache.DB.close [\#201](https://github.com/future-architect/vuls/pull/201) ([kotakanbe](https://github.com/kotakanbe))
- Fix a help message of -report-azure-blob option [\#195](https://github.com/future-architect/vuls/pull/195) ([kotakanbe](https://github.com/kotakanbe))
- Fix error handling in tui [\#193](https://github.com/future-architect/vuls/pull/193) ([kotakanbe](https://github.com/kotakanbe))
- Fix not working changelog cache on Container [\#189](https://github.com/future-architect/vuls/pull/189) ([kotakanbe](https://github.com/kotakanbe))
- Fix release version detection on FreeBSD [\#184](https://github.com/future-architect/vuls/pull/184) ([kotakanbe](https://github.com/kotakanbe))
- Fix defer cahce.DB.close\(\) [\#183](https://github.com/future-architect/vuls/pull/183) ([kotakanbe](https://github.com/kotakanbe))
- Fix a mode of files/dir \(report, log\) [\#182](https://github.com/future-architect/vuls/pull/182) ([kotakanbe](https://github.com/kotakanbe))
- Fix a error when no json dirs are found under results \#180 [\#181](https://github.com/future-architect/vuls/pull/181) ([kotakanbe](https://github.com/kotakanbe))
- ssh-external option of configtest is not working \#178 [\#179](https://github.com/future-architect/vuls/pull/179) ([kotakanbe](https://github.com/kotakanbe))
**Closed issues:**
- --enable-repos of yum option [\#246](https://github.com/future-architect/vuls/issues/246)
- --skip-broken at yum option [\#245](https://github.com/future-architect/vuls/issues/245)
- Recent changes to gobui cause build failures [\#228](https://github.com/future-architect/vuls/issues/228)
- https://hub.docker.com/r/vuls/go-cve-dictionary/ is empty [\#208](https://github.com/future-architect/vuls/issues/208)
- Not able to install gomail fails [\#202](https://github.com/future-architect/vuls/issues/202)
- No results file created - vuls tui failed [\#199](https://github.com/future-architect/vuls/issues/199)
- Wrong file permissions for results/\*.json in official Docker container [\#197](https://github.com/future-architect/vuls/issues/197)
- Failed: Unknown OS Type [\#196](https://github.com/future-architect/vuls/issues/196)
- Segmentation fault with configtest [\#192](https://github.com/future-architect/vuls/issues/192)
- Failed to scan. err: No server defined. Check the configuration [\#187](https://github.com/future-architect/vuls/issues/187)
- vuls configtest -ssh-external doesnt work [\#178](https://github.com/future-architect/vuls/issues/178)
- apt-get update: time out [\#175](https://github.com/future-architect/vuls/issues/175)
- scanning on Centos6, but vuls recognizes debian. [\#174](https://github.com/future-architect/vuls/issues/174)
- Fix READMEja \#164 [\#173](https://github.com/future-architect/vuls/issues/173)
**Merged pull requests:**
- Update README \#225 [\#242](https://github.com/future-architect/vuls/pull/242) ([kotakanbe](https://github.com/kotakanbe))
- fix readme [\#241](https://github.com/future-architect/vuls/pull/241) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
- Fix README \#234 [\#237](https://github.com/future-architect/vuls/pull/237) ([kotakanbe](https://github.com/kotakanbe))
- Update glide files [\#236](https://github.com/future-architect/vuls/pull/236) ([kotakanbe](https://github.com/kotakanbe))
- fix README [\#226](https://github.com/future-architect/vuls/pull/226) ([usiusi360](https://github.com/usiusi360))
- fix some misspelling. [\#221](https://github.com/future-architect/vuls/pull/221) ([ymomoi](https://github.com/ymomoi))
- fix docker readme [\#214](https://github.com/future-architect/vuls/pull/214) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
- Fix ja document about typo [\#213](https://github.com/future-architect/vuls/pull/213) ([shokohara](https://github.com/shokohara))
- fix readme [\#212](https://github.com/future-architect/vuls/pull/212) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
- fix README [\#207](https://github.com/future-architect/vuls/pull/207) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
- fix typo [\#204](https://github.com/future-architect/vuls/pull/204) ([usiusi360](https://github.com/usiusi360))
- fix gitignore [\#191](https://github.com/future-architect/vuls/pull/191) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
- Update glide.lock [\#188](https://github.com/future-architect/vuls/pull/188) ([kotakanbe](https://github.com/kotakanbe))
- Fix path in setup/docker/README [\#186](https://github.com/future-architect/vuls/pull/186) ([dladuke](https://github.com/dladuke))
- Vuls and vulsrepo are now separated [\#163](https://github.com/future-architect/vuls/pull/163) ([hikachan](https://github.com/hikachan))
## [v0.1.6](https://github.com/future-architect/vuls/tree/v0.1.6) (2016-09-12)
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.5...v0.1.6)
**Implemented enhancements:**
- High speed scan on Ubuntu/Debian [\#172](https://github.com/future-architect/vuls/pull/172) ([kotakanbe](https://github.com/kotakanbe))
- Support CWE\(Common Weakness Enumeration\) [\#169](https://github.com/future-architect/vuls/pull/169) ([kotakanbe](https://github.com/kotakanbe))
- Enable to scan without sudo on amazon linux [\#167](https://github.com/future-architect/vuls/pull/167) ([kotakanbe](https://github.com/kotakanbe))
- Remove deprecated options -use-unattended-upgrades,-use-yum-plugin-security [\#161](https://github.com/future-architect/vuls/pull/161) ([kotakanbe](https://github.com/kotakanbe))
- delete sqlite3 [\#152](https://github.com/future-architect/vuls/pull/152) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
**Fixed bugs:**
- Failed to setup vuls docker [\#170](https://github.com/future-architect/vuls/issues/170)
- yum check-update error occurred when no reboot after kernel updating [\#165](https://github.com/future-architect/vuls/issues/165)
- error thrown from 'docker build .' [\#157](https://github.com/future-architect/vuls/issues/157)
- CVE-ID is truncated to 4 digits [\#153](https://github.com/future-architect/vuls/issues/153)
- 'yum update --changelog' stalled in 'vuls scan'. if ssh user is not 'root'. [\#150](https://github.com/future-architect/vuls/issues/150)
- Panic on packet scan [\#131](https://github.com/future-architect/vuls/issues/131)
- Update glide.lock \#170 [\#171](https://github.com/future-architect/vuls/pull/171) ([kotakanbe](https://github.com/kotakanbe))
- Fix detecting a platform on Azure [\#168](https://github.com/future-architect/vuls/pull/168) ([kotakanbe](https://github.com/kotakanbe))
- Fix parse error for yum check-update \#165 [\#166](https://github.com/future-architect/vuls/pull/166) ([kotakanbe](https://github.com/kotakanbe))
- Fix bug: Vuls on Docker [\#159](https://github.com/future-architect/vuls/pull/159) ([tjinjin](https://github.com/tjinjin))
- Fix CVE-ID is truncated to 4 digits [\#155](https://github.com/future-architect/vuls/pull/155) ([usiusi360](https://github.com/usiusi360))
- Fix yum update --changelog stalled when non-root ssh user on CentOS \#150 [\#151](https://github.com/future-architect/vuls/pull/151) ([kotakanbe](https://github.com/kotakanbe))
**Closed issues:**
- Support su for root privilege escalation [\#44](https://github.com/future-architect/vuls/issues/44)
- Support FreeBSD [\#34](https://github.com/future-architect/vuls/issues/34)
**Merged pull requests:**
- Change scripts for data fetching from jvn [\#164](https://github.com/future-architect/vuls/pull/164) ([kotakanbe](https://github.com/kotakanbe))
- Fix: setup vulsrepo [\#162](https://github.com/future-architect/vuls/pull/162) ([tjinjin](https://github.com/tjinjin))
- Fix-docker-vulsrepo-install [\#160](https://github.com/future-architect/vuls/pull/160) ([usiusi360](https://github.com/usiusi360))
- Reduce regular expression compilation [\#158](https://github.com/future-architect/vuls/pull/158) ([itchyny](https://github.com/itchyny))
- Add testcases for \#153 [\#156](https://github.com/future-architect/vuls/pull/156) ([kotakanbe](https://github.com/kotakanbe))
## [v0.1.5](https://github.com/future-architect/vuls/tree/v0.1.5) (2016-08-16)
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.4...v0.1.5)
@@ -141,7 +409,7 @@
- Maximum 6 nodes available to scan [\#12](https://github.com/future-architect/vuls/issues/12)
- panic: runtime error: index out of range [\#5](https://github.com/future-architect/vuls/issues/5)
- Fix sudo option on RedHat like Linux and change some messages. [\#20](https://github.com/future-architect/vuls/pull/20) ([kotakanbe](https://github.com/kotakanbe))
- Typo fix and updated readme [\#19](https://github.com/future-architect/vuls/pull/19) ([Euan-Kerr](https://github.com/Euan-Kerr))
- Typo fix and updated readme [\#19](https://github.com/future-architect/vuls/pull/19) ([EuanKerr](https://github.com/EuanKerr))
- remove a period at the end of error messages. [\#18](https://github.com/future-architect/vuls/pull/18) ([kotakanbe](https://github.com/kotakanbe))
- fix error while yum updateinfo --security update on rhel@aws [\#17](https://github.com/future-architect/vuls/pull/17) ([kotakanbe](https://github.com/kotakanbe))
- Fixed typos [\#15](https://github.com/future-architect/vuls/pull/15) ([radarhere](https://github.com/radarhere))

View File

@@ -1,4 +1,9 @@
.PHONY: \
glide \
deps \
update \
build \
install \
all \
vendor \
lint \
@@ -11,13 +16,29 @@
clean
SRCS = $(shell git ls-files '*.go')
PKGS = ./. ./db ./config ./models ./report ./cveapi ./scan ./util ./commands
PKGS = ./. ./config ./models ./report ./cveapi ./scan ./util ./commands ./cache
VERSION := $(shell git describe --tags --abbrev=0)
REVISION := $(shell git rev-parse --short HEAD)
LDFLAGS := -X 'main.version=$(VERSION)' \
-X 'main.revision=$(REVISION)'
all: test
all: glide deps build test
glide:
go get github.com/Masterminds/glide
deps: glide
glide install
update: glide
glide update
build: main.go deps
go build -ldflags "$(LDFLAGS)" -o vuls $<
install: main.go deps
go install -ldflags "$(LDFLAGS)"
# vendor:
# @ go get -v github.com/mjibson/party
# party -d external -c -u
lint:
@ go get -v github.com/golang/lint/golint
@@ -36,7 +57,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);)

View File

@@ -40,7 +40,7 @@ Vuls est un outil crée pour palier aux problèmes listés ci-dessus. Voici ses
# Caractéristiques principales
- Recherche de vulnérabilités sur des serveurs Linux
- Supporte Ubuntu, Debian, CentOS, Amazon Linux, RHEL
- Supporte Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Raspbian
- Cloud, auto-hébergement, Docker
- Scan d'intergiciels non inclus dans le gestionnaire de paquets de l'OS
- Scan d'intergiciels, de libraries de language de programmation et framework pour des vulnérabilités
@@ -107,14 +107,14 @@ Vuls requiert l'installation des paquets suivants :
- sqlite
- git
- gcc
- go v1.6
- go v1.7.1 or later
- https://golang.org/doc/install
```bash
$ ssh ec2-user@52.100.100.100 -i ~/.ssh/private.pem
$ sudo yum -y install sqlite git gcc
$ wget https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz
$ sudo tar -C /usr/local -xzf go1.6.linux-amd64.tar.gz
$ wget https://storage.googleapis.com/golang/go1.7.1.linux-amd64.tar.gz
$ sudo tar -C /usr/local -xzf go1.7.1.linux-amd64.tar.gz
$ mkdir $HOME/go
```
Ajoutez les lignes suivantes dans /etc/profile.d/goenv.sh
@@ -201,7 +201,7 @@ Summary Unspecified vulnerability in the Java SE and Java SE Embedded co
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 Claculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0494&vector=(AV:N/AC:L/Au:N/C:C/I:C/A:C)
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

File diff suppressed because it is too large Load Diff

1235
README.md

File diff suppressed because it is too large Load Diff

191
cache/bolt.go vendored Normal file
View File

@@ -0,0 +1,191 @@
/* 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 cache
import (
"encoding/json"
"fmt"
"time"
"github.com/Sirupsen/logrus"
"github.com/boltdb/bolt"
"github.com/future-architect/vuls/util"
)
// Bolt holds a pointer of bolt.DB
// boltdb is used to store a cache of Changelogs of Ubuntu/Debian
type Bolt struct {
Path string
Log *logrus.Entry
db *bolt.DB
}
// SetupBolt opens a boltdb and creates a meta bucket if not exists.
func SetupBolt(path string, l *logrus.Entry) error {
l.Infof("Open boltDB: %s", path)
db, err := bolt.Open(path, 0600, nil)
if err != nil {
return err
}
b := Bolt{
Path: path,
Log: l,
db: db,
}
if err = b.createBucketIfNotExists(metabucket); err != nil {
return err
}
DB = b
return nil
}
// Close a db.
func (b Bolt) Close() error {
if b.db == nil {
return nil
}
return b.db.Close()
}
// CreateBucketIfNotExists creates a buket that is specified by arg.
func (b *Bolt) createBucketIfNotExists(name string) error {
return b.db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(name))
if err != nil {
return fmt.Errorf("Failed to create bucket: %s", err)
}
return nil
})
}
// GetMeta gets a Meta Information os the servername to boltdb.
func (b Bolt) GetMeta(serverName string) (meta Meta, found bool, err error) {
err = b.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket([]byte(metabucket))
v := bkt.Get([]byte(serverName))
if len(v) == 0 {
found = false
return nil
}
if e := json.Unmarshal(v, &meta); e != nil {
return e
}
found = true
return nil
})
return
}
// RefreshMeta gets a Meta Information os the servername to boltdb.
func (b Bolt) RefreshMeta(meta Meta) error {
meta.CreatedAt = time.Now()
jsonBytes, err := json.Marshal(meta)
if err != nil {
return fmt.Errorf("Failed to marshal to JSON: %s", err)
}
return b.db.Update(func(tx *bolt.Tx) error {
bkt := tx.Bucket([]byte(metabucket))
if err := bkt.Put([]byte(meta.Name), jsonBytes); err != nil {
return err
}
b.Log.Debugf("Refreshed Meta: %s", meta.Name)
return nil
})
}
// EnsureBuckets puts a Meta information and create a buket that holds changelogs.
func (b Bolt) EnsureBuckets(meta Meta) error {
jsonBytes, err := json.Marshal(meta)
if err != nil {
return fmt.Errorf("Failed to marshal to JSON: %s", err)
}
return b.db.Update(func(tx *bolt.Tx) error {
b.Log.Debugf("Put to meta: %s", meta.Name)
bkt := tx.Bucket([]byte(metabucket))
if err := bkt.Put([]byte(meta.Name), jsonBytes); err != nil {
return err
}
// re-create a bucket (bucket name: servername)
bkt = tx.Bucket([]byte(meta.Name))
if bkt != nil {
b.Log.Debugf("Delete bucket: %s", meta.Name)
if err := tx.DeleteBucket([]byte(meta.Name)); err != nil {
return err
}
b.Log.Debugf("Bucket deleted: %s", meta.Name)
}
b.Log.Debugf("Create bucket: %s", meta.Name)
if _, err := tx.CreateBucket([]byte(meta.Name)); err != nil {
return err
}
b.Log.Debugf("Bucket created: %s", meta.Name)
return nil
})
}
// PrettyPrint is for debug
func (b Bolt) PrettyPrint(meta Meta) error {
return b.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket([]byte(metabucket))
v := bkt.Get([]byte(meta.Name))
b.Log.Debugf("Meta: key:%s, value:%s", meta.Name, v)
bkt = tx.Bucket([]byte(meta.Name))
c := bkt.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
b.Log.Debugf("key:%s, len: %d, %s...",
k, len(v), util.Truncate(string(v), 30))
}
return nil
})
}
// GetChangelog get the changelgo of specified packName from the Bucket
func (b Bolt) GetChangelog(servername, packName string) (changelog string, err error) {
err = b.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket([]byte(servername))
if bkt == nil {
return fmt.Errorf("Faild to get Bucket: %s", servername)
}
v := bkt.Get([]byte(packName))
if v == nil {
changelog = ""
return nil
}
changelog = string(v)
return nil
})
return
}
// PutChangelog put the changelgo of specified packName into the Bucket
func (b Bolt) PutChangelog(servername, packName, changelog string) error {
return b.db.Update(func(tx *bolt.Tx) error {
bkt := tx.Bucket([]byte(servername))
if bkt == nil {
return fmt.Errorf("Faild to get Bucket: %s", servername)
}
if err := bkt.Put([]byte(packName), []byte(changelog)); err != nil {
return err
}
return nil
})
}

137
cache/bolt_test.go vendored Normal file
View File

@@ -0,0 +1,137 @@
/* 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 cache
import (
"os"
"reflect"
"testing"
"github.com/Sirupsen/logrus"
"github.com/boltdb/bolt"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
)
const path = "/tmp/vuls-test-cache-11111111.db"
const servername = "server1"
var meta = Meta{
Name: servername,
Distro: config.Distro{
Family: "ubuntu",
Release: "16.04",
},
Packs: []models.PackageInfo{
{
Name: "apt",
Version: "1",
},
},
}
func TestSetupBolt(t *testing.T) {
log := logrus.NewEntry(&logrus.Logger{})
err := SetupBolt(path, log)
if err != nil {
t.Errorf("Failed to setup bolt: %s", err)
}
defer os.Remove(path)
if err := DB.Close(); err != nil {
t.Errorf("Failed to close bolt: %s", err)
}
// check if meta bucket exists
db, err := bolt.Open(path, 0600, nil)
if err != nil {
t.Errorf("Failed to open bolt: %s", err)
}
db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket([]byte(metabucket))
if bkt == nil {
t.Errorf("Meta bucket nof found")
}
return nil
})
}
func TestEnsureBuckets(t *testing.T) {
log := logrus.NewEntry(&logrus.Logger{})
if err := SetupBolt(path, log); err != nil {
t.Errorf("Failed to setup bolt: %s", err)
}
if err := DB.EnsureBuckets(meta); err != nil {
t.Errorf("Failed to ensure buckets: %s", err)
}
defer os.Remove(path)
m, found, err := DB.GetMeta(servername)
if err != nil {
t.Errorf("Failed to get meta: %s", err)
}
if !found {
t.Errorf("Not Found in meta")
}
if meta.Name != m.Name || meta.Distro != m.Distro {
t.Errorf("expected %v, actual %v", meta, m)
}
if !reflect.DeepEqual(meta.Packs, m.Packs) {
t.Errorf("expected %v, actual %v", meta.Packs, m.Packs)
}
if err := DB.Close(); err != nil {
t.Errorf("Failed to close bolt: %s", err)
}
db, err := bolt.Open(path, 0600, nil)
if err != nil {
t.Errorf("Failed to open bolt: %s", err)
}
db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket([]byte(servername))
if bkt == nil {
t.Errorf("Meta bucket nof found")
}
return nil
})
}
func TestPutGetChangelog(t *testing.T) {
clog := "changelog-text"
log := logrus.NewEntry(&logrus.Logger{})
if err := SetupBolt(path, log); err != nil {
t.Errorf("Failed to setup bolt: %s", err)
}
defer os.Remove(path)
if err := DB.EnsureBuckets(meta); err != nil {
t.Errorf("Failed to ensure buckets: %s", err)
}
if err := DB.PutChangelog(servername, "apt", clog); err != nil {
t.Errorf("Failed to put changelog: %s", err)
}
if actual, err := DB.GetChangelog(servername, "apt"); err != nil {
t.Errorf("Failed to get changelog: %s", err)
} else {
if actual != clog {
t.Errorf("changelog is not same. e: %s, a: %s", clog, actual)
}
}
}

60
cache/db.go vendored Normal file
View File

@@ -0,0 +1,60 @@
/* 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 cache
import (
"time"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
)
// DB has a cache instance
var DB Cache
const metabucket = "changelog-meta"
// Cache is a interface of cache
type Cache interface {
Close() error
GetMeta(string) (Meta, bool, error)
RefreshMeta(Meta) error
EnsureBuckets(Meta) error
PrettyPrint(Meta) error
GetChangelog(string, string) (string, error)
PutChangelog(string, string, string) error
}
// Meta holds a server name, distro information of the scanned server and
// package information that was collected at the last scan.
type Meta struct {
Name string
Distro config.Distro
Packs []models.PackageInfo
CreatedAt time.Time
}
// FindPack search a PackageInfo
func (m Meta) FindPack(name string) (pack models.PackageInfo, found bool) {
for _, p := range m.Packs {
if name == p.Name {
return p, true
}
}
return pack, false
}

View File

@@ -18,15 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package commands
import (
"context"
"flag"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/Sirupsen/logrus"
"github.com/google/subcommands"
"golang.org/x/net/context"
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/scan"
@@ -36,8 +33,11 @@ import (
// ConfigtestCmd is Subcommand
type ConfigtestCmd struct {
configPath string
logDir string
askKeyPassword bool
sshExternal bool
httpProxy string
timeoutSec int
debug bool
}
@@ -52,12 +52,15 @@ func (*ConfigtestCmd) Synopsis() string { return "Test configuration" }
func (*ConfigtestCmd) Usage() string {
return `configtest:
configtest
[-config=/path/to/config.toml]
[-ask-key-password]
[-ssh-external]
[-debug]
[-config=/path/to/config.toml]
[-log-dir=/path/to/log]
[-ask-key-password]
[-timeout=300]
[-ssh-external]
[-http-proxy=http://192.168.0.1:8080]
[-debug]
[SERVER]...
[SERVER]...
`
}
@@ -67,8 +70,13 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
defaultConfPath := filepath.Join(wd, "config.toml")
f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
defaultLogDir := util.GetDefaultLogDir()
f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
f.BoolVar(&p.debug, "debug", false, "debug mode")
f.IntVar(&p.timeoutSec, "timeout", 5*60, "Timeout(Sec)")
f.BoolVar(
&p.askKeyPassword,
"ask-key-password",
@@ -76,6 +84,13 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
"Ask ssh privatekey password before scanning",
)
f.StringVar(
&p.httpProxy,
"http-proxy",
"",
"http://proxy-url:port (default: empty)",
)
f.BoolVar(
&p.sshExternal,
"ssh-external",
@@ -85,41 +100,34 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
// Execute execute
func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
// Setup Logger
c.Conf.Debug = p.debug
c.Conf.LogDir = p.logDir
util.Log = util.NewCustomLogger(c.ServerInfo{})
var keyPass string
var err error
if p.askKeyPassword {
prompt := "SSH key password: "
if keyPass, err = getPasswd(prompt); err != nil {
logrus.Error(err)
util.Log.Error(err)
return subcommands.ExitFailure
}
}
c.Conf.Debug = p.debug
err = c.Load(p.configPath, keyPass)
if err != nil {
logrus.Errorf("Error loading %s, %s", p.configPath, err)
util.Log.Errorf("Error loading %s, %s", p.configPath, err)
util.Log.Errorf("If you update Vuls and get this error, there may be incompatible changes in config.toml")
util.Log.Errorf("Please check README: https://github.com/future-architect/vuls#configuration")
return subcommands.ExitUsageError
}
c.Conf.SSHExternal = p.sshExternal
c.Conf.HTTPProxy = p.httpProxy
var servernames []string
if 0 < len(f.Args()) {
servernames = f.Args()
} else {
stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) == 0 {
bytes, err := ioutil.ReadAll(os.Stdin)
if err != nil {
logrus.Errorf("Failed to read stdin: %s", err)
return subcommands.ExitFailure
}
fields := strings.Fields(string(bytes))
if 0 < len(fields) {
servernames = fields
}
}
}
target := make(map[string]c.ServerInfo)
@@ -133,7 +141,7 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
}
}
if !found {
logrus.Errorf("%s is not in config", arg)
util.Log.Errorf("%s is not in config", arg)
return subcommands.ExitUsageError
}
}
@@ -141,22 +149,23 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
c.Conf.Servers = target
}
// logger
Log := util.NewCustomLogger(c.ServerInfo{})
Log.Info("Validating Config...")
if !c.Conf.Validate() {
util.Log.Info("Validating config...")
if !c.Conf.ValidateOnConfigtest() {
return subcommands.ExitUsageError
}
Log.Info("Detecting Server/Contianer OS... ")
scan.InitServers(Log)
Log.Info("Checking sudo configuration... ")
if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers. err: %s", err)
util.Log.Info("Detecting Server/Container OS... ")
if err := scan.InitServers(); err != nil {
util.Log.Errorf("Failed to init servers: %s", err)
return subcommands.ExitFailure
}
util.Log.Info("Checking dependendies...")
scan.CheckDependencies(p.timeoutSec)
util.Log.Info("Checking sudo settings...")
scan.CheckIfSudoNoPasswd(p.timeoutSec)
scan.PrintSSHableServerNames()
return subcommands.ExitSuccess
}

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package commands
import (
"context"
"flag"
"fmt"
"os"
@@ -25,7 +26,6 @@ import (
"text/template"
"github.com/google/subcommands"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
ps "github.com/kotakanbe/go-pingscanner"
@@ -98,9 +98,9 @@ iconEmoji = ":ghost:"
authUser = "username"
notifyUsers = ["@username"]
[mail]
[email]
smtpAddr = "smtp.gmail.com"
smtpPort = "465"
smtpPort = "587"
user = "username"
password = "password"
from = "from@address.com"
@@ -115,10 +115,13 @@ subjectPrefix = "[vuls]"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
#containers = ["${running}"]
#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml"
#ignoreCves = ["CVE-2014-6271"]
#optional = [
# ["key", "value"],
#]
#containers = ["${running}"]
[servers]
{{- $names:= .Names}}
@@ -131,10 +134,17 @@ host = "{{$ip}}"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
#containers = ["${running}"]
#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml"
#ignoreCves = ["CVE-2014-0160"]
#optional = [
# ["key", "value"],
#]
#[servers.{{index $names $i}}.containers]
#type = "docker" #or "lxd" defualt: docker
#includes = ["${running}"]
#excludes = ["container_name_a", "4aa37a8b63b9"]
{{end}}
`

View File

@@ -18,27 +18,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package commands
import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/db"
"github.com/future-architect/vuls/models"
"github.com/google/subcommands"
)
// HistoryCmd is Subcommand of list scanned results
type HistoryCmd struct {
debug bool
debugSQL bool
dbpath string
debug bool
debugSQL bool
resultsDir string
}
// Name return subcommand name
@@ -53,7 +49,7 @@ func (*HistoryCmd) Synopsis() string {
func (*HistoryCmd) Usage() string {
return `history:
history
[-dbpath=/path/to/vuls.sqlite3]
[-results-dir=/path/to/results]
`
}
@@ -62,47 +58,41 @@ func (p *HistoryCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
wd, _ := os.Getwd()
defaultDBPath := filepath.Join(wd, "vuls.sqlite3")
f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3")
defaultResultsDir := filepath.Join(wd, "results")
f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
}
// Execute execute
func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
c.Conf.DebugSQL = p.debugSQL
c.Conf.DBPath = p.dbpath
c.Conf.ResultsDir = p.resultsDir
// _, err := scanHistories()
histories, err := scanHistories()
if err != nil {
logrus.Error("Failed to select scan histories: ", err)
var err error
var dirs jsonDirs
if dirs, err = lsValidJSONDirs(); err != nil {
return subcommands.ExitFailure
}
const timeLayout = "2006-01-02 15:04"
for _, history := range histories {
names := []string{}
for _, result := range history.ScanResults {
if 0 < len(result.Container.ContainerID) {
names = append(names, result.Container.Name)
} else {
names = append(names, result.ServerName)
}
for _, d := range dirs {
var files []os.FileInfo
if files, err = ioutil.ReadDir(d); err != nil {
return subcommands.ExitFailure
}
fmt.Printf("%-3d %s scanned %d servers: %s\n",
history.ID,
history.ScannedAt.Format(timeLayout),
len(history.ScanResults),
strings.Join(names, ", "),
var hosts []string
for _, f := range files {
if filepath.Ext(f.Name()) != ".json" {
continue
}
fileBase := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
hosts = append(hosts, fileBase)
}
splitPath := strings.Split(d, string(os.PathSeparator))
timeStr := splitPath[len(splitPath)-1]
fmt.Printf("%s %d servers: %s\n",
timeStr,
len(hosts),
strings.Join(hosts, ", "),
)
}
return subcommands.ExitSuccess
}
func scanHistories() (histories []models.ScanHistory, err error) {
if err := db.OpenDB(); err != nil {
return histories, fmt.Errorf(
"Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
}
histories, err = db.SelectScanHistories()
return
}

View File

@@ -1,164 +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 commands
import (
"flag"
"os"
"path/filepath"
"github.com/Sirupsen/logrus"
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/scan"
"github.com/future-architect/vuls/util"
"github.com/google/subcommands"
"golang.org/x/net/context"
)
// PrepareCmd is Subcommand of host discovery mode
type PrepareCmd struct {
debug bool
configPath string
askSudoPassword bool
askKeyPassword bool
useUnattendedUpgrades bool
}
// Name return subcommand name
func (*PrepareCmd) Name() string { return "prepare" }
// Synopsis return synopsis
func (*PrepareCmd) Synopsis() string {
// return "Install packages Ubuntu: unattended-upgrade, CentOS: yum-plugin-security)"
return `Install required packages to scan.
CentOS: yum-plugin-security, yum-plugin-changelog
Amazon: None
RHEL: TODO
Ubuntu: None
`
}
// Usage return usage
func (*PrepareCmd) Usage() string {
return `prepare:
prepare
[-config=/path/to/config.toml]
[-ask-key-password]
[-debug]
[SERVER]...
`
}
// SetFlags set flag
func (p *PrepareCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&p.debug, "debug", false, "debug mode")
wd, _ := os.Getwd()
defaultConfPath := filepath.Join(wd, "config.toml")
f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
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 REASON. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication",
)
f.BoolVar(
&p.useUnattendedUpgrades,
"use-unattended-upgrades",
false,
"[Deprecated] For Ubuntu, install unattended-upgrades",
)
}
// Execute execute
func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
var keyPass string
var err error
if p.askKeyPassword {
prompt := "SSH key password: "
if keyPass, err = getPasswd(prompt); err != nil {
logrus.Error(err)
return subcommands.ExitFailure
}
}
if p.askSudoPassword {
logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication")
return subcommands.ExitFailure
}
err = c.Load(p.configPath, keyPass)
if err != nil {
logrus.Errorf("Error loading %s, %s", p.configPath, err)
return subcommands.ExitUsageError
}
logrus.Infof("Start Preparing (config: %s)", p.configPath)
target := make(map[string]c.ServerInfo)
for _, arg := range f.Args() {
found := false
for servername, info := range c.Conf.Servers {
if servername == arg {
target[servername] = info
found = true
break
}
}
if !found {
logrus.Errorf("%s is not in config", arg)
return subcommands.ExitUsageError
}
}
if 0 < len(f.Args()) {
c.Conf.Servers = target
}
c.Conf.Debug = p.debug
c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
// Set up custom logger
logger := util.NewCustomLogger(c.ServerInfo{})
logger.Info("Detecting OS... ")
scan.InitServers(logger)
logger.Info("Installing...")
if errs := scan.Prepare(); 0 < len(errs) {
for _, e := range errs {
logger.Errorf("Failed: %s", e)
}
return subcommands.ExitFailure
}
logger.Info("Success")
return subcommands.ExitSuccess
}

451
commands/report.go Normal file
View File

@@ -0,0 +1,451 @@
/* 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"
)
// ReportCmd is subcommand for reporting
type ReportCmd struct {
lang string
debug bool
debugSQL bool
configPath string
resultsDir string
logDir string
refreshCve bool
cvssScoreOver float64
ignoreUnscoredCves bool
httpProxy string
cvedbtype string
cvedbpath string
cvedbURL string
toSlack bool
toEMail bool
toLocalFile bool
toS3 bool
toAzureBlob bool
formatJSON bool
formatXML bool
formatOneEMail bool
formatOneLineText bool
formatShortText bool
formatFullText bool
gzip bool
awsProfile string
awsS3Bucket string
awsRegion string
azureAccount string
azureKey string
azureContainer string
pipe bool
diff bool
}
// 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]
[-log-dir=/path/to/log]
[-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]
[-diff]
[-ignore-unscored-cves]
[-to-email]
[-to-slack]
[-to-localfile]
[-to-s3]
[-to-azure-blob]
[-format-json]
[-format-xml]
[-format-one-email]
[-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]
[-pipe]
[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")
defaultLogDir := util.GetDefaultLogDir()
f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
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.cvedbURL,
"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.diff,
"diff",
false,
fmt.Sprintf("Difference between previous result and current result "))
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.formatOneEMail,
"format-one-email",
false,
"Send all the host report via only one EMail (Specify with -to-email)")
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")
f.BoolVar(
&p.pipe,
"pipe",
false,
"Use args passed via PIPE")
}
// Execute execute
func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
c.Conf.Debug = p.debug
c.Conf.DebugSQL = p.debugSQL
c.Conf.LogDir = p.logDir
util.Log = util.NewCustomLogger(c.ServerInfo{})
if err := c.Load(p.configPath, ""); err != nil {
util.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.CveDBURL = p.cvedbURL
c.Conf.CvssScoreOver = p.cvssScoreOver
c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
c.Conf.HTTPProxy = p.httpProxy
c.Conf.Pipe = p.pipe
c.Conf.FormatXML = p.formatXML
c.Conf.FormatJSON = p.formatJSON
c.Conf.FormatOneEMail = p.formatOneEMail
c.Conf.FormatOneLineText = p.formatOneLineText
c.Conf.FormatShortText = p.formatShortText
c.Conf.FormatFullText = p.formatFullText
c.Conf.GZIP = p.gzip
c.Conf.Diff = p.diff
var dir string
var err error
if p.diff {
dir, err = jsonDir([]string{})
} else {
dir, err = jsonDir(f.Args())
}
if err != nil {
util.Log.Errorf("Failed to read from JSON: %s", err)
return subcommands.ExitFailure
}
// 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: dir,
})
}
if p.toS3 {
c.Conf.AwsRegion = p.awsRegion
c.Conf.AwsProfile = p.awsProfile
c.Conf.S3Bucket = p.awsS3Bucket
if err := report.CheckIfBucketExists(); err != nil {
util.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 {
util.Log.Error("Azure storage container name is requied with --azure-container option")
return subcommands.ExitUsageError
}
if err := report.CheckIfAzureContainerExists(); err != nil {
util.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
}
util.Log.Info("Validating config...")
if !c.Conf.ValidateOnReport() {
return subcommands.ExitUsageError
}
if ok, err := cveapi.CveClient.CheckHealth(); !ok {
util.Log.Errorf("CVE HTTP server is not running. err: %s", err)
util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with --cvedb-path option")
return subcommands.ExitFailure
}
if c.Conf.CveDBURL != "" {
util.Log.Infof("cve-dictionary: %s", c.Conf.CveDBURL)
} else {
if c.Conf.CveDBType == "sqlite3" {
util.Log.Infof("cve-dictionary: %s", c.Conf.CveDBPath)
}
}
var history models.ScanHistory
history, err = loadOneScanHistory(dir)
if err != nil {
util.Log.Error(err)
return subcommands.ExitFailure
}
util.Log.Infof("Loaded: %s", jsonDir)
var results []models.ScanResult
for _, r := range history.ScanResults {
if p.refreshCve || needToRefreshCve(r) {
util.Log.Debugf("need to refresh")
if c.Conf.CveDBType == "sqlite3" && c.Conf.CveDBURL == "" {
if _, err := os.Stat(c.Conf.CveDBPath); os.IsNotExist(err) {
util.Log.Errorf("SQLite3 DB(CVE-Dictionary) is not exist: %s",
c.Conf.CveDBPath)
return subcommands.ExitFailure
}
}
filled, err := fillCveInfoFromCveDB(r)
if err != nil {
util.Log.Errorf("Failed to fill CVE information: %s", err)
return subcommands.ExitFailure
}
filled.Lang = c.Conf.Lang
if err := overwriteJSONFile(dir, *filled); err != nil {
util.Log.Errorf("Failed to write JSON: %s", err)
return subcommands.ExitFailure
}
results = append(results, *filled)
} else {
util.Log.Debugf("no need to refresh")
results = append(results, r)
}
}
if p.diff {
currentHistory := models.ScanHistory{ScanResults: results}
previousHistory, err := loadPreviousScanHistory(currentHistory)
if err != nil {
util.Log.Error(err)
return subcommands.ExitFailure
}
history, err = diff(currentHistory, previousHistory)
if err != nil {
util.Log.Error(err)
return subcommands.ExitFailure
}
results = []models.ScanResult{}
for _, r := range history.ScanResults {
filled, _ := r.FillCveDetail()
results = append(results, *filled)
}
}
var res models.ScanResults
for _, r := range results {
res = append(res, r.FilterByCvssOver())
}
for _, w := range reports {
if err := w.Write(res...); err != nil {
util.Log.Errorf("Failed to report: %s", err)
return subcommands.ExitFailure
}
}
return subcommands.ExitSuccess
}

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package commands
import (
"context"
"flag"
"fmt"
"io/ioutil"
@@ -25,56 +26,26 @@ import (
"path/filepath"
"strings"
"github.com/Sirupsen/logrus"
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/cveapi"
"github.com/future-architect/vuls/db"
"github.com/future-architect/vuls/report"
"github.com/future-architect/vuls/scan"
"github.com/future-architect/vuls/util"
"github.com/google/subcommands"
"golang.org/x/net/context"
"github.com/k0kubun/pp"
)
// ScanCmd is Subcommand of host discovery mode
type ScanCmd struct {
lang string
debug bool
debugSQL bool
configPath string
dbpath string
cvedbpath string
cveDictionaryURL string
cvssScoreOver float64
ignoreUnscoredCves bool
httpProxy string
askSudoPassword bool
askKeyPassword bool
// reporting
reportSlack bool
reportMail bool
reportJSON bool
reportText bool
reportS3 bool
reportAzureBlob bool
awsProfile string
awsS3Bucket string
awsRegion string
azureAccount string
azureKey string
azureContainer string
useYumPluginSecurity bool
useUnattendedUpgrades bool
sshExternal bool
debug bool
configPath string
resultsDir string
logDir string
cacheDBPath string
httpProxy string
askKeyPassword bool
containersOnly bool
skipBroken bool
sshExternal bool
pipe bool
}
// Name return subcommand name
@@ -87,30 +58,17 @@ func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities" }
func (*ScanCmd) Usage() string {
return `scan:
scan
[-lang=en|ja]
[-config=/path/to/config.toml]
[-dbpath=/path/to/vuls.sqlite3]
[-cve-dictionary-dbpath=/path/to/cve.sqlite3]
[-cve-dictionary-url=http://127.0.0.1:1323]
[-cvss-over=7]
[-ignore-unscored-cves]
[-results-dir=/path/to/results]
[-log-dir=/path/to/log]
[-cachedb-path=/path/to/cache.db]
[-ssh-external]
[-report-azure-blob]
[-report-json]
[-report-mail]
[-report-s3]
[-report-slack]
[-report-text]
[-containers-only]
[-skip-broken]
[-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]
[-pipe]
[SERVER]...
`
@@ -118,42 +76,25 @@ 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()
defaultConfPath := filepath.Join(wd, "config.toml")
f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
defaultDBPath := filepath.Join(wd, "vuls.sqlite3")
f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3")
defaultResultsDir := filepath.Join(wd, "results")
f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
defaultLogDir := util.GetDefaultLogDir()
f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
defaultCacheDBPath := filepath.Join(wd, "cache.db")
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")
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")
&p.cacheDBPath,
"cachedb-path",
defaultCacheDBPath,
"/path/to/cache.db (local cache of changelog for Ubuntu/Debian)")
f.BoolVar(
&p.sshExternal,
@@ -161,6 +102,18 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
false,
"Use external ssh command. Default: Use the Go native implementation")
f.BoolVar(
&p.containersOnly,
"containers-only",
false,
"Scan containers only. Default: Scan both of hosts and containers")
f.BoolVar(
&p.skipBroken,
"skip-broken",
false,
"[For CentOS] yum update changelog with --skip-broken option")
f.StringVar(
&p.httpProxy,
"http-proxy",
@@ -168,37 +121,6 @@ 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.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 S3 (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",
@@ -207,73 +129,54 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
)
f.BoolVar(
&p.askSudoPassword,
"ask-sudo-password",
&p.pipe,
"pipe",
false,
"[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication",
)
f.BoolVar(
&p.useYumPluginSecurity,
"use-yum-plugin-security",
false,
"[Deprecated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)",
)
f.BoolVar(
&p.useUnattendedUpgrades,
"use-unattended-upgrades",
false,
"[Deprecated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)",
)
"Use stdin via PIPE")
}
// Execute execute
func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
// Setup Logger
c.Conf.Debug = p.debug
c.Conf.LogDir = p.logDir
util.Log = util.NewCustomLogger(c.ServerInfo{})
var keyPass string
var err error
if p.askKeyPassword {
prompt := "SSH key password: "
if keyPass, err = getPasswd(prompt); err != nil {
logrus.Error(err)
util.Log.Error(err)
return subcommands.ExitFailure
}
}
if p.askSudoPassword {
logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication")
return subcommands.ExitFailure
}
err = c.Load(p.configPath, keyPass)
if err != nil {
logrus.Errorf("Error loading %s, %s", p.configPath, err)
util.Log.Errorf("Error loading %s, %s", p.configPath, err)
util.Log.Errorf("If you update Vuls and get this error, there may be incompatible changes in config.toml")
util.Log.Errorf("Please check README: https://github.com/future-architect/vuls#configuration")
return subcommands.ExitUsageError
}
logrus.Info("Start scanning")
logrus.Infof("config: %s", p.configPath)
if p.cvedbpath != "" {
logrus.Infof("cve-dictionary: %s", p.cvedbpath)
} else {
logrus.Infof("cve-dictionary: %s", p.cveDictionaryURL)
}
util.Log.Info("Start scanning")
util.Log.Infof("config: %s", p.configPath)
c.Conf.Pipe = p.pipe
var servernames []string
if 0 < len(f.Args()) {
servernames = f.Args()
} else {
stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) == 0 {
bytes, err := ioutil.ReadAll(os.Stdin)
if err != nil {
logrus.Errorf("Failed to read stdin: %s", err)
return subcommands.ExitFailure
}
fields := strings.Fields(string(bytes))
if 0 < len(fields) {
servernames = fields
}
} else if c.Conf.Pipe {
bytes, err := ioutil.ReadAll(os.Stdin)
if err != nil {
util.Log.Errorf("Failed to read stdin: %s", err)
return subcommands.ExitFailure
}
fields := strings.Fields(string(bytes))
if 0 < len(fields) {
servernames = fields
}
}
@@ -288,143 +191,44 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
}
}
if !found {
logrus.Errorf("%s is not in config", arg)
util.Log.Errorf("%s is not in config", arg)
return subcommands.ExitUsageError
}
}
if 0 < len(servernames) {
c.Conf.Servers = target
}
util.Log.Debugf("%s", pp.Sprintf("%v", target))
c.Conf.Lang = p.lang
c.Conf.Debug = p.debug
c.Conf.DebugSQL = p.debugSQL
// logger
Log := util.NewCustomLogger(c.ServerInfo{})
// 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{})
}
if p.reportText {
reports = append(reports, report.TextFileWriter{})
}
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 c.Conf.AzureAccount == "" {
c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
}
c.Conf.AzureKey = p.azureKey
if c.Conf.AzureKey == "" {
c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
}
c.Conf.AzureContainer = p.azureContainer
if c.Conf.AzureContainer == "" {
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.DBPath = p.dbpath
c.Conf.CveDBPath = p.cvedbpath
c.Conf.CveDictionaryURL = p.cveDictionaryURL
c.Conf.CvssScoreOver = p.cvssScoreOver
c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
c.Conf.ResultsDir = p.resultsDir
c.Conf.CacheDBPath = p.cacheDBPath
c.Conf.SSHExternal = p.sshExternal
c.Conf.HTTPProxy = p.httpProxy
c.Conf.UseYumPluginSecurity = p.useYumPluginSecurity
c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
c.Conf.ContainersOnly = p.containersOnly
c.Conf.SkipBroken = p.skipBroken
Log.Info("Validating Config...")
if !c.Conf.Validate() {
util.Log.Info("Validating config...")
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")
util.Log.Info("Detecting Server/Container OS... ")
if err := scan.InitServers(); err != nil {
util.Log.Errorf("Failed to init servers: %s", err)
return subcommands.ExitFailure
}
Log.Info("Detecting Server/Contianer OS... ")
scan.InitServers(Log)
util.Log.Info("Detecting Platforms... ")
scan.DetectPlatforms()
Log.Info("Checking sudo configuration... ")
if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers")
util.Log.Info("Scanning vulnerabilities... ")
if err := scan.Scan(); err != nil {
util.Log.Errorf("Failed to scan. err: %s", err)
return subcommands.ExitFailure
}
Log.Info("Detecting Platforms... ")
scan.DetectPlatforms(Log)
Log.Info("Scanning vulnerabilities... ")
if errs := scan.Scan(); 0 < len(errs) {
for _, e := range errs {
Log.Errorf("Failed to scan. err: %s", e)
}
return subcommands.ExitFailure
}
scanResults, err := scan.GetScanResults()
if err != nil {
Log.Fatal(err)
return subcommands.ExitFailure
}
Log.Info("Insert to DB...")
if err := db.OpenDB(); err != nil {
Log.Errorf("Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
return subcommands.ExitFailure
}
if err := db.MigrateDB(); err != nil {
Log.Errorf("Failed to migrate. err: %s", err)
return subcommands.ExitFailure
}
if err := db.Insert(scanResults); err != nil {
Log.Fatalf("Failed to insert. dbpath: %s, err: %s", c.Conf.DBPath, 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
}

View File

@@ -18,26 +18,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package commands
import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"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/future-architect/vuls/util"
"github.com/google/subcommands"
"golang.org/x/net/context"
)
// TuiCmd is Subcommand of host discovery mode
type TuiCmd struct {
lang string
debugSQL bool
dbpath string
debug bool
logDir string
resultsDir string
refreshCve bool
cvedbtype string
cvedbpath string
cveDictionaryURL string
pipe bool
}
// Name return subcommand name
@@ -49,7 +55,16 @@ func (*TuiCmd) Synopsis() string { return "Run Tui view to anayze vulnerabilites
// Usage return usage
func (*TuiCmd) Usage() string {
return `tui:
tui [-dbpath=/path/to/vuls.sqlite3]
tui
[-cvedb-type=sqlite3|mysql]
[-cvedb-path=/path/to/cve.sqlite3]
[-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
[-refresh-cve]
[-results-dir=/path/to/results]
[-log-dir=/path/to/log]
[-debug]
[-debug-sql]
[-pipe]
`
}
@@ -58,40 +73,107 @@ func (*TuiCmd) Usage() string {
func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
// f.StringVar(&p.lang, "lang", "en", "[en|ja]")
f.BoolVar(&p.debugSQL, "debug-sql", false, "debug SQL")
f.BoolVar(&p.debug, "debug", false, "debug mode")
defaultLogDir := util.GetDefaultLogDir()
f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
wd, _ := os.Getwd()
defaultResultsDir := filepath.Join(wd, "results")
f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
defaultDBPath := filepath.Join(wd, "vuls.sqlite3")
f.StringVar(&p.dbpath, "dbpath", defaultDBPath,
fmt.Sprintf("/path/to/sqlite3 (default: %s)", defaultDBPath))
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.BoolVar(
&p.pipe,
"pipe",
false,
"Use stdin via PIPE")
}
// Execute execute
func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
c.Conf.Lang = "en"
c.Conf.DebugSQL = p.debugSQL
c.Conf.DBPath = p.dbpath
historyID := ""
if 0 < len(f.Args()) {
if _, err := strconv.Atoi(f.Args()[0]); err != nil {
log.Errorf("First Argument have to be scan_histores record ID: %s", err)
return subcommands.ExitFailure
}
historyID = f.Args()[0]
} else {
stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) == 0 {
bytes, err := ioutil.ReadAll(os.Stdin)
// Setup Logger
c.Conf.Debug = p.debug
c.Conf.DebugSQL = p.debugSQL
c.Conf.LogDir = p.logDir
util.Log = util.NewCustomLogger(c.ServerInfo{})
log := util.Log
c.Conf.ResultsDir = p.resultsDir
c.Conf.CveDBType = p.cvedbtype
c.Conf.CveDBPath = p.cvedbpath
c.Conf.CveDBURL = p.cveDictionaryURL
log.Info("Validating config...")
if !c.Conf.ValidateOnTui() {
return subcommands.ExitUsageError
}
c.Conf.Pipe = p.pipe
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
}
}
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) {
historyID = 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(historyID)
history.ScanResults = results
return report.RunTui(history)
}

333
commands/util.go Normal file
View File

@@ -0,0 +1,333 @@
/* 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"
"time"
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 arg 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
if c.Conf.Pipe {
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
}
// loadOneServerScanResult read JSON data of one server
func loadOneServerScanResult(jsonFile string) (result models.ScanResult, err error) {
var data []byte
if data, err = ioutil.ReadFile(jsonFile); err != nil {
err = fmt.Errorf("Failed to read %s: %s", jsonFile, err)
return
}
if json.Unmarshal(data, &result) != nil {
err = fmt.Errorf("Failed to parse %s: %s", jsonFile, err)
}
return
}
// 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" || strings.HasSuffix(f.Name(), "_diff.json") {
continue
}
var r models.ScanResult
path := filepath.Join(jsonDir, f.Name())
if r, err = loadOneServerScanResult(path); err != nil {
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) (*models.ScanResult, error) {
var err error
var vs []models.VulnInfo
sInfo := c.Conf.Servers[r.ServerName]
vs, err = scanVulnByCpeNames(sInfo.CpeNames, r.ScannedCves)
if err != nil {
return nil, err
}
r.ScannedCves = vs
return r.FillCveDetail()
}
func loadPreviousScanHistory(current models.ScanHistory) (previous models.ScanHistory, err error) {
var dirs jsonDirs
if dirs, err = lsValidJSONDirs(); err != nil {
return
}
for _, result := range current.ScanResults {
for _, dir := range dirs[1:] {
var r models.ScanResult
path := filepath.Join(dir, result.ServerName+".json")
if r, err = loadOneServerScanResult(path); err != nil {
continue
}
if r.Family == result.Family && r.Release == result.Release {
previous.ScanResults = append(previous.ScanResults, r)
break
}
}
}
return previous, nil
}
func diff(currentHistory, previousHistory models.ScanHistory) (diffHistory models.ScanHistory, err error) {
for _, currentResult := range currentHistory.ScanResults {
found := false
var previousResult models.ScanResult
for _, previousResult = range previousHistory.ScanResults {
if currentResult.ServerName == previousResult.ServerName {
found = true
break
}
}
if found {
currentResult.ScannedCves = getNewCves(previousResult, currentResult)
currentResult.KnownCves = []models.CveInfo{}
currentResult.UnknownCves = []models.CveInfo{}
currentResult.Packages = models.PackageInfoList{}
for _, s := range currentResult.ScannedCves {
currentResult.Packages = append(currentResult.Packages, s.Packages...)
}
currentResult.Packages = currentResult.Packages.UniqByName()
}
diffHistory.ScanResults = append(diffHistory.ScanResults, currentResult)
}
return diffHistory, err
}
func getNewCves(previousResult, currentResult models.ScanResult) (newVulninfos []models.VulnInfo) {
previousCveIDsSet := map[string]bool{}
for _, previousVulnInfo := range previousResult.ScannedCves {
previousCveIDsSet[previousVulnInfo.CveID] = true
}
for _, v := range currentResult.ScannedCves {
if previousCveIDsSet[v.CveID] {
if isCveInfoUpdated(currentResult, previousResult, v.CveID) {
newVulninfos = append(newVulninfos, v)
}
} else {
newVulninfos = append(newVulninfos, v)
}
}
return
}
func isCveInfoUpdated(currentResult, previousResult models.ScanResult, CveID string) bool {
type lastModified struct {
Nvd time.Time
Jvn time.Time
}
previousModifies := lastModified{}
for _, c := range previousResult.KnownCves {
if CveID == c.CveID {
previousModifies.Nvd = c.CveDetail.Nvd.LastModifiedDate
previousModifies.Jvn = c.CveDetail.Jvn.LastModifiedDate
}
}
currentModifies := lastModified{}
for _, c := range currentResult.KnownCves {
if CveID == c.CveDetail.CveID {
currentModifies.Nvd = c.CveDetail.Nvd.LastModifiedDate
currentModifies.Jvn = c.CveDetail.Jvn.LastModifiedDate
}
}
return !currentModifies.Nvd.Equal(previousModifies.Nvd) ||
!currentModifies.Jvn.Equal(previousModifies.Jvn)
}
func overwriteJSONFile(dir string, r models.ScanResult) error {
before := c.Conf.FormatJSON
beforeDiff := c.Conf.Diff
c.Conf.FormatJSON = true
c.Conf.Diff = false
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
c.Conf.Diff = beforeDiff
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
val.Confidence = models.CpeNameMatch
set[detail.CveID] = val
} else {
v := models.VulnInfo{
CveID: detail.CveID,
CpeNames: []string{name},
Confidence: models.CpeNameMatch,
}
v.NilSliceToEmpty()
set[detail.CveID] = v
}
}
}
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
}

294
commands/util_test.go Normal file
View File

@@ -0,0 +1,294 @@
/* 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 (
"testing"
"time"
"reflect"
"github.com/future-architect/vuls/models"
"github.com/k0kubun/pp"
cve "github.com/kotakanbe/go-cve-dictionary/models"
)
func TestDiff(t *testing.T) {
atCurrent, _ := time.Parse("2006-01-02", "2014-12-31")
atPrevious, _ := time.Parse("2006-01-02", "2014-11-31")
var tests = []struct {
inCurrent models.ScanHistory
inPrevious models.ScanHistory
out models.ScanResult
}{
{
models.ScanHistory{
ScanResults: models.ScanResults{
{
ScannedAt: atCurrent,
ServerName: "u16",
Family: "ubuntu",
Release: "16.04",
ScannedCves: []models.VulnInfo{
{
CveID: "CVE-2012-6702",
Packages: models.PackageInfoList{
{
Name: "libexpat1",
Version: "2.1.0-7",
Release: "",
NewVersion: "2.1.0-7ubuntu0.16.04.2",
NewRelease: "",
Repository: "",
},
},
DistroAdvisories: []models.DistroAdvisory{},
CpeNames: []string{},
},
{
CveID: "CVE-2014-9761",
Packages: models.PackageInfoList{
{
Name: "libc-bin",
Version: "2.21-0ubuntu5",
Release: "",
NewVersion: "2.23-0ubuntu5",
NewRelease: "",
Repository: "",
},
},
DistroAdvisories: []models.DistroAdvisory{},
CpeNames: []string{},
},
},
KnownCves: []models.CveInfo{},
UnknownCves: []models.CveInfo{},
IgnoredCves: []models.CveInfo{},
Packages: models.PackageInfoList{},
Errors: []string{},
Optional: [][]interface{}{},
},
},
},
models.ScanHistory{
ScanResults: models.ScanResults{
{
ScannedAt: atPrevious,
ServerName: "u16",
Family: "ubuntu",
Release: "16.04",
ScannedCves: []models.VulnInfo{
{
CveID: "CVE-2012-6702",
Packages: models.PackageInfoList{
{
Name: "libexpat1",
Version: "2.1.0-7",
Release: "",
NewVersion: "2.1.0-7ubuntu0.16.04.2",
NewRelease: "",
Repository: "",
},
},
DistroAdvisories: []models.DistroAdvisory{},
CpeNames: []string{},
},
{
CveID: "CVE-2014-9761",
Packages: models.PackageInfoList{
{
Name: "libc-bin",
Version: "2.21-0ubuntu5",
Release: "",
NewVersion: "2.23-0ubuntu5",
NewRelease: "",
Repository: "",
},
},
DistroAdvisories: []models.DistroAdvisory{},
CpeNames: []string{},
},
},
KnownCves: []models.CveInfo{},
UnknownCves: []models.CveInfo{},
IgnoredCves: []models.CveInfo{},
Packages: models.PackageInfoList{},
Errors: []string{},
Optional: [][]interface{}{},
},
},
},
models.ScanResult{
ScannedAt: atCurrent,
ServerName: "u16",
Family: "ubuntu",
Release: "16.04",
KnownCves: []models.CveInfo{},
UnknownCves: []models.CveInfo{},
IgnoredCves: []models.CveInfo{},
// Packages: models.PackageInfoList{},
Errors: []string{},
Optional: [][]interface{}{},
},
},
{
models.ScanHistory{
ScanResults: models.ScanResults{
{
ScannedAt: atCurrent,
ServerName: "u16",
Family: "ubuntu",
Release: "16.04",
ScannedCves: []models.VulnInfo{
{
CveID: "CVE-2016-6662",
Packages: models.PackageInfoList{
{
Name: "mysql-libs",
Version: "5.1.73",
Release: "7.el6",
NewVersion: "5.1.73",
NewRelease: "8.el6_8",
Repository: "",
},
},
DistroAdvisories: []models.DistroAdvisory{},
CpeNames: []string{},
},
},
KnownCves: []models.CveInfo{
{
CveDetail: cve.CveDetail{
CveID: "CVE-2016-6662",
Nvd: cve.Nvd{
LastModifiedDate: time.Date(2016, 1, 1, 0, 0, 0, 0, time.Local),
},
},
VulnInfo: models.VulnInfo{
CveID: "CVE-2016-6662",
},
},
},
UnknownCves: []models.CveInfo{},
IgnoredCves: []models.CveInfo{},
},
},
},
models.ScanHistory{
ScanResults: models.ScanResults{
{
ScannedAt: atPrevious,
ServerName: "u16",
Family: "ubuntu",
Release: "16.04",
ScannedCves: []models.VulnInfo{
{
CveID: "CVE-2016-6662",
Packages: models.PackageInfoList{
{
Name: "mysql-libs",
Version: "5.1.73",
Release: "7.el6",
NewVersion: "5.1.73",
NewRelease: "8.el6_8",
Repository: "",
},
},
DistroAdvisories: []models.DistroAdvisory{},
CpeNames: []string{},
},
},
KnownCves: []models.CveInfo{
{
CveDetail: cve.CveDetail{
CveID: "CVE-2016-6662",
Nvd: cve.Nvd{
LastModifiedDate: time.Date(2017, 3, 15, 13, 40, 57, 0, time.Local),
},
},
VulnInfo: models.VulnInfo{
CveID: "CVE-2016-6662",
},
},
},
UnknownCves: []models.CveInfo{},
IgnoredCves: []models.CveInfo{},
},
},
},
models.ScanResult{
ScannedAt: atCurrent,
ServerName: "u16",
Family: "ubuntu",
Release: "16.04",
ScannedCves: []models.VulnInfo{
{
CveID: "CVE-2016-6662",
Packages: models.PackageInfoList{
{
Name: "mysql-libs",
Version: "5.1.73",
Release: "7.el6",
NewVersion: "5.1.73",
NewRelease: "8.el6_8",
Repository: "",
},
},
DistroAdvisories: []models.DistroAdvisory{},
CpeNames: []string{},
},
},
KnownCves: []models.CveInfo{},
UnknownCves: []models.CveInfo{},
IgnoredCves: []models.CveInfo{},
Packages: models.PackageInfoList{
models.PackageInfo{
Name: "mysql-libs",
Version: "5.1.73",
Release: "7.el6",
NewVersion: "5.1.73",
NewRelease: "8.el6_8",
Repository: "",
Changelog: models.Changelog{
Contents: "",
Method: "",
},
},
},
},
},
}
var s models.ScanHistory
for _, tt := range tests {
s, _ = diff(tt.inCurrent, tt.inPrevious)
for _, actual := range s.ScanResults {
if !reflect.DeepEqual(actual, tt.out) {
h := pp.Sprint(actual)
x := pp.Sprint(tt.out)
t.Errorf("diff result : \n %s \n output result : \n %s", h, x)
}
}
}
}

View File

@@ -19,6 +19,8 @@ package config
import (
"fmt"
"runtime"
"strconv"
"strings"
log "github.com/Sirupsen/logrus"
@@ -34,21 +36,35 @@ type Config struct {
DebugSQL bool
Lang string
Mail smtpConf
EMail SMTPConf
Slack SlackConf
Default ServerInfo
Servers map[string]ServerInfo
CveDictionaryURL string `valid:"url"`
CvssScoreOver float64
IgnoreUnscoredCves bool
SSHExternal bool
SSHExternal bool
ContainersOnly bool
SkipBroken bool
HTTPProxy string `valid:"url"`
DBPath string
CveDBPath string
HTTPProxy string `valid:"url"`
LogDir string
ResultsDir string
CveDBType string
CveDBPath string
CveDBURL string
CacheDBPath string
FormatXML bool
FormatJSON bool
FormatOneEMail bool
FormatOneLineText bool
FormatShortText bool
FormatFullText bool
GZIP bool
AwsProfile string
AwsRegion string
@@ -58,27 +74,61 @@ type Config struct {
AzureKey string
AzureContainer string
// CpeNames []string
// SummaryMode bool
UseYumPluginSecurity bool
UseUnattendedUpgrades bool
Pipe bool
Diff bool
}
// Validate configuration
func (c Config) Validate() bool {
// ValidateOnConfigtest validates
func (c Config) ValidateOnConfigtest() bool {
errs := []error{}
if len(c.DBPath) != 0 {
if ok, _ := valid.IsFilePath(c.DBPath); !ok {
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(
"SQLite3 DB path must be a *Absolute* file path. dbpath: %s", c.DBPath))
"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
}
}
if len(c.CveDBPath) != 0 {
if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
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(
"SQLite3 DB(Cve Dictionary) path must be a *Absolute* file path. dbpath: %s", c.CveDBPath))
"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
}
}
if len(c.CacheDBPath) != 0 {
if ok, _ := valid.IsFilePath(c.CacheDBPath); !ok {
errs = append(errs, fmt.Errorf(
"Cache DB path must be a *Absolute* file path. -cache-dbpath: %s", c.CacheDBPath))
}
}
@@ -87,7 +137,47 @@ 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))
}
}
switch c.CveDBType {
case "sqlite3":
if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
errs = append(errs, fmt.Errorf(
"SQLite3 DB(CVE-Dictionary) path must be a *Absolute* file path. -cvedb-path: %s",
c.CveDBPath))
}
case "mysql":
if c.CveDBURL == "" {
errs = append(errs, fmt.Errorf(
`MySQL connection string is needed. -cvedb-url="user:pass@tcp(localhost:3306)/dbname"`))
}
default:
errs = append(errs, fmt.Errorf(
"CVE DB type must be either 'sqlite3' or 'mysql'. -cvedb-type: %s", c.CveDBType))
}
_, err := valid.ValidateStruct(c)
if err != nil {
errs = append(errs, err)
}
if mailerrs := c.EMail.Validate(); 0 < len(mailerrs) {
errs = append(errs, mailerrs...)
}
@@ -102,8 +192,38 @@ func (c Config) Validate() bool {
return len(errs) == 0
}
// smtpConf is smtp config
type smtpConf struct {
// 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
SMTPPort string `valid:"port"`
@@ -130,7 +250,7 @@ func checkEmails(emails []string) (errs []error) {
}
// Validate SMTP configuration
func (c *smtpConf) Validate() (errs []error) {
func (c *SMTPConf) Validate() (errs []error) {
if !c.UseThisTime {
return
@@ -181,7 +301,6 @@ type SlackConf struct {
// Validate validates configuration
func (c *SlackConf) Validate() (errs []error) {
if !c.UseThisTime {
return
}
@@ -221,18 +340,53 @@ type ServerInfo struct {
KeyPath string
KeyPassword string
CpeNames []string
CpeNames []string
DependencyCheckXMLPath string
// Container Names or IDs
Containers []string
Containers Containers
IgnoreCves []string
// Optional key-value set that will be outputted to JSON
Optional [][]interface{}
// For CentOS, RHEL, Amazon
Enablerepo string
// used internal
LogMsgAnsiColor string // DebugLog Color
Container Container
Family string
Distro Distro
}
// GetServerName returns ServerName if this serverInfo is about host.
// If this serverInfo is abount a container, returns containerID@ServerName
func (s ServerInfo) GetServerName() string {
if len(s.Container.ContainerID) == 0 {
return s.ServerName
}
return fmt.Sprintf("%s@%s", s.Container.ContainerID, s.ServerName)
}
// Distro has distribution info
type Distro struct {
Family string
Release string
}
func (l Distro) String() string {
return fmt.Sprintf("%s %s", l.Family, l.Release)
}
// MajorVersion returns Major version
func (l Distro) MajorVersion() (ver int, err error) {
if 0 < len(l.Release) {
ver, err = strconv.Atoi(strings.Split(l.Release, ".")[0])
} else {
err = fmt.Errorf("Release is empty")
}
return
}
// IsContainer returns whether this ServerInfo is about container
@@ -245,9 +399,16 @@ func (s *ServerInfo) SetContainer(d Container) {
s.Container = d
}
// Containers has Containers information.
type Containers struct {
Type string
Includes []string
Excludes []string
}
// Container has Container information.
type Container struct {
ContainerID string
Name string
Type string
Image string
}

View File

@@ -20,10 +20,11 @@ package config
import (
"fmt"
"os"
"strings"
"github.com/BurntSushi/toml"
log "github.com/Sirupsen/logrus"
"github.com/k0kubun/pp"
"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
)
// TOMLLoader loads config
@@ -31,14 +32,18 @@ type TOMLLoader struct {
}
// Load load the configuraiton TOML file specified by path arg.
func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
func (c TOMLLoader) Load(pathToToml, keyPass string) error {
if Conf.Debug {
log.SetLevel(log.DebugLevel)
}
var conf Config
if _, err := toml.DecodeFile(pathToToml, &conf); err != nil {
log.Error("Load config failed", err)
return err
}
Conf.Mail = conf.Mail
Conf.EMail = conf.EMail
Conf.Slack = conf.Slack
d := conf.Default
@@ -51,24 +56,14 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
i := 0
for name, v := range conf.Servers {
if 0 < len(v.KeyPassword) {
log.Warn("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE.")
}
s := ServerInfo{ServerName: name}
switch {
case v.User != "":
s.User = v.User
case d.User != "":
s.User = d.User
default:
return fmt.Errorf("%s is invalid. User is empty", name)
}
s.Host = v.Host
if s.Host == "" {
if len(s.Host) == 0 {
return fmt.Errorf("%s is invalid. host is empty", name)
}
@@ -81,8 +76,19 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
s.Port = "22"
}
switch {
case v.User != "":
s.User = v.User
case d.User != "":
s.User = d.User
default:
if s.Port != "local" {
return fmt.Errorf("%s is invalid. User is empty", name)
}
}
s.KeyPath = v.KeyPath
if s.KeyPath == "" {
if len(s.KeyPath) == 0 {
s.KeyPath = d.KeyPath
}
if s.KeyPath != "" {
@@ -94,7 +100,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
// s.KeyPassword = keyPass
s.KeyPassword = v.KeyPassword
if s.KeyPassword == "" {
if len(s.KeyPassword) == 0 {
s.KeyPassword = d.KeyPassword
}
@@ -103,11 +109,42 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
s.CpeNames = d.CpeNames
}
s.DependencyCheckXMLPath = v.DependencyCheckXMLPath
if len(s.DependencyCheckXMLPath) == 0 {
s.DependencyCheckXMLPath = d.DependencyCheckXMLPath
}
// Load CPEs from OWASP Dependency Check XML
if len(s.DependencyCheckXMLPath) != 0 {
cpes, err := parser.Parse(s.DependencyCheckXMLPath)
if err != nil {
return fmt.Errorf(
"Failed to read OWASP Dependency Check XML: %s", err)
}
log.Debugf("Loaded from OWASP Dependency Check XML: %s",
s.ServerName)
s.CpeNames = append(s.CpeNames, cpes...)
}
s.Containers = v.Containers
if len(s.Containers) == 0 {
if len(s.Containers.Includes) == 0 {
s.Containers = d.Containers
}
s.IgnoreCves = v.IgnoreCves
for _, cve := range d.IgnoreCves {
found := false
for _, c := range s.IgnoreCves {
if cve == c {
found = true
break
}
}
if !found {
s.IgnoreCves = append(s.IgnoreCves, cve)
}
}
s.Optional = v.Optional
for _, dkv := range d.Optional {
found := false
@@ -122,13 +159,28 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
}
}
s.Enablerepo = v.Enablerepo
if len(s.Enablerepo) == 0 {
s.Enablerepo = d.Enablerepo
}
if len(s.Enablerepo) != 0 {
for _, repo := range strings.Split(s.Enablerepo, ",") {
switch repo {
case "base", "updates":
// nop
default:
return fmt.Errorf(
"For now, enablerepo have to be base or updates: %s, servername: %s",
s.Enablerepo, name)
}
}
}
s.LogMsgAnsiColor = Colors[i%len(Colors)]
i++
servers[name] = s
}
log.Debug("Config loaded")
log.Debugf("%s", pp.Sprintf("%v", servers))
Conf.Servers = servers
return
return nil
}

View File

@@ -0,0 +1,64 @@
package parser
import (
"encoding/xml"
"fmt"
"io/ioutil"
"os"
"sort"
"strings"
)
type analysis struct {
Dependencies []dependency `xml:"dependencies>dependency"`
}
type dependency struct {
Identifiers []identifier `xml:"identifiers>identifier"`
}
type identifier struct {
Name string `xml:"name"`
Type string `xml:"type,attr"`
}
func appendIfMissing(slice []string, str string) []string {
for _, s := range slice {
if s == str {
return slice
}
}
return append(slice, str)
}
// Parse parses XML and collect list of cpe
func Parse(path string) ([]string, error) {
file, err := os.Open(path)
if err != nil {
return []string{}, fmt.Errorf("Failed to open: %s", err)
}
defer file.Close()
b, err := ioutil.ReadAll(file)
if err != nil {
return []string{}, fmt.Errorf("Failed to read: %s", err)
}
var anal analysis
if err := xml.Unmarshal(b, &anal); err != nil {
fmt.Errorf("Failed to unmarshal: %s", err)
}
cpes := []string{}
for _, d := range anal.Dependencies {
for _, ident := range d.Identifiers {
if ident.Type == "cpe" {
name := strings.TrimPrefix(ident.Name, "(")
name = strings.TrimSuffix(name, ")")
cpes = appendIfMissing(cpes, name)
}
}
}
sort.Strings(cpes)
return cpes, nil
}

View File

@@ -27,7 +27,6 @@ import (
"github.com/cenkalti/backoff"
"github.com/parnurzeal/gorequest"
log "github.com/Sirupsen/logrus"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/util"
cveconfig "github.com/kotakanbe/go-cve-dictionary/config"
@@ -44,12 +43,12 @@ type cvedictClient struct {
}
func (api *cvedictClient) initialize() {
api.baseURL = config.Conf.CveDictionaryURL
api.baseURL = config.Conf.CveDBURL
}
func (api cvedictClient) CheckHealth() (ok bool, err error) {
if config.Conf.CveDBPath != "" {
log.Debugf("get cve-dictionary from sqlite3")
if config.Conf.CveDBURL == "" || config.Conf.CveDBType == "mysql" {
util.Log.Debugf("get cve-dictionary from %s", config.Conf.CveDBType)
return true, nil
}
@@ -59,7 +58,7 @@ func (api cvedictClient) CheckHealth() (ok bool, err error) {
var resp *http.Response
resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
// resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
if len(errs) > 0 || resp == nil || resp.StatusCode != 200 {
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
return false, fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v", url, errs)
}
return true, nil
@@ -71,11 +70,12 @@ type response struct {
}
func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDetails, err error) {
if config.Conf.CveDBPath != "" {
switch config.Conf.CveDBType {
case "sqlite3", "mysql":
return api.FetchCveDetailsFromCveDB(cveIDs)
}
api.baseURL = config.Conf.CveDictionaryURL
api.baseURL = config.Conf.CveDBURL
reqChan := make(chan string, len(cveIDs))
resChan := make(chan response, len(cveIDs))
errChan := make(chan error, len(cveIDs))
@@ -99,7 +99,7 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDet
if err != nil {
errChan <- err
} else {
log.Debugf("HTTP Request to %s", url)
util.Log.Debugf("HTTP Request to %s", url)
api.httpGet(cveID, url, resChan, errChan)
}
}
@@ -129,14 +129,19 @@ 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
}
func (api cvedictClient) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails cve.CveDetails, err error) {
log.Debugf("open cve-dictionary db")
cveconfig.Conf.DBPath = config.Conf.CveDBPath
util.Log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
cveconfig.Conf.DBType = config.Conf.CveDBType
if config.Conf.CveDBType == "sqlite3" {
cveconfig.Conf.DBPath = config.Conf.CveDBPath
} else {
cveconfig.Conf.DBPath = config.Conf.CveDBURL
}
cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
if err := cvedb.OpenDB(); err != nil {
return []cve.CveDetail{},
fmt.Errorf("Failed to open DB. err: %s", err)
@@ -170,7 +175,7 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
return nil
}
notify := func(err error, t time.Duration) {
log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", t, err)
util.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", t, err)
}
err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
if err != nil {
@@ -186,59 +191,25 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
}
}
// func (api cvedictClient) httpGet(key, url string, query map[string]string, resChan chan<- response, errChan chan<- error) {
// var body string
// var errs []error
// var resp *http.Response
// f := func() (err error) {
// req := gorequest.New().SetDebug(true).Proxy(api.httpProxy).Get(url)
// for key := range query {
// req = req.Query(fmt.Sprintf("%s=%s", key, query[key])).Set("Content-Type", "application/x-www-form-urlencoded")
// }
// pp.Println(req)
// resp, body, errs = req.End()
// if len(errs) > 0 || resp.StatusCode != 200 {
// errChan <- fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
// }
// return nil
// }
// notify := func(err error, t time.Duration) {
// log.Warnf("Failed to get. retrying in %s seconds. err: %s", t, err)
// }
// err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
// if err != nil {
// errChan <- fmt.Errorf("HTTP Error %s", err)
// }
// // resChan <- body
// cveDetail := cve.CveDetail{}
// if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
// errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
// }
// resChan <- response{
// key,
// cveDetail,
// }
// }
type responseGetCveDetailByCpeName struct {
CpeName string
CveDetails []cve.CveDetail
}
func (api cvedictClient) FetchCveDetailsByCpeName(cpeName string) ([]cve.CveDetail, error) {
if config.Conf.CveDBPath != "" {
switch config.Conf.CveDBType {
case "sqlite3", "mysql":
return api.FetchCveDetailsByCpeNameFromDB(cpeName)
}
api.baseURL = config.Conf.CveDictionaryURL
api.baseURL = config.Conf.CveDBURL
url, err := util.URLPathJoin(api.baseURL, "cpes")
if err != nil {
return []cve.CveDetail{}, err
}
query := map[string]string{"name": cpeName}
log.Debugf("HTTP Request to %s, query: %#v", url, query)
util.Log.Debugf("HTTP Request to %s, query: %#v", url, query)
return api.httpPost(cpeName, url, query)
}
@@ -252,13 +223,13 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
req = req.Send(fmt.Sprintf("%s=%s", key, query[key])).Type("json")
}
resp, body, errs = req.End()
if len(errs) > 0 || resp == nil || resp.StatusCode != 200 {
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
return fmt.Errorf("HTTP POST error: %v, url: %s, resp: %v", errs, url, resp)
}
return nil
}
notify := func(err error, t time.Duration) {
log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %s", t, err)
util.Log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %s", t, err)
}
err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
if err != nil {
@@ -274,8 +245,15 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
}
func (api cvedictClient) FetchCveDetailsByCpeNameFromDB(cpeName string) ([]cve.CveDetail, error) {
log.Debugf("open cve-dictionary db")
cveconfig.Conf.DBPath = config.Conf.CveDBPath
util.Log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
cveconfig.Conf.DBType = config.Conf.CveDBType
if config.Conf.CveDBType == "sqlite3" {
cveconfig.Conf.DBPath = config.Conf.CveDBPath
} else {
cveconfig.Conf.DBPath = config.Conf.CveDBURL
}
cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
if err := cvedb.OpenDB(); err != nil {
return []cve.CveDetail{},
fmt.Errorf("Failed to open DB. err: %s", err)

324
db/db.go
View File

@@ -1,324 +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 db
import (
"fmt"
"sort"
"strconv"
"time"
"github.com/future-architect/vuls/config"
m "github.com/future-architect/vuls/models"
"github.com/jinzhu/gorm"
cvedb "github.com/kotakanbe/go-cve-dictionary/db"
cve "github.com/kotakanbe/go-cve-dictionary/models"
)
var db *gorm.DB
// OpenDB opens Database
func OpenDB() (err error) {
db, err = gorm.Open("sqlite3", config.Conf.DBPath)
if err != nil {
err = fmt.Errorf("Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
return
}
db.LogMode(config.Conf.DebugSQL)
return
}
// MigrateDB migrates Database
func MigrateDB() error {
if err := db.AutoMigrate(
&m.ScanHistory{},
&m.ScanResult{},
// &m.NWLink{},
&m.Container{},
&m.CveInfo{},
&m.CpeName{},
&m.PackageInfo{},
&m.DistroAdvisory{},
&cve.CveDetail{},
&cve.Jvn{},
&cve.Nvd{},
&cve.Reference{},
&cve.Cpe{},
).Error; err != nil {
return fmt.Errorf("Failed to migrate. err: %s", err)
}
errMsg := "Failed to create index. err: %s"
// if err := db.Model(&m.NWLink{}).
// AddIndex("idx_n_w_links_scan_result_id", "scan_result_id").Error; err != nil {
// return fmt.Errorf(errMsg, err)
// }
if err := db.Model(&m.Container{}).
AddIndex("idx_containers_scan_result_id", "scan_result_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&m.CveInfo{}).
AddIndex("idx_cve_infos_scan_result_id", "scan_result_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&m.CpeName{}).
AddIndex("idx_cpe_names_cve_info_id", "cve_info_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&m.PackageInfo{}).
AddIndex("idx_package_infos_cve_info_id", "cve_info_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&m.DistroAdvisory{}).
//TODO check table name
AddIndex("idx_distro_advisories_cve_info_id", "cve_info_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&cve.CveDetail{}).
AddIndex("idx_cve_details_cve_info_id", "cve_info_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&cve.CveDetail{}).
AddIndex("idx_cve_details_cveid", "cve_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&cve.Nvd{}).
AddIndex("idx_nvds_cve_detail_id", "cve_detail_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&cve.Jvn{}).
AddIndex("idx_jvns_cve_detail_id", "cve_detail_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&cve.Cpe{}).
AddIndex("idx_cpes_jvn_id", "jvn_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&cve.Reference{}).
AddIndex("idx_references_jvn_id", "jvn_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&cve.Cpe{}).
AddIndex("idx_cpes_nvd_id", "nvd_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&cve.Reference{}).
AddIndex("idx_references_nvd_id", "nvd_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
return nil
}
// Insert inserts scan results into DB
func Insert(results []m.ScanResult) error {
for _, r := range results {
r.KnownCves = resetGormIDs(r.KnownCves)
r.UnknownCves = resetGormIDs(r.UnknownCves)
}
history := m.ScanHistory{
ScanResults: results,
ScannedAt: time.Now(),
}
db = db.Set("gorm:save_associations", false)
if err := db.Create(&history).Error; err != nil {
return err
}
for _, scanResult := range history.ScanResults {
scanResult.ScanHistoryID = history.ID
if err := db.Create(&scanResult).Error; err != nil {
return err
}
scanResult.Container.ScanResultID = scanResult.ID
if err := db.Create(&scanResult.Container).Error; err != nil {
return err
}
if err := insertCveInfos(scanResult.ID, scanResult.KnownCves); err != nil {
return err
}
if err := insertCveInfos(scanResult.ID, scanResult.UnknownCves); err != nil {
return err
}
}
return nil
}
func insertCveInfos(scanResultID uint, infos []m.CveInfo) error {
for _, cveInfo := range infos {
cveInfo.ScanResultID = scanResultID
if err := db.Create(&cveInfo).Error; err != nil {
return err
}
for _, pack := range cveInfo.Packages {
pack.CveInfoID = cveInfo.ID
if err := db.Create(&pack).Error; err != nil {
return err
}
}
for _, distroAdvisory := range cveInfo.DistroAdvisories {
distroAdvisory.CveInfoID = cveInfo.ID
if err := db.Create(&distroAdvisory).Error; err != nil {
return err
}
}
for _, cpeName := range cveInfo.CpeNames {
cpeName.CveInfoID = cveInfo.ID
if err := db.Create(&cpeName).Error; err != nil {
return err
}
}
db = db.Set("gorm:save_associations", true)
cveDetail := cveInfo.CveDetail
cveDetail.CveInfoID = cveInfo.ID
if err := db.Create(&cveDetail).Error; err != nil {
return err
}
db = db.Set("gorm:save_associations", false)
}
return nil
}
func resetGormIDs(infos []m.CveInfo) []m.CveInfo {
for i := range infos {
infos[i].CveDetail.ID = 0
// NVD
infos[i].CveDetail.Nvd.ID = 0
for j := range infos[i].CveDetail.Nvd.Cpes {
infos[i].CveDetail.Nvd.Cpes[j].ID = 0
}
for j := range infos[i].CveDetail.Nvd.References {
infos[i].CveDetail.Nvd.References[j].ID = 0
}
// JVN
infos[i].CveDetail.Jvn.ID = 0
for j := range infos[i].CveDetail.Jvn.Cpes {
infos[i].CveDetail.Jvn.Cpes[j].ID = 0
}
for j := range infos[i].CveDetail.Jvn.References {
infos[i].CveDetail.Jvn.References[j].ID = 0
}
//Packages
for j := range infos[i].Packages {
infos[i].Packages[j].ID = 0
infos[i].Packages[j].CveInfoID = 0
}
}
return infos
}
// SelectScanHistory select scan history from DB
func SelectScanHistory(historyID string) (m.ScanHistory, error) {
var err error
scanHistory := m.ScanHistory{}
if historyID == "" {
// select latest
db.Order("scanned_at desc").First(&scanHistory)
} else {
var id int
if id, err = strconv.Atoi(historyID); err != nil {
return m.ScanHistory{},
fmt.Errorf("historyID have to be numeric number: %s", err)
}
db.First(&scanHistory, id)
}
if scanHistory.ID == 0 {
return m.ScanHistory{}, fmt.Errorf("No scanHistory records")
}
// results := []m.ScanResult{}
results := m.ScanResults{}
db.Model(&scanHistory).Related(&results, "ScanResults")
scanHistory.ScanResults = results
for i, r := range results {
// nw := []m.NWLink{}
// db.Model(&r).Related(&nw, "NWLinks")
// scanHistory.ScanResults[i].NWLinks = nw
di := m.Container{}
db.Model(&r).Related(&di, "Container")
scanHistory.ScanResults[i].Container = di
knownCves := selectCveInfos(&r, "KnownCves")
sort.Sort(m.CveInfos(knownCves))
scanHistory.ScanResults[i].KnownCves = knownCves
}
sort.Sort(scanHistory.ScanResults)
return scanHistory, nil
}
func selectCveInfos(result *m.ScanResult, fieldName string) []m.CveInfo {
cveInfos := []m.CveInfo{}
db.Model(&result).Related(&cveInfos, fieldName)
for i, cveInfo := range cveInfos {
cveDetail := cve.CveDetail{}
db.Model(&cveInfo).Related(&cveDetail, "CveDetail")
id := cveDetail.CveID
filledCveDetail := cvedb.Get(id, db)
cveInfos[i].CveDetail = filledCveDetail
packs := []m.PackageInfo{}
db.Model(&cveInfo).Related(&packs, "Packages")
cveInfos[i].Packages = packs
advisories := []m.DistroAdvisory{}
db.Model(&cveInfo).Related(&advisories, "DistroAdvisories")
cveInfos[i].DistroAdvisories = advisories
names := []m.CpeName{}
db.Model(&cveInfo).Related(&names, "CpeNames")
cveInfos[i].CpeNames = names
}
return cveInfos
}
// SelectScanHistories select latest scan history from DB
func SelectScanHistories() ([]m.ScanHistory, error) {
scanHistories := []m.ScanHistory{}
db.Order("scanned_at desc").Find(&scanHistories)
if len(scanHistories) == 0 {
return []m.ScanHistory{}, fmt.Errorf("No scanHistory records")
}
for i, history := range scanHistories {
results := m.ScanResults{}
db.Model(&history).Related(&results, "ScanResults")
scanHistories[i].ScanResults = results
for j, r := range results {
di := m.Container{}
db.Model(&r).Related(&di, "Container")
scanHistories[i].ScanResults[j].Container = di
}
}
return scanHistories, nil
}

110
glide.lock generated
View File

@@ -1,117 +1,123 @@
hash: 9683c87b3cf998e7fac1b12c4a94bf2bd18cb5422e9108539811546e703a439a
updated: 2016-07-12T16:20:45.462913061+09:00
hash: c3167d83e68562cd7ef73f138ce60cb9e60b72b50394e8615388d1f3ba9fbef2
updated: 2017-02-08T11:59:59.893522816+09:00
imports:
- name: github.com/asaskevich/govalidator
version: df81827fdd59d8b4fb93d8910b286ab7a3919520
version: 7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877
- name: github.com/aws/aws-sdk-go
version: 90dec2183a5f5458ee79cbaf4b8e9ab910bc81a6
version: 5b341290c488aa6bd76b335d819b4a68516ec3ab
subpackages:
- aws
- aws/credentials
- aws/session
- service/s3
- aws/awserr
- aws/client
- aws/corehandlers
- aws/defaults
- aws/request
- private/endpoints
- aws/awsutil
- aws/client
- aws/client/metadata
- aws/signer/v4
- private/protocol
- private/protocol/restxml
- private/waiter
- aws/corehandlers
- aws/credentials
- aws/credentials/ec2rolecreds
- aws/credentials/endpointcreds
- aws/credentials/stscreds
- aws/defaults
- aws/ec2metadata
- private/protocol/rest
- aws/request
- aws/session
- aws/signer/v4
- private/endpoints
- private/protocol
- private/protocol/query
- private/protocol/xml/xmlutil
- private/protocol/query/queryutil
- private/protocol/rest
- private/protocol/restxml
- private/protocol/xml/xmlutil
- private/waiter
- service/s3
- service/sts
- name: github.com/Azure/azure-sdk-for-go
version: 58a13e378daf3b06e65925397185684b16321111
version: 27ae5c8b5bc5d90ab0540b4c5d0f2632c8db8b57
subpackages:
- storage
- name: github.com/boltdb/bolt
version: 315c65d4cf4f5278c73477a35fb1f9b08365d340
- name: github.com/BurntSushi/toml
version: ffaa107fbd880f6d18cd6fec9b511668dcad8639
version: 99064174e013895bbd9b025c31100bd1d9b590ca
- name: github.com/cenkalti/backoff
version: cdf48bbc1eb78d1349cbda326a4a037f7ba565c6
version: 8edc80b07f38c27352fb186d971c628a6c32552b
- name: github.com/cheggaaa/pb
version: 04b234c80d661c663dbcebd52fc7218fdacc6d0c
version: ad4efe000aa550bb54918c06ebbadc0ff17687b9
- name: github.com/go-ini/ini
version: cf53f9204df4fbdd7ec4164b57fa6184ba168292
version: 6e4869b434bd001f6983749881c7ead3545887d8
- name: github.com/go-sql-driver/mysql
version: d512f204a577a4ab037a1816604c48c9c13210be
- name: github.com/google/subcommands
version: 1c7173745a6001f67d8d96ab4e178284c77f7759
version: a71b91e238406bd68766ee52db63bebedce0e9f6
- name: github.com/gosuri/uitable
version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42
subpackages:
- util/strutil
- util/wordwrap
- name: github.com/howeyc/gopass
version: 66487b23f2880ba32e185121d2cd51a338ea069a
version: f5387c492211eb133053880d23dfae62aa14123d
- name: github.com/jinzhu/gorm
version: 613c0655691abb7691b70c5fda80a716d9e20b1b
version: 39165d498058a823126af3cbf4d2a3b0e1acf11e
subpackages:
- dialects/mysql
- dialects/sqlite
- name: github.com/jinzhu/inflection
version: 8f4d3a0d04ce0b7c0cf3126fb98524246d00d102
version: 74387dc39a75e970e7a3ae6a3386b5bd2e5c5cff
- name: github.com/jmespath/go-jmespath
version: 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74
version: bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
- name: github.com/jroimartin/gocui
version: 2dcda558bf18ec07c7065bf1eaf071b5305f7c0c
version: ba396278de0a3c63658bbaba13d2d2fa392edb11
- name: github.com/k0kubun/pp
version: f5dce6ed0ccf6c350f1679964ff6b61f3d6d2033
- name: github.com/kotakanbe/go-cve-dictionary
version: 1a336b8ac785badfe89a175ee926d39574901232
version: bbfdd41e7785a9b7163b5109b10ac2dea8f36d84
subpackages:
- config
- db
- models
- log
- jvn
- log
- models
- nvd
- util
- name: github.com/kotakanbe/go-pingscanner
version: 58e188a3e4f6ab1a6371e33421e4502e26fa1e80
- name: github.com/kotakanbe/logrus-prefixed-formatter
version: f4f7d41649cf1e75e736884da8d05324aa76ea25
- name: github.com/mattn/go-colorable
version: 9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59
version: 6c903ff4aa50920ca86087a280590b36b3152b9c
- name: github.com/mattn/go-isatty
version: 56b76bdf51f7708750eac80fa38b952bb9f32639
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
- name: github.com/mattn/go-runewidth
version: d6bea18f789704b5f83375793155289da36a3c7f
version: 737072b4e32b7a5018b4a7125da8d12de90e8045
- name: github.com/mattn/go-sqlite3
version: 38ee283dabf11c9cbdb968eebd79b1fa7acbabe6
version: 5510da399572b4962c020184bb291120c0a412e2
- name: github.com/mgutz/ansi
version: c286dcecd19ff979eeb73ea444e479b903f2cfcb
- name: github.com/moul/http2curl
version: b1479103caacaa39319f75e7f57fc545287fca0d
- name: github.com/nsf/termbox-go
version: c45773466a30b680355d6494cc8826113c93cd0f
version: b6acae516ace002cb8105a89024544a1480655a5
- name: github.com/parnurzeal/gorequest
version: 6e8ad4ebdee4bec2934ed5afaaa1c7b877832a17
version: e37b9d1efacf7c94820b29b75dd7d0c2996b3fb1
- name: github.com/rifflock/lfshook
version: 05a24e24fa8d3a2eca8c2baf23aa2d5a2c51490c
version: 3f9d976bd7402de39b46357069fb6325a974572e
- name: github.com/Sirupsen/logrus
version: f3cfb454f4c209e6668c95216c4744b8fddb2356
version: 3ec0642a7fb6488f65b06f9040adc67e3990296a
- name: golang.org/x/crypto
version: c2f4947f41766b144bb09066e919466da5eddeae
version: 9477e0b78b9ac3d0b03822fd95422e2fe07627cd
subpackages:
- ssh
- ssh/agent
- ssh/terminal
- curve25519
- ed25519
- ed25519/internal/edwards25519
- ssh
- ssh/agent
- ssh/terminal
- name: golang.org/x/net
version: f841c39de738b1d0df95b5a7187744f0e03d8112
version: 1d7a0b2100da090d8b02afcfb42f97e2c77e71a4
subpackages:
- context
- publicsuffix
- name: golang.org/x/sys
version: a408501be4d17ee978c04a618e7a1b22af058c0e
version: 9bb9f0998d48b31547d975974935ae9b48c7a03c
subpackages:
- unix
- name: gopkg.in/alexcesaro/quotedprintable.v3
version: 2caba252f4dc53eaf6b553000885530023f54623
- name: gopkg.in/gomail.v2
version: 81ebce5c23dfd25c6c67194b37d3dd3f338c98b1
devImports: []
testImports: []

View File

@@ -12,17 +12,18 @@ import:
- aws/credentials
- aws/session
- service/s3
- package: github.com/boltdb/bolt
- package: github.com/cenkalti/backoff
- package: github.com/google/subcommands
- 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
@@ -33,7 +34,3 @@ import:
subpackages:
- ssh
- ssh/agent
- package: golang.org/x/net
subpackages:
- context
- package: gopkg.in/gomail.v2

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -72,7 +72,7 @@ FreeBSD: pkg<y:LabelModel>
<y:Geometry height="56.0" width="268.0" x="10.0" y="287.8409153761062"/>
<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="32.265625" modelName="custom" textColor="#000000" visible="true" width="260.83984375" x="3.580078125" y="11.8671875">Get upgradable packages
<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="260.83984375" x="3.580078125" y="11.8671875">Check upgradable packages
Debian/Ubuntu: apt-get upgrade --dry-run<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
@@ -168,22 +168,6 @@ FreeBSD: pkg audit<y:LabelModel>
</data>
</node>
<node id="n9">
<data key="d6">
<y:GenericNode configuration="com.yworks.flowchart.dataBase">
<y:Geometry height="64.1719342604298" width="111.96965865992411" x="687.3850119398792" y="807.0697396491782"/>
<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="48.56640625" x="31.701626204962054" y="23.019560880214726">Vuls DB<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="-8.881784197001252E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
</y:GenericNode>
</data>
</node>
<node id="n10">
<data key="d6">
<y:GenericNode configuration="com.yworks.flowchart.dataBase">
<y:Geometry height="65.22882427307195" width="136.83944374209864" x="411.5802781289507" y="687.385587863464"/>
@@ -199,13 +183,13 @@ FreeBSD: pkg audit<y:LabelModel>
</y:GenericNode>
</data>
</node>
<node id="n11">
<node id="n10">
<data key="d6">
<y:GenericNode configuration="com.yworks.flowchart.process">
<y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="716.4553275126422"/>
<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="32.265625" modelName="custom" textColor="#000000" visible="true" width="126.396484375" x="70.8017578125" y="11.8671875">Insert results into DB
<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="152.634765625" x="57.6826171875" y="11.8671875">Write results to JSON files
Reporting<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
@@ -216,13 +200,13 @@ Reporting<y:LabelModel>
</y:GenericNode>
</data>
</node>
<node id="n12">
<node id="n11">
<data key="d6">
<y:GenericNode configuration="com.yworks.flowchart.process">
<y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="287.8409153761062"/>
<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="32.265625" modelName="custom" textColor="#000000" visible="true" width="271.369140625" x="-1.6845703124999432" y="11.8671875">Get all changelogs by using package manager
<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="293.06640625" x="-12.533203124999943" y="11.8671875">Get all changelogs of updatable packages at once
CentOS: yum update --changelog<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
@@ -233,7 +217,7 @@ CentOS: yum update --changelog<y:LabelModel>
</y:GenericNode>
</data>
</node>
<node id="n13">
<node id="n12">
<data key="d6">
<y:GenericNode configuration="com.yworks.flowchart.process">
<y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="373.8409153761062"/>
@@ -364,7 +348,7 @@ FreeBSD<y:LabelModel>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e9" source="n7" target="n11">
<edge id="e9" source="n7" target="n10">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
@@ -374,7 +358,7 @@ FreeBSD<y:LabelModel>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e10" source="n7" target="n10">
<edge id="e10" source="n7" target="n9">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="-134.01566143419018" sy="6.159084623893818" tx="0.0" ty="-29.333162136535975">
@@ -386,18 +370,7 @@ FreeBSD<y:LabelModel>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e11" source="n11" target="n9">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.86721713021484"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="none"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e12" source="n1" target="n12">
<data key="d9"/>
<edge id="e11" source="n1" target="n11">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="20.0" tx="0.0" ty="-28.0"/>
@@ -415,8 +388,7 @@ FreeBSD<y:LabelModel>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e13" source="n12" target="n13">
<data key="d9"/>
<edge id="e12" source="n11" target="n12">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
@@ -426,8 +398,7 @@ FreeBSD<y:LabelModel>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e14" source="n13" target="n7">
<data key="d9"/>
<edge id="e13" source="n12" target="n7">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="134.00000000000006" sy="0.0" tx="0.0" ty="-28.0">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 72 KiB

15
main.go
View File

@@ -22,15 +22,18 @@ import (
"fmt"
"os"
"golang.org/x/net/context"
"context"
"github.com/future-architect/vuls/commands"
"github.com/future-architect/vuls/version"
"github.com/google/subcommands"
_ "github.com/mattn/go-sqlite3"
)
// Version of Vuls
var version = "0.3.0"
// Revision of Git
var revision string
func main() {
subcommands.Register(subcommands.HelpCommand(), "")
subcommands.Register(subcommands.FlagsCommand(), "")
@@ -38,8 +41,8 @@ func main() {
subcommands.Register(&commands.DiscoverCmd{}, "discover")
subcommands.Register(&commands.TuiCmd{}, "tui")
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")
@@ -47,7 +50,7 @@ func main() {
flag.Parse()
if *v {
fmt.Printf("%s %s\n", version.Name, version.Version)
fmt.Printf("vuls %s %s\n", version, revision)
os.Exit(int(subcommands.ExitSuccess))
}

View File

@@ -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,99 +53,164 @@ 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:"-"`
ScanHistoryID uint `json:"-"`
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
Errors []string
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 nil, err
}
known, unknown, ignored := CveInfos{}, CveInfos{}, CveInfos{}
for _, d := range ds {
cinfo := CveInfo{
CveDetail: d,
VulnInfo: set[d.CveID],
}
cinfo.NilSliceToEmpty()
// ignored
found := false
for _, icve := range config.Conf.Servers[r.ServerName].IgnoreCves {
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
func (r ScanResult) ServerInfo() string {
hostinfo := ""
if r.Container.ContainerID == "" {
hostinfo = fmt.Sprintf(
"%s (%s%s)",
r.ServerName,
r.Family,
r.Release,
)
} else {
hostinfo = fmt.Sprintf(
"%s / %s (%s%s) on %s",
r.Container.Name,
r.Container.ContainerID,
r.Family,
r.Release,
r.ServerName,
)
if len(r.Container.ContainerID) == 0 {
return fmt.Sprintf("%s (%s%s)",
r.ServerName, r.Family, r.Release)
}
return hostinfo
return fmt.Sprintf(
"%s / %s (%s%s) on %s",
r.Container.Name,
r.Container.ContainerID,
r.Family,
r.Release,
r.ServerName,
)
}
// ServerInfoTui returns server infromation for TUI sidebar
func (r ScanResult) ServerInfoTui() string {
hostinfo := ""
if r.Container.ContainerID == "" {
hostinfo = fmt.Sprintf(
"%s (%s%s)",
r.ServerName,
r.Family,
r.Release,
)
} else {
hostinfo = fmt.Sprintf(
"|-- %s (%s%s)",
r.Container.Name,
r.Family,
r.Release,
// r.Container.ContainerID,
)
if len(r.Container.ContainerID) == 0 {
return fmt.Sprintf("%s (%s%s)",
r.ServerName, r.Family, r.Release)
}
return hostinfo
return fmt.Sprintf(
"|-- %s (%s%s)",
r.Container.Name,
r.Family,
r.Release,
// r.Container.ContainerID,
)
}
// FormatServerName returns server and container name
func (r ScanResult) FormatServerName() string {
if len(r.Container.ContainerID) == 0 {
return r.ServerName
}
return fmt.Sprintf("%s@%s",
r.Container.Name, r.ServerName)
}
// 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:
@@ -156,24 +219,136 @@ 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
func (r ScanResult) AllCves() []CveInfo {
return append(r.KnownCves, r.UnknownCves...)
}
// NWLink has network link information.
type NWLink struct {
gorm.Model `json:"-"`
ScanResultID uint `json:"-"`
IPAddress string
Netmask string
DevName string
LinkState string
}
// Confidence is a ranking how confident the CVE-ID was deteted correctly
// Score: 0 - 100
type Confidence struct {
Score int
DetectionMethod string
}
func (c Confidence) String() string {
return fmt.Sprintf("%d / %s", c.Score, c.DetectionMethod)
}
const (
// CpeNameMatchStr is a String representation of CpeNameMatch
CpeNameMatchStr = "CpeNameMatch"
// YumUpdateSecurityMatchStr is a String representation of YumUpdateSecurityMatch
YumUpdateSecurityMatchStr = "YumUpdateSecurityMatch"
// PkgAuditMatchStr is a String representation of PkgAuditMatch
PkgAuditMatchStr = "PkgAuditMatch"
// ChangelogExactMatchStr is a String representation of ChangelogExactMatch
ChangelogExactMatchStr = "ChangelogExactMatch"
// ChangelogLenientMatchStr is a String representation of ChangelogLenientMatch
ChangelogLenientMatchStr = "ChangelogLenientMatch"
// FailedToGetChangelog is a String representation of FailedToGetChangelog
FailedToGetChangelog = "FailedToGetChangelog"
// FailedToFindVersionInChangelog is a String representation of FailedToFindVersionInChangelog
FailedToFindVersionInChangelog = "FailedToFindVersionInChangelog"
)
// CpeNameMatch is a ranking how confident the CVE-ID was deteted correctly
var CpeNameMatch = Confidence{100, CpeNameMatchStr}
// YumUpdateSecurityMatch is a ranking how confident the CVE-ID was deteted correctly
var YumUpdateSecurityMatch = Confidence{100, YumUpdateSecurityMatchStr}
// PkgAuditMatch is a ranking how confident the CVE-ID was deteted correctly
var PkgAuditMatch = Confidence{100, PkgAuditMatchStr}
// ChangelogExactMatch is a ranking how confident the CVE-ID was deteted correctly
var ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr}
// ChangelogLenientMatch is a ranking how confident the CVE-ID was deteted correctly
var ChangelogLenientMatch = Confidence{50, ChangelogLenientMatchStr}
// VulnInfos is VulnInfo list, getter/setter, sortable methods.
type VulnInfos []VulnInfo
// VulnInfo holds a vulnerability information and unsecure packages
type VulnInfo struct {
CveID string
Confidence Confidence
Packages PackageInfoList
DistroAdvisories []DistroAdvisory // for Aamazon, RHEL, FreeBSD
CpeNames []string
}
// NilSliceToEmpty set nil slice fields to empty slice to avoid null in JSON
func (v *VulnInfo) NilSliceToEmpty() {
if v.CpeNames == nil {
v.CpeNames = []string{}
}
if v.DistroAdvisories == nil {
v.DistroAdvisories = []DistroAdvisory{}
}
if v.Packages == nil {
v.Packages = PackageInfoList{}
}
}
// 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
@@ -190,26 +365,29 @@ func (c CveInfos) Less(i, j int) bool {
if c[i].CveDetail.CvssScore(lang) == c[j].CveDetail.CvssScore(lang) {
return c[i].CveDetail.CveID < c[j].CveDetail.CveID
}
return c[i].CveDetail.CvssScore(lang) > c[j].CveDetail.CvssScore(lang)
return c[j].CveDetail.CvssScore(lang) < c[i].CveDetail.CvssScore(lang)
}
// CveInfo has Cve Information.
type CveInfo struct {
gorm.Model `json:"-"`
ScanResultID uint `json:"-"`
CveDetail cve.CveDetail
Packages []PackageInfo
DistroAdvisories []DistroAdvisory
CpeNames []CpeName
CveDetail cve.CveDetail
VulnInfo
}
// CpeName has CPE name
type CpeName struct {
gorm.Model `json:"-"`
CveInfoID uint `json:"-"`
Name string
// NilSliceToEmpty set nil slice fields to empty slice to avoid null in JSON
func (c *CveInfo) NilSliceToEmpty() {
if c.CveDetail.Nvd.Cpes == nil {
c.CveDetail.Nvd.Cpes = []cve.Cpe{}
}
if c.CveDetail.Jvn.Cpes == nil {
c.CveDetail.Jvn.Cpes = []cve.Cpe{}
}
if c.CveDetail.Nvd.References == nil {
c.CveDetail.Nvd.References = []cve.Reference{}
}
if c.CveDetail.Jvn.References == nil {
c.CveDetail.Jvn.References = []cve.Reference{}
}
}
// PackageInfoList is slice of PackageInfo
@@ -253,6 +431,36 @@ 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
set := make(map[string]bool)
for _, p := range ps {
if len(p.NewVersion) != 0 && !set[p.Name] {
count++
set[p.Name] = true
}
}
return count
}
// FormatUpdatablePacksSummary returns a summary of updatable packages
func (ps PackageInfoList) FormatUpdatablePacksSummary() 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 {
@@ -270,17 +478,30 @@ func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found boo
// return PackageInfo{}, false
// }
// PackageInfosByName implements sort.Interface for []PackageInfo based on
// the Name field.
type PackageInfosByName []PackageInfo
func (a PackageInfosByName) Len() int { return len(a) }
func (a PackageInfosByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
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:"-"`
CveInfoID uint `json:"-"`
Name string
Version string
Release string
Name string
Version string
Release string
NewVersion string
NewRelease string
Repository string
Changelog Changelog
}
// Changelog has contents of changelog and how to get it.
// Method: modesl.detectionMethodStr
type Changelog struct {
Contents string
Method string
}
// ToStringCurrentVersion returns package name-version-release
@@ -309,9 +530,6 @@ func (p PackageInfo) ToStringNewVersion() string {
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
type DistroAdvisory struct {
gorm.Model `json:"-"`
CveInfoID uint `json:"-"`
AdvisoryID string
Severity string
Issued time.Time
@@ -320,18 +538,14 @@ type DistroAdvisory struct {
// Container has Container information
type Container struct {
gorm.Model `json:"-"`
ScanResultID uint `json:"-"`
ContainerID string
Name string
Image string
Type string
}
// Platform has platform information
type Platform struct {
gorm.Model `json:"-"`
ScanResultID uint `json:"-"`
Name string // aws or azure or gcp or other...
InstanceID string
}

View File

@@ -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)
}
}
}

View File

@@ -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 := formatOneLineSummary(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(formatShortPlainText(r))
if err := createBlockBlob(cli, k, b); err != nil {
return err
}
}
if c.Conf.FormatFullText {
k := key + "_full.txt"
b := []byte(formatFullPlainText(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("20060102_1504")
name := ""
if res.Container.ContainerID == "" {
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
}

137
report/email.go Normal file
View File

@@ -0,0 +1,137 @@
/* 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"
"net"
"net/mail"
"net/smtp"
"strings"
"time"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
)
// EMailWriter send mail
type EMailWriter struct{}
func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
conf := config.Conf
var message string
var totalResult models.ScanResult
sender := NewEMailSender()
for _, r := range rs {
if conf.FormatOneEMail {
message += formatFullPlainText(r) + "\r\n\r\n"
totalResult.KnownCves = append(totalResult.KnownCves, r.KnownCves...)
totalResult.UnknownCves = append(totalResult.UnknownCves, r.UnknownCves...)
} else {
var subject string
if len(r.Errors) != 0 {
subject = fmt.Sprintf("%s%s An error occurred while scanning",
conf.EMail.SubjectPrefix, r.ServerInfo())
} else {
subject = fmt.Sprintf("%s%s %s",
conf.EMail.SubjectPrefix, r.ServerInfo(), r.CveSummary())
}
message = formatFullPlainText(r)
if err := sender.Send(subject, message); err != nil {
return err
}
}
}
if conf.FormatOneEMail {
message = fmt.Sprintf(
`
One Line Summary
================
%s
%s`,
formatOneLineSummary(rs...), message)
subject := fmt.Sprintf("%s %s",
conf.EMail.SubjectPrefix,
totalResult.CveSummary(),
)
return sender.Send(subject, message)
}
return nil
}
// EMailSender is interface of sending e-mail
type EMailSender interface {
Send(subject, body string) error
}
type emailSender struct {
conf config.SMTPConf
send func(string, smtp.Auth, string, []string, []byte) error
}
func (e *emailSender) Send(subject, body string) (err error) {
emailConf := e.conf
to := strings.Join(emailConf.To[:], ", ")
cc := strings.Join(emailConf.Cc[:], ", ")
mailAddresses := append(emailConf.To, emailConf.Cc...)
if _, err := mail.ParseAddressList(strings.Join(mailAddresses[:], ", ")); err != nil {
return fmt.Errorf("Failed to parse email addresses: %s", err)
}
headers := make(map[string]string)
headers["From"] = emailConf.From
headers["To"] = to
headers["Cc"] = cc
headers["Subject"] = subject
headers["Date"] = time.Now().Format(time.RFC1123Z)
headers["Content-Type"] = "text/plain; charset=utf-8"
var header string
for k, v := range headers {
header += fmt.Sprintf("%s: %s\r\n", k, v)
}
message := fmt.Sprintf("%s\r\n%s", header, body)
smtpServer := net.JoinHostPort(emailConf.SMTPAddr, emailConf.SMTPPort)
err = e.send(
smtpServer,
smtp.PlainAuth(
"",
emailConf.User,
emailConf.Password,
emailConf.SMTPAddr,
),
emailConf.From,
mailAddresses,
[]byte(message),
)
if err != nil {
return fmt.Errorf("Failed to send emails: %s", err)
}
return nil
}
// NewEMailSender creates emailSender
func NewEMailSender() EMailSender {
return &emailSender{config.Conf.EMail, smtp.SendMail}
}

132
report/email_test.go Normal file
View File

@@ -0,0 +1,132 @@
/* 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 (
"net/smtp"
"reflect"
"strings"
"testing"
"github.com/future-architect/vuls/config"
)
type emailRecorder struct {
addr string
auth smtp.Auth
from string
to []string
body string
}
type mailTest struct {
in config.SMTPConf
out emailRecorder
}
var mailTests = []mailTest{
{
config.SMTPConf{
SMTPAddr: "127.0.0.1",
SMTPPort: "25",
From: "from@address.com",
To: []string{"to@address.com"},
Cc: []string{"cc@address.com"},
},
emailRecorder{
addr: "127.0.0.1:25",
auth: smtp.PlainAuth("", "", "", "127.0.0.1"),
from: "from@address.com",
to: []string{"to@address.com", "cc@address.com"},
body: "body",
},
},
{
config.SMTPConf{
SMTPAddr: "127.0.0.1",
SMTPPort: "25",
User: "vuls",
Password: "password",
From: "from@address.com",
To: []string{"to1@address.com", "to2@address.com"},
Cc: []string{"cc1@address.com", "cc2@address.com"},
},
emailRecorder{
addr: "127.0.0.1:25",
auth: smtp.PlainAuth(
"",
"vuls",
"password",
"127.0.0.1",
),
from: "from@address.com",
to: []string{"to1@address.com", "to2@address.com",
"cc1@address.com", "cc2@address.com"},
body: "body",
},
},
}
func TestSend(t *testing.T) {
for i, test := range mailTests {
f, r := mockSend(nil)
sender := &emailSender{conf: test.in, send: f}
subject := "subject"
body := "body"
if err := sender.Send(subject, body); err != nil {
t.Errorf("unexpected error: %s", err)
}
if r.addr != test.out.addr {
t.Errorf("#%d: wrong 'addr' field.\r\nexpected: %s\n got: %s", i, test.out.addr, r.addr)
}
if !reflect.DeepEqual(r.auth, test.out.auth) {
t.Errorf("#%d: wrong 'auth' field.\r\nexpected: %v\n got: %v", i, test.out.auth, r.auth)
}
if r.from != test.out.from {
t.Errorf("#%d: wrong 'from' field.\r\nexpected: %v\n got: %v", i, test.out.from, r.from)
}
if !reflect.DeepEqual(r.to, test.out.to) {
t.Errorf("#%d: wrong 'to' field.\r\nexpected: %v\n got: %v", i, test.out.to, r.to)
}
if r.body != test.out.body {
t.Errorf("#%d: wrong 'body' field.\r\nexpected: %v\n got: %v", i, test.out.body, r.body)
}
}
}
func mockSend(errToReturn error) (func(string, smtp.Auth, string, []string, []byte) error, *emailRecorder) {
r := new(emailRecorder)
return func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
// Split into header and body
messages := strings.Split(string(msg), "\r\n\r\n")
body := messages[1]
*r = emailRecorder{addr, a, from, to, body}
return errToReturn
}, r
}

View File

@@ -1,62 +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"
"path/filepath"
"github.com/future-architect/vuls/models"
)
// JSONWriter writes results to file.
type JSONWriter struct{}
func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
path, err := ensureResultDir()
var jsonBytes []byte
if jsonBytes, err = json.Marshal(scanResults); err != nil {
return fmt.Errorf("Failed to Marshal to JSON: %s", err)
}
all := filepath.Join(path, "all.json")
if err := ioutil.WriteFile(all, jsonBytes, 0644); err != nil {
return fmt.Errorf("Failed to write JSON. path: %s, err: %s", all, err)
}
for _, r := range scanResults {
jsonPath := ""
if r.Container.ContainerID == "" {
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, 0644); err != nil {
return fmt.Errorf("Failed to write JSON. path: %s, err: %s", jsonPath, err)
}
}
return nil
}

135
report/localfile.go Normal file
View File

@@ -0,0 +1,135 @@
/* 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 := formatOneLineSummary(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 {
var p string
if c.Conf.Diff {
p = path + "_diff.json"
} else {
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 {
var p string
if c.Conf.Diff {
p = path + "_short_diff.txt"
} else {
p = path + "_short.txt"
}
if err := writeFile(
p, []byte(formatShortPlainText(r)), 0600); err != nil {
return fmt.Errorf(
"Failed to write text files. path: %s, err: %s", p, err)
}
}
if c.Conf.FormatFullText {
var p string
if c.Conf.Diff {
p = path + "_full_diff.txt"
} else {
p = path + "_full.txt"
}
if err := writeFile(
p, []byte(formatFullPlainText(r)), 0600); err != nil {
return fmt.Errorf(
"Failed to write text files. path: %s, err: %s", p, err)
}
}
if c.Conf.FormatXML {
var p string
if c.Conf.Diff {
p = path + "_diff.xml"
} else {
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
}

View File

@@ -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, 0666)
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
}

View File

@@ -1,70 +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 (
"crypto/tls"
"fmt"
"strconv"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"gopkg.in/gomail.v2"
)
// MailWriter send mail
type MailWriter struct{}
func (w MailWriter) Write(scanResults []models.ScanResult) (err error) {
conf := config.Conf
for _, s := range scanResults {
m := gomail.NewMessage()
m.SetHeader("From", conf.Mail.From)
m.SetHeader("To", conf.Mail.To...)
m.SetHeader("Cc", conf.Mail.Cc...)
subject := fmt.Sprintf("%s%s %s",
conf.Mail.SubjectPrefix,
s.ServerInfo(),
s.CveSummary(),
)
m.SetHeader("Subject", subject)
var body string
if body, err = toPlainText(s); err != nil {
return err
}
m.SetBody("text/plain", body)
port, _ := strconv.Atoi(conf.Mail.SMTPPort)
d := gomail.NewPlainDialer(
conf.Mail.SMTPAddr,
port,
conf.Mail.User,
conf.Mail.Password,
)
d.TLSConfig = &tls.Config{
InsecureSkipVerify: true,
}
if err := d.DialAndSend(m); err != nil {
panic(err)
}
}
return nil
}

View File

@@ -20,11 +20,14 @@ package report
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
@@ -32,6 +35,83 @@ import (
"github.com/future-architect/vuls/models"
)
// S3Writer writes results to S3
type S3Writer struct{}
func getS3() *s3.S3 {
Config := &aws.Config{
Region: aws.String(c.Conf.AwsRegion),
Credentials: credentials.NewChainCredentials([]credentials.Provider{
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: c.Conf.AwsProfile},
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(session.New())},
}),
}
return s3.New(session.New(Config))
}
// 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 := formatOneLineSummary(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 := formatShortPlainText(r)
if err := putObject(svc, k, []byte(text)); err != nil {
return err
}
}
if c.Conf.FormatFullText {
k := key + "_full.txt"
text := formatFullPlainText(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,56 +137,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("20060102_1504")
key := fmt.Sprintf("%s/%s", timestr, "all.json")
_, err = svc.PutObject(&s3.PutObjectInput{
if _, 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)
}
for _, r := range scanResults {
key := ""
if r.Container.ContainerID == "" {
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)
}
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
}

View File

@@ -20,6 +20,7 @@ package report
import (
"encoding/json"
"fmt"
"sort"
"strings"
"time"
@@ -56,59 +57,109 @@ 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),
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
Channel: channel,
Attachments: toSlackAttachments(s),
}
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 len(errs) > 0 {
return errs[0]
}
if 0 < len(r.Errors) {
serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
notifyUsers := getNotifyUsers(config.Conf.Slack.NotifyUsers)
txt := fmt.Sprintf("%s\n%s\nError: %s", notifyUsers, serverInfo, r.Errors)
msg := message{
Text: txt,
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
Channel: channel,
}
return nil
if err := send(msg); err != nil {
return err
}
continue
}
notify := func(err error, t time.Duration) {
log.Warn("Retrying in ", t)
// A maximum of 100 attachments are allowed on a message.
// Split into chunks with 100 elements
// https://api.slack.com/methods/chat.postMessage
maxAttachments := 100
m := map[int][]*attachment{}
for i, a := range toSlackAttachments(r) {
m[i/maxAttachments] = append(m[i/maxAttachments], a)
}
if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
return fmt.Errorf("HTTP Error: %s", err)
chunkKeys := []int{}
for k := range m {
chunkKeys = append(chunkKeys, k)
}
sort.Ints(chunkKeys)
for i, k := range chunkKeys {
txt := ""
if i == 0 {
txt = msgText(r)
}
msg := message{
Text: txt,
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
Channel: channel,
Attachments: m[k],
}
if err := send(msg); err != nil {
return err
}
}
}
return nil
}
func msgText(r models.ScanResult) string {
func send(msg message) error {
conf := config.Conf.Slack
count, retryMax := 0, 10
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 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
count++
if count == retryMax {
return nil
}
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.Warnf("Error %s", err)
log.Warn("Retrying in ", t)
}
boff := backoff.NewExponentialBackOff()
if err := backoff.RetryNotify(f, boff, notify); err != nil {
return fmt.Errorf("HTTP error: %s", err)
}
if count == retryMax {
return fmt.Errorf("Retry count exceeded")
}
return nil
}
func msgText(r models.ScanResult) string {
notifyUsers := ""
if 0 < len(r.KnownCves) || 0 < len(r.UnknownCves) {
notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers)
}
serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, serverInfo, r.CveSummary())
}
func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
cves := scanResult.KnownCves
if !config.Conf.IgnoreUnscoredCves {
cves = append(cves, scanResult.UnknownCves...)
@@ -121,8 +172,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{}
@@ -170,41 +221,54 @@ func color(cvssScore float64) string {
}
func attachmentText(cveInfo models.CveInfo, osFamily string) string {
linkText := links(cveInfo, osFamily)
switch {
case config.Conf.Lang == "ja" &&
0 < cveInfo.CveDetail.Jvn.CvssScore():
jvn := cveInfo.CveDetail.Jvn
return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s\n*Confidence:* %v",
cveInfo.CveDetail.CvssScore(config.Conf.Lang),
jvn.CvssSeverity(),
fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, jvn.CvssVector()),
fmt.Sprintf(cvssV2CalcURLTemplate,
cveInfo.CveDetail.CveID, jvn.CvssVector()),
jvn.CvssVector(),
jvn.CveTitle(),
linkText,
cveInfo.VulnInfo.Confidence,
)
case 0 < cveInfo.CveDetail.CvssScore("en"):
nvd := cveInfo.CveDetail.Nvd
return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s\n*Confidence:* %v",
cveInfo.CveDetail.CvssScore(config.Conf.Lang),
nvd.CvssSeverity(),
fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, nvd.CvssVector()),
fmt.Sprintf(cvssV2CalcURLTemplate,
cveInfo.CveDetail.CveID, nvd.CvssVector()),
nvd.CvssVector(),
nvd.CveSummary(),
linkText,
cveInfo.VulnInfo.Confidence,
)
default:
nvd := cveInfo.CveDetail.Nvd
return fmt.Sprintf("?\n%s\n%s", nvd.CveSummary(), linkText)
return fmt.Sprintf("?\n%s\n%s\n*Confidence:* %v",
nvd.CveSummary(), linkText, cveInfo.VulnInfo.Confidence)
}
}
func links(cveInfo models.CveInfo, osFamily string) string {
links := []string{}
cweID := cveInfo.CveDetail.CweID()
if 0 < len(cweID) {
links = append(links, fmt.Sprintf("<%s|%s>",
cweURL(cweID), cweID))
if config.Conf.Lang == "ja" {
links = append(links, fmt.Sprintf("<%s|%s(JVN)>",
cweJvnURL(cweID), cweID))
}
}
cveID := cveInfo.CveDetail.CveID
if config.Conf.Lang == "ja" && 0 < len(cveInfo.CveDetail.Jvn.Link()) {
jvn := fmt.Sprintf("<%s|JVN>", cveInfo.CveDetail.Jvn.Link())

View File

@@ -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.Println("One Line Summary")
fmt.Println("================")
fmt.Printf("%s\n", formatScanSummary(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(formatOneLineSummary(rs...))
fmt.Print("\n")
}
if c.Conf.FormatShortText {
for _, r := range rs {
fmt.Println(formatShortPlainText(r))
}
}
if c.Conf.FormatFullText {
for _, r := range rs {
fmt.Println(formatFullPlainText(r))
}
fmt.Println(text)
}
return nil
}

View File

@@ -1,63 +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"
"github.com/future-architect/vuls/models"
)
// TextFileWriter writes results to file.
type TextFileWriter struct{}
func (w TextFileWriter) Write(scanResults []models.ScanResult) (err error) {
path, err := ensureResultDir()
all := []string{}
for _, r := range scanResults {
textFilePath := ""
if r.Container.ContainerID == "" {
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, 0644); 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, 0644); err != nil {
return fmt.Errorf("Failed to write text files. path: %s, err: %s", allPath, err)
}
return nil
}

View File

@@ -20,13 +20,13 @@ package report
import (
"bytes"
"fmt"
"os"
"strings"
"text/template"
"time"
log "github.com/Sirupsen/logrus"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/db"
"github.com/future-architect/vuls/models"
"github.com/google/subcommands"
"github.com/gosuri/uitable"
@@ -40,45 +40,33 @@ var currentCveInfo int
var currentDetailLimitY int
// RunTui execute main logic
func RunTui(historyID string) subcommands.ExitStatus {
var err error
scanHistory, err = selectScanHistory(historyID)
if err != nil {
log.Fatal(err)
return subcommands.ExitFailure
}
func RunTui(history models.ScanHistory) subcommands.ExitStatus {
scanHistory = history
g := gocui.NewGui()
if err := g.Init(); err != nil {
log.Panicln(err)
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Errorf("%s", err)
return subcommands.ExitFailure
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
if err := keybindings(g); err != nil {
log.Panicln(err)
log.Errorf("%s", err)
return subcommands.ExitFailure
}
g.SelBgColor = gocui.ColorGreen
g.SelFgColor = gocui.ColorBlack
g.Cursor = true
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
return subcommands.ExitFailure
if err := g.MainLoop(); err != nil {
g.Close()
log.Errorf("%s", err)
os.Exit(1)
}
return subcommands.ExitSuccess
}
func selectScanHistory(historyID string) (latest models.ScanHistory, err error) {
if err := db.OpenDB(); err != nil {
return latest, fmt.Errorf(
"Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
}
latest, err = db.SelectScanHistory(historyID)
return
}
func keybindings(g *gocui.Gui) (err error) {
errs := []error{}
@@ -145,6 +133,27 @@ func keybindings(g *gocui.Gui) (err error) {
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, nextView))
// changelog
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyTab, gocui.ModNone, nextView))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlQ, gocui.ModNone, previousView))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlH, gocui.ModNone, nextView))
// errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlL, gocui.ModNone, nextView))
// errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowUp, gocui.ModAlt, previousView))
// errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowLeft, gocui.ModAlt, nextView))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeySpace, gocui.ModNone, cursorPageDown))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
// errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyEnter, gocui.ModNone, nextView))
// errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, showMsg))
@@ -163,35 +172,45 @@ func keybindings(g *gocui.Gui) (err error) {
}
func nextView(g *gocui.Gui, v *gocui.View) error {
var err error
if v == nil {
return g.SetCurrentView("side")
_, err = g.SetCurrentView("side")
}
switch v.Name() {
case "side":
return g.SetCurrentView("summary")
_, err = g.SetCurrentView("summary")
case "summary":
return g.SetCurrentView("detail")
_, err = g.SetCurrentView("detail")
case "detail":
return g.SetCurrentView("side")
_, err = g.SetCurrentView("changelog")
case "changelog":
_, err = g.SetCurrentView("side")
default:
return g.SetCurrentView("summary")
_, err = g.SetCurrentView("summary")
}
return err
}
func previousView(g *gocui.Gui, v *gocui.View) error {
var err error
if v == nil {
return g.SetCurrentView("side")
_, err = g.SetCurrentView("side")
}
switch v.Name() {
case "side":
return g.SetCurrentView("side")
_, err = g.SetCurrentView("side")
case "summary":
return g.SetCurrentView("side")
_, err = g.SetCurrentView("side")
case "detail":
return g.SetCurrentView("summary")
_, err = g.SetCurrentView("summary")
case "changelog":
_, err = g.SetCurrentView("detail")
default:
return g.SetCurrentView("side")
_, err = g.SetCurrentView("side")
}
return err
}
func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
@@ -203,7 +222,7 @@ func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
}
return true, yLimit
case "summary":
yLimit = len(currentScanResult.KnownCves) - 1
yLimit = len(currentScanResult.AllCves()) - 1
if yLimit < nextY {
return false, yLimit
}
@@ -213,6 +232,11 @@ func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
return false, currentDetailLimitY
}
return true, currentDetailLimitY
case "changelog":
if currentDetailLimitY < nextY {
return false, currentDetailLimitY
}
return true, currentDetailLimitY
default:
return true, 0
}
@@ -223,7 +247,7 @@ func pageUpDownJumpCount(v *gocui.View) int {
switch v.Name() {
case "side", "summary":
jump = 8
case "detail":
case "detail", "changelog":
jump = 30
default:
jump = 8
@@ -238,6 +262,9 @@ func onMovingCursorRedrawView(g *gocui.Gui, v *gocui.View) error {
if err := redrawDetail(g); err != nil {
return err
}
if err := redrawChangelog(g); err != nil {
return err
}
case "side":
if err := changeHost(g, v); err != nil {
return err
@@ -332,7 +359,7 @@ func cursorUp(g *gocui.Gui, v *gocui.View) error {
if v != nil {
ox, oy := v.Origin()
cx, cy := v.Cursor()
if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
if err := v.SetCursor(cx, cy-1); err != nil && 0 < oy {
if err := v.SetOrigin(ox, oy-1); err != nil {
return err
}
@@ -360,7 +387,7 @@ func cursorPageUp(g *gocui.Gui, v *gocui.View) error {
func previousSummary(g *gocui.Gui, v *gocui.View) error {
if v != nil {
// cursor to summary
if err := g.SetCurrentView("summary"); err != nil {
if _, err := g.SetCurrentView("summary"); err != nil {
return err
}
// move next line
@@ -368,7 +395,7 @@ func previousSummary(g *gocui.Gui, v *gocui.View) error {
return err
}
// cursor to detail
if err := g.SetCurrentView("detail"); err != nil {
if _, err := g.SetCurrentView("detail"); err != nil {
return err
}
}
@@ -378,7 +405,7 @@ func previousSummary(g *gocui.Gui, v *gocui.View) error {
func nextSummary(g *gocui.Gui, v *gocui.View) error {
if v != nil {
// cursor to summary
if err := g.SetCurrentView("summary"); err != nil {
if _, err := g.SetCurrentView("summary"); err != nil {
return err
}
// move next line
@@ -386,7 +413,7 @@ func nextSummary(g *gocui.Gui, v *gocui.View) error {
return err
}
// cursor to detail
if err := g.SetCurrentView("detail"); err != nil {
if _, err := g.SetCurrentView("detail"); err != nil {
return err
}
}
@@ -401,6 +428,9 @@ func changeHost(g *gocui.Gui, v *gocui.View) error {
if err := g.DeleteView("detail"); err != nil {
return err
}
if err := g.DeleteView("changelog"); err != nil {
return err
}
_, cy := v.Cursor()
l, err := v.Line(cy)
@@ -422,6 +452,9 @@ func changeHost(g *gocui.Gui, v *gocui.View) error {
if err := setDetailLayout(g); err != nil {
return err
}
if err := setChangelogLayout(g); err != nil {
return err
}
return nil
}
@@ -436,6 +469,17 @@ func redrawDetail(g *gocui.Gui) error {
return nil
}
func redrawChangelog(g *gocui.Gui) error {
if err := g.DeleteView("changelog"); err != nil {
return err
}
if err := setChangelogLayout(g); err != nil {
return err
}
return nil
}
func getLine(g *gocui.Gui, v *gocui.View) error {
var l string
var err error
@@ -451,7 +495,7 @@ func getLine(g *gocui.Gui, v *gocui.View) error {
return err
}
fmt.Fprintln(v, l)
if err := g.SetCurrentView("msg"); err != nil {
if _, err := g.SetCurrentView("msg"); err != nil {
return err
}
}
@@ -473,7 +517,7 @@ func showMsg(g *gocui.Gui, v *gocui.View) error {
return err
}
fmt.Fprintln(v, l)
if err := g.SetCurrentView("msg"); err != nil {
if _, err := g.SetCurrentView("msg"); err != nil {
return err
}
}
@@ -484,7 +528,7 @@ func delMsg(g *gocui.Gui, v *gocui.View) error {
if err := g.DeleteView("msg"); err != nil {
return err
}
if err := g.SetCurrentView("summary"); err != nil {
if _, err := g.SetCurrentView("summary"); err != nil {
return err
}
return nil
@@ -504,12 +548,15 @@ func layout(g *gocui.Gui) error {
if err := setDetailLayout(g); err != nil {
return err
}
if err := setChangelogLayout(g); err != nil {
return err
}
return nil
}
func setSideLayout(g *gocui.Gui) error {
_, maxY := g.Size()
if v, err := g.SetView("side", -1, -1, 40, maxY); err != nil {
if v, err := g.SetView("side", -1, -1, 40, int(float64(maxY)*0.2)); err != nil {
if err != gocui.ErrUnknownView {
return err
}
@@ -518,8 +565,11 @@ 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 {
if _, err := g.SetCurrentView("side"); err != nil {
return err
}
}
@@ -533,7 +583,7 @@ func setSummaryLayout(g *gocui.Gui) error {
return err
}
lines := summaryLines(currentScanResult)
lines := summaryLines()
fmt.Fprintf(v, lines)
v.Highlight = true
@@ -543,21 +593,25 @@ func setSummaryLayout(g *gocui.Gui) error {
return nil
}
func summaryLines(data models.ScanResult) string {
func summaryLines() string {
stable := uitable.New()
stable.MaxColWidth = 1000
stable.Wrap = false
if len(currentScanResult.Errors) != 0 {
return "Error: Scan with --debug to view the details"
}
indexFormat := ""
if len(data.KnownCves) < 10 {
if len(currentScanResult.AllCves()) < 10 {
indexFormat = "[%1d]"
} else if len(data.KnownCves) < 100 {
} else if len(currentScanResult.AllCves()) < 100 {
indexFormat = "[%2d]"
} else {
indexFormat = "[%3d]"
}
for i, d := range data.KnownCves {
for i, d := range currentScanResult.AllCves() {
var cols []string
// packs := []string{}
// for _, pack := range d.Packages {
@@ -568,11 +622,9 @@ func summaryLines(data models.ScanResult) string {
cols = []string{
fmt.Sprintf(indexFormat, i+1),
d.CveDetail.CveID,
fmt.Sprintf("| %-4.1f(%s)",
d.CveDetail.CvssScore(config.Conf.Lang),
d.CveDetail.Jvn.CvssSeverity(),
),
// strings.Join(packs, ","),
fmt.Sprintf("| %4.1f",
d.CveDetail.CvssScore(config.Conf.Lang)),
fmt.Sprintf("| %3d |", d.VulnInfo.Confidence.Score),
summary,
}
} else {
@@ -580,18 +632,17 @@ func summaryLines(data models.ScanResult) string {
var cvssScore string
if d.CveDetail.CvssScore("en") <= 0 {
cvssScore = "| ?"
cvssScore = "| ?"
} else {
cvssScore = fmt.Sprintf("| %-4.1f(%s)",
d.CveDetail.CvssScore(config.Conf.Lang),
d.CveDetail.Nvd.CvssSeverity(),
)
cvssScore = fmt.Sprintf("| %4.1f",
d.CveDetail.CvssScore(config.Conf.Lang))
}
cols = []string{
fmt.Sprintf(indexFormat, i+1),
d.CveDetail.CveID,
cvssScore,
fmt.Sprintf("| %3d |", d.VulnInfo.Confidence.Score),
summary,
}
}
@@ -616,14 +667,10 @@ func setDetailLayout(g *gocui.Gui) error {
_, oy := summaryView.Origin()
currentCveInfo = cy + oy
if v, err := g.SetView("detail", 40, int(float64(maxY)*0.2), maxX, maxY); err != nil {
if v, err := g.SetView("detail", -1, int(float64(maxY)*0.2), int(float64(maxX)*0.5), maxY); err != nil {
if err != gocui.ErrUnknownView {
return err
}
// text := report.ToPlainTextDetailsLangEn(
// currentScanResult.KnownCves[currentCveInfo],
// currentScanResult.Family)
text, err := detailLines()
if err != nil {
return err
@@ -637,26 +684,70 @@ func setDetailLayout(g *gocui.Gui) error {
return nil
}
func setChangelogLayout(g *gocui.Gui) error {
maxX, maxY := g.Size()
summaryView, err := g.View("summary")
if err != nil {
return err
}
_, cy := summaryView.Cursor()
_, oy := summaryView.Origin()
currentCveInfo = cy + oy
if v, err := g.SetView("changelog", int(float64(maxX)*0.5), int(float64(maxY)*0.2), maxX, maxY); err != nil {
if err != gocui.ErrUnknownView {
return err
}
if len(currentScanResult.Errors) != 0 || len(currentScanResult.AllCves()) == 0 {
return nil
}
lines := []string{}
cveInfo := currentScanResult.AllCves()[currentCveInfo]
for _, pack := range cveInfo.Packages {
for _, p := range currentScanResult.Packages {
if pack.Name == p.Name {
lines = append(lines, formatOneChangelog(p), "\n")
}
}
}
text := strings.Join(lines, "\n")
fmt.Fprint(v, text)
v.Editable = false
v.Wrap = true
currentDetailLimitY = len(strings.Split(text, "\n")) - 1
}
return nil
}
type dataForTmpl struct {
CveID string
CvssScore string
CvssVector string
CvssSeverity string
Summary string
Confidence models.Confidence
CweURL string
VulnSiteLinks []string
References []cve.Reference
Packages []string
CpeNames []models.CpeName
CpeNames []string
PublishedDate time.Time
LastModifiedDate time.Time
}
func detailLines() (string, error) {
if len(currentScanResult.KnownCves) == 0 {
if len(currentScanResult.Errors) != 0 {
return "", nil
}
if len(currentScanResult.AllCves()) == 0 {
return "No vulnerable packages", nil
}
cveInfo := currentScanResult.KnownCves[currentCveInfo]
cveInfo := currentScanResult.AllCves()[currentCveInfo]
cveID := cveInfo.CveDetail.CveID
tmpl, err := template.New("detail").Parse(detailTemplate())
@@ -682,6 +773,8 @@ func detailLines() (string, error) {
refs = nvd.VulnSiteReferences()
}
cweURL := cweURL(cveInfo.CveDetail.CweID())
links := []string{
fmt.Sprintf("[NVD]( %s )", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID)),
fmt.Sprintf("[MITRE]( %s )", fmt.Sprintf("%s%s", mitreBaseURL, cveID)),
@@ -715,6 +808,8 @@ func detailLines() (string, error) {
CvssSeverity: cvssSeverity,
CvssVector: cvssVector,
Summary: summary,
Confidence: cveInfo.VulnInfo.Confidence,
CweURL: cweURL,
VulnSiteLinks: links,
References: refs,
Packages: packages,
@@ -729,8 +824,6 @@ func detailLines() (string, error) {
return string(buf.Bytes()), nil
}
// * {{.Name}}-{{.Version}}-{{.Release}}
func detailTemplate() string {
return `
{{.CveID}}
@@ -746,14 +839,24 @@ Summary
{{.Summary }}
Confidence
--------------
{{.Confidence }}
CWE
--------------
{{.CweURL }}
Package/CPE
--------------
{{range $pack := .Packages -}}
* {{$pack}}
{{end -}}
{{range .CpeNames -}}
* {{.Name}}
{{range $name := .CpeNames -}}
* {{$name}}
{{end}}
Links
--------------

View File

@@ -20,88 +20,68 @@ 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() (path string, err error) {
if resultDirPath != "" {
return resultDirPath, nil
}
const maxColWidth = 80
const timeLayout = "20060102_1504"
timedir := time.Now().Format(timeLayout)
wd, _ := os.Getwd()
dir := filepath.Join(wd, "results", timedir)
if err := os.MkdirAll(dir, 0755); err != nil {
return "", fmt.Errorf("Failed to create dir: %s", err)
}
symlinkPath := filepath.Join(wd, "results", "current")
if _, err := os.Stat(symlinkPath); err == nil {
if err := os.Remove(symlinkPath); err != nil {
return "", fmt.Errorf(
"Failed to remove symlink. path: %s, err: %s", symlinkPath, err)
func formatScanSummary(rs ...models.ScanResult) string {
table := uitable.New()
table.MaxColWidth = maxColWidth
table.Wrap = true
for _, r := range rs {
var cols []interface{}
if len(r.Errors) == 0 {
cols = []interface{}{
r.FormatServerName(),
fmt.Sprintf("%s%s", r.Family, r.Release),
fmt.Sprintf("%d CVEs", len(r.ScannedCves)),
r.Packages.FormatUpdatablePacksSummary(),
}
} else {
cols = []interface{}{
r.FormatServerName(),
"Error",
"",
"Run with --debug to view the details",
}
}
table.AddRow(cols...)
}
if err := os.Symlink(dir, symlinkPath); err != nil {
return "", fmt.Errorf(
"Failed to create symlink: path: %s, err: %s", symlinkPath, err)
}
return dir, 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 formatOneLineSummary(rs ...models.ScanResult) string {
table := uitable.New()
table.MaxColWidth = maxColWidth
table.Wrap = true
for _, r := range rs {
var cols []interface{}
if len(r.Errors) == 0 {
cols = []interface{}{
r.FormatServerName(),
r.CveSummary(),
r.Packages.FormatUpdatablePacksSummary(),
}
} else {
cols = []interface{}{
r.FormatServerName(),
"Error: Scan with --debug to view the details",
"",
}
}
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 formatShortPlainText(r models.ScanResult) string {
stable := uitable.New()
stable.MaxColWidth = 84
stable.MaxColWidth = maxColWidth
stable.Wrap = true
cves := r.KnownCves
@@ -109,14 +89,52 @@ 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.FormatUpdatablePacksSummary(),
)
if len(r.Errors) != 0 {
return fmt.Sprintf(
"%s\nError: Scan with --debug to view the details\n%s\n\n",
header, r.Errors)
}
if len(cves) == 0 {
return fmt.Sprintf(`
%s
No CVE-IDs are found in updatable packages.
%s
`, header, r.Packages.FormatUpdatablePacksSummary())
}
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%sConfidence: %v",
d.CveDetail.Jvn.CveTitle(),
d.CveDetail.Jvn.Link(),
distroLinks(d, r.Family)[0].url,
packsVer,
d.VulnInfo.Confidence,
)
scols = []string{
d.CveDetail.CveID,
fmt.Sprintf("%-4.1f (%s)",
@@ -125,8 +143,16 @@ 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%sConfidence: %v",
d.CveDetail.Nvd.CveSummary(),
cveDetailsBaseURL,
d.CveDetail.CveID,
distroLinks(d, r.Family)[0].url,
packsVer,
d.VulnInfo.Confidence,
)
scols = []string{
d.CveDetail.CveID,
fmt.Sprintf("%-4.1f (%s)",
@@ -136,10 +162,12 @@ func ToPlainTextSummary(r models.ScanResult) string {
summary,
}
default:
summary := fmt.Sprintf("%s\n%sConfidence: %v",
distroLinks(d, r.Family)[0].url, packsVer, d.VulnInfo.Confidence)
scols = []string{
d.CveDetail.CveID,
"?",
d.CveDetail.Nvd.CveSummary(),
summary,
}
}
@@ -148,45 +176,94 @@ 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 formatFullPlainText(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.FormatUpdatablePacksSummary(),
)
if len(r.Errors) != 0 {
return fmt.Sprintf(
"%s\nError: Scan with --debug to view the details\n%s\n\n",
header, r.Errors)
}
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.FormatUpdatablePacksSummary())
}
scoredReport, unscoredReport := []string{}, []string{}
scoredReport, unscoredReport = formatPlainTextDetails(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%s", header, detail, formatChangelogs(r))
}
func formatPlainTextDetails(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() {
scoredReport = append(
scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
scoredReport, formatPlainTextDetailsLangEn(cve, osFamily))
} else {
scoredReport = append(
scoredReport, toPlainTextUnknownCve(cve, osFamily))
scoredReport, formatPlainTextUnknownCve(cve, osFamily))
}
case "ja":
if 0 < cve.CveDetail.Jvn.CvssScore() {
scoredReport = append(
scoredReport, toPlainTextDetailsLangJa(cve, osFamily))
scoredReport, formatPlainTextDetailsLangJa(cve, osFamily))
} else if 0 < cve.CveDetail.Nvd.CvssScore() {
scoredReport = append(
scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
scoredReport, formatPlainTextDetailsLangEn(cve, osFamily))
} else {
scoredReport = append(
scoredReport, toPlainTextUnknownCve(cve, osFamily))
scoredReport, formatPlainTextUnknownCve(cve, osFamily))
}
}
}
for _, cve := range data.UnknownCves {
for _, cve := range r.UnknownCves {
unscoredReport = append(
unscoredReport, toPlainTextUnknownCve(cve, osFamily))
unscoredReport, formatPlainTextUnknownCve(cve, osFamily))
}
return
}
func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
func formatPlainTextUnknownCve(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("-------------")
@@ -200,19 +277,20 @@ 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)
dtable.AddRow("Confidence", cveInfo.VulnInfo.Confidence)
return fmt.Sprintf("%s", dtable)
}
func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
func formatPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
cveDetail := cveInfo.CveDetail
cveID := cveDetail.CveID
jvn := cveDetail.Jvn
dtable := uitable.New()
//TODO resize
dtable.MaxColWidth = 100
dtable.MaxColWidth = maxColWidth
dtable.Wrap = true
dtable.AddRow(cveID)
dtable.AddRow("-------------")
@@ -228,6 +306,8 @@ func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
dtable.AddRow("Vector", jvn.CvssVector())
dtable.AddRow("Title", jvn.CveTitle())
dtable.AddRow("Description", jvn.CveSummary())
dtable.AddRow(cveDetail.CweID(), cweURL(cveDetail.CweID()))
dtable.AddRow(cveDetail.CweID()+"(JVN)", cweJvnURL(cveDetail.CweID()))
dtable.AddRow("JVN", jvn.Link())
dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
@@ -242,18 +322,18 @@ func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
dtable = addPackageInfos(dtable, cveInfo.Packages)
dtable = addCpeNames(dtable, cveInfo.CpeNames)
dtable.AddRow("Confidence", cveInfo.VulnInfo.Confidence)
return fmt.Sprintf("%s", dtable)
}
func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
func formatPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
cveDetail := d.CveDetail
cveID := cveDetail.CveID
nvd := cveDetail.Nvd
dtable := uitable.New()
//TODO resize
dtable.MaxColWidth = 100
dtable.MaxColWidth = maxColWidth
dtable.Wrap = true
dtable.AddRow(cveID)
dtable.AddRow("-------------")
@@ -270,6 +350,8 @@ func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
dtable.AddRow("Vector", nvd.CvssVector())
dtable.AddRow("Summary", nvd.CveSummary())
dtable.AddRow("CWE", cweURL(cveDetail.CweID()))
dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
dtable.AddRow("MITRE", fmt.Sprintf("%s%s", mitreBaseURL, cveID))
dtable.AddRow("CVE Details", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
@@ -281,6 +363,7 @@ func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
}
dtable = addPackageInfos(dtable, d.Packages)
dtable = addCpeNames(dtable, d.CpeNames)
dtable.AddRow("Confidence", d.VulnInfo.Confidence)
return fmt.Sprintf("%s\n", dtable)
}
@@ -310,6 +393,21 @@ func distroLinks(cveInfo models.CveInfo, osFamily string) []distroLink {
})
}
return links
case "oraclelinux":
links := []distroLink{
{
"Oracle-CVE",
fmt.Sprintf(oracleSecurityBaseURL, cveID),
},
}
for _, advisory := range cveInfo.DistroAdvisories {
links = append(links, distroLink{
// "Oracle-ELSA"
advisory.AdvisoryID,
fmt.Sprintf(oracleELSABaseBaseURL, advisory.AdvisoryID),
})
}
return links
case "amazon":
links := []distroLink{
{
@@ -355,13 +453,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())
@@ -370,9 +467,58 @@ 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
}
func cweURL(cweID string) string {
return fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html",
strings.TrimPrefix(cweID, "CWE-"))
}
func cweJvnURL(cweID string) string {
return fmt.Sprintf("http://jvndb.jvn.jp/ja/cwe/%s.html", cweID)
}
func formatChangelogs(r models.ScanResult) string {
buf := []string{}
for _, p := range r.Packages {
if p.NewVersion == "" {
continue
}
clog := formatOneChangelog(p)
buf = append(buf, clog, "\n\n")
}
return strings.Join(buf, "\n")
}
func formatOneChangelog(p models.PackageInfo) string {
buf := []string{}
if p.NewVersion == "" {
return ""
}
packVer := fmt.Sprintf("%s -> %s",
p.ToStringCurrentVersion(), p.ToStringNewVersion())
var delim bytes.Buffer
for i := 0; i < len(packVer); i++ {
delim.WriteString("-")
}
clog := p.Changelog.Contents
if lines := strings.Split(clog, "\n"); len(lines) != 0 {
clog = strings.Join(lines[0:len(lines)-1], "\n")
}
switch p.Changelog.Method {
case models.FailedToGetChangelog:
clog = "No changelogs"
case models.FailedToFindVersionInChangelog:
clog = "Failed to parse changelogs. For detials, check yourself"
}
buf = append(buf, packVer, delim.String(), clog)
return strings.Join(buf, "\n")
}

View File

@@ -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"
@@ -28,16 +33,34 @@ const (
redhatSecurityBaseURL = "https://access.redhat.com/security/cve"
redhatRHSABaseBaseURL = "https://rhn.redhat.com/errata/%s.html"
amazonSecurityBaseURL = "https://alas.aws.amazon.com/%s.html"
oracleSecurityBaseURL = "https://linux.oracle.com/cve/%s.html"
oracleELSABaseBaseURL = "https://linux.oracle.com/errata/%s.html"
ubuntuSecurityBaseURL = "http://people.ubuntu.com/~ubuntu-security/cve"
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
}
var resultDirPath string
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
}

View File

@@ -19,29 +19,29 @@ package scan
import (
"fmt"
"regexp"
"sort"
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/cveapi"
"github.com/future-architect/vuls/models"
)
type base struct {
ServerInfo config.ServerInfo
Distro config.Distro
Platform models.Platform
Family string
Release string
Platform models.Platform
osPackages
log *logrus.Entry
errs []error
}
func (l *base) ssh(cmd string, sudo bool) sshResult {
return sshExec(l.ServerInfo, cmd, sudo, l.log)
func (l *base) exec(cmd string, sudo bool) execResult {
return exec(l.ServerInfo, cmd, sudo, l.log)
}
func (l *base) setServerInfo(c config.ServerInfo) {
@@ -52,13 +52,20 @@ func (l base) getServerInfo() config.ServerInfo {
return l.ServerInfo
}
func (l *base) setDistributionInfo(fam, rel string) {
l.Family = fam
l.Release = rel
func (l *base) setDistro(fam, rel string) {
d := config.Distro{
Family: fam,
Release: rel,
}
l.Distro = d
s := l.getServerInfo()
s.Distro = d
l.setServerInfo(s)
}
func (l base) getDistributionInfo() string {
return fmt.Sprintf("%s %s", l.Family, l.Release)
func (l base) getDistro() config.Distro {
return l.Distro
}
func (l *base) setPlatform(p models.Platform) {
@@ -70,56 +77,83 @@ func (l base) getPlatform() models.Platform {
}
func (l base) allContainers() (containers []config.Container, err error) {
switch l.ServerInfo.Container.Type {
switch l.ServerInfo.Containers.Type {
case "", "docker":
stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}}'")
stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}} {{.Image}}'")
if err != nil {
return containers, err
}
return l.parseDockerPs(stdout)
case "lxd":
stdout, err := l.lxdPs("-c n")
if err != nil {
return containers, err
}
return l.parseLxdPs(stdout)
default:
return containers, fmt.Errorf(
"Not supported yet: %s", l.ServerInfo.Container.Type)
"Not supported yet: %s", l.ServerInfo.Containers.Type)
}
}
func (l *base) runningContainers() (containers []config.Container, err error) {
switch l.ServerInfo.Container.Type {
switch l.ServerInfo.Containers.Type {
case "", "docker":
stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}}'")
stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}} {{.Image}}'")
if err != nil {
return containers, err
}
return l.parseDockerPs(stdout)
case "lxd":
stdout, err := l.lxdPs("volatile.last_state.power=RUNNING -c n")
if err != nil {
return containers, err
}
return l.parseLxdPs(stdout)
default:
return containers, fmt.Errorf(
"Not supported yet: %s", l.ServerInfo.Container.Type)
"Not supported yet: %s", l.ServerInfo.Containers.Type)
}
}
func (l *base) exitedContainers() (containers []config.Container, err error) {
switch l.ServerInfo.Container.Type {
switch l.ServerInfo.Containers.Type {
case "", "docker":
stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}}'")
stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}} {{.Image}}'")
if err != nil {
return containers, err
}
return l.parseDockerPs(stdout)
case "lxd":
stdout, err := l.lxdPs("volatile.last_state.power=STOPPED -c n")
if err != nil {
return containers, err
}
return l.parseLxdPs(stdout)
default:
return containers, fmt.Errorf(
"Not supported yet: %s", l.ServerInfo.Container.Type)
"Not supported yet: %s", l.ServerInfo.Containers.Type)
}
}
func (l *base) dockerPs(option string) (string, error) {
cmd := fmt.Sprintf("docker ps %s", option)
r := l.ssh(cmd, noSudo)
r := l.exec(cmd, noSudo)
if !r.isSuccess() {
return "", fmt.Errorf("Failed to SSH: %s", r)
}
return r.Stdout, nil
}
func (l *base) lxdPs(option string) (string, error) {
cmd := fmt.Sprintf("lxc list %s", option)
r := l.exec(cmd, noSudo)
if !r.isSuccess() {
return "", fmt.Errorf("failed to SSH: %s", r)
}
return r.Stdout, nil
}
func (l *base) parseDockerPs(stdout string) (containers []config.Container, err error) {
lines := strings.Split(stdout, "\n")
for _, line := range lines {
@@ -127,46 +161,65 @@ func (l *base) parseDockerPs(stdout string) (containers []config.Container, err
if len(fields) == 0 {
break
}
if len(fields) != 2 {
if len(fields) != 3 {
return containers, fmt.Errorf("Unknown format: %s", line)
}
containers = append(containers, config.Container{
ContainerID: fields[0],
Name: fields[1],
Image: fields[2],
})
}
return
}
func (l *base) detectPlatform() error {
func (l *base) parseLxdPs(stdout string) (containers []config.Container, err error) {
lines := strings.Split(stdout, "\n")
for i, line := range lines[3:] {
if i%2 == 1 {
continue
}
fields := strings.Fields(strings.Replace(line, "|", " ", -1))
if len(fields) == 0 {
break
}
if len(fields) != 1 {
return containers, fmt.Errorf("Unknown format: %s", line)
}
containers = append(containers, config.Container{
ContainerID: fields[0],
Name: fields[0],
})
}
return
}
func (l *base) detectPlatform() {
ok, instanceID, err := l.detectRunningOnAws()
if err != nil {
return err
l.setPlatform(models.Platform{Name: "other"})
return
}
if ok {
l.setPlatform(models.Platform{
Name: "aws",
InstanceID: instanceID,
})
return nil
return
}
//TODO Azure, GCP...
l.setPlatform(models.Platform{
Name: "other",
})
return nil
l.setPlatform(models.Platform{Name: "other"})
return
}
func (l base) detectRunningOnAws() (ok bool, instanceID string, err error) {
if r := l.ssh("type curl", noSudo); r.isSuccess() {
if r := l.exec("type curl", noSudo); r.isSuccess() {
cmd := "curl --max-time 1 --retry 3 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id"
r := l.ssh(cmd, noSudo)
r := l.exec(cmd, noSudo)
if r.isSuccess() {
id := strings.TrimSpace(r.Stdout)
if id == "not found" {
// status: 0, stdout: "not found" on degitalocean or Azure
if !l.isAwsInstanceID(id) {
return false, "", nil
}
return true, id, nil
@@ -181,11 +234,14 @@ func (l base) detectRunningOnAws() (ok bool, instanceID string, err error) {
}
}
if r := l.ssh("type wget", noSudo); r.isSuccess() {
if r := l.exec("type wget", noSudo); r.isSuccess() {
cmd := "wget --tries=3 --timeout=1 --no-proxy -q -O - http://169.254.169.254/latest/meta-data/instance-id"
r := l.ssh(cmd, noSudo)
r := l.exec(cmd, noSudo)
if r.isSuccess() {
id := strings.TrimSpace(r.Stdout)
if !l.isAwsInstanceID(id) {
return false, "", nil
}
return true, id, nil
}
@@ -202,90 +258,52 @@ func (l base) detectRunningOnAws() (ok bool, instanceID string, err error) {
l.ServerInfo.ServerName, l.ServerInfo.Container.Name)
}
func (l *base) convertToModel() (models.ScanResult, error) {
var scoredCves, unscoredCves models.CveInfos
for _, p := range l.UnsecurePackages {
if p.CveDetail.CvssScore(config.Conf.Lang) <= 0 {
unscoredCves = append(unscoredCves, models.CveInfo{
CveDetail: p.CveDetail,
Packages: p.Packs,
DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
})
continue
}
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/resource-ids.html
var awsInstanceIDPattern = regexp.MustCompile(`^i-[0-9a-f]+$`)
cpenames := []models.CpeName{}
for _, cpename := range p.CpeNames {
cpenames = append(cpenames,
models.CpeName{Name: cpename})
}
func (l base) isAwsInstanceID(str string) bool {
return awsInstanceIDPattern.MatchString(str)
}
cve := models.CveInfo{
CveDetail: p.CveDetail,
Packages: p.Packs,
DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
CpeNames: cpenames,
}
scoredCves = append(scoredCves, cve)
func (l *base) convertToModel() models.ScanResult {
for _, p := range l.VulnInfos {
sort.Sort(models.PackageInfosByName(p.Packages))
}
sort.Sort(l.VulnInfos)
ctype := l.ServerInfo.Containers.Type
if l.ServerInfo.Container.ContainerID != "" && ctype == "" {
ctype = "docker"
}
container := models.Container{
ContainerID: l.ServerInfo.Container.ContainerID,
Name: l.ServerInfo.Container.Name,
Image: l.ServerInfo.Container.Image,
Type: ctype,
}
sort.Sort(scoredCves)
sort.Sort(unscoredCves)
errs := []string{}
for _, e := range l.errs {
errs = append(errs, fmt.Sprintf("%s", e))
}
// Avoid null slice being null in JSON
for i := range l.VulnInfos {
l.VulnInfos[i].NilSliceToEmpty()
}
return models.ScanResult{
ServerName: l.ServerInfo.ServerName,
Family: l.Family,
Release: l.Release,
ScannedAt: time.Now(),
Family: l.Distro.Family,
Release: l.Distro.Release,
Container: container,
Platform: l.Platform,
KnownCves: scoredCves,
UnknownCves: unscoredCves,
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},
}
}
}
Errors: errs,
}
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) {

View File

@@ -30,16 +30,18 @@ func TestParseDockerPs(t *testing.T) {
in string
expected []config.Container
}{
`c7ca0992415a romantic_goldberg
f570ae647edc agitated_lovelace`,
`c7ca0992415a romantic_goldberg ubuntu:14.04.5
f570ae647edc agitated_lovelace centos:latest`,
[]config.Container{
{
ContainerID: "c7ca0992415a",
Name: "romantic_goldberg",
Image: "ubuntu:14.04.5",
},
{
ContainerID: "f570ae647edc",
Name: "agitated_lovelace",
Image: "centos:latest",
},
},
}
@@ -56,3 +58,63 @@ f570ae647edc agitated_lovelace`,
}
}
}
func TestParseLxdPs(t *testing.T) {
var test = struct {
in string
expected []config.Container
}{
`+-------+
| NAME |
+-------+
| test1 |
+-------+
| test2 |
+-------+`,
[]config.Container{
{
ContainerID: "test1",
Name: "test1",
},
{
ContainerID: "test2",
Name: "test2",
},
},
}
r := newRedhat(config.ServerInfo{})
actual, err := r.parseLxdPs(test.in)
if err != nil {
t.Errorf("Error occurred. in: %s, err: %s", test.in, err)
return
}
for i, e := range test.expected {
if !reflect.DeepEqual(e, actual[i]) {
t.Errorf("expected %v, actual %v", e, actual[i])
}
}
}
func TestIsAwsInstanceID(t *testing.T) {
var tests = []struct {
in string
expected bool
}{
{"i-1234567a", true},
{"i-1234567890abcdef0", true},
{"i-1234567890abcdef0000000", true},
{"e-1234567890abcdef0", false},
{"i-1234567890abcdef0 foo bar", false},
{"no data", false},
}
r := newRedhat(config.ServerInfo{})
for _, tt := range tests {
actual := r.isAwsInstanceID(tt.in)
if tt.expected != actual {
t.Errorf("expected %t, actual %t, str: %s", tt.expected, actual, tt.in)
}
}
}

View File

@@ -24,8 +24,8 @@ import (
"strings"
"time"
"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"
)
@@ -39,75 +39,87 @@ type debian struct {
func newDebian(c config.ServerInfo) *debian {
d := &debian{}
d.log = util.NewCustomLogger(c)
d.setServerInfo(c)
return d
}
// Ubuntu, Debian
// Ubuntu, Debian, Raspbian
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/debian.rb
func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err error) {
deb = newDebian(c)
deb.setServerInfo(c)
if r := sshExec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
if r := exec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
if r.Error != nil {
return false, deb, r.Error
return false, deb, nil
}
if r.ExitStatus == 255 {
return false, deb, fmt.Errorf(
"Unable to connect via SSH. Check SSH settings. %s", r)
}
Log.Debugf("Not Debian like Linux. %s", r)
util.Log.Debugf("Not Debian like Linux. %s", r)
return false, deb, nil
}
if r := sshExec(c, "lsb_release -ir", noSudo); r.isSuccess() {
// Raspbian
// lsb_release in Raspbian Jessie returns 'Distributor ID: Raspbian'.
// However, lsb_release in Raspbian Wheezy returns 'Distributor ID: Debian'.
if r := exec(c, "cat /etc/issue", noSudo); r.isSuccess() {
// e.g.
// Raspbian GNU/Linux 7 \n \l
result := strings.Fields(r.Stdout)
if len(result) > 2 && result[0] == "Raspbian" {
distro := strings.ToLower(trim(result[0]))
deb.setDistro(distro, trim(result[2]))
return true, deb, nil
}
}
if r := exec(c, "lsb_release -ir", noSudo); r.isSuccess() {
// e.g.
// root@fa3ec524be43:/# lsb_release -ir
// Distributor ID: Ubuntu
// Release: 14.04
re, _ := regexp.Compile(
`(?s)^Distributor ID:\s*(.+?)\n*Release:\s*(.+?)$`)
re := regexp.MustCompile(`(?s)^Distributor ID:\s*(.+?)\n*Release:\s*(.+?)$`)
result := re.FindStringSubmatch(trim(r.Stdout))
if len(result) == 0 {
deb.setDistributionInfo("debian/ubuntu", "unknown")
Log.Warnf(
deb.setDistro("debian/ubuntu", "unknown")
util.Log.Warnf(
"Unknown Debian/Ubuntu version. lsb_release -ir: %s", r)
} else {
distro := strings.ToLower(trim(result[1]))
deb.setDistributionInfo(distro, trim(result[2]))
deb.setDistro(distro, trim(result[2]))
}
return true, deb, nil
}
if r := sshExec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
if r := exec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
// e.g.
// DISTRIB_ID=Ubuntu
// DISTRIB_RELEASE=14.04
// DISTRIB_CODENAME=trusty
// DISTRIB_DESCRIPTION="Ubuntu 14.04.2 LTS"
re, _ := regexp.Compile(
`(?s)^DISTRIB_ID=(.+?)\n*DISTRIB_RELEASE=(.+?)\n.*$`)
re := regexp.MustCompile(`(?s)^DISTRIB_ID=(.+?)\n*DISTRIB_RELEASE=(.+?)\n.*$`)
result := re.FindStringSubmatch(trim(r.Stdout))
if len(result) == 0 {
Log.Warnf(
util.Log.Warnf(
"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s", r)
deb.setDistributionInfo("debian/ubuntu", "unknown")
deb.setDistro("debian/ubuntu", "unknown")
} else {
distro := strings.ToLower(trim(result[1]))
deb.setDistributionInfo(distro, trim(result[2]))
deb.setDistro(distro, trim(result[2]))
}
return true, deb, nil
}
// Debian
cmd := "cat /etc/debian_version"
if r := sshExec(c, cmd, noSudo); r.isSuccess() {
deb.setDistributionInfo("debian", trim(r.Stdout))
if r := exec(c, cmd, noSudo); r.isSuccess() {
deb.setDistro("debian", trim(r.Stdout))
return true, deb, nil
}
Log.Debugf("Not Debian like Linux: %s", c.ServerName)
util.Log.Debugf("Not Debian like Linux: %s", c.ServerName)
return false, deb, nil
}
@@ -116,58 +128,36 @@ func trim(str string) string {
}
func (o *debian) checkIfSudoNoPasswd() error {
r := o.ssh("apt-get -v", sudo)
cmd := util.PrependProxyEnv("apt-get update")
o.log.Infof("Checking... sudo %s", cmd)
r := o.exec(cmd, sudo)
if !r.isSuccess() {
o.log.Errorf("sudo error on %s", r)
return fmt.Errorf("Failed to sudo: %s", r)
}
o.log.Infof("sudo ... OK")
o.log.Infof("Sudo... Pass")
return nil
}
func (o *debian) install() error {
func (o *debian) checkDependencies() error {
switch o.Distro.Family {
case "ubuntu", "raspbian":
return nil
// apt-get update
o.log.Infof("apt-get update...")
cmd := util.PrependProxyEnv("apt-get update")
if r := o.ssh(cmd, sudo); !r.isSuccess() {
msg := fmt.Sprintf("Failed to SSH: %s", r)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
if o.Family == "debian" {
// install aptitude
cmd = util.PrependProxyEnv("apt-get install --force-yes -y aptitude")
if r := o.ssh(cmd, sudo); !r.isSuccess() {
msg := fmt.Sprintf("Failed to SSH: %s", r)
case "debian":
// Debian needs aptitude to get changelogs.
// Because unable to get changelogs via apt-get changelog on Debian.
if r := o.exec("test -f /usr/bin/aptitude", noSudo); !r.isSuccess() {
msg := fmt.Sprintf("aptitude is not installed: %s", r)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
o.log.Infof("Installed: aptitude")
}
// install unattended-upgrades
if !config.Conf.UseUnattendedUpgrades {
o.log.Infof("Dependencies... Pass")
return nil
}
if r := o.ssh("type unattended-upgrade", noSudo); r.isSuccess() {
o.log.Infof(
"Ignored: unattended-upgrade already installed")
return nil
default:
return fmt.Errorf("Not implemented yet: %s", o.Distro)
}
cmd = util.PrependProxyEnv(
"apt-get install --force-yes -y unattended-upgrades")
if r := o.ssh(cmd, sudo); !r.isSuccess() {
msg := fmt.Sprintf("Failed to SSH: %s", r)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
o.log.Infof("Installed: unattended-upgrades")
return nil
}
func (o *debian) scanPackages() error {
@@ -179,17 +169,17 @@ 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
}
func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error) {
r := o.ssh("dpkg-query -W", noSudo)
r := o.exec("dpkg-query -W", noSudo)
if !r.isSuccess() {
return packs, fmt.Errorf("Failed to SSH: %s", r)
}
@@ -214,12 +204,16 @@ func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error)
return
}
var packageLinePattern = regexp.MustCompile(`^([^\t']+)\t(.+)$`)
func (o *debian) parseScannedPackagesLine(line string) (name, version string, err error) {
re, _ := regexp.Compile(`^([^\t']+)\t(.+)$`)
result := re.FindStringSubmatch(line)
result := packageLinePattern.FindStringSubmatch(line)
if len(result) == 3 {
// remove :amd64, i386...
name = regexp.MustCompile(":.+").ReplaceAllString(result[1], "")
name = result[1]
if i := strings.IndexRune(name, ':'); i >= 0 {
name = name[:i]
}
version = result[2]
return
}
@@ -227,174 +221,122 @@ func (o *debian) parseScannedPackagesLine(line string) (name, version string, er
return "", "", fmt.Errorf("Unknown format: %s", line)
}
// unattended-upgrade command need to check security upgrades).
func (o *debian) checkRequiredPackagesInstalled() error {
if o.Family == "debian" {
if r := o.ssh("test -f /usr/bin/aptitude", noSudo); !r.isSuccess() {
msg := fmt.Sprintf("aptitude is not installed: %s", r)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
}
if !config.Conf.UseUnattendedUpgrades {
return nil
}
if r := o.ssh("type unattended-upgrade", noSudo); !r.isSuccess() {
msg := fmt.Sprintf("unattended-upgrade is not installed: %s", r)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
return nil
}
//TODO return whether already expired.
func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInfo, error) {
// cmd := prependProxyEnv(conf.HTTPProxy, "apt-get update | cat; echo 1")
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() {
if r := o.exec(cmd, sudo); !r.isSuccess() {
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
var upgradablePackNames []string
var err error
if config.Conf.UseUnattendedUpgrades {
upgradablePackNames, err = o.GetUnsecurePackNamesUsingUnattendedUpgrades()
if err != nil {
return []CvePacksInfo{}, err
}
} else {
upgradablePackNames, err = o.GetUpgradablePackNames()
if err != nil {
return []CvePacksInfo{}, err
}
// Convert the name of upgradable packages to PackageInfo struct
upgradableNames, err := o.GetUpgradablePackNames()
if err != nil {
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: upgradablePacks,
}
o.log.Debugf("Ensure changelog cache: %s", current.Name)
var meta *cache.Meta
if meta, err = o.ensureChangelogCache(current); err != nil {
return nil, err
}
// Collect CVE information of upgradable packages
cvePacksInfos, err := o.scanPackageCveInfos(unsecurePacks)
vulnInfos, err := o.scanVulnInfos(upgradablePacks, meta)
if err != nil {
return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
}
return cvePacksInfos, nil
return vulnInfos, nil
}
func (o *debian) fillCandidateVersion(packs []models.PackageInfo) ([]models.PackageInfo, error) {
reqChan := make(chan models.PackageInfo, len(packs))
resChan := make(chan models.PackageInfo, len(packs))
errChan := make(chan error, len(packs))
defer close(resChan)
defer close(errChan)
defer close(reqChan)
go func() {
for _, pack := range packs {
reqChan <- pack
}
}()
timeout := time.After(5 * 60 * time.Second)
concurrency := 5
tasks := util.GenWorkers(concurrency)
for range packs {
tasks <- func() {
select {
case pack := <-reqChan:
func(p models.PackageInfo) {
cmd := fmt.Sprintf("LANG=en_US.UTF-8 apt-cache policy %s", p.Name)
r := o.ssh(cmd, sudo)
if !r.isSuccess() {
errChan <- fmt.Errorf("Failed to SSH: %s.", r)
return
}
ver, err := o.parseAptCachePolicy(r.Stdout, p.Name)
if err != nil {
errChan <- fmt.Errorf("Failed to parse %s", err)
}
p.NewVersion = ver.Candidate
resChan <- p
}(pack)
}
}
}
errs := []error{}
result := []models.PackageInfo{}
for i := 0; i < len(packs); i++ {
select {
case pack := <-resChan:
result = append(result, pack)
o.log.Infof("(%d/%d) Upgradable: %s-%s -> %s",
i+1, len(packs), pack.Name, pack.Version, pack.NewVersion)
case err := <-errChan:
errs = append(errs, err)
case <-timeout:
return nil, fmt.Errorf("Timeout fillCandidateVersion")
}
}
if 0 < len(errs) {
return nil, fmt.Errorf("%v", errs)
}
return result, nil
}
func (o *debian) GetUnsecurePackNamesUsingUnattendedUpgrades() (packNames []string, err error) {
cmd := util.PrependProxyEnv("unattended-upgrades --dry-run -d 2>&1 ")
release, err := strconv.ParseFloat(o.Release, 64)
func (o *debian) ensureChangelogCache(current cache.Meta) (*cache.Meta, error) {
// Search from cache
cached, found, err := cache.DB.GetMeta(current.Name)
if err != nil {
return packNames, fmt.Errorf(
"OS Release Version is invalid, %s, %s", o.Family, o.Release)
}
switch {
case release < 12:
return packNames, fmt.Errorf(
"Support expired. %s, %s", o.Family, o.Release)
case 12 < release && release < 14:
cmd += `| grep 'pkgs that look like they should be upgraded:' |
sed -e 's/pkgs that look like they should be upgraded://g'`
case 14 < release:
cmd += `| grep 'Packages that will be upgraded:' |
sed -e 's/Packages that will be upgraded://g'`
default:
return packNames, fmt.Errorf(
"Not supported yet. %s, %s", o.Family, o.Release)
return nil, fmt.Errorf(
"Failed to get meta. Please remove cache.db and then try again. err: %s", err)
}
r := o.ssh(cmd, sudo)
if r.isSuccess(0, 1) {
packNames = strings.Split(strings.TrimSpace(r.Stdout), " ")
return packNames, nil
if !found {
o.log.Debugf("Not found in meta: %s", current.Name)
err = cache.DB.EnsureBuckets(current)
if err != nil {
return nil, fmt.Errorf("Failed to ensure buckets. err: %s", err)
}
return &current, nil
}
return packNames, fmt.Errorf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
if current.Distro.Family != cached.Distro.Family ||
current.Distro.Release != cached.Distro.Release {
o.log.Debugf("Need to refesh meta: %s", current.Name)
err = cache.DB.EnsureBuckets(current)
if err != nil {
return nil, fmt.Errorf("Failed to ensure buckets. err: %s", err)
}
return &current, nil
}
o.log.Debugf("Reuse meta: %s", current.Name)
if config.Conf.Debug {
cache.DB.PrettyPrint(current)
}
return &cached, nil
}
func (o *debian) fillCandidateVersion(before models.PackageInfoList) (filled []models.PackageInfo, err error) {
names := []string{}
for _, p := range before {
names = append(names, p.Name)
}
cmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 apt-cache policy %s", strings.Join(names, " "))
r := o.exec(cmd, noSudo)
if !r.isSuccess() {
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
packChangelog := o.splitAptCachePolicy(r.Stdout)
for k, v := range packChangelog {
ver, err := o.parseAptCachePolicy(v, k)
if err != nil {
return nil, fmt.Errorf("Failed to parse %s", err)
}
p, found := before.FindByName(k)
if !found {
return nil, fmt.Errorf("Not found: %s", k)
}
p.NewVersion = ver.Candidate
filled = append(filled, p)
}
return
}
func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
cmd := util.PrependProxyEnv("LANG=en_US.UTF-8 apt-get upgrade --dry-run")
r := o.ssh(cmd, sudo)
cmd := util.PrependProxyEnv("LANGUAGE=en_US.UTF-8 apt-get upgrade --dry-run")
r := o.exec(cmd, noSudo)
if r.isSuccess(0, 1) {
return o.parseAptGetUpgrade(r.Stdout)
}
@@ -404,8 +346,8 @@ func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
}
func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, err error) {
startRe, _ := regexp.Compile(`The following packages will be upgraded:`)
stopRe, _ := regexp.Compile(`^(\d+) upgraded.*`)
startRe := regexp.MustCompile(`The following packages will be upgraded:`)
stopRe := regexp.MustCompile(`^(\d+) upgraded.*`)
startLineFound, stopLineFound := false, false
lines := strings.Split(stdout, "\n")
@@ -445,24 +387,19 @@ func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, er
return
}
func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePacksList CvePacksList, err error) {
// { CVE ID: [packageInfo] }
cvePackages := make(map[string][]models.PackageInfo)
type strarray []string
func (o *debian) scanVulnInfos(upgradablePacks []models.PackageInfo, meta *cache.Meta) (models.VulnInfos, error) {
resChan := make(chan struct {
models.PackageInfo
strarray
}, len(unsecurePacks))
errChan := make(chan error, len(unsecurePacks))
reqChan := make(chan models.PackageInfo, len(unsecurePacks))
DetectedCveIDs
}, 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
}
}()
@@ -470,17 +407,30 @@ 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:
func(p models.PackageInfo) {
changelog := o.getChangelogCache(meta, p)
if 0 < len(changelog) {
cveIDs, _ := o.getCveIDsFromChangelog(changelog, p.Name, p.Version)
resChan <- struct {
models.PackageInfo
DetectedCveIDs
}{p, cveIDs}
return
}
// if the changelog is not in cache or failed to get from local cache,
// get the changelog of the package via internet.
// After that, store it in the cache.
if cveIDs, err := o.scanPackageCveIDs(p); err != nil {
errChan <- err
} else {
resChan <- struct {
models.PackageInfo
strarray
DetectedCveIDs
}{p, cveIDs}
}
}(pack)
@@ -488,130 +438,261 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
}
}
// { DetectedCveID{} : [packageInfo] }
cvePackages := make(map[DetectedCveID][]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
cveIDs := pair.strarray
cveIDs := pair.DetectedCveIDs
for _, cveID := range cveIDs {
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) {
return nil, fmt.Errorf("%v", errs)
}
var cveIDs []string
var cveIDs []DetectedCveID
for k := range cvePackages {
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.CveID,
Confidence: k.Confidence,
Packages: v,
})
}
return
// Update meta package information of changelog cache to the latest one.
meta.Packs = upgradablePacks
if err := cache.DB.RefreshMeta(*meta); err != nil {
return nil, err
}
return vinfos, nil
}
func (o *debian) scanPackageCveIDs(pack models.PackageInfo) ([]string, error) {
func (o *debian) getChangelogCache(meta *cache.Meta, pack models.PackageInfo) string {
cachedPack, found := meta.FindPack(pack.Name)
if !found {
o.log.Debugf("Not found: %s", pack.Name)
return ""
}
if cachedPack.NewVersion != pack.NewVersion {
o.log.Debugf("Expired: %s, cache: %s, new: %s",
pack.Name, cachedPack.NewVersion, pack.NewVersion)
return ""
}
changelog, err := cache.DB.GetChangelog(meta.Name, pack.Name)
if err != nil {
o.log.Warnf("Failed to get changelog. bucket: %s, key:%s, err: %s",
meta.Name, pack.Name, err)
return ""
}
if len(changelog) == 0 {
o.log.Debugf("Empty string: %s", pack.Name)
return ""
}
o.log.Debugf("Hit: %s, %s, cache: %s, new: %s len: %d, %s...",
meta.Name, pack.Name, cachedPack.NewVersion, pack.NewVersion, len(changelog), util.Truncate(changelog, 30))
return changelog
}
func (o *debian) scanPackageCveIDs(pack models.PackageInfo) ([]DetectedCveID, error) {
cmd := ""
switch o.Family {
case "ubuntu":
cmd = fmt.Sprintf(`apt-get changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
switch o.Distro.Family {
case "ubuntu", "raspbian":
cmd = fmt.Sprintf(`PAGER=cat apt-get -q=2 changelog %s`, pack.Name)
case "debian":
cmd = fmt.Sprintf(`aptitude changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
cmd = fmt.Sprintf(`PAGER=cat aptitude -q=2 changelog %s`, pack.Name)
}
cmd = util.PrependProxyEnv(cmd)
r := o.ssh(cmd, noSudo)
r := o.exec(cmd, noSudo)
if !r.isSuccess() {
o.log.Warnf("Failed to SSH: %s", r)
// Ignore this Error.
return nil, nil
}
return o.getCveIDParsingChangelog(r.Stdout, pack.Name, pack.Version)
stdout := strings.Replace(r.Stdout, "\r", "", -1)
cveIDs, clog := o.getCveIDsFromChangelog(
stdout, pack.Name, pack.Version)
if clog.Method != models.FailedToGetChangelog {
err := cache.DB.PutChangelog(o.getServerInfo().GetServerName(), pack.Name, clog.Contents)
if err != nil {
return nil, fmt.Errorf("Failed to put changelog into cache")
}
}
// No error will be returned. Only logging.
return cveIDs, nil
}
func (o *debian) getCveIDParsingChangelog(changelog string,
packName string, versionOrLater string) (cveIDs []string, err error) {
// Debian Version Numbers
// https://readme.phys.ethz.ch/documentation/debian_version_numbers/
func (o *debian) getCveIDsFromChangelog(
changelog, name, ver string) ([]DetectedCveID, models.Changelog) {
cveIDs, err = o.parseChangelog(changelog, packName, versionOrLater)
if err == nil {
return
if cveIDs, relevant, err := o.parseChangelog(
changelog, name, ver, models.ChangelogExactMatch); err == nil {
return cveIDs, relevant
}
ver := strings.Split(versionOrLater, "ubuntu")[0]
cveIDs, err = o.parseChangelog(changelog, packName, ver)
if err == nil {
return
}
var verAfterColon string
var err error
splittedByColon := strings.Split(versionOrLater, ":")
splittedByColon := strings.Split(ver, ":")
if 1 < len(splittedByColon) {
ver = splittedByColon[1]
}
cveIDs, err = o.parseChangelog(changelog, packName, ver)
if err == nil {
return
verAfterColon = splittedByColon[1]
if cveIDs, relevant, err := o.parseChangelog(
changelog, name, verAfterColon, models.ChangelogLenientMatch); err == nil {
return cveIDs, relevant
}
}
//TODO report as unable to parse changelog.
delim := []string{"+", "~", "build"}
switch o.Distro.Family {
case "ubuntu":
delim = append(delim, "ubuntu")
case "debian":
case "Raspbian":
}
for _, d := range delim {
ss := strings.Split(ver, d)
if 1 < len(ss) {
if cveIDs, relevant, err := o.parseChangelog(
changelog, name, ss[0], models.ChangelogLenientMatch); err == nil {
return cveIDs, relevant
}
}
ss = strings.Split(verAfterColon, d)
if 1 < len(ss) {
if cveIDs, relevant, err := o.parseChangelog(
changelog, name, ss[0], models.ChangelogLenientMatch); err == nil {
return cveIDs, relevant
}
}
}
// Only logging the error.
o.log.Error(err)
return []string{}, nil
for i, p := range o.Packages {
if p.Name == name {
o.Packages[i].Changelog = models.Changelog{
Contents: "",
Method: models.FailedToFindVersionInChangelog,
}
}
}
// If the version is not in changelog, return entire changelog to put into cache
return []DetectedCveID{}, models.Changelog{
Contents: changelog,
Method: models.FailedToFindVersionInChangelog,
}
}
// DetectedCveID has CveID, Confidence and DetectionMethod fields
// LenientMatching will be true if this vulnerability is not detected by accurate version matching.
// see https://github.com/future-architect/vuls/pull/328
type DetectedCveID struct {
CveID string
Confidence models.Confidence
}
// DetectedCveIDs is a slice of DetectedCveID
type DetectedCveIDs []DetectedCveID
var cveRe = regexp.MustCompile(`(CVE-\d{4}-\d{4,})`)
// Collect CVE-IDs included in the changelog.
// The version which specified in argument(versionOrLater) is excluded.
func (o *debian) parseChangelog(changelog string,
packName string, versionOrLater string) (cveIDs []string, err error) {
cveRe, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`)
stopRe, _ := regexp.Compile(fmt.Sprintf(`\(%s\)`, regexp.QuoteMeta(versionOrLater)))
func (o *debian) parseChangelog(changelog, name, ver string, confidence models.Confidence) ([]DetectedCveID, models.Changelog, error) {
buf, cveIDs := []string{}, []string{}
stopRe := regexp.MustCompile(fmt.Sprintf(`\(%s\)`, regexp.QuoteMeta(ver)))
stopLineFound := false
lines := strings.Split(changelog, "\n")
for _, line := range lines {
if matche := stopRe.MatchString(line); matche {
o.log.Debugf("Found the stop line. line: %s", line)
buf = append(buf, line)
if match := stopRe.MatchString(line); match {
// o.log.Debugf("Found the stop line: %s", line)
stopLineFound = true
break
} else if matches := cveRe.FindAllString(line, -1); len(matches) > 0 {
} else if matches := cveRe.FindAllString(line, -1); 0 < len(matches) {
for _, m := range matches {
cveIDs = util.AppendIfMissing(cveIDs, m)
}
}
}
if !stopLineFound {
return []string{}, fmt.Errorf(
"Failed to scan CVE IDs. The version is not in changelog. name: %s, version: %s",
packName,
versionOrLater,
)
return nil, models.Changelog{
Contents: "",
Method: models.FailedToFindVersionInChangelog,
}, fmt.Errorf(
"Failed to scan CVE IDs. The version is not in changelog. name: %s, version: %s",
name,
ver,
)
}
return
clog := models.Changelog{
Contents: strings.Join(buf, "\n"),
Method: string(confidence.DetectionMethod),
}
for i, p := range o.Packages {
if p.Name == name {
o.Packages[i].Changelog = clog
}
}
cves := []DetectedCveID{}
for _, id := range cveIDs {
cves = append(cves, DetectedCveID{id, confidence})
}
return cves, clog, nil
}
func (o *debian) splitAptCachePolicy(stdout string) map[string]string {
re := regexp.MustCompile(`(?m:^[^ \t]+:\r?\n)`)
ii := re.FindAllStringIndex(stdout, -1)
ri := []int{}
for i := len(ii) - 1; 0 <= i; i-- {
ri = append(ri, ii[i][0])
}
splitted := []string{}
lasti := len(stdout)
for _, i := range ri {
splitted = append(splitted, stdout[i:lasti])
lasti = i
}
packChangelog := map[string]string{}
for _, r := range splitted {
packName := r[:strings.Index(r, ":")]
packChangelog[packName] = r
}
return packChangelog
}
type packCandidateVer struct {

View File

@@ -18,10 +18,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package scan
import (
"os"
"reflect"
"testing"
"github.com/Sirupsen/logrus"
"github.com/future-architect/vuls/cache"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/k0kubun/pp"
)
@@ -54,11 +58,12 @@ func TestParseScannedPackagesLineDebian(t *testing.T) {
}
func TestgetCveIDParsingChangelog(t *testing.T) {
func TestGetCveIDsFromChangelog(t *testing.T) {
var tests = []struct {
in []string
expected []string
in []string
cveIDs []DetectedCveID
changelog models.Changelog
}{
{
// verubuntu1
@@ -73,73 +78,99 @@ CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
systemd (228-5) unstable; urgency=medium
systemd (228-4) unstable; urgency=medium
systemd (228-3) unstable; urgency=medium
systemd (228-2) unstable; urgency=medium
systemd (228-1) unstable; urgency=medium
systemd (227-3) unstable; urgency=medium
systemd (227-2) unstable; urgency=medium
systemd (227-1) unstable; urgency=medium`,
systemd (228-3) unstable; urgency=medium`,
},
[]string{
"CVE-2015-2325",
"CVE-2015-2326",
"CVE-2015-3210",
[]DetectedCveID{
{"CVE-2015-2325", models.ChangelogLenientMatch},
{"CVE-2015-2326", models.ChangelogLenientMatch},
{"CVE-2015-3210", models.ChangelogLenientMatch},
},
models.Changelog{
Contents: `systemd (229-2) unstable; urgency=medium
systemd (229-1) unstable; urgency=medium
systemd (228-6) unstable; urgency=medium
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
systemd (228-5) unstable; urgency=medium
systemd (228-4) unstable; urgency=medium`,
Method: models.ChangelogLenientMatchStr,
},
},
{
// ver
[]string{
"libpcre3",
"2:8.38-1ubuntu1",
"2:8.35-7.1ubuntu1",
`pcre3 (2:8.38-2) unstable; urgency=low
pcre3 (2:8.38-1) unstable; urgency=low
pcre3 (2:8.35-8) unstable; urgency=low
pcre3 (2:8.35-7.4) unstable; urgency=medium
pcre3 (2:8.35-7.3) unstable; urgency=medium
pcre3 (2:8.35-7.2) unstable; urgency=low
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
pcre3 (2:8.35-7.1) unstable; urgency=medium
pcre3 (2:8.35-7) unstable; urgency=medium`,
pcre3 (2:8.38-1) unstable; urgency=low
pcre3 (2:8.35-8) unstable; urgency=low
pcre3 (2:8.35-7.4) unstable; urgency=medium
pcre3 (2:8.35-7.3) unstable; urgency=medium
pcre3 (2:8.35-7.2) unstable; urgency=low
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
pcre3 (2:8.35-7.1) unstable; urgency=medium
pcre3 (2:8.35-7) unstable; urgency=medium`,
},
[]string{
"CVE-2015-2325",
"CVE-2015-2326",
"CVE-2015-3210",
[]DetectedCveID{
{"CVE-2015-2325", models.ChangelogLenientMatch},
{"CVE-2015-2326", models.ChangelogLenientMatch},
{"CVE-2015-3210", models.ChangelogLenientMatch},
},
models.Changelog{
Contents: `pcre3 (2:8.38-2) unstable; urgency=low
pcre3 (2:8.38-1) unstable; urgency=low
pcre3 (2:8.35-8) unstable; urgency=low
pcre3 (2:8.35-7.4) unstable; urgency=medium
pcre3 (2:8.35-7.3) unstable; urgency=medium
pcre3 (2:8.35-7.2) unstable; urgency=low
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
pcre3 (2:8.35-7.1) unstable; urgency=medium`,
Method: models.ChangelogLenientMatchStr,
},
},
{
// ver-ubuntu3
[]string{
"sysvinit",
"2.88dsf-59.2ubuntu3",
`sysvinit (2.88dsf-59.3ubuntu1) xenial; urgency=low
sysvinit (2.88dsf-59.3) unstable; urgency=medium
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium
sysvinit (2.88dsf-59.2ubuntu2) wily; urgency=medium
sysvinit (2.88dsf-59.2ubuntu1) wily; urgency=medium
CVE-2015-2321: heap buffer overflow in pcre_compile2(). (Closes: #783285)
sysvinit (2.88dsf-59.2) unstable; urgency=medium
sysvinit (2.88dsf-59.1ubuntu3) wily; urgency=medium
CVE-2015-2322: heap buffer overflow in pcre_compile2(). (Closes: #783285)
sysvinit (2.88dsf-59.1ubuntu2) wily; urgency=medium
sysvinit (2.88dsf-59.1ubuntu1) wily; urgency=medium
sysvinit (2.88dsf-59.1) unstable; urgency=medium
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
sysvinit (2.88dsf-59) unstable; urgency=medium
sysvinit (2.88dsf-58) unstable; urgency=low
sysvinit (2.88dsf-57) unstable; urgency=low`,
sysvinit (2.88dsf-59.3) unstable; urgency=medium
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium
sysvinit (2.88dsf-59.2ubuntu2) wily; urgency=medium
sysvinit (2.88dsf-59.2ubuntu1) wily; urgency=medium
CVE-2015-2321: heap buffer overflow in pcre_compile2(). (Closes: #783285)
sysvinit (2.88dsf-59.2) unstable; urgency=medium
sysvinit (2.88dsf-59.1ubuntu3) wily; urgency=medium
CVE-2015-2322: heap buffer overflow in pcre_compile2(). (Closes: #783285)
sysvinit (2.88dsf-59.1ubuntu2) wily; urgency=medium
sysvinit (2.88dsf-59.1ubuntu1) wily; urgency=medium
sysvinit (2.88dsf-59.1) unstable; urgency=medium
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
sysvinit (2.88dsf-59) unstable; urgency=medium
sysvinit (2.88dsf-58) unstable; urgency=low
sysvinit (2.88dsf-57) unstable; urgency=low`,
},
[]string{
"CVE-2015-2325",
"CVE-2015-2326",
"CVE-2015-3210",
[]DetectedCveID{
{"CVE-2015-2325", models.ChangelogExactMatch},
{"CVE-2015-2326", models.ChangelogExactMatch},
{"CVE-2015-3210", models.ChangelogExactMatch},
},
models.Changelog{
Contents: `sysvinit (2.88dsf-59.3ubuntu1) xenial; urgency=low
sysvinit (2.88dsf-59.3) unstable; urgency=medium
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium`,
Method: models.ChangelogExactMatchStr,
},
},
{
@@ -147,59 +178,119 @@ sysvinit (2.88dsf-57) unstable; urgency=low`,
[]string{
"bsdutils",
"1:2.27.1-1ubuntu3",
` util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
util-linux (2.27.1-3) unstable; urgency=medium
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
util-linux (2.27.1-2) unstable; urgency=medium
util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
util-linux (2.27.1-1) unstable; urgency=medium
util-linux (2.27-3ubuntu1) xenial; urgency=medium
util-linux (2.27-3) unstable; urgency=medium
util-linux (2.27-2) unstable; urgency=medium
util-linux (2.27-1) unstable; urgency=medium
util-linux (2.27~rc2-2) experimental; urgency=medium
util-linux (2.27~rc2-1) experimental; urgency=medium
util-linux (2.27~rc1-1) experimental; urgency=medium
util-linux (2.26.2-9) unstable; urgency=medium
util-linux (2.26.2-8) experimental; urgency=medium
util-linux (2.26.2-7) experimental; urgency=medium
util-linux (2.26.2-6ubuntu3) wily; urgency=medium
CVE-2015-2329: heap buffer overflow in compile_branch(). (Closes: #781795)
util-linux (2.26.2-6ubuntu2) wily; urgency=medium
util-linux (2.26.2-6ubuntu1) wily; urgency=medium
util-linux (2.26.2-6) unstable; urgency=medium`,
`util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
util-linux (2.27.1-3) unstable; urgency=medium
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
util-linux (2.27.1-2) unstable; urgency=medium
util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
util-linux (2.27.1-1) unstable; urgency=medium
util-linux (2.27-3ubuntu1) xenial; urgency=medium`,
},
[]DetectedCveID{
{"CVE-2015-2325", models.ChangelogLenientMatch},
{"CVE-2015-2326", models.ChangelogLenientMatch},
{"CVE-2015-3210", models.ChangelogLenientMatch},
{"CVE-2016-1000000", models.ChangelogLenientMatch},
},
models.Changelog{
Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
util-linux (2.27.1-3) unstable; urgency=medium
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
util-linux (2.27.1-2) unstable; urgency=medium
util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
util-linux (2.27.1-1ubuntu3) xenial; urgency=medium`,
Method: models.ChangelogLenientMatchStr,
},
},
{
// 1:ver-ubuntu3
[]string{
"CVE-2015-2325",
"CVE-2015-2326",
"CVE-2015-3210",
"bsdutils",
"1:2.27-3ubuntu3",
`util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
util-linux (2.27.1-3) unstable; urgency=medium
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
util-linux (2.27.1-2) unstable; urgency=medium
util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
util-linux (2.27.1-1) unstable; urgency=medium
util-linux (2.27-3) xenial; urgency=medium`,
},
[]DetectedCveID{
{"CVE-2015-2325", models.ChangelogLenientMatch},
{"CVE-2015-2326", models.ChangelogLenientMatch},
{"CVE-2015-3210", models.ChangelogLenientMatch},
{"CVE-2016-1000000", models.ChangelogLenientMatch},
},
models.Changelog{
Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
util-linux (2.27.1-3) unstable; urgency=medium
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
util-linux (2.27.1-2) unstable; urgency=medium
util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
util-linux (2.27.1-1) unstable; urgency=medium
util-linux (2.27-3) xenial; urgency=medium`,
Method: models.ChangelogLenientMatchStr,
},
},
{
// https://github.com/future-architect/vuls/pull/350
[]string{
"tar",
"1.27.1-2+b1",
`tar (1.27.1-2+deb8u1) jessie-security; urgency=high
* CVE-2016-6321: Bypassing the extract path name.
tar (1.27.1-2) unstable; urgency=low`,
},
[]DetectedCveID{
{"CVE-2016-6321", models.ChangelogLenientMatch},
},
models.Changelog{
Contents: `tar (1.27.1-2+deb8u1) jessie-security; urgency=high
* CVE-2016-6321: Bypassing the extract path name.
tar (1.27.1-2) unstable; urgency=low`,
Method: models.ChangelogLenientMatchStr,
},
},
}
d := newDebian(config.ServerInfo{})
for _, tt := range tests {
actual, _ := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], tt.in[1])
if len(actual) != len(tt.expected) {
t.Errorf("Len of return array are'nt same. expected %#v, actual %#v", tt.expected, actual)
d.Distro.Family = "ubuntu"
for i, tt := range tests {
aCveIDs, aClog := d.getCveIDsFromChangelog(tt.in[2], tt.in[0], tt.in[1])
if len(aCveIDs) != len(tt.cveIDs) {
t.Errorf("[%d] Len of return array are'nt same. expected %#v, actual %#v", i, tt.cveIDs, aCveIDs)
t.Errorf(pp.Sprintf("%s", tt.in))
continue
}
for i := range tt.expected {
if actual[i] != tt.expected[i] {
t.Errorf("expected %s, actual %s", tt.expected[i], actual[i])
for j := range tt.cveIDs {
if !reflect.DeepEqual(tt.cveIDs[j], aCveIDs[j]) {
t.Errorf("[%d] expected %v, actual %v", i, tt.cveIDs[j], aCveIDs[j])
}
}
}
for _, tt := range tests {
_, err := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], "version number do'nt match case")
if err != nil {
t.Errorf("Returning error is unexpected")
if aClog.Contents != tt.changelog.Contents {
t.Errorf(pp.Sprintf("expected: %s, actual: %s", tt.changelog.Contents, aClog.Contents))
}
if aClog.Method != tt.changelog.Method {
t.Errorf(pp.Sprintf("expected: %s, actual: %s", tt.changelog.Method, aClog.Method))
}
}
}
@@ -520,6 +611,95 @@ Calculating upgrade... Done
}
}
func TestGetChangelogCache(t *testing.T) {
const servername = "server1"
pack := models.PackageInfo{
Name: "apt",
Version: "1.0.0",
NewVersion: "1.0.1",
}
var meta = cache.Meta{
Name: servername,
Distro: config.Distro{
Family: "ubuntu",
Release: "16.04",
},
Packs: []models.PackageInfo{pack},
}
const path = "/tmp/vuls-test-cache-11111111.db"
log := logrus.NewEntry(&logrus.Logger{})
if err := cache.SetupBolt(path, log); err != nil {
t.Errorf("Failed to setup bolt: %s", err)
}
defer os.Remove(path)
if err := cache.DB.EnsureBuckets(meta); err != nil {
t.Errorf("Failed to ensure buckets: %s", err)
}
d := newDebian(config.ServerInfo{})
actual := d.getChangelogCache(&meta, pack)
if actual != "" {
t.Errorf("Failed to get empty stirng from cache:")
}
clog := "changelog-text"
if err := cache.DB.PutChangelog(servername, "apt", clog); err != nil {
t.Errorf("Failed to put changelog: %s", err)
}
actual = d.getChangelogCache(&meta, pack)
if actual != clog {
t.Errorf("Failed to get changelog from cache: %s", actual)
}
// increment a version of the pack
pack.NewVersion = "1.0.2"
actual = d.getChangelogCache(&meta, pack)
if actual != "" {
t.Errorf("The changelog is not invalidated: %s", actual)
}
// change a name of the pack
pack.Name = "bash"
actual = d.getChangelogCache(&meta, pack)
if actual != "" {
t.Errorf("The changelog is not invalidated: %s", actual)
}
}
func TestSplitAptCachePolicy(t *testing.T) {
var tests = []struct {
stdout string
expected map[string]string
}{
// This function parse apt-cache policy by using Regexp multi-line mode.
// So, test data includes "\r\n"
{
"apt:\r\n Installed: 1.2.6\r\n Candidate: 1.2.12~ubuntu16.04.1\r\n Version table:\r\n 1.2.12~ubuntu16.04.1 500\r\n 500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n 1.2.10ubuntu1 500\r\n 500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n 100 /var/lib/dpkg/status\r\napt-utils:\r\n Installed: 1.2.6\r\n Candidate: 1.2.12~ubuntu16.04.1\r\n Version table:\r\n 1.2.12~ubuntu16.04.1 500\r\n 500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n 1.2.10ubuntu1 500\r\n 500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n 100 /var/lib/dpkg/status\r\nbase-files:\r\n Installed: 9.4ubuntu3\r\n Candidate: 9.4ubuntu4.2\r\n Version table:\r\n 9.4ubuntu4.2 500\r\n 500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n 9.4ubuntu4 500\r\n 500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 9.4ubuntu3 100\r\n 100 /var/lib/dpkg/status\r\n",
map[string]string{
"apt": "apt:\r\n Installed: 1.2.6\r\n Candidate: 1.2.12~ubuntu16.04.1\r\n Version table:\r\n 1.2.12~ubuntu16.04.1 500\r\n 500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n 1.2.10ubuntu1 500\r\n 500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n 100 /var/lib/dpkg/status\r\n",
"apt-utils": "apt-utils:\r\n Installed: 1.2.6\r\n Candidate: 1.2.12~ubuntu16.04.1\r\n Version table:\r\n 1.2.12~ubuntu16.04.1 500\r\n 500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n 1.2.10ubuntu1 500\r\n 500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n 100 /var/lib/dpkg/status\r\n",
"base-files": "base-files:\r\n Installed: 9.4ubuntu3\r\n Candidate: 9.4ubuntu4.2\r\n Version table:\r\n 9.4ubuntu4.2 500\r\n 500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n 9.4ubuntu4 500\r\n 500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 9.4ubuntu3 100\r\n 100 /var/lib/dpkg/status\r\n",
},
},
}
d := newDebian(config.ServerInfo{})
for _, tt := range tests {
actual := d.splitAptCachePolicy(tt.stdout)
if !reflect.DeepEqual(tt.expected, actual) {
e := pp.Sprintf("%v", tt.expected)
a := pp.Sprintf("%v", actual)
t.Errorf("expected %s, actual %s", e, a)
}
}
}
func TestParseAptCachePolicy(t *testing.T) {
var tests = []struct {

View File

@@ -25,8 +25,7 @@ import (
"io/ioutil"
"net"
"os"
"os/exec"
"runtime"
ex "os/exec"
"strings"
"syscall"
"time"
@@ -40,7 +39,7 @@ import (
"github.com/future-architect/vuls/util"
)
type sshResult struct {
type execResult struct {
Servername string
Host string
Port string
@@ -51,16 +50,13 @@ type sshResult struct {
Error error
}
func (s sshResult) String() string {
func (s execResult) String() string {
return fmt.Sprintf(
"SSHResult: servername: %s, cmd: %s, exitstatus: %d, stdout: %s, stderr: %s, err: %s",
"execResult: servername: %s\n cmd: %s\n exitstatus: %d\n stdout: %s\n stderr: %s\n err: %s",
s.Servername, s.Cmd, s.ExitStatus, s.Stdout, s.Stderr, s.Error)
}
func (s sshResult) isSuccess(expectedStatusCodes ...int) bool {
if s.Error != nil {
return false
}
func (s execResult) isSuccess(expectedStatusCodes ...int) bool {
if len(expectedStatusCodes) == 0 {
return s.ExitStatus == 0
}
@@ -69,38 +65,36 @@ func (s sshResult) isSuccess(expectedStatusCodes ...int) bool {
return true
}
}
if s.Error != nil {
return false
}
return false
}
// Sudo is Const value for sudo mode
// sudo is Const value for sudo mode
const sudo = true
// NoSudo is Const value for normal user mode
// noSudo is Const value for normal user mode
const noSudo = false
func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []error) {
resChan := make(chan string, len(servers))
errChan := make(chan error, len(servers))
defer close(errChan)
// Issue commands to the target servers in parallel via SSH or local execution. If execution fails, the server will be excluded from the target server list(servers) and added to the error server list(errServers).
func parallelExec(fn func(osTypeInterface) error, timeoutSec ...int) {
resChan := make(chan osTypeInterface, len(servers))
defer close(resChan)
for _, s := range servers {
go func(s osTypeInterface) {
defer func() {
if p := recover(); p != nil {
logrus.Debugf("Panic: %s on %s",
p, s.getServerInfo().ServerName)
util.Log.Debugf("Panic: %s on %s",
p, s.getServerInfo().GetServerName())
}
}()
if err := fn(s); err != nil {
errChan <- fmt.Errorf("%s@%s:%s: %s",
s.getServerInfo().User,
s.getServerInfo().Host,
s.getServerInfo().Port,
err,
)
s.setErrs([]error{err})
resChan <- s
} else {
resChan <- s.getServerInfo().ServerName
resChan <- s
}
}(s)
}
@@ -112,48 +106,55 @@ func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []
timeout = timeoutSec[0]
}
var snames []string
var successes []osTypeInterface
isTimedout := false
for i := 0; i < len(servers); i++ {
select {
case s := <-resChan:
snames = append(snames, s)
case err := <-errChan:
errs = append(errs, err)
if len(s.getErrs()) == 0 {
successes = append(successes, s)
} else {
util.Log.Errorf("Error: %s, err: %s",
s.getServerInfo().GetServerName(), s.getErrs())
errServers = append(errServers, s)
}
case <-time.After(time.Duration(timeout) * time.Second):
isTimedout = true
}
}
// collect timed out servernames
var timedoutSnames []string
if isTimedout {
// set timed out error and append to errServers
for _, s := range servers {
name := s.getServerInfo().ServerName
name := s.getServerInfo().GetServerName()
found := false
for _, t := range snames {
if name == t {
for _, ss := range successes {
if name == ss.getServerInfo().GetServerName() {
found = true
break
}
}
if !found {
timedoutSnames = append(timedoutSnames, name)
msg := fmt.Sprintf("Timed out: %s",
s.getServerInfo().GetServerName())
util.Log.Errorf(msg)
s.setErrs([]error{fmt.Errorf(msg)})
errServers = append(errServers, s)
}
}
}
if isTimedout {
errs = append(errs, fmt.Errorf(
"Timed out: %s", timedoutSnames))
}
servers = successes
return
}
func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result sshResult) {
if isSSHExecNative() {
result = sshExecNative(c, cmd, sudo)
} else {
func exec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result execResult) {
if c.Port == "local" &&
(c.Host == "127.0.0.1" || c.Host == "localhost") {
result = localExec(c, cmd, sudo)
} else if conf.Conf.SSHExternal {
result = sshExecExternal(c, cmd, sudo)
} else {
result = sshExecNative(c, cmd, sudo)
}
logger := getSSHLogger(log...)
@@ -161,11 +162,37 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
return
}
func isSSHExecNative() bool {
return runtime.GOOS == "windows" || !conf.Conf.SSHExternal
func localExec(c conf.ServerInfo, cmdstr string, sudo bool) (result execResult) {
cmdstr = decorateCmd(c, cmdstr, sudo)
var cmd *ex.Cmd
if c.Distro.Family == "FreeBSD" {
cmd = ex.Command("/bin/sh", "-c", cmdstr)
} else {
cmd = ex.Command("/bin/bash", "-c", cmdstr)
}
var stdoutBuf, stderrBuf bytes.Buffer
cmd.Stdout = &stdoutBuf
cmd.Stderr = &stderrBuf
if err := cmd.Run(); err != nil {
result.Error = err
if exitError, ok := err.(*ex.ExitError); ok {
waitStatus := exitError.Sys().(syscall.WaitStatus)
result.ExitStatus = waitStatus.ExitStatus()
} else {
result.ExitStatus = 999
}
} else {
result.ExitStatus = 0
}
result.Stdout = stdoutBuf.String()
result.Stderr = stderrBuf.String()
result.Cmd = strings.Replace(cmdstr, "\n", "", -1)
return
}
func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult) {
func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result execResult) {
result.Servername = c.ServerName
result.Host = c.Host
result.Port = c.Port
@@ -195,7 +222,7 @@ func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult)
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
if err = session.RequestPty("xterm", 400, 256, modes); err != nil {
if err = session.RequestPty("xterm", 400, 1000, modes); err != nil {
result.Error = fmt.Errorf(
"Failed to request for pseudo terminal. servername: %s, err: %s",
c.ServerName, err)
@@ -207,7 +234,7 @@ func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult)
session.Stdout = &stdoutBuf
session.Stderr = &stderrBuf
cmd = decolateCmd(c, cmd, sudo)
cmd = decorateCmd(c, cmd, sudo)
if err := session.Run(cmd); err != nil {
if exitErr, ok := err.(*ssh.ExitError); ok {
result.ExitStatus = exitErr.ExitStatus()
@@ -224,14 +251,14 @@ func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult)
return
}
func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result sshResult) {
sshBinaryPath, err := exec.LookPath("ssh")
func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result execResult) {
sshBinaryPath, err := ex.LookPath("ssh")
if err != nil {
return sshExecNative(c, cmd, sudo)
}
defaultSSHArgs := []string{
"-t",
"-tt",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "LogLevel=quiet",
@@ -257,17 +284,17 @@ func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result sshResult
args = append(args, "-o", "PasswordAuthentication=no")
}
cmd = decolateCmd(c, cmd, sudo)
// cmd = fmt.Sprintf("stty cols 256; set -o pipefail; %s", cmd)
cmd = decorateCmd(c, cmd, sudo)
cmd = fmt.Sprintf("stty cols 1000; %s", cmd)
args = append(args, cmd)
execCmd := exec.Command(sshBinaryPath, args...)
execCmd := ex.Command(sshBinaryPath, args...)
var stdoutBuf, stderrBuf bytes.Buffer
execCmd.Stdout = &stdoutBuf
execCmd.Stderr = &stderrBuf
if err := execCmd.Run(); err != nil {
if e, ok := err.(*exec.ExitError); ok {
if e, ok := err.(*ex.ExitError); ok {
if s, ok := e.Sys().(syscall.WaitStatus); ok {
result.ExitStatus = s.ExitStatus()
} else {
@@ -296,21 +323,27 @@ func getSSHLogger(log ...*logrus.Entry) *logrus.Entry {
return log[0]
}
func decolateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
func decorateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
if sudo && c.User != "root" && !c.IsContainer() {
cmd = fmt.Sprintf("sudo -S %s", cmd)
cmd = strings.Replace(cmd, "|", "| sudo ", -1)
}
if c.Family != "FreeBSD" {
// set pipefail option. Bash only
// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
}
// If you are using pipe and you want to detect preprocessing errors, remove comment out
// switch c.Distro.Family {
// case "FreeBSD", "ubuntu", "debian", "raspbian":
// default:
// // set pipefail option. Bash only
// // http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
// cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
// }
if c.IsContainer() {
switch c.Container.Type {
switch c.Containers.Type {
case "", "docker":
cmd = fmt.Sprintf(`docker exec %s /bin/bash -c "%s"`, c.Container.ContainerID, cmd)
case "lxd":
cmd = fmt.Sprintf(`lxc exec %s -- /bin/bash -c "%s"`, c.Container.Name, cmd)
}
}
// cmd = fmt.Sprintf("set -x; %s", cmd)
@@ -318,7 +351,7 @@ func decolateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
}
func getAgentAuth() (auth ssh.AuthMethod, ok bool) {
if sock := os.Getenv("SSH_AUTH_SOCK"); len(sock) > 0 {
if sock := os.Getenv("SSH_AUTH_SOCK"); 0 < len(sock) {
if agconn, err := net.Dial("unix", sock); err == nil {
ag := agent.NewClient(agconn)
auth = ssh.PublicKeysCallback(ag.Signers)

View File

@@ -1,3 +1,20 @@
/* 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 scan
import (
@@ -5,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"
)
@@ -19,37 +35,37 @@ type bsd struct {
func newBsd(c config.ServerInfo) *bsd {
d := &bsd{}
d.log = util.NewCustomLogger(c)
d.setServerInfo(c)
return d
}
//https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/freebsd.rb
func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) {
bsd = newBsd(c)
c.Family = "FreeBSD"
if r := sshExec(c, "uname", noSudo); r.isSuccess() {
// Prevent from adding `set -o pipefail` option
c.Distro = config.Distro{Family: "FreeBSD"}
if r := exec(c, "uname", noSudo); r.isSuccess() {
if strings.Contains(r.Stdout, "FreeBSD") == true {
if b := sshExec(c, "uname -r", noSudo); b.isSuccess() {
bsd.setDistributionInfo("FreeBSD", strings.TrimSpace(b.Stdout))
bsd.setServerInfo(c)
if b := exec(c, "freebsd-version", noSudo); b.isSuccess() {
rel := strings.TrimSpace(b.Stdout)
bsd.setDistro("FreeBSD", rel)
return true, bsd
}
}
}
Log.Debugf("Not FreeBSD. servernam: %s", c.ServerName)
util.Log.Debugf("Not FreeBSD. servernam: %s", c.ServerName)
return false, bsd
}
func (o *bsd) checkIfSudoNoPasswd() error {
// FreeBSD doesn't need root privilege
o.log.Infof("sudo ... OK")
o.log.Infof("sudo ... No need")
return nil
}
func (o *bsd) install() error {
return nil
}
func (o *bsd) checkRequiredPackagesInstalled() error {
func (o *bsd) checkDependencies() error {
return nil
}
@@ -62,40 +78,40 @@ 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
}
func (o *bsd) scanInstalledPackages() ([]models.PackageInfo, error) {
cmd := util.PrependProxyEnv("pkg version -v")
r := o.ssh(cmd, noSudo)
r := o.exec(cmd, noSudo)
if !r.isSuccess() {
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
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)
r := o.exec(cmd, noSudo)
if !r.isSuccess(0) {
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
cmd = util.PrependProxyEnv("pkg audit -F -r -f " + vulndbPath)
r = o.ssh(cmd, noSudo)
r = o.exec(cmd, noSudo)
if !r.isSuccess(0, 1) {
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
if r.ExitStatus == 0 {
// no vulnerabilities
return []CvePacksInfo{}, nil
return []models.VulnInfo{}, nil
}
var packAdtRslt []pkgAuditResult
@@ -126,35 +142,24 @@ 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,
Confidence: models.PkgAuditMatch,
})
}
return

View File

@@ -139,17 +139,17 @@ WWW: https://vuxml.FreeBSD.org/freebsd/ab3e98d9-8175-11e4-907d-d050992ecde8.html
d := newBsd(config.ServerInfo{})
for _, tt := range tests {
aName, aCveIDs, aVunlnID := d.parseBlock(tt.in)
aName, aCveIDs, aVulnID := d.parseBlock(tt.in)
if tt.name != aName {
t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVunlnID)
t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVulnID)
}
for i := range tt.cveIDs {
if tt.cveIDs[i] != aCveIDs[i] {
t.Errorf("expected cveID: %s, actual %s", tt.cveIDs[i], aCveIDs[i])
}
}
if tt.vulnID != aVunlnID {
t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVunlnID)
if tt.vulnID != aVulnID {
t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVulnID)
}
}
}

View File

@@ -21,12 +21,10 @@ import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"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"
@@ -42,179 +40,188 @@ type redhat struct {
func newRedhat(c config.ServerInfo) *redhat {
r := &redhat{}
r.log = util.NewCustomLogger(c)
r.setServerInfo(c)
return r
}
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/redhat.rb
func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
red = newRedhat(c)
red.setServerInfo(c)
if r := sshExec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
red.setDistributionInfo("fedora", "unknown")
Log.Warn("Fedora not tested yet: %s", r)
if r := exec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
red.setDistro("fedora", "unknown")
util.Log.Warn("Fedora not tested yet: %s", r)
return true, red
}
if r := sshExec(c, "ls /etc/redhat-release", noSudo); r.isSuccess() {
if r := exec(c, "ls /etc/oracle-release", noSudo); r.isSuccess() {
// Need to discover Oracle Linux first, because it provides an
// /etc/redhat-release that matches the upstream distribution
if r := exec(c, "cat /etc/oracle-release", noSudo); r.isSuccess() {
re := regexp.MustCompile(`(.*) release (\d[\d.]*)`)
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) != 3 {
util.Log.Warn("Failed to parse Oracle Linux version: %s", r)
return true, red
}
release := result[2]
red.setDistro("oraclelinux", release)
return true, red
}
}
if r := exec(c, "ls /etc/redhat-release", noSudo); r.isSuccess() {
// https://www.rackaid.com/blog/how-to-determine-centos-or-red-hat-version/
// e.g.
// $ cat /etc/redhat-release
// CentOS release 6.5 (Final)
if r := sshExec(c, "cat /etc/redhat-release", noSudo); r.isSuccess() {
re, _ := regexp.Compile(`(.*) release (\d[\d.]*)`)
if r := exec(c, "cat /etc/redhat-release", noSudo); r.isSuccess() {
re := regexp.MustCompile(`(.*) release (\d[\d.]*)`)
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) != 3 {
Log.Warn("Failed to parse RedHat/CentOS version: %s", r)
util.Log.Warn("Failed to parse RedHat/CentOS version: %s", r)
return true, red
}
release := result[2]
switch strings.ToLower(result[1]) {
case "centos", "centos linux":
red.setDistributionInfo("centos", release)
red.setDistro("centos", release)
default:
red.setDistributionInfo("rhel", release)
red.setDistro("rhel", release)
}
return true, red
}
return true, red
}
if r := sshExec(c, "ls /etc/system-release", noSudo); r.isSuccess() {
if r := exec(c, "ls /etc/system-release", noSudo); r.isSuccess() {
family := "amazon"
release := "unknown"
if r := sshExec(c, "cat /etc/system-release", noSudo); r.isSuccess() {
if r := exec(c, "cat /etc/system-release", noSudo); r.isSuccess() {
fields := strings.Fields(r.Stdout)
if len(fields) == 5 {
release = fields[4]
}
}
red.setDistributionInfo(family, release)
red.setDistro(family, release)
return true, red
}
Log.Debugf("Not RedHat like Linux. servername: %s", c.ServerName)
util.Log.Debugf("Not RedHat like Linux. servername: %s", c.ServerName)
return false, red
}
func (o *redhat) checkIfSudoNoPasswd() error {
r := o.ssh("yum --version", sudo)
if !r.isSuccess() {
o.log.Errorf("sudo error on %s", r)
return fmt.Errorf("Failed to sudo: %s", r)
}
o.log.Infof("sudo ... OK")
return nil
}
// CentOS 5 ... yum-plugin-security, yum-changelog
// CentOS 6 ... yum-plugin-security, yum-plugin-changelog
// CentOS 7 ... yum-plugin-security, yum-plugin-changelog
// RHEL, Amazon ... no additinal packages needed
func (o *redhat) install() error {
switch o.Family {
case "rhel", "amazon":
o.log.Infof("Nothing to do")
if !o.sudo() {
o.log.Infof("sudo ... No need")
return nil
}
if err := o.installYumPluginSecurity(); err != nil {
return err
type cmd struct {
cmd string
expectedStatusCodes []int
}
return o.installYumChangelog()
}
var cmds []cmd
var zero = []int{0}
func (o *redhat) installYumPluginSecurity() error {
if r := o.ssh("rpm -q yum-plugin-security", noSudo); r.isSuccess() {
o.log.Infof("Ignored: yum-plugin-security already installed")
return nil
}
o.log.Info("Installing yum-plugin-security...")
cmd := util.PrependProxyEnv("yum install -y yum-plugin-security")
if r := o.ssh(cmd, sudo); !r.isSuccess() {
return fmt.Errorf("Failed to SSH: %s", r)
}
return nil
}
func (o *redhat) installYumChangelog() error {
if o.Family == "centos" {
var majorVersion int
if 0 < len(o.Release) {
majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
} else {
return fmt.Errorf(
"Not implemented yet. family: %s, release: %s",
o.Family, o.Release)
switch o.Distro.Family {
case "centos":
cmds = []cmd{
{"yum --changelog --assumeno update yum", []int{0, 1}},
}
case "rhel", "oraclelinux":
majorVersion, err := o.Distro.MajorVersion()
if err != nil {
return fmt.Errorf("Not implemented yet: %s, err: %s", o.Distro, err)
}
var packName = ""
if majorVersion < 6 {
packName = "yum-changelog"
cmds = []cmd{
{"yum --color=never repolist", zero},
{"yum --color=never check-update", []int{0, 100}},
{"yum --color=never list-security --security", zero},
{"yum --color=never info-security", zero},
}
} else {
packName = "yum-plugin-changelog"
}
cmd := "rpm -q " + packName
if r := o.ssh(cmd, noSudo); r.isSuccess() {
o.log.Infof("Ignored: %s already installed", packName)
return nil
}
o.log.Infof("Installing %s...", packName)
cmd = util.PrependProxyEnv("yum install -y " + packName)
if r := o.ssh(cmd, sudo); !r.isSuccess() {
return fmt.Errorf("Failed to SSH: %s", r)
}
o.log.Infof("Installed: %s", packName)
}
return nil
}
func (o *redhat) checkRequiredPackagesInstalled() error {
if config.Conf.UseYumPluginSecurity {
// check if yum-plugin-security is installed.
// Amazon Linux, REHL can execute 'yum updateinfo --security updates' without yum-plugin-security
if o.Family == "centos" {
cmd := "rpm -q yum-plugin-security"
if r := o.ssh(cmd, noSudo); !r.isSuccess() {
msg := "yum-plugin-security is not installed"
o.log.Errorf(msg)
return fmt.Errorf(msg)
cmds = []cmd{
{"yum --color=never repolist", zero},
{"yum --color=never check-update", []int{0, 100}},
{"yum --color=never --security updateinfo list updates", zero},
{"yum --color=never --security updateinfo updates", zero},
}
}
}
for _, c := range cmds {
cmd := util.PrependProxyEnv(c.cmd)
o.log.Infof("Checking... sudo %s", cmd)
r := o.exec(util.PrependProxyEnv(cmd), o.sudo())
if !r.isSuccess(c.expectedStatusCodes...) {
o.log.Errorf("Check sudo or proxy settings: %s", r)
return fmt.Errorf("Failed to sudo: %s", r)
}
}
o.log.Infof("Sudo... Pass")
return nil
}
// CentOS 6, 7 ... yum-plugin-changelog
// RHEL 5 ... yum-security
// RHEL 6, 7 ... -
// Amazon ... -
func (o *redhat) checkDependencies() error {
var packName string
if o.Distro.Family == "amazon" {
return nil
}
if o.Family == "centos" {
var majorVersion int
if 0 < len(o.Release) {
majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
} else {
msg := fmt.Sprintf("Not implemented yet. family: %s, release: %s", o.Family, o.Release)
majorVersion, err := o.Distro.MajorVersion()
if err != nil {
msg := fmt.Sprintf("Not implemented yet: %s, err: %s", o.Distro, err)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
if o.Distro.Family == "centos" {
if majorVersion < 6 {
msg := fmt.Sprintf("CentOS %s is not supported", o.Distro.Release)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
var packName = ""
if majorVersion < 6 {
packName = "yum-changelog"
} else {
packName = "yum-plugin-changelog"
}
cmd := "rpm -q " + packName
if r := o.ssh(cmd, noSudo); !r.isSuccess() {
msg := fmt.Sprintf("%s is not installed", packName)
// --assumeno option of yum is needed.
cmd := "yum -h | grep assumeno"
if r := o.exec(cmd, noSudo); !r.isSuccess() {
msg := fmt.Sprintf("Installed yum is old. Please update yum and then retry")
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
}
switch o.Distro.Family {
case "centos":
packName = "yum-plugin-changelog"
case "rhel", "oraclelinux":
if majorVersion < 6 {
packName = "yum-security"
} else {
// yum-plugin-security is installed by default on RHEL6, 7
return nil
}
default:
return fmt.Errorf("Not implemented yet: %s", o.Distro)
}
cmd := "rpm -q " + packName
if r := o.exec(cmd, noSudo); !r.isSuccess() {
msg := fmt.Sprintf("%s is not installed", packName)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
o.log.Infof("Dependencies... Pass")
return nil
}
@@ -227,18 +234,18 @@ 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
}
func (o *redhat) scanInstalledPackages() (installedPackages models.PackageInfoList, err error) {
cmd := "rpm -qa --queryformat '%{NAME}\t%{VERSION}\t%{RELEASE}\n'"
r := o.ssh(cmd, noSudo)
r := o.exec(cmd, noSudo)
if r.isSuccess() {
// e.g.
// openssl 1.0.1e 30.el6.11
@@ -273,9 +280,9 @@ func (o *redhat) parseScannedPackagesLine(line string) (models.PackageInfo, erro
}, nil
}
func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
if o.Family != "centos" || config.Conf.UseYumPluginSecurity {
// Amazon, RHEL has yum updateinfo as default
func (o *redhat) scanVulnInfos() ([]models.VulnInfo, error) {
if o.Distro.Family != "centos" {
// Amazon, RHEL, Oracle Linux has yum updateinfo as default
// yum updateinfo can collenct vendor advisory information.
return o.scanUnsecurePackagesUsingYumPluginSecurity()
}
@@ -284,11 +291,16 @@ func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
return o.scanUnsecurePackagesUsingYumCheckUpdate()
}
//TODO return whether already expired.
func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error) {
// For CentOS
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)
} else {
cmd = fmt.Sprintf(cmd, "")
}
cmd := "LANG=en_US.UTF-8 yum --color=never check-update"
r := o.ssh(util.PrependProxyEnv(cmd), sudo)
r := o.exec(util.PrependProxyEnv(cmd), noSudo)
if !r.isSuccess(0, 100) {
//returns an exit code of 100 if there are available updates.
return nil, fmt.Errorf("Failed to SSH: %s", r)
@@ -301,24 +313,42 @@ 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
}
rpm2changelog, err = o.parseAllChangelog(allChangelog)
// { packageName: changelog-lines }
var rpm2changelog map[string]*string
rpm2changelog, err = o.divideChangelogByPackage(allChangelog)
if err != nil {
return nil, fmt.Errorf("Failed to parseAllChangelog. err: %s", err)
}
for name, clog := range rpm2changelog {
for i, p := range o.Packages {
n := fmt.Sprintf("%s-%s-%s",
p.Name, p.NewVersion, p.NewRelease)
if name == n {
o.Packages[i].Changelog = models.Changelog{
Contents: *clog,
Method: models.ChangelogExactMatchStr,
}
break
}
}
}
var results []PackInfoCveIDs
for i, packInfo := range packInfoList {
changelog := o.getChangelogCVELines(rpm2changelog, packInfo)
@@ -370,39 +400,21 @@ 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,
Confidence: models.ChangelogExactMatch,
})
}
return cvePacksList, nil
return vinfos, nil
}
// parseYumCheckUpdateLines parse yum check-update to get package name, candidate version
@@ -416,7 +428,9 @@ func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.Package
continue
}
if needToParse {
if strings.HasPrefix(line, "Obsoleting") {
if strings.HasPrefix(line, "Obsoleting") ||
strings.HasPrefix(line, "Security:") {
// see https://github.com/future-architect/vuls/issues/165
continue
}
candidate, err := o.parseYumCheckUpdateLine(line)
@@ -433,6 +447,7 @@ func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.Package
}
installed.NewVersion = candidate.NewVersion
installed.NewRelease = candidate.NewRelease
installed.Repository = candidate.Repository
results = append(results, installed)
}
}
@@ -441,7 +456,7 @@ func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.Package
func (o *redhat) parseYumCheckUpdateLine(line string) (models.PackageInfo, error) {
fields := strings.Fields(line)
if len(fields) != 3 {
if len(fields) < 3 {
return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line)
}
splitted := strings.Split(fields[0], ".")
@@ -452,16 +467,19 @@ func (o *redhat) parseYumCheckUpdateLine(line string) (models.PackageInfo, error
packName = strings.Join(strings.Split(fields[0], ".")[0:(len(splitted)-1)], ".")
}
fields = strings.Split(fields[1], "-")
if len(fields) != 2 {
verfields := strings.Split(fields[1], "-")
if len(verfields) != 2 {
return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line)
}
version := o.regexpReplace(fields[0], `^[0-9]+:`, "")
release := fields[1]
version := o.regexpReplace(verfields[0], `^[0-9]+:`, "")
release := verfields[1]
repos := strings.Join(fields[2:len(fields)], " ")
return models.PackageInfo{
Name: packName,
NewVersion: version,
NewRelease: release,
Repository: repos,
}, nil
}
@@ -471,18 +489,19 @@ func (o *redhat) mkPstring() *string {
}
func (o *redhat) regexpReplace(src string, pat string, rep string) string {
r := regexp.MustCompile(pat)
return r.ReplaceAllString(src, rep)
re := regexp.MustCompile(pat)
return re.ReplaceAllString(src, rep)
}
var changeLogCVEPattern = regexp.MustCompile(`CVE-[0-9]+-[0-9]+`)
func (o *redhat) getChangelogCVELines(rpm2changelog map[string]*string, packInfo models.PackageInfo) string {
rpm := fmt.Sprintf("%s-%s-%s", packInfo.Name, packInfo.NewVersion, packInfo.NewRelease)
retLine := ""
if rpm2changelog[rpm] != nil {
lines := strings.Split(*rpm2changelog[rpm], "\n")
for _, line := range lines {
match, _ := regexp.MatchString("CVE-[0-9]+-[0-9]+", line)
if match {
if changeLogCVEPattern.MatchString(line) {
retLine += fmt.Sprintf("%s\n", line)
}
}
@@ -490,27 +509,26 @@ func (o *redhat) getChangelogCVELines(rpm2changelog map[string]*string, packInfo
return retLine
}
func (o *redhat) parseAllChangelog(allChangelog string) (map[string]*string, error) {
func (o *redhat) divideChangelogByPackage(allChangelog string) (map[string]*string, error) {
var majorVersion int
if 0 < len(o.Release) && o.Family == "centos" {
majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
} else {
return nil, fmt.Errorf(
"Not implemented yet. family: %s, release: %s",
o.Family, o.Release)
var err error
if o.Distro.Family == "centos" {
majorVersion, err = o.Distro.MajorVersion()
if err != nil {
return nil, fmt.Errorf("Not implemented yet: %s, err: %s", o.Distro, err)
}
}
orglines := strings.Split(allChangelog, "\n")
tmpline := ""
var lines []string
var prev, now bool
var err error
for i := range orglines {
if majorVersion == 5 {
/* for CentOS5 (yum-util < 1.1.20) */
prev = false
now = false
if i > 0 {
if 0 < i {
prev, err = o.isRpmPackageNameLine(orglines[i-1])
if err != nil {
return nil, err
@@ -560,8 +578,7 @@ func (o *redhat) parseAllChangelog(allChangelog string) (map[string]*string, err
rpm2changelog[rpm] = pNewString
}
} else {
stop, _ := regexp.MatchString("^Dependencies Resolved", line)
if stop {
if strings.HasPrefix(line, "Dependencies Resolved") {
return rpm2changelog, nil
}
*writePointer += fmt.Sprintf("%s\n", line)
@@ -570,6 +587,7 @@ func (o *redhat) parseAllChangelog(allChangelog string) (map[string]*string, err
return rpm2changelog, nil
}
// CentOS
func (o *redhat) getAllChangelog(packInfoList models.PackageInfoList) (stdout string, err error) {
packageNames := ""
for _, packInfo := range packInfoList {
@@ -577,23 +595,28 @@ func (o *redhat) getAllChangelog(packInfoList models.PackageInfoList) (stdout st
}
command := ""
if o.ServerInfo.User == "root" {
command = "echo N | "
}
if 0 < len(config.Conf.HTTPProxy) {
command += util.ProxyEnv()
}
// yum update --changelog doesn't have --color option.
command += fmt.Sprintf(" LANG=en_US.UTF-8 yum update --changelog %s", packageNames)
yumopts := ""
if o.getServerInfo().Enablerepo != "" {
yumopts = " --enablerepo=" + o.getServerInfo().Enablerepo
}
if config.Conf.SkipBroken {
yumopts += " --skip-broken"
}
r := o.ssh(command, sudo)
// yum update --changelog doesn't have --color option.
command += fmt.Sprintf(" LANGUAGE=en_US.UTF-8 yum --changelog --assumeno update %s ", yumopts) + packageNames
r := o.exec(command, sudo)
if !r.isSuccess(0, 1) {
return "", fmt.Errorf(
"Failed to get changelog. status: %d, stdout: %s, stderr: %s",
r.ExitStatus, r.Stdout, r.Stderr)
}
return r.Stdout, nil
return strings.Replace(r.Stdout, "\r", "", -1), nil
}
type distroAdvisoryCveIDs struct {
@@ -602,33 +625,41 @@ type distroAdvisoryCveIDs struct {
}
// Scaning unsecure packages using yum-plugin-security.
//TODO return whether already expired.
func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, error) {
if o.Family == "centos" {
// Amazon, RHEL, Oracle Linux
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")
}
cmd := "yum --color=never repolist"
r := o.ssh(util.PrependProxyEnv(cmd), sudo)
r := o.exec(util.PrependProxyEnv(cmd), o.sudo())
if !r.isSuccess() {
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
// get advisoryID(RHSA, ALAS) - package name,version
cmd = "yum --color=never updateinfo list available --security"
r = o.ssh(util.PrependProxyEnv(cmd), sudo)
// get advisoryID(RHSA, ALAS, ELSA) - package name,version
major, err := (o.Distro.MajorVersion())
if err != nil {
return nil, fmt.Errorf("Not implemented yet: %s, err: %s", o.Distro, err)
}
if (o.Distro.Family == "rhel" || o.Distro.Family == "oraclelinux") && major == 5 {
cmd = "yum --color=never list-security --security"
} else {
cmd = "yum --color=never --security updateinfo list updates"
}
r = o.exec(util.PrependProxyEnv(cmd), o.sudo())
if !r.isSuccess() {
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
advIDPackNamesList, err := o.parseYumUpdateinfoListAvailable(r.Stdout)
// get package name, version, rel to be upgrade.
// cmd = "yum check-update --security"
cmd = "LANG=en_US.UTF-8 yum --color=never check-update"
r = o.ssh(util.PrependProxyEnv(cmd), sudo)
cmd = "LANGUAGE=en_US.UTF-8 yum --color=never check-update"
r = o.exec(util.PrependProxyEnv(cmd), o.sudo())
if !r.isSuccess(0, 100) {
//returns an exit code of 100 if there are available updates.
return nil, fmt.Errorf("Failed to SSH: %s", r)
@@ -639,6 +670,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{}
@@ -654,58 +688,57 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
dict[advIDPackNames.AdvisoryID] = packInfoList
}
// get advisoryID(RHSA, ALAS) - CVE IDs
cmd = "yum --color=never updateinfo --security update"
r = o.ssh(util.PrependProxyEnv(cmd), sudo)
// get advisoryID(RHSA, ALAS, ELSA) - CVE IDs
if (o.Distro.Family == "rhel" || o.Distro.Family == "oraclelinux") && major == 5 {
cmd = "yum --color=never info-security"
} else {
cmd = "yum --color=never --security updateinfo updates"
}
r = o.exec(util.PrependProxyEnv(cmd), o.sudo())
if !r.isSuccess() {
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
advisoryCveIDsList, err := o.parseYumUpdateinfo(r.Stdout)
if err != nil {
return CvePacksList{}, err
return nil, err
}
// pp.Println(advisoryCveIDsList)
// All information collected.
// Convert to CvePacksList.
o.log.Info("Fetching CVE details...")
result := CvePacksList{}
// Convert to VulnInfos.
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],
Confidence: models.YumUpdateSecurityMatch,
}
result = append(result, cpinfo)
vinfos = append(vinfos, cpinfo)
}
}
}
o.log.Info("Done")
return result, nil
return vinfos, nil
}
var horizontalRulePattern = regexp.MustCompile(`^=+$`)
func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveIDs, err error) {
sectionState := Outside
lines := strings.Split(stdout, "\n")
@@ -723,7 +756,7 @@ func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveID
line = strings.TrimSpace(line)
// find the new section pattern
if match, _ := o.isHorizontalRule(line); match {
if horizontalRulePattern.MatchString(line) {
// set previous section's result to return-variable
if sectionState == Content {
@@ -750,13 +783,13 @@ func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveID
switch sectionState {
case Header:
switch o.Family {
switch o.Distro.Family {
case "centos":
// CentOS has no security channel.
// So use yum check-update && parse changelog
return result, fmt.Errorf(
"yum updateinfo is not suppported on CentOS")
case "rhel", "amazon":
case "rhel", "amazon", "oraclelinux":
// nop
}
@@ -817,9 +850,8 @@ func (o *redhat) changeSectionState(state int) (newState int) {
return newState
}
func (o *redhat) isHorizontalRule(line string) (bool, error) {
return regexp.MatchString("^=+$", line)
}
var rpmPackageArchPattern = regexp.MustCompile(
`^[^ ]+\.(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)$`)
func (o *redhat) isRpmPackageNameLine(line string) (bool, error) {
s := strings.TrimPrefix(line, "ChangeLog for: ")
@@ -829,71 +861,43 @@ func (o *redhat) isRpmPackageNameLine(line string) (bool, error) {
}
for _, s := range ss {
s = strings.TrimRight(s, " \r\n")
ok, err := regexp.MatchString(
`^[^ ]+\.(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)$`, s)
if !ok {
return false, err
if !rpmPackageArchPattern.MatchString(s) {
return false, nil
}
}
return true, nil
}
// see test case
func (o *redhat) parseYumUpdateinfoHeaderCentOS(line string) (packs []models.PackageInfo, err error) {
pkgs := strings.Split(strings.TrimSpace(line), ",")
for _, pkg := range pkgs {
packs = append(packs, models.PackageInfo{})
s := strings.Split(pkg, "-")
if len(s) == 3 {
packs[len(packs)-1].Name = s[0]
packs[len(packs)-1].Version = s[1]
packs[len(packs)-1].Release = s[2]
} else {
return packs, fmt.Errorf("CentOS: Unknown Header format: %s", line)
}
}
return
}
func (o *redhat) parseYumUpdateinfoHeaderAmazon(line string) (a models.DistroAdvisory, names []string, err error) {
re, _ := regexp.Compile(`(ALAS-.+): (.+) priority package update for (.+)$`)
result := re.FindStringSubmatch(line)
if len(result) == 4 {
a.AdvisoryID = result[1]
a.Severity = result[2]
spaceSeparatedPacknames := result[3]
names = strings.Fields(spaceSeparatedPacknames)
return
}
err = fmt.Errorf("Amazon Linux: Unknown Header Format. %s", line)
return
}
var yumCveIDPattern = regexp.MustCompile(`(CVE-\d{4}-\d{4,})`)
func (o *redhat) parseYumUpdateinfoLineToGetCveIDs(line string) []string {
re, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`)
return re.FindAllString(line, -1)
return yumCveIDPattern.FindAllString(line, -1)
}
var yumAdvisoryIDPattern = regexp.MustCompile(`^ *Update ID : (.*)$`)
func (o *redhat) parseYumUpdateinfoToGetAdvisoryID(line string) (advisoryID string, found bool) {
re, _ := regexp.Compile(`^ *Update ID : (.*)$`)
result := re.FindStringSubmatch(line)
result := yumAdvisoryIDPattern.FindStringSubmatch(line)
if len(result) != 2 {
return "", false
}
return strings.TrimSpace(result[1]), true
}
var yumIssuedPattern = regexp.MustCompile(`^\s*Issued : (\d{4}-\d{2}-\d{2})`)
func (o *redhat) parseYumUpdateinfoLineToGetIssued(line string) (date time.Time, found bool) {
return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Issued : (\d{4}-\d{2}-\d{2})`)
return o.parseYumUpdateinfoLineToGetDate(line, yumIssuedPattern)
}
var yumUpdatedPattern = regexp.MustCompile(`^\s*Updated : (\d{4}-\d{2}-\d{2})`)
func (o *redhat) parseYumUpdateinfoLineToGetUpdated(line string) (date time.Time, found bool) {
return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Updated : (\d{4}-\d{2}-\d{2})`)
return o.parseYumUpdateinfoLineToGetDate(line, yumUpdatedPattern)
}
func (o *redhat) parseYumUpdateinfoLineToGetDate(line, regexpFormat string) (date time.Time, found bool) {
re, _ := regexp.Compile(regexpFormat)
result := re.FindStringSubmatch(line)
func (o *redhat) parseYumUpdateinfoLineToGetDate(line string, regexpPattern *regexp.Regexp) (date time.Time, found bool) {
result := regexpPattern.FindStringSubmatch(line)
if len(result) != 2 {
return date, false
}
@@ -904,14 +908,16 @@ func (o *redhat) parseYumUpdateinfoLineToGetDate(line, regexpFormat string) (dat
return t, true
}
var yumDescriptionPattern = regexp.MustCompile(`^\s*Description : `)
func (o *redhat) isDescriptionLine(line string) bool {
re, _ := regexp.Compile(`^\s*Description : `)
return re.MatchString(line)
return yumDescriptionPattern.MatchString(line)
}
var yumSeverityPattern = regexp.MustCompile(`^ *Severity : (.*)$`)
func (o *redhat) parseYumUpdateinfoToGetSeverity(line string) (severity string, found bool) {
re, _ := regexp.Compile(`^ *Severity : (.*)$`)
result := re.FindStringSubmatch(line)
result := yumSeverityPattern.FindStringSubmatch(line)
if len(result) != 2 {
return "", false
}
@@ -944,15 +950,15 @@ func (o *redhat) extractPackNameVerRel(nameVerRel string) (name, ver, rel string
return
}
// parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS), packages
// parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS, ELSA), packages
func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacksList, error) {
result := []advisoryIDPacks{}
lines := strings.Split(stdout, "\n")
for _, line := range lines {
if !(strings.HasPrefix(line, "RHSA") ||
strings.HasPrefix(line, "ALAS")) {
strings.HasPrefix(line, "ALAS") ||
strings.HasPrefix(line, "ELSA")) {
continue
}
@@ -990,3 +996,12 @@ func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacks
func (o *redhat) clone() osTypeInterface {
return o
}
func (o *redhat) sudo() bool {
switch o.Distro.Family {
case "amazon":
return false
default:
return true
}
}

View File

@@ -91,47 +91,6 @@ func TestChangeSectionState(t *testing.T) {
}
}
func TestParseYumUpdateinfoHeader(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var tests = []struct {
in string
out []models.PackageInfo
}{
{
" nodejs-0.10.36-3.el6,libuv-0.10.34-1.el6,v8-3.14.5.10-17.el6 ",
[]models.PackageInfo{
{
Name: "nodejs",
Version: "0.10.36",
Release: "3.el6",
},
{
Name: "libuv",
Version: "0.10.34",
Release: "1.el6",
},
{
Name: "v8",
Version: "3.14.5.10",
Release: "17.el6",
},
},
},
}
for _, tt := range tests {
if a, err := r.parseYumUpdateinfoHeaderCentOS(tt.in); err != nil {
t.Errorf("err: %s", err)
} else {
if !reflect.DeepEqual(a, tt.out) {
e := pp.Sprintf("%#v", tt.out)
a := pp.Sprintf("%#v", a)
t.Errorf("expected %s, actual %s", e, a)
}
}
}
}
func TestParseYumUpdateinfoLineToGetCveIDs(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var tests = []struct {
@@ -143,11 +102,11 @@ func TestParseYumUpdateinfoLineToGetCveIDs(t *testing.T) {
[]string{"CVE-2015-0278"},
},
{
": 1195457 - nodejs-0.10.35 causes undefined symbolsCVE-2015-0278, CVE-2015-0278, CVE-2015-0277",
": 1195457 - nodejs-0.10.35 causes undefined symbolsCVE-2015-0278, CVE-2015-0278, CVE-2015-02770000000 ",
[]string{
"CVE-2015-0278",
"CVE-2015-0278",
"CVE-2015-0277",
"CVE-2015-02770000000",
},
},
}
@@ -385,6 +344,111 @@ func TestParseYumUpdateinfoToGetSeverity(t *testing.T) {
}
}
func TestParseYumUpdateinfoOL(t *testing.T) {
stdout := `===============================================================================
bind security update
===============================================================================
Update ID : ELSA-2017-0276
Release : Oracle Linux 7
Type : security
Status : final
Issued : 2017-02-15
CVEs : CVE-2017-3135
Description : [32:9.9.4-38.2]
: - Fix CVE-2017-3135 (ISC change 4557)
: - Fix and test caching CNAME before DNAME (ISC
: change 4558)
Severity : Moderate
===============================================================================
openssl security update
===============================================================================
Update ID : ELSA-2017-0286
Release : Oracle Linux 7
Type : security
Status : final
Issued : 2017-02-15
CVEs : CVE-2016-8610
: CVE-2017-3731
Description : [1.0.1e-48.4]
: - fix CVE-2017-3731 - DoS via truncated packets
: with RC4-MD5 cipher
: - fix CVE-2016-8610 - DoS of single-threaded
: servers via excessive alerts
Severity : Moderate
===============================================================================
Unbreakable Enterprise kernel security update
===============================================================================
Update ID : ELSA-2017-3520
Release : Oracle Linux 7
Type : security
Status : final
Issued : 2017-02-15
CVEs : CVE-2017-6074
Description : kernel-uek
: [4.1.12-61.1.28]
: - dccp: fix freeing skb too early for
: IPV6_RECVPKTINFO (Andrey Konovalov) [Orabug:
: 25598257] {CVE-2017-6074}
Severity : Important
`
issued, _ := time.Parse("2006-01-02", "2017-02-15")
r := newRedhat(config.ServerInfo{})
r.Distro = config.Distro{Family: "oraclelinux"}
var tests = []struct {
in string
out []distroAdvisoryCveIDs
}{
{
stdout,
[]distroAdvisoryCveIDs{
{
DistroAdvisory: models.DistroAdvisory{
AdvisoryID: "ELSA-2017-0276",
Severity: "Moderate",
Issued: issued,
},
CveIDs: []string{"CVE-2017-3135"},
},
{
DistroAdvisory: models.DistroAdvisory{
AdvisoryID: "ELSA-2017-0286",
Severity: "Moderate",
Issued: issued,
},
CveIDs: []string{
"CVE-2016-8610",
"CVE-2017-3731",
},
},
{
DistroAdvisory: models.DistroAdvisory{
AdvisoryID: "ELSA-2017-3520",
Severity: "Important",
Issued: issued,
},
CveIDs: []string{"CVE-2017-6074"},
},
},
},
}
for _, tt := range tests {
actual, _ := r.parseYumUpdateinfo(tt.in)
for i, advisoryCveIDs := range actual {
if !reflect.DeepEqual(tt.out[i], advisoryCveIDs) {
e := pp.Sprintf("%v", tt.out[i])
a := pp.Sprintf("%v", advisoryCveIDs)
t.Errorf("[%d] Alas is not same. \nexpected: %s\nactual: %s",
i, e, a)
}
}
}
}
func TestParseYumUpdateinfoRHEL(t *testing.T) {
stdout := `===============================================================================
@@ -451,7 +515,7 @@ Description : The Berkeley Internet Name Domain (BIND) is an implementation of
updated, _ := time.Parse("2006-01-02", "2015-09-04")
r := newRedhat(config.ServerInfo{})
r.Family = "redhat"
r.Distro = config.Distro{Family: "redhat"}
var tests = []struct {
in string
@@ -511,7 +575,7 @@ Description : The Berkeley Internet Name Domain (BIND) is an implementation of
func TestParseYumUpdateinfoAmazon(t *testing.T) {
r := newRedhat(config.ServerInfo{})
r.Family = "amazon"
r.Distro = config.Distro{Family: "redhat"}
issued, _ := time.Parse("2006-01-02", "2015-12-15")
updated, _ := time.Parse("2006-01-02", "2015-12-16")
@@ -601,7 +665,7 @@ Description : Package updates are available for Amazon Linux AMI that fix the
func TestParseYumCheckUpdateLines(t *testing.T) {
r := newRedhat(config.ServerInfo{})
r.Family = "centos"
r.Distro = config.Distro{Family: "centos"}
stdout := `Loaded plugins: changelog, fastestmirror, keys, protect-packages, protectbase, security
Loading mirror speeds from cached hostfile
* base: mirror.fairway.ne.jp
@@ -616,6 +680,7 @@ Obsoleting Packages
python-libs.i686 2.6.6-64.el6 rhui-REGION-rhel-server-releases
python-ordereddict.noarch 1.1-3.el6ev installed
bind-utils.x86_64 30:9.3.6-25.P1.el5_11.8 updates
pytalloc.x86_64 2.0.7-2.el6 @CentOS 6.5/6.5
`
r.Packages = []models.PackageInfo{
@@ -644,6 +709,11 @@ bind-utils.x86_64 30:9.3.6-25.P1.el5_11.8 updates
Version: "1.0",
Release: "1",
},
{
Name: "pytalloc",
Version: "2.0.1",
Release: "0",
},
}
var tests = []struct {
in string
@@ -658,6 +728,7 @@ bind-utils.x86_64 30:9.3.6-25.P1.el5_11.8 updates
Release: "4.el6",
NewVersion: "2.3.7",
NewRelease: "5.el6",
Repository: "base",
},
{
Name: "bash",
@@ -665,6 +736,7 @@ bind-utils.x86_64 30:9.3.6-25.P1.el5_11.8 updates
Release: "33",
NewVersion: "4.1.2",
NewRelease: "33.el6_7.1",
Repository: "updates",
},
{
Name: "python-libs",
@@ -672,6 +744,7 @@ bind-utils.x86_64 30:9.3.6-25.P1.el5_11.8 updates
Release: "1.1-0",
NewVersion: "2.6.6",
NewRelease: "64.el6",
Repository: "rhui-REGION-rhel-server-releases",
},
{
Name: "python-ordereddict",
@@ -679,6 +752,7 @@ bind-utils.x86_64 30:9.3.6-25.P1.el5_11.8 updates
Release: "1",
NewVersion: "1.1",
NewRelease: "3.el6ev",
Repository: "installed",
},
{
Name: "bind-utils",
@@ -686,6 +760,15 @@ bind-utils.x86_64 30:9.3.6-25.P1.el5_11.8 updates
Release: "1",
NewVersion: "9.3.6",
NewRelease: "25.P1.el5_11.8",
Repository: "updates",
},
{
Name: "pytalloc",
Version: "2.0.1",
Release: "0",
NewVersion: "2.0.7",
NewRelease: "2.el6",
Repository: "@CentOS 6.5/6.5",
},
},
},
@@ -709,7 +792,7 @@ bind-utils.x86_64 30:9.3.6-25.P1.el5_11.8 updates
func TestParseYumCheckUpdateLinesAmazon(t *testing.T) {
r := newRedhat(config.ServerInfo{})
r.Family = "amazon"
r.Distro = config.Distro{Family: "amazon"}
stdout := `Loaded plugins: priorities, update-motd, upgrade-helper
34 package(s) needed for security, out of 71 available
@@ -747,6 +830,7 @@ if-not-architecture 100-200 amzn-main
Release: "0.33.rc1.45.amzn1",
NewVersion: "9.8.2",
NewRelease: "0.37.rc1.45.amzn1",
Repository: "amzn-main",
},
{
Name: "java-1.7.0-openjdk",
@@ -754,6 +838,7 @@ if-not-architecture 100-200 amzn-main
Release: "2.6.4.0.0.amzn1",
NewVersion: "1.7.0.95",
NewRelease: "2.6.4.0.65.amzn1",
Repository: "amzn-main",
},
{
Name: "if-not-architecture",
@@ -761,6 +846,7 @@ if-not-architecture 100-200 amzn-main
Release: "20",
NewVersion: "100",
NewRelease: "200",
Repository: "amzn-main",
},
},
},
@@ -782,31 +868,6 @@ if-not-architecture 100-200 amzn-main
}
}
func TestParseYumUpdateinfoAmazonLinuxHeader(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var tests = []struct {
in string
out models.DistroAdvisory
}{
{
"Amazon Linux AMI 2014.03 - ALAS-2015-598: low priority package update for grep",
models.DistroAdvisory{
AdvisoryID: "ALAS-2015-598",
Severity: "low",
},
},
}
for _, tt := range tests {
a, _, _ := r.parseYumUpdateinfoHeaderAmazon(tt.in)
if !reflect.DeepEqual(a, tt.out) {
e := pp.Sprintf("%v", tt.out)
a := pp.Sprintf("%v", a)
t.Errorf("expected %s, actual %s", e, a)
}
}
}
func TestParseYumUpdateinfoListAvailable(t *testing.T) {
r := newRedhat(config.ServerInfo{})
rhelStdout := `RHSA-2015:2315 Moderate/Sec. NetworkManager-1:1.0.6-27.el7.x86_64
@@ -1110,10 +1171,12 @@ func TestGetChangelogCVELines(t *testing.T) {
}
r := newRedhat(config.ServerInfo{})
r.Family = "centos"
r.Release = "6.7"
r.Distro = config.Distro{
Family: "centos",
Release: "6.7",
}
for _, tt := range testsCentos6 {
rpm2changelog, err := r.parseAllChangelog(stdoutCentos6)
rpm2changelog, err := r.divideChangelogByPackage(stdoutCentos6)
if err != nil {
t.Errorf("err: %s", err)
}
@@ -1194,9 +1257,12 @@ func TestGetChangelogCVELines(t *testing.T) {
},
}
r.Release = "5.6"
r.Distro = config.Distro{
Family: "centos",
Release: "5.6",
}
for _, tt := range testsCentos5 {
rpm2changelog, err := r.parseAllChangelog(stdoutCentos5)
rpm2changelog, err := r.divideChangelogByPackage(stdoutCentos5)
if err != nil {
t.Errorf("err: %s", err)
}

View File

@@ -1,37 +1,52 @@
/* 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 scan
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/Sirupsen/logrus"
"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"
"github.com/future-architect/vuls/util"
)
// Log for localhsot
var Log *logrus.Entry
var servers []osTypeInterface
var servers, errServers []osTypeInterface
// Base Interface of redhat, debian, freebsd
type osTypeInterface interface {
setServerInfo(config.ServerInfo)
getServerInfo() config.ServerInfo
setDistributionInfo(string, string)
getDistributionInfo() string
checkIfSudoNoPasswd() error
detectPlatform() error
setDistro(string, string)
getDistro() config.Distro
detectPlatform()
getPlatform() models.Platform
checkRequiredPackagesInstalled() error
// checkDependencies checks if dependencies are installed on the target server.
checkDependencies() error
checkIfSudoNoPasswd() error
scanPackages() error
scanVulnByCpeName() error
install() error
convertToModel() (models.ScanResult, error)
convertToModel() models.ScanResult
runningContainers() ([]config.Container, error)
exitedContainers() ([]config.Container, error)
@@ -41,70 +56,21 @@ type osTypeInterface interface {
setErrs([]error)
}
// osPackages included by linux struct
// osPackages is included by base struct
type osPackages struct {
// installed packages
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) {
@@ -113,30 +79,34 @@ func detectOS(c config.ServerInfo) (osType osTypeInterface) {
itsMe, osType, fatalErr = detectDebian(c)
if fatalErr != nil {
osType.setServerInfo(c)
osType.setErrs([]error{fatalErr})
osType.setErrs([]error{
fmt.Errorf("Failed to detect OS: %s", fatalErr)})
return
} else if itsMe {
Log.Debugf("Debian like Linux. Host: %s:%s", c.Host, c.Port)
}
if itsMe {
util.Log.Debugf("Debian like Linux. Host: %s:%s", c.Host, c.Port)
return
}
if itsMe, osType = detectRedhat(c); itsMe {
Log.Debugf("Redhat like Linux. Host: %s:%s", c.Host, c.Port)
util.Log.Debugf("Redhat like Linux. Host: %s:%s", c.Host, c.Port)
return
}
if itsMe, osType = detectFreebsd(c); itsMe {
Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port)
util.Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port)
return
}
osType.setServerInfo(c)
//TODO darwin https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/darwin.rb
osType.setErrs([]error{fmt.Errorf("Unknown OS Type")})
return
}
// PrintSSHableServerNames print SSH-able servernames
func PrintSSHableServerNames() {
Log.Info("SSH-able servers are below...")
util.Log.Info("Scannable servers are below...")
for _, s := range servers {
if s.getServerInfo().IsContainer() {
fmt.Printf("%s@%s ",
@@ -151,58 +121,74 @@ func PrintSSHableServerNames() {
}
// InitServers detect the kind of OS distribution of target servers
func InitServers(localLogger *logrus.Entry) {
Log = localLogger
servers = detectServerOSes()
containers := detectContainerOSes()
servers = append(servers, containers...)
func InitServers() error {
servers, errServers = detectServerOSes()
if len(servers) == 0 {
return fmt.Errorf("No scannable servers")
}
actives, inactives := detectContainerOSes()
if config.Conf.ContainersOnly {
servers = actives
errServers = inactives
} else {
servers = append(servers, actives...)
errServers = append(errServers, inactives...)
}
return nil
}
func detectServerOSes() (sshAbleOses []osTypeInterface) {
Log.Info("Detecting OS of servers... ")
func detectServerOSes() (servers, errServers []osTypeInterface) {
util.Log.Info("Detecting OS of servers... ")
osTypeChan := make(chan osTypeInterface, len(config.Conf.Servers))
defer close(osTypeChan)
for _, s := range config.Conf.Servers {
go func(s config.ServerInfo) {
defer func() {
if p := recover(); p != nil {
Log.Debugf("Panic: %s on %s", p, s.ServerName)
util.Log.Debugf("Panic: %s on %s", p, s.ServerName)
}
}()
osTypeChan <- detectOS(s)
}(s)
}
var oses []osTypeInterface
timeout := time.After(30 * time.Second)
for i := 0; i < len(config.Conf.Servers); i++ {
select {
case res := <-osTypeChan:
oses = append(oses, res)
if 0 < len(res.getErrs()) {
Log.Errorf("(%d/%d) Failed: %s, err: %s",
errServers = append(errServers, res)
util.Log.Errorf("(%d/%d) Failed: %s, err: %s",
i+1, len(config.Conf.Servers),
res.getServerInfo().ServerName,
res.getErrs())
} else {
Log.Infof("(%d/%d) Detected: %s: %s",
servers = append(servers, res)
util.Log.Infof("(%d/%d) Detected: %s: %s",
i+1, len(config.Conf.Servers),
res.getServerInfo().ServerName,
res.getDistributionInfo())
res.getDistro())
}
case <-timeout:
msg := "Timed out while detecting servers"
Log.Error(msg)
for servername := range config.Conf.Servers {
util.Log.Error(msg)
for servername, sInfo := range config.Conf.Servers {
found := false
for _, o := range oses {
for _, o := range append(servers, errServers...) {
if servername == o.getServerInfo().ServerName {
found = true
break
}
}
if !found {
Log.Errorf("(%d/%d) Timed out: %s",
u := &unknown{}
u.setServerInfo(sInfo)
u.setErrs([]error{
fmt.Errorf("Timed out"),
})
errServers = append(errServers, u)
util.Log.Errorf("(%d/%d) Timed out: %s",
i+1, len(config.Conf.Servers),
servername)
i++
@@ -210,32 +196,25 @@ func detectServerOSes() (sshAbleOses []osTypeInterface) {
}
}
}
for _, o := range oses {
if len(o.getErrs()) == 0 {
sshAbleOses = append(sshAbleOses, o)
}
}
return
}
func detectContainerOSes() (actives []osTypeInterface) {
Log.Info("Detecting OS of containers... ")
func detectContainerOSes() (actives, inactives []osTypeInterface) {
util.Log.Info("Detecting OS of containers... ")
osTypesChan := make(chan []osTypeInterface, len(servers))
defer close(osTypesChan)
for _, s := range servers {
go func(s osTypeInterface) {
defer func() {
if p := recover(); p != nil {
Log.Debugf("Panic: %s on %s",
p, s.getServerInfo().ServerName)
util.Log.Debugf("Panic: %s on %s",
p, s.getServerInfo().GetServerName())
}
}()
osTypesChan <- detectContainerOSesOnServer(s)
}(s)
}
var oses []osTypeInterface
timeout := time.After(30 * time.Second)
for i := 0; i < len(servers); i++ {
select {
@@ -243,42 +222,44 @@ func detectContainerOSes() (actives []osTypeInterface) {
for _, osi := range res {
sinfo := osi.getServerInfo()
if 0 < len(osi.getErrs()) {
Log.Errorf("Failed: %s err: %s", sinfo.ServerName, osi.getErrs())
inactives = append(inactives, osi)
util.Log.Errorf("Failed: %s err: %s", sinfo.ServerName, osi.getErrs())
continue
}
oses = append(oses, res...)
Log.Infof("Detected: %s@%s: %s",
sinfo.Container.Name, sinfo.ServerName, osi.getDistributionInfo())
actives = append(actives, osi)
util.Log.Infof("Detected: %s@%s: %s",
sinfo.Container.Name, sinfo.ServerName, osi.getDistro())
}
case <-timeout:
msg := "Timed out while detecting containers"
Log.Error(msg)
for servername := range config.Conf.Servers {
util.Log.Error(msg)
for servername, sInfo := range config.Conf.Servers {
found := false
for _, o := range oses {
for _, o := range append(actives, inactives...) {
if servername == o.getServerInfo().ServerName {
found = true
break
}
}
if !found {
Log.Errorf("Timed out: %s", servername)
u := &unknown{}
u.setServerInfo(sInfo)
u.setErrs([]error{
fmt.Errorf("Timed out"),
})
inactives = append(inactives)
util.Log.Errorf("Timed out: %s", servername)
}
}
}
}
for _, o := range oses {
if len(o.getErrs()) == 0 {
actives = append(actives, o)
}
}
return
}
func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeInterface) {
containerHostInfo := containerHost.getServerInfo()
if len(containerHostInfo.Containers) == 0 {
if len(containerHostInfo.Containers.Includes) == 0 {
return
}
@@ -290,12 +271,24 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn
return append(oses, containerHost)
}
if containerHostInfo.Containers[0] == "${running}" {
if containerHostInfo.Containers.Includes[0] == "${running}" {
for _, containerInfo := range running {
found := false
for _, ex := range containerHost.getServerInfo().Containers.Excludes {
if containerInfo.Name == ex || containerInfo.ContainerID == ex {
found = true
}
}
if found {
continue
}
copied := containerHostInfo
copied.SetContainer(config.Container{
ContainerID: containerInfo.ContainerID,
Name: containerInfo.Name,
Image: containerInfo.Image,
})
os := detectOS(copied)
oses = append(oses, os)
@@ -312,7 +305,7 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn
}
var exited, unknown []string
for _, container := range containerHostInfo.Containers {
for _, container := range containerHostInfo.Containers.Includes {
found := false
for _, c := range running {
if c.ContainerID == container || c.Name == container {
@@ -348,29 +341,28 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn
return oses
}
// CheckDependencies checks dependencies are installed on target servers.
func CheckDependencies(timeoutSec int) {
parallelExec(func(o osTypeInterface) error {
return o.checkDependencies()
}, timeoutSec)
return
}
// CheckIfSudoNoPasswd checks whether vuls can sudo with nopassword via SSH
func CheckIfSudoNoPasswd(localLogger *logrus.Entry) error {
timeoutSec := 1 * 15
errs := parallelSSHExec(func(o osTypeInterface) error {
func CheckIfSudoNoPasswd(timeoutSec int) {
parallelExec(func(o osTypeInterface) error {
return o.checkIfSudoNoPasswd()
}, timeoutSec)
if 0 < len(errs) {
return fmt.Errorf(fmt.Sprintf("%s", errs))
}
return nil
return
}
// DetectPlatforms detects the platform of each servers.
func DetectPlatforms(localLogger *logrus.Entry) {
errs := detectPlatforms()
if 0 < len(errs) {
// Only logging
Log.Warnf("Failed to detect platforms. err: %v", errs)
}
func DetectPlatforms() {
detectPlatforms()
for i, s := range servers {
if s.getServerInfo().IsContainer() {
Log.Infof("(%d/%d) %s on %s is running on %s",
util.Log.Infof("(%d/%d) %s on %s is running on %s",
i+1, len(servers),
s.getServerInfo().Container.Name,
s.getServerInfo().ServerName,
@@ -378,7 +370,7 @@ func DetectPlatforms(localLogger *logrus.Entry) {
)
} else {
Log.Infof("(%d/%d) %s is running on %s",
util.Log.Infof("(%d/%d) %s is running on %s",
i+1, len(servers),
s.getServerInfo().ServerName,
s.getPlatform().Name,
@@ -388,79 +380,112 @@ func DetectPlatforms(localLogger *logrus.Entry) {
return
}
func detectPlatforms() []error {
func detectPlatforms() {
timeoutSec := 1 * 60
return parallelSSHExec(func(o osTypeInterface) error {
return o.detectPlatform()
}, timeoutSec)
}
// Prepare installs requred packages to scan vulnerabilities.
func Prepare() []error {
return parallelSSHExec(func(o osTypeInterface) error {
if err := o.install(); err != nil {
return err
}
parallelExec(func(o osTypeInterface) error {
o.detectPlatform()
// Logging only if platform can not be specified
return nil
})
}, timeoutSec)
return
}
// Scan scan
func Scan() []error {
func Scan() error {
if len(servers) == 0 {
return []error{fmt.Errorf("No server defined. Check the configuration")}
return fmt.Errorf("No server defined. Check the configuration")
}
Log.Info("Check required packages for scanning...")
if errs := checkRequiredPackagesInstalled(); errs != nil {
Log.Error("Please execute with [prepare] subcommand to install required packages before scanning")
return errs
if err := setupChangelogCache(); err != nil {
return err
}
defer func() {
if cache.DB != nil {
cache.DB.Close()
}
}()
util.Log.Info("Scanning vulnerable OS packages...")
scannedAt := time.Now()
dir, err := ensureResultDir(scannedAt)
if err != nil {
return err
}
if err := scanVulns(dir, scannedAt); err != nil {
return err
}
Log.Info("Scanning vulnerable OS packages...")
if errs := scanPackages(); errs != nil {
return errs
}
return nil
}
Log.Info("Scanning vulnerable software specified in the CPE...")
if errs := scanVulnByCpeName(); errs != nil {
return errs
func setupChangelogCache() error {
needToSetupCache := false
for _, s := range servers {
switch s.getDistro().Family {
case "ubuntu", "debian", "raspbian":
needToSetupCache = true
break
}
}
if needToSetupCache {
if err := cache.SetupBolt(config.Conf.CacheDBPath, util.Log); err != nil {
return err
}
}
return nil
}
func checkRequiredPackagesInstalled() []error {
timeoutSec := 30 * 60
return parallelSSHExec(func(o osTypeInterface) error {
return o.checkRequiredPackagesInstalled()
}, 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 {
parallelExec(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)
}
for _, s := range append(servers, errServers...) {
r := s.convertToModel()
r.ScannedAt = scannedAt
results = append(results, r)
}
return
config.Conf.FormatJSON = true
ws := []report.ResultWriter{
report.LocalFileWriter{CurrentDir: jsonDir},
}
for _, w := range ws {
if err := w.Write(results...); err != nil {
return fmt.Errorf("Failed to write summary report: %s", err)
}
}
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
}

View File

@@ -1,47 +1 @@
package scan
import "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)
}
}
}

View File

@@ -15,10 +15,21 @@ 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 version
package scan
// Name is Vuls
const Name string = "vuls"
// inherit OsTypeInterface
type unknown struct {
base
}
// Version of Vuls
const Version string = "0.1.5"
func (o *unknown) checkIfSudoNoPasswd() error {
return nil
}
func (o unknown) checkDependencies() error {
return nil
}
func (o *unknown) scanPackages() error {
return nil
}

View File

@@ -1,101 +0,0 @@
# Vuls on Docker
## What's Vuls-On-Docker
- 数個のコマンドを実行するだけでVulsとvulsrepoのセットアップが出来るスクリプト
- Dockerコンテナ上にVulsと[vulsrepo](https://github.com/usiusi360/vulsrepo)をセットアップ可能
- スキャン結果をvulsrepoでブラウザで分析可能
- 脆弱性データベースの更新が可能
- モジュールのアップデートが可能
## Setting up your machine
1. [Install Docker](https://docs.docker.com/engine/installation/)
2. [Install Docker-Compose](https://docs.docker.com/compose/install/)
3. 実行前に以下のコマンドが実行可能なことを確認する
```
$ docker version
$ docker-compose version
```
4. Vulsをgit clone
```
mkdir work
cd work
git clone https://github.com/future-architect/vuls.git
cd vuls/setup/docker
```
## Start A Vuls Container
- 以下のコマンドを実行してコンテナをビルドする
```
$ docker-compose up -d
```
## Setting up Vuls
1. スキャン対象サーバのSSH秘密鍵を保存(vuls/setup/docker/conf/)する
2. config.toml(vuls/docker/conf/config.toml) を環境に合わせて作成する
```
[servers]
[servers.172-31-4-82]
host = "172.31.4.82"
user = "ec2-user"
keyPath = "conf/id_rsa"
```
## Fetch Vulnerability database
- NVDから脆弱性データベースを取得する
```
$ docker exec -t vuls scripts/fetch_nvd_all.sh
```
- レポートを日本語化する場合は、JVNから脆弱性データを取得する
```
$ docker exec -t vuls scripts/fetch_jvn_all.sh
```
## Scan servers with Vuls-On-Docker
- スキャンを実行する
```
$ docker exec -t vuls vuls prepare -config=conf/config.toml
$ docker exec -t vuls scripts/scan_for_vulsrepo.sh
```
## See the results in a browser
```
http://${Vuls_Host}/vulsrepo/
```
# Update modules
- vuls, go-cve-dictionary, vulsrepoのモジュールをアップデートする
```
$ docker exec -t vuls scripts/update_modules.sh
```
# Update Vulnerability database
- NVDの過去年分の脆弱性データベースを更新する
```
$ docker exec -t vuls scripts/fetch_nvd_last2y.sh
```
- JVNの過去ヶ月分の脆弱性データベースを更新する
```
$ docker exec -t vuls scripts/fetch_jvn_month.sh
```
- JVNの過去1週間分の脆弱性データベースを更新する
```
$ docker exec -t vuls scripts/fetch_jvn_week.sh
```

View File

@@ -1,87 +1,191 @@
# Vuls on Docker
# Vuls Docker components
## What's Vuls-On-Docker
This is the Git repo of the official Docker image for vuls.
- This is a dockernized-Vuls with vulsrepo UI in it.
- It's designed to reduce the cost of installation and the dependencies that vuls requires.
- You can run install and run Vuls on your machine with only a few commands.
- The result can be viewed with a browser
# Supported tags and respective `Dockerfile` links
## Setting up your machine
1. [Install Docker](https://docs.docker.com/engine/installation/)
2. [Install Docker-Compose](https://docs.docker.com/compose/install/)
3. Make sure that you can run the following commands before you move on.
- go-cve-dictionary
- [`latest` (*go-cve-dictionary:latest Dockerfile*)]()
- vuls
- [`latest` (*vuls:latest Dockerfile*)]()
- vulsrepo
- [`latest` (*vulsrepo:latest Dockerfile*)]()
```
$ docker version
$ docker-compose version
```
4. git clone vuls
```
mkdir work
cd work
git clone https://github.com/future-architect/vuls.git
cd vuls/setup/docker
```
This image version is same as the github repository version.
# Caution
This image is built per commit.
If you want to use the latest docker image, you should remove the existing image, and pull it once again.
## Start A Vuls Container
1. Confirm your vuls version
- Execute the following command to build and run a Vuls Container
- go-cve-dictionary
```
$ docker-compose up -d
```
```console
$ docker run --rm vuls/go-cve-dictionary -v
## Setting up Vuls
1. Locate ssh-keys of targer servers in (vuls/docker/conf/)
2. Create and ajust config.toml(vuls/docker/conf/config.toml) to your environment
```
[servers]
[servers.172-31-4-82]
host = "172.31.4.82"
user = "ec2-user"
keyPath = "conf/id_rsa"
```
## Fetch Vulnerability database
- Fetch Vulnerability database from NVD
```
$ docker exec -t vuls scripts/fetch_nvd_all.sh
```
## Scan servers with Vuls-On-Docker
- Use the embedded script to scan servers for vulsrepo(or run whatever with docker exec)
```
$ docker exec -t vuls vuls prepare -config=conf/config.toml
$ docker exec -t vuls scripts/scan_for_vulsrepo.sh
```
## See the results in a browser
```
http://${Vuls_Host}/vulsrepo/
go-cve-dictionary v0.0.xxx xxxx
```
# Update modules
- vuls
- update vuls, go-cve-dictionary, vulsrepo
```
$ docker exec -t vuls scripts/update_modules.sh
```
```console
$ docker run --rm vuls/vuls -v
# Update Vulnerability database
vuls v0.0.xxx xxxx
```
- Fetch Vulnerability database from NVD
```
$ docker exec -t vuls scripts/fetch_nvd_last2y.sh
```
2. Remove your old docker images
- go-cve-dictionary
```
$ docker rmi vuls/go-cve-dictionary
```
- vuls
```
$ docker rmi vuls/vuls
```
3. Pull new vuls docker images
- go-cve-dictionary
```
$ docker pull vuls/go-cve-dictionary
```
- vuls
```
$ docker pull vuls/vuls
```
4. Confirm your vuls version
```console
$ docker run --rm vuls/go-cve-dictionary -v
go-cve-dictionary v0.1.xxx xxxx
```
- vuls
```console
$ docker run --rm vuls/vuls -v
vuls v0.1.xxx xxxx
```
# How to use this image
1. fetch nvd (vuls/go-cve-dictionary)
1. configuration (vuls/vuls)
1. configtest (vuls/vuls)
1. scan (vuls/vuls)
1. vulsrepo (vuls/vulsrepo)
## Step1. Fetch NVD
```console
$ for i in `seq 2002 $(date +"%Y")`; do \
docker run --rm -it \
-v $PWD:/vuls \
-v $PWD/go-cve-dictionary-log:/var/log/vuls \
vuls/go-cve-dictionary fetchnvd -years $i; \
done
```
## Step2. Configuration
Create config.toml referring to [this](https://github.com/future-architect/vuls#configuration).
```toml
[servers]
[servers.amazon]
host = "54.249.93.16"
port = "22"
user = "vuls-user"
keyPath = "/root/.ssh/id_rsa" # path to ssh private key in docker
```
```console
$ docker run --rm \
-v ~/.ssh:/root/.ssh:ro \
-v $PWD:/vuls \
-v $PWD/vuls-log:/var/log/vuls \
vuls/vuls configtest \
-config=./config.toml # path to config.toml in docker
```
## Step3. Configtest
```console
$ docker run --rm -it\
-v ~/.ssh:/root/.ssh:ro \
-v $PWD:/vuls \
-v $PWD/vuls-log:/var/log/vuls \
vuls/vuls configtest \
-config=./config.toml # path to config.toml in docker
```
## Step4. Scan
```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 \
-e "TZ=Asia/Tokyo" \
vuls/vuls scan \
-config=./config.toml # path to config.toml in docker
```
## 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 \
-v $PWD:/vuls \
-p 80:80 \
vuls/vulsrepo
```
# User Feedback
## Documentation
Documentation for this image is stored in the [`docker/` directory]() of the [`future-architect/vuls` GitHub repo](https://github.com/future-architect/vuls).
## Issues
If you have any problems with or questions about this image, please contact us through a [GitHub issue](https://github.com/future-architect/vuls/issues).
## Contributing
1. fork a repository: github.com/future-architect/vuls to github.com/you/repo
1. get original code: go get github.com/future-architect/vuls
1. work on original code
1. add remote to your repo: git remote add myfork https://github.com/you/repo.git
1. push your changes: git push myfork
1. create a new Pull Request

View File

@@ -1,11 +0,0 @@
version: '2'
services:
vuls:
container_name: vuls
build: ./dockerfile
image: vuls-docker:0.1
volumes:
- ./conf:/opt/vuls/conf
ports:
- "80:80"

View File

@@ -1,89 +0,0 @@
FROM buildpack-deps:jessie-scm
# golang Install
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
gcc \
libc6-dev \
make \
curl \
&& rm -rf /var/lib/apt/lists/*
ENV GOLANG_VERSION 1.6.2
ENV GOLANG_DOWNLOAD_URL https://golang.org/dl/go$GOLANG_VERSION.linux-amd64.tar.gz
ENV GOLANG_DOWNLOAD_SHA256 e40c36ae71756198478624ed1bb4ce17597b3c19d243f3f0899bb5740d56212a
RUN curl -fsSL "$GOLANG_DOWNLOAD_URL" -o golang.tar.gz \
&& echo "$GOLANG_DOWNLOAD_SHA256 golang.tar.gz" | sha256sum -c - \
&& tar -C /usr/local -xzf golang.tar.gz \
&& rm golang.tar.gz
ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"
# glide install
ENV GLIDE_VERSION 0.10.2
ENV GLIDE_DOWNLOAD_URL https://github.com/Masterminds/glide/releases/download/$GLIDE_VERSION/glide-$GLIDE_VERSION-linux-amd64.tar.gz
RUN curl -fsSL "$GLIDE_DOWNLOAD_URL" -o glide.tar.gz \
&& mkdir /usr/local/glide \
&& tar -C /usr/local/glide -xzf glide.tar.gz \
&& ln -s /usr/local/glide/linux-amd64/glide /usr/local/bin/ \
&& rm glide.tar.gz
# nginx Install
RUN apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62 \
&& echo "deb http://nginx.org/packages/mainline/debian/ jessie nginx" >> /etc/apt/sources.list \
&& apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \
ca-certificates \
nginx \
nginx-module-xslt \
nginx-module-geoip \
nginx-module-image-filter \
nginx-module-perl \
nginx-module-njs \
gettext-base \
wget \
unzip \
&& rm -rf /var/lib/apt/lists/*
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
COPY nginx.conf /etc/nginx/nginx.conf
#Vuls Install
ENV VULS_ROOT /opt/vuls
RUN mkdir -p /var/log/vuls ${VULS_ROOT}/conf /root/.ssh/
RUN chmod 700 -R /var/log/vuls $VULS_ROOT
# RUN go get github.com/kotakanbe/go-cve-dictionary
# RUN go get github.com/future-architect/vuls
RUN go get -v -d github.com/kotakanbe/go-cve-dictionary \
&& cd $GOPATH/src/github.com/kotakanbe/go-cve-dictionary \
&& glide install \
&& go install
RUN go get -v -d github.com/future-architect/vuls \
&& cd $GOPATH/src/github.com/future-architect/vuls \
&& glide install \
&& go install
# Copy custom Scripts
COPY ./scripts/ ${VULS_ROOT}/scripts
RUN chmod 755 ${VULS_ROOT}/scripts/*
#Vulrepo Install
RUN git clone https://github.com/usiusi360/vulsrepo /tmp/vulsrepo
RUN mkdir /usr/share/nginx/html/vulsrepo/
RUN cp -rp /tmp/vulsrepo/src/* /usr/share/nginx/html/vulsrepo
RUN rm -rf /tmp/vulsrepo
#Home
WORKDIR /opt/vuls
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,32 +0,0 @@
user root;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}

View File

@@ -1,6 +0,0 @@
#!/bin/bash
VULS_ROOT=/opt/vuls
#VULS_CONF=${VULS_ROOT}/conf
cd $VULS_ROOT
go-cve-dictionary fetchjvn -entire

View File

@@ -1,6 +0,0 @@
#!/bin/bash
VULS_ROOT=/opt/vuls
#VULS_CONF=${VULS_ROOT}/conf
cd $VULS_ROOT
go-cve-dictionary fetchjvn -month

View File

@@ -1,6 +0,0 @@
#!/bin/bash
VULS_ROOT=/opt/vuls
#VULS_CONF=${VULS_ROOT}/conf
cd $VULS_ROOT
go-cve-dictionary fetchjvn -week

View File

@@ -1,6 +0,0 @@
#!/bin/bash
VULS_ROOT=/opt/vuls
#VULS_CONF=${VULS_ROOT}/conf
cd $VULS_ROOT
for i in {2002..2016}; do go-cve-dictionary fetchnvd -years $i; done

View File

@@ -1,6 +0,0 @@
#!/bin/bash
VULS_ROOT=/opt/vuls
#VULS_CONF=${VULS_ROOT}/conf
cd $VULS_ROOT
go-cve-dictionary fetchnvd -last2y

View File

@@ -1,7 +0,0 @@
#!/bin/bash
VULS_ROOT=/opt/vuls
VULS_CONF=${VULS_ROOT}/conf
NGINX_VULSREPO_ROOT=/usr/share/nginx/html/vulsrepo
cd $VULS_ROOT
vuls scan -report-json --cve-dictionary-dbpath=${VULS_ROOT}/cve.sqlite3 -config=${VULS_CONF}/config.toml
ln -sf ${VULS_ROOT}/results/current ${NGINX_VULSREPO_ROOT}/current

View File

@@ -1,17 +0,0 @@
#!/bin/bash
cd $GOPATH/src/github.com/future-architect/vuls
git pull origin master
glide install
go install
cd $GOPATH/src/github.com/kotakanbe/go-cve-dictionary
git pull origin master
glide install
go install
git clone https://github.com/usiusi360/vulsrepo /tmp/vulsrepo
cp -rp /tmp/vulsrepo/src/* /usr/share/nginx/html/vulsrepo
rm -rf /tmp/vulsrepo

View File

@@ -0,0 +1,19 @@
FROM golang:latest
MAINTAINER hikachan sadayuki-matsuno
ENV REPOSITORY github.com/kotakanbe/go-cve-dictionary
ENV LOGDIR /var/log/vuls
ENV WORKDIR /vuls
# go-cve-dictionary install
RUN git clone https://$REPOSITORY.git $GOPATH/src/$REPOSITORY \
&& cd $GOPATH/src/$REPOSITORY \
&& make install \
&& mkdir -p $LOGDIR
VOLUME [$WORKDIR, $LOGDIR]
WORKDIR $WORKDIR
ENV PWD $WORKDIR
ENTRYPOINT ["go-cve-dictionary"]
CMD ["--help"]

View File

@@ -0,0 +1,89 @@
# go-cve-dictionary-Docker
This is the Git repo of the official Docker image for go-cve-dictionary.
See the [Hub page](https://hub.docker.com/r/vuls/go-cve-dictionary/) for the full readme on how to use the Docker image and for information regarding contributing and issues.
# Supported tags and respective `Dockerfile` links
- [`latest` (*go-cve-dictionary:latest Dockerfile*)](https://github.com/future-architect/vuls/blob/master/setup/docker/go-cve-dictionary/latest/Dockerfile)
# Caution
This image is built per commit.
If you want to use the latest docker image, you should remove the existing image, and pull it once again.
- Remove old docker image
```
$ docker rmi vuls/go-cve-dictionary
```
- Pull new docker image
```
$ docker pull vuls/go-cve-dictionary
```
# What is go-cve-dictionary?
This is tool to build a local copy of the NVD (National Vulnerabilities Database) [1] and the Japanese JVN [2], which contain security vulnerabilities according to their CVE identifiers [3] including exhaustive information and a risk score. The local copy is generated in sqlite format, and the tool has a server mode for easy querying.
[1] https://en.wikipedia.org/wiki/National_Vulnerability_Database
[2] https://en.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures
[3] http://jvndb.jvn.jp/apis/termsofuse.html
# How to use this image
## check vuls version
```
$ docker run --rm vuls/go-cve-dictionary -v
```
## fetchnvd
```console
$ for i in `seq 2002 $(date +"%Y")`; do \
docker run --rm -it \
-v $PWD:/vuls \
-v $PWD/go-cve-dictionary-log:/var/log/vuls \
vuls/go-cve-dictionary fetchnvd -years $i; \
done
```
## server
```console
$ docker run -dt \
--name go-cve-dictionary \
-v $PWD:/vuls \
-v $PWD/go-cve-dictionary-log:/var/log/vuls \
--expose 1323 \
-p 1323:1323 \
vuls/go-cve-dictionary server --bind=0.0.0.0
```
Prease refer to [this](https://hub.docker.com/r/vuls/go-cve-dictionary).
## vuls
Please refer to [this](https://hub.docker.com/r/vuls/vuls/).
# User Feedback
## Documentation
Documentation for this image is stored in the [`docker/` directory](https://github.com/future-architect/vuls/tree/master/setup/docker) of the [`future-architect/vuls` GitHub repo](https://github.com/future-architect/vuls).
## Issues
If you have any problems with or questions about this image, please contact us through a [GitHub issue](https://github.com/future-architect/vuls/issues).
## Contributing
1. fork a repository: github.com/future-architect/vuls to github.com/you/repo
1. get original code: go get github.com/future-architect/vuls
1. work on original code
1. add remote to your repo: git remote add myfork https://github.com/you/repo.git
1. push your changes: git push myfork
1. create a new Pull Request

View File

@@ -0,0 +1,19 @@
FROM golang:latest
MAINTAINER hikachan sadayuki-matsuno
ENV REPOSITORY github.com/future-architect/vuls
ENV LOGDIR /var/log/vuls
ENV WORKDIR /vuls
# go-cve-dictionary install
RUN git clone https://$REPOSITORY.git $GOPATH/src/$REPOSITORY \
&& cd $GOPATH/src/$REPOSITORY \
&& make install \
&& mkdir -p $LOGDIR
VOLUME [$WORKDIR, $LOGDIR]
WORKDIR $WORKDIR
ENV PWD $WORKDIR
ENTRYPOINT ["vuls"]
CMD ["--help"]

View File

@@ -0,0 +1,125 @@
# Vuls-Docker
This is the Git repo of the official Docker image for vuls.
See the [Hub page](https://hub.docker.com/r/vuls/vuls/) for the full readme on how to use the Docker image and for information regarding contributing and issues.
# Supported tags and respective `Dockerfile` links
- [`latest` (*vuls:latest Dockerfile*)](https://github.com/future-architect/vuls/blob/master/setup/docker/vuls/latest/Dockerfile)
# Caution
This image is built per commit.
If you want to use the latest docker image, you should remove the existing image, and pull it once again.
- Remove old docker image
```
$ docker rmi vuls/vuls
```
- Pull new docker image
```
$ docker pull vuls/vuls
```
# What is Vuls?
Vuls is the Vulnerability scanner for Linux/FreeBSD, agentless, written in golang.
Please see the [Documentation](https://github.com/future-architect/vuls)
![logo](https://github.com/future-architect/vuls/blob/master/img/vuls_logo.png?raw=true)
# How to use this image
## check vuls version
```
$ docker run --rm vuls/vuls -v
```
## config
Create config.toml referring to [this](https://github.com/future-architect/vuls#configuration).
```toml
[servers]
[servers.amazon]
host = "54.249.93.16"
port = "22"
user = "vuls-user"
keyPath = "/root/.ssh/id_rsa" # path to ssh private key in docker
```
## configtest
```console
$ docker run --rm -it \
-v ~/.ssh:/root/.ssh:ro \
-v $PWD:/vuls \
-v $PWD/vuls-log:/var/log/vuls \
vuls/vuls configtest \
-config=./config.toml # path to config.toml in docker
```
## scan
```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 scan \
-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
```console
$ docker run --rm -it \
-v $PWD:/vuls \
-v $PWD/vuls-log:/var/log/vuls \
vuls/vuls tui \
-cvedb-path=/vuls/cve.sqlite3
```
## vulsrepo
Prease refer to [this](https://hub.docker.com/r/vuls/vulsrepo/).
# User Feedback
## Documentation
Documentation for this image is stored in the [`docker/` directory](https://github.com/future-architect/vuls/tree/master/setup/docker) of the [`future-architect/vuls` GitHub repo](https://github.com/future-architect/vuls).
## Issues
If you have any problems with or questions about this image, please contact us through a [GitHub issue](https://github.com/future-architect/vuls/issues).
## Contributing
1. fork a repository: github.com/future-architect/vuls to github.com/you/repo
1. get original code: go get github.com/future-architect/vuls
1. work on original code
1. add remote to your repo: git remote add myfork https://github.com/you/repo.git
1. push your changes: git push myfork
1. create a new Pull Request

View File

@@ -0,0 +1,31 @@
FROM httpd:2.4
MAINTAINER hikachan sadayuki-matsuno
# install packages
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
vim \
git \
libcgi-pm-perl \
libjson-perl \
&& rm -r /var/lib/apt/lists/*
# env
ENV HTTPD_PREFIX /usr/local/apache2
VOLUME /vuls
WORKDIR ${HTTPD_PREFIX}/htdocs
RUN git clone https://github.com/usiusi360/vulsrepo.git \
&& echo "LoadModule cgid_module modules/mod_cgid.so" >> $HTTPD_PREFIX/conf/httpd.conf \
&& echo "<Directory \"$HTTPD_PREFIX/htdocs/vulsrepo/dist/cgi\">" >> $HTTPD_PREFIX/conf/httpd.conf \
&& echo " Options +ExecCGI +FollowSymLinks" >> $HTTPD_PREFIX/conf/httpd.conf \
&& echo " AddHandler cgi-script cgi" >> $HTTPD_PREFIX/conf/httpd.conf \
&& echo "</Directory>" >> $HTTPD_PREFIX/conf/httpd.conf \
&& sed -i -e 's/User daemon/#User/g' $HTTPD_PREFIX/conf/httpd.conf \
&& sed -i -e 's/Group daemon/#Group/g' $HTTPD_PREFIX/conf/httpd.conf \
&& ln -snf /vuls/results /usr/local/apache2/htdocs/vulsrepo/results
EXPOSE 80
CMD ["httpd-foreground"]

View File

@@ -0,0 +1,47 @@
# VulsRepo-Docker
This is the Git repo of the official Docker image for vulsrepo.
See the [Hub page](https://hub.docker.com/r/vuls/vulsrepo/) for the full readme on how to use the Docker image and for information regarding contributing and issues.
# Supported tags and respective `Dockerfile` links
- [`latest` (*vulsrepo:latest Dockerfile*)](https://github.com/future-architect/vuls/blob/master/setup/docker/vulsrepo/latest/Dockerfile)
# Caution
This image is built per commit.
If you want to use the latest docker image, you should remove the existing image, and pull it once again.
# What is vulsrepo?
VulsRepo is visualized based on the json report output in [vuls](https://github.com/future-architect/vuls).
# How to use this image
## vulsrepo
```console
$docker run -dt \
-v $PWD:/vuls \
-p 80:80 \
vuls/vulsrepo
```
# User Feedback
## Documentation
Documentation for this image is stored in the [`docker/` directory](https://github.com/future-architect/vuls/tree/master/setup/docker) of the [`future-architect/vuls` GitHub repo](https://github.com/future-architect/vuls).
## Issues
If you have any problems with or questions about this image, please contact us through a [GitHub issue](https://github.com/future-architect/vuls/issues).
## Contributing
1. fork a repository: github.com/future-architect/vuls to github.com/you/repo
1. get original code: go get github.com/future-architect/vuls
1. work on original code
1. add remote to your repo: git remote add myfork https://github.com/you/repo.git
1. push your changes: git push myfork
1. create a new Pull Request

View File

@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package util
import (
"fmt"
"os"
"path/filepath"
"runtime"
@@ -30,6 +29,9 @@ import (
formatter "github.com/kotakanbe/logrus-prefixed-formatter"
)
// Log for localhsot
var Log *logrus.Entry
// NewCustomLogger creates logrus
func NewCustomLogger(c config.ServerInfo) *logrus.Entry {
log := logrus.New()
@@ -41,24 +43,20 @@ func NewCustomLogger(c config.ServerInfo) *logrus.Entry {
}
// File output
logDir := "/var/log/vuls"
if runtime.GOOS == "windows" {
logDir = filepath.Join(os.Getenv("APPDATA"), "vuls")
logDir := GetDefaultLogDir()
if 0 < len(config.Conf.LogDir) {
logDir = config.Conf.LogDir
}
if _, err := os.Stat(logDir); os.IsNotExist(err) {
if err := os.Mkdir(logDir, 0666); err != nil {
logrus.Errorf("Failed to create log directory: %s", err)
if err := os.Mkdir(logDir, 0700); err != nil {
log.Errorf("Failed to create log directory: %s", err)
}
}
whereami := "localhost"
if 0 < len(c.ServerName) {
if 0 < len(c.Container.ContainerID) {
whereami = fmt.Sprintf(
"%s_%s", c.ServerName, c.Container.Name)
} else {
whereami = fmt.Sprintf("%s", c.ServerName)
}
whereami = c.GetServerName()
}
if _, err := os.Stat(logDir); err == nil {
@@ -76,3 +74,12 @@ func NewCustomLogger(c config.ServerInfo) *logrus.Entry {
fields := logrus.Fields{"prefix": whereami}
return log.WithFields(fields)
}
// GetDefaultLogDir returns default log directory
func GetDefaultLogDir() string {
defaultLogDir := "/var/log/vuls"
if runtime.GOOS == "windows" {
defaultLogDir = filepath.Join(os.Getenv("APPDATA"), "vuls")
}
return defaultLogDir
}

View File

@@ -95,7 +95,7 @@ func URLPathParamJoin(baseURL string, paths []string, params map[string]string)
// ProxyEnv returns shell environment variables to set proxy
func ProxyEnv() string {
httpProxyEnv := "env"
httpProxyEnv := ""
keys := []string{
"http_proxy",
"https_proxy",
@@ -111,7 +111,7 @@ func ProxyEnv() string {
// PrependProxyEnv prepends proxy enviroment variable
func PrependProxyEnv(cmd string) string {
if config.Conf.HTTPProxy == "" {
if len(config.Conf.HTTPProxy) == 0 {
return cmd
}
return fmt.Sprintf("%s %s", ProxyEnv(), cmd)
@@ -124,3 +124,14 @@ func PrependProxyEnv(cmd string) string {
// }
// return time.Unix(i, 0), nil
// }
// Truncate truncates string to the length
func Truncate(str string, length int) string {
if length < 0 {
return str
}
if length <= len(str) {
return str[:length]
}
return str
}

View File

@@ -105,14 +105,14 @@ func TestPrependHTTPProxyEnv(t *testing.T) {
"http://proxy.co.jp:8080",
"yum check-update",
},
`env http_proxy="http://proxy.co.jp:8080" https_proxy="http://proxy.co.jp:8080" HTTP_PROXY="http://proxy.co.jp:8080" HTTPS_PROXY="http://proxy.co.jp:8080" yum check-update`,
` http_proxy="http://proxy.co.jp:8080" https_proxy="http://proxy.co.jp:8080" HTTP_PROXY="http://proxy.co.jp:8080" HTTPS_PROXY="http://proxy.co.jp:8080" yum check-update`,
},
{
[]string{
"http://proxy.co.jp:8080",
"",
},
`env http_proxy="http://proxy.co.jp:8080" https_proxy="http://proxy.co.jp:8080" HTTP_PROXY="http://proxy.co.jp:8080" HTTPS_PROXY="http://proxy.co.jp:8080" `,
` http_proxy="http://proxy.co.jp:8080" https_proxy="http://proxy.co.jp:8080" HTTP_PROXY="http://proxy.co.jp:8080" HTTPS_PROXY="http://proxy.co.jp:8080" `,
},
{
[]string{
@@ -131,3 +131,43 @@ func TestPrependHTTPProxyEnv(t *testing.T) {
}
}
func TestTruncate(t *testing.T) {
var tests = []struct {
in string
length int
out string
}{
{
in: "abcde",
length: 3,
out: "abc",
},
{
in: "abcdefg",
length: 5,
out: "abcde",
},
{
in: "abcdefg",
length: 10,
out: "abcdefg",
},
{
in: "abcdefg",
length: 0,
out: "",
},
{
in: "abcdefg",
length: -1,
out: "abcdefg",
},
}
for _, tt := range tests {
actual := Truncate(tt.in, tt.length)
if actual != tt.out {
t.Errorf("\nexpected: %s\n actual: %s", tt.out, actual)
}
}
}