Compare commits

...

105 Commits

Author SHA1 Message Date
kota kanbe
f82e5a281d Update CHANGELOG 2016-05-25 08:30:24 +09:00
kota kanbe
904e6241e4 Bump up version 2016-05-25 08:24:25 +09:00
Kota Kanbe
ce39a3daf9 Update README.md 2016-05-24 20:38:50 +09:00
Kota Kanbe
f2c7f74beb Merge pull request #69 from future-architect/enable_to_show_history
Enable to show previous scan result
2016-05-24 20:35:43 +09:00
kota kanbe
20db997fc2 Enable to show previous scan result 2016-05-24 20:19:56 +09:00
Kota Kanbe
7188e97444 Merge pull request #68 from future-architect/ignore-unscored-cves
Add ignore-unscored-cves option
2016-05-24 09:17:12 +09:00
kota kanbe
6d528e741d Add ignore-unscored-cves option 2016-05-24 08:35:36 +09:00
Kota Kanbe
d356e8370d Merge pull request #67 from future-architect/docker_container
Add docker container support
2016-05-24 08:05:01 +09:00
kota kanbe
5e336b5928 Add docker container support 2016-05-23 09:38:52 +09:00
Kota Kanbe
787ad0629b Merge pull request #57 from theonlydoo/patch-2
Update Dockerfile
2016-05-20 13:45:29 +09:00
Kota Kanbe
53e4adf24e Merge pull request #56 from theonlydoo/patch-1
Update run.sh
2016-05-20 13:45:04 +09:00
Kota Kanbe
6af811d63e Merge pull request #66 from future-architect/pointless_sudo_debian
Fix pointless sudo in debian.go #29
2016-05-19 17:21:41 +09:00
kota kanbe
359dab3380 fix pointless sudo in debian.go #29 2016-05-19 17:20:09 +09:00
Kota Kanbe
97a8e6e965 Merge pull request #65 from future-architect/version
Add version flag
2016-05-19 15:22:04 +09:00
kota kanbe
8ea699aa08 Add version flag 2016-05-19 15:02:17 +09:00
Kota Kanbe
7d924d2b0c Merge pull request #64 from future-architect/fix_nilpointer_when_error_in_cve_client
Fix error handling of httpGet in cve-client #58
2016-05-16 20:06:05 +09:00
kota kanbe
3c85613ada Fix error handling of httpGet in cve-client #58 2016-05-16 20:00:22 +09:00
Kota Kanbe
c536d26db3 Merge pull request #63 from future-architect/fix_nilpointer_when_error_in_cve_client
Fix nil pointer at error handling of cve_client #58
2016-05-16 19:26:53 +09:00
kota kanbe
4350ff2692 Fix nil pointer at error handling of cve_client #58 2016-05-16 19:23:43 +09:00
Kota Kanbe
0b9a1e7bb4 Merge pull request #55 from pabroff/modify
Fix scan on Japanese environment.
2016-05-16 14:48:27 +09:00
Kota Kanbe
714ad18fa0 Merge pull request #54 from jody-frankowski/fix_deprecated_typo
Fix a typo: replace Depricated by Deprecated.
2016-05-16 14:41:16 +09:00
pabroff
f81f785813 Fix again on Japanese environment. 2016-05-16 14:20:57 +09:00
Kota Kanbe
76c32af46f Merge pull request #60 from future-architect/fix_cvss_over
Fix -cvss-over flag #59
2016-05-16 12:31:07 +09:00
kota kanbe
cd108263e1 Fix -cvss-over flag #59 2016-05-16 12:13:21 +09:00
theonlydoo
093c47b59c Update Dockerfile
to comply with https://github.com/future-architect/vuls/compare/master...theonlydoo:patch-1
2016-05-11 17:37:29 +02:00
theonlydoo
56a40ec51a Update run.sh
Update to provide some more reliability on server boot time
2016-05-11 17:36:55 +02:00
pabroff
1337be2b84 Fix scan on Japanese environment. 2016-05-11 20:31:44 +09:00
Jody Frankowski
eecd2c60f5 Fix a typo: replace Depricated by Deprecated. 2016-05-11 11:18:32 +02:00
Kota Kanbe
da071cb120 Merge pull request #50 from pabroff/modify
Fix yes no infinite loop while doing yum update --changelog on root@CentOS #47
2016-05-10 13:58:28 +09:00
Dog of Pavlov
012cfa3cbe Fix #47
Default set  "Is this ok [y/N]:" is N.
2016-05-10 12:22:51 +09:00
Kota Kanbe
21180847dc Update README.fr.md 2016-04-30 11:07:36 +09:00
Kota Kanbe
9e9e538846 Merge pull request #45 from future-architect/fix_prefix_of_servername_slack
Fix $servername in output of discover command
2016-04-30 11:06:26 +09:00
kota kanbe
66025b1ae2 Fix $servername in output of discover command 2016-04-30 11:01:27 +09:00
Kota Kanbe
5999361358 Update README.md 2016-04-30 10:57:16 +09:00
Kota Kanbe
e8699d1cb7 Update README.md 2016-04-25 15:38:56 +09:00
Kota Kanbe
9292448e73 Merge pull request #33 from mattn/windows
Support Windows
2016-04-22 18:37:09 +09:00
kota kanbe
d7e156613d Merge branch 'master' of https://github.com/future-architect/vuls
* 'master' of https://github.com/future-architect/vuls:
  Fix yum to yum --color=never #36
  Update README
2016-04-21 23:24:22 +09:00
kota kanbe
c3604aa66d Update CHANGELOG 2016-04-21 23:23:51 +09:00
kota kanbe
49dd12fef3 Bump up version 2016-04-21 23:19:11 +09:00
Kota Kanbe
5e037b1743 Merge pull request #41 from theonlydoo/patch-1
Update README
2016-04-21 19:17:27 +09:00
Kota Kanbe
ebc79805ed Merge pull request #42 from future-architect/yum_color_never
Fix yum to yum --color=never #36
2016-04-21 19:13:20 +09:00
kota kanbe
c37e56e51d Fix yum to yum --color=never #36 2016-04-21 19:10:36 +09:00
theonlydoo
28a93c02e6 Update README
not so sparse documentation
2016-04-21 11:46:24 +02:00
Kota Kanbe
0996c58894 Merge pull request #40 from future-architect/fix_parse_yum_check_update
Fix parse yum check update
2016-04-21 17:43:24 +09:00
kota kanbe
56ecf32565 Fix yum check-update --security to yum check-update 2016-04-21 17:42:13 +09:00
Kota Kanbe
416fb3c937 Update README.md 2016-04-21 15:50:26 +09:00
Kota Kanbe
d48b8315c9 Merge pull request #38 from theonlydoo/Dockerfile
Sparse dockerization
2016-04-21 15:27:18 +09:00
arnaudb
7c6d1eb585 Sparse dockerization 2016-04-20 15:42:14 +02:00
kota kanbe
fae04dce81 Fix Error while parsing yum check-update if the package not in rpm -qa 2016-04-20 09:02:43 +09:00
Kota Kanbe
86a5433312 Update README.md 2016-04-19 17:49:02 +09:00
Kota Kanbe
d9cf63a9fe Merge pull request #35 from future-architect/fix_to_no_password_in_config
Fix no password in config
2016-04-19 17:35:13 +09:00
kota kanbe
88bf643363 Merge branch 'master' into fix_to_no_password_in_config
* master:
  Update README.md
2016-04-19 17:29:20 +09:00
kota kanbe
e0b680b305 Merge branch 'master' into fix_to_no_password_in_config
* master:
  Update README.md
2016-04-19 16:58:47 +09:00
Kota Kanbe
d6356408b8 Update README.md 2016-04-19 16:07:40 +09:00
kota kanbe
4d28de17b4 No password in config file 2016-04-18 23:03:13 +09:00
Kota Kanbe
fdd918d970 Update README.md 2016-04-16 22:52:02 +09:00
Yasuhiro Matsumoto
da16f9673e Support Windows 2016-04-15 19:25:41 +09:00
Kota Kanbe
b02b7c9081 Merge pull request #31 from blue119/master
fix typo
2016-04-15 01:22:29 +09:00
Yao-Po Wang
ea82149dbe fix typo 2016-04-14 23:40:14 +08:00
Kota Kanbe
9d64f039ab Merge pull request #30 from future-architect/fix_error_while_parse_yum_check_update_obsoleting_line
Fix error while parsing yum check-update #24
2016-04-14 20:49:23 +09:00
kota kanbe
cd9cbd795b Fix error while parsing yum check-update #24 2016-04-14 20:28:01 +09:00
Kota Kanbe
929d561de8 Update README.md 2016-04-14 14:55:22 +09:00
Kota Kanbe
245abe5b6b Update README.md 2016-04-14 14:37:40 +09:00
Kota Kanbe
768364fc77 Update README.md 2016-04-14 01:29:06 +09:00
Kota Kanbe
60a3e9532a Merge pull request #23 from novakin/fr_README_translation
Fr readme translation
2016-04-14 01:27:30 +09:00
Novakin
dcd6ba0a82 Update README.fr.md 2016-04-13 14:52:18 +03:00
Novakin
9f2dc2c6a3 Update README.fr.md 2016-04-13 14:51:27 +03:00
Novakin
7498a540d4 Update README.fr.md 2016-04-13 14:47:19 +03:00
Novakin
26ae01d960 Update README.fr.md 2016-04-13 14:46:15 +03:00
Novakin
f72781c30c Update README.fr.md 2016-04-13 14:37:30 +03:00
Novakin
21e957159d Update README.fr.md 2016-04-13 14:36:21 +03:00
Novakin
a66b425da0 Create README.fr.md 2016-04-13 14:16:57 +03:00
Kota Kanbe
804fffd009 Update README.md 2016-04-13 15:31:06 +09:00
Kota Kanbe
ac77cc1f87 Update README.md 2016-04-13 14:38:41 +09:00
kota kanbe
d0d360a6e7 Update CHANGELOG.md 2016-04-12 15:20:03 +09:00
kota kanbe
9708533565 Bump up version 2016-04-12 15:16:03 +09:00
Kota Kanbe
ac98b908e3 Merge pull request #20 from future-architect/fix_sudo_option_redhat
Fix sudo option on RedHat like Linux, change some messages.
2016-04-12 14:00:36 +09:00
Kota Kanbe
9bacd98577 Merge pull request #19 from Euan-Kerr/typo_fix_and_updated_readme
Typo fix and updated readme
2016-04-12 01:40:23 +09:00
kota kanbe
d750205f31 Fix sudo option on Redhat like Linux, change some messages. 2016-04-12 01:14:40 +09:00
Euan
b4d0aa7532 Typo fix and updated readme
Updated readme to include chmod 600 on the authorized_keys file to force
the correct permissions.
2016-04-11 13:13:19 +01:00
Kota Kanbe
3e846233a3 Merge pull request #18 from future-architect/remove_period_at_end_of_errmsg
remove a period at the end of error messages.
2016-04-10 19:10:32 +09:00
kota kanbe
1a943776c3 remove a period at the end of error messages. 2016-04-10 19:08:46 +09:00
Kota Kanbe
57ef45ebcd Merge pull request #17 from future-architect/fix_error_rhel_on_aws
fix error while yum updateinfo --security update on rhel@aws
2016-04-10 18:42:02 +09:00
kota kanbe
b64115f283 fix error while yum updateinfo --security update on rhel@aws 2016-04-10 18:37:55 +09:00
Kota Kanbe
018eb29ce5 Update README.md 2016-04-10 09:49:06 +09:00
Kota Kanbe
77c7d2fe26 Merge pull request #9 from cpobrien/master
Revise small grammar mistakes in serverapi.go
2016-04-10 08:58:53 +09:00
Kota Kanbe
336b72bbca Merge pull request #15 from radarhere/master
Fixed typos
2016-04-10 08:56:45 +09:00
Andrew Murray
0deb1032cd Fixed typos 2016-04-09 12:31:05 +10:00
Connor O'Brien
34c5644e63 Remove period from error log 2016-04-07 23:35:42 -07:00
Kota Kanbe
1f80738bef Update README.md 2016-04-08 10:42:55 +09:00
Kota Kanbe
66501663a0 Merge pull request #14 from Bregor/issues/typo
Typo fix in error messages
2016-04-08 09:20:05 +09:00
Maxim Filatov
f677939975 Typo fix in error messages 2016-04-07 21:28:04 +03:00
Kota Kanbe
c465faeb6c Merge pull request #13 from future-architect/fix_index_out_of_range_when_7_servers
Fix index out of range error when the number of servers is over 6. #12
2016-04-08 02:13:28 +09:00
kota kanbe
6a6c7bf8a4 Fix index out of range error when the number of servers is over 6. #12 2016-04-08 02:07:02 +09:00
Connor O'Brien
d19afe665f Revise small grammar mistakes in serverapi.go 2016-04-06 21:42:42 -07:00
Kota Kanbe
c62ca7c645 Merge pull request #7 from future-architect/not_send_to_errchan_while_backoff
Fix error handling in HTTP backoff function
2016-04-07 03:27:22 +09:00
kota kanbe
855b48f0c9 Fix error handling in HTTP backoff function 2016-04-07 03:22:51 +09:00
Kota Kanbe
555e34d035 Update README.md 2016-04-07 02:34:13 +09:00
Kota Kanbe
6b12ff35cd Update README.md 2016-04-07 02:33:29 +09:00
Kota Kanbe
d9813e822f Update README.md 2016-04-06 17:20:32 +09:00
kota kanbe
26273e7387 Update CHANGELOG.md 2016-04-06 15:04:00 +09:00
kota kanbe
b52f0120ff Bump up version 2016-04-06 12:34:13 +09:00
kota kanbe
76ade4c3b4 Fix panic: runtime error: index out of range in tui mode #5 2016-04-06 12:09:29 +09:00
Kota Kanbe
110d74a91e Merge pull request #6 from toli/patch-1
Typo in Exapmle
2016-04-06 11:33:27 +09:00
Toli Kuznets
1819edf724 Typo in Exapmle 2016-04-05 13:19:18 -07:00
42 changed files with 2427 additions and 580 deletions

View File

@@ -1,5 +1,97 @@
# Change Log
0.1.0 (2013-03-23)
## [v0.1.4](https://github.com/future-architect/vuls/tree/v0.1.4) (2016-05-24)
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.3...v0.1.4)
Initial public release
**Implemented enhancements:**
- Initial fetch from NVD is too heavy \(2.3 GB of memory consumed\) [\#27](https://github.com/future-architect/vuls/issues/27)
- Enable to show previous scan result [\#69](https://github.com/future-architect/vuls/pull/69) ([kotakanbe](https://github.com/kotakanbe))
- Add ignore-unscored-cves option [\#68](https://github.com/future-architect/vuls/pull/68) ([kotakanbe](https://github.com/kotakanbe))
- Support dynamic scanning docker container [\#67](https://github.com/future-architect/vuls/pull/67) ([kotakanbe](https://github.com/kotakanbe))
- Add version flag [\#65](https://github.com/future-architect/vuls/pull/65) ([kotakanbe](https://github.com/kotakanbe))
- Update Dockerfile [\#57](https://github.com/future-architect/vuls/pull/57) ([theonlydoo](https://github.com/theonlydoo))
- Update run.sh [\#56](https://github.com/future-architect/vuls/pull/56) ([theonlydoo](https://github.com/theonlydoo))
- Support Windows [\#33](https://github.com/future-architect/vuls/pull/33) ([mattn](https://github.com/mattn))
**Fixed bugs:**
- vuls scan -cvss-over does not work. [\#59](https://github.com/future-architect/vuls/issues/59)
- `panic: runtime error: invalid memory address or nil pointer dereference` when scan CentOS5.5 [\#58](https://github.com/future-architect/vuls/issues/58)
- It rans out of memory. [\#47](https://github.com/future-architect/vuls/issues/47)
- BUG: vuls scan on CentOS with Japanese environment. [\#43](https://github.com/future-architect/vuls/issues/43)
- yum --color=never [\#36](https://github.com/future-architect/vuls/issues/36)
- Failed to parse yum check-update [\#32](https://github.com/future-architect/vuls/issues/32)
- Pointless sudo [\#29](https://github.com/future-architect/vuls/issues/29)
- Can't init database in a path having blanks [\#26](https://github.com/future-architect/vuls/issues/26)
- Fix pointless sudo in debian.go \#29 [\#66](https://github.com/future-architect/vuls/pull/66) ([kotakanbe](https://github.com/kotakanbe))
- Fix error handling of httpGet in cve-client \#58 [\#64](https://github.com/future-architect/vuls/pull/64) ([kotakanbe](https://github.com/kotakanbe))
- Fix nil pointer at error handling of cve\_client \#58 [\#63](https://github.com/future-architect/vuls/pull/63) ([kotakanbe](https://github.com/kotakanbe))
- Set language en\_US. [\#61](https://github.com/future-architect/vuls/pull/61) ([pabroff](https://github.com/pabroff))
- Fix -cvss-over flag \#59 [\#60](https://github.com/future-architect/vuls/pull/60) ([kotakanbe](https://github.com/kotakanbe))
- Fix scan on Japanese environment. [\#55](https://github.com/future-architect/vuls/pull/55) ([pabroff](https://github.com/pabroff))
- Fix a typo: replace Depricated by Deprecated. [\#54](https://github.com/future-architect/vuls/pull/54) ([jody-frankowski](https://github.com/jody-frankowski))
- Fix yes no infinite loop while doing yum update --changelog on root@CentOS \#47 [\#50](https://github.com/future-architect/vuls/pull/50) ([pabroff](https://github.com/pabroff))
- Fix $servername in output of discover command [\#45](https://github.com/future-architect/vuls/pull/45) ([kotakanbe](https://github.com/kotakanbe))
## [v0.1.3](https://github.com/future-architect/vuls/tree/v0.1.3) (2016-04-21)
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.2...v0.1.3)
**Implemented enhancements:**
- Add sudo support for prepare [\#11](https://github.com/future-architect/vuls/issues/11)
- Dockerfile? [\#10](https://github.com/future-architect/vuls/issues/10)
- Update README [\#41](https://github.com/future-architect/vuls/pull/41) ([theonlydoo](https://github.com/theonlydoo))
- Sparse dockerization [\#38](https://github.com/future-architect/vuls/pull/38) ([theonlydoo](https://github.com/theonlydoo))
- No password in config [\#35](https://github.com/future-architect/vuls/pull/35) ([kotakanbe](https://github.com/kotakanbe))
- Fr readme translation [\#23](https://github.com/future-architect/vuls/pull/23) ([novakin](https://github.com/novakin))
**Fixed bugs:**
- Issues updating CVE database behind https proxy [\#39](https://github.com/future-architect/vuls/issues/39)
- Vuls failed to parse yum check-update [\#24](https://github.com/future-architect/vuls/issues/24)
- Fix yum to yum --color=never \#36 [\#42](https://github.com/future-architect/vuls/pull/42) ([kotakanbe](https://github.com/kotakanbe))
- Fix parse yum check update [\#40](https://github.com/future-architect/vuls/pull/40) ([kotakanbe](https://github.com/kotakanbe))
- fix typo [\#31](https://github.com/future-architect/vuls/pull/31) ([blue119](https://github.com/blue119))
- Fix error while parsing yum check-update \#24 [\#30](https://github.com/future-architect/vuls/pull/30) ([kotakanbe](https://github.com/kotakanbe))
**Closed issues:**
- Unable to scan on ubuntu because changelog.ubuntu.com is down... [\#21](https://github.com/future-architect/vuls/issues/21)
- err: Not initialize\(d\) yet.. [\#16](https://github.com/future-architect/vuls/issues/16)
- Errors when using fish shell [\#8](https://github.com/future-architect/vuls/issues/8)
## [v0.1.2](https://github.com/future-architect/vuls/tree/v0.1.2) (2016-04-12)
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.1...v0.1.2)
**Fixed bugs:**
- 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))
- 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))
- Typo fix in error messages [\#14](https://github.com/future-architect/vuls/pull/14) ([Bregor](https://github.com/Bregor))
- Fix index out of range error when the number of servers is over 6. \#12 [\#13](https://github.com/future-architect/vuls/pull/13) ([kotakanbe](https://github.com/kotakanbe))
- Revise small grammar mistakes in serverapi.go [\#9](https://github.com/future-architect/vuls/pull/9) ([cpobrien](https://github.com/cpobrien))
- Fix error handling in HTTP backoff function [\#7](https://github.com/future-architect/vuls/pull/7) ([kotakanbe](https://github.com/kotakanbe))
## [v0.1.1](https://github.com/future-architect/vuls/tree/v0.1.1) (2016-04-06)
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.0...v0.1.1)
**Fixed bugs:**
- Typo in Exapmle [\#6](https://github.com/future-architect/vuls/pull/6) ([toli](https://github.com/toli))
## [v0.1.0](https://github.com/future-architect/vuls/tree/v0.1.0) (2016-04-04)
**Merged pull requests:**
- English translation [\#4](https://github.com/future-architect/vuls/pull/4) ([hikachan](https://github.com/hikachan))
- English translation [\#3](https://github.com/future-architect/vuls/pull/3) ([chewyinping](https://github.com/chewyinping))
- Add a Bitdeli Badge to README [\#2](https://github.com/future-architect/vuls/pull/2) ([bitdeli-chef](https://github.com/bitdeli-chef))
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*

View File

@@ -7,7 +7,6 @@
fmtcheck \
pretest \
test \
integration \
cov \
clean
@@ -16,16 +15,16 @@ PKGS = ./. ./db ./config ./models ./report ./cveapi ./scan ./util ./commands
all: test
vendor:
@ go get -v github.com/mjibson/party
party -d external -c -u
# vendor:
# @ go get -v github.com/mjibson/party
# party -d external -c -u
lint:
@ go get -v github.com/golang/lint/golint
$(foreach file,$(SRCS),golint $(file) || exit;)
vet:
@-go get -v golang.org/x/tools/cmd/vet
# @-go get -v golang.org/x/tools/cmd/vet
$(foreach pkg,$(PKGS),go vet $(pkg);)
fmt:

652
README.fr.md Normal file
View File

@@ -0,0 +1,652 @@
# Vuls: VULnerability Scanner
[![Slack](https://img.shields.io/badge/slack-join-blue.svg)](http://goo.gl/forms/xm5KFo35tu)
Scanneur de vulnérabilité Linux, sans agent, écrit en golang
Nous avons une équipe Slack. [Rejoignez notre Slack Team](http://goo.gl/forms/xm5KFo35tu)
[README en Japonais](https://github.com/future-architect/vuls/blob/master/README.ja.md)
[![asciicast](https://asciinema.org/a/3y9zrf950agiko7klg8abvyck.png)](https://asciinema.org/a/3y9zrf950agiko7klg8abvyck)
![Vuls-slack](img/vuls-slack-en.png)
----
# Résumé
Effectuer des recherches de vulnérabilités et des mises à jour quotidiennes peut etre un fardeau pour un administrateur système.
Afin d'éviter des interruptions systèmes dans un environnement de production, il est fréquent pour un administrateur système de choisir de ne pas utiliser la fonction de mise à jour automatique proposée par le gestionnaire de paquets et d'effecter ces mises à jour manuellement.
Ce qui implique les problèmes suivants :
- L'administrateur système devra surveiller constamment toutes les nouvelles vulnérabilités dans NVD (National Vulnerability Database) etc.
- Il pourrait être impossible pour un administrateur système de surveiller tous les logiciels installés sur un serveur.
- Il est coûteux d'effectuer une analyse pour déterminer quels sont les serveurs affectés par de nouvelles vulnérabilités. La possibilité de négliger un serveur ou deux est bien présente.
Vuls est un outil crée pour palier aux problèmes listés ci-dessus. Voici ses caractéristiques.
- Informer les utilisateurs des vulnérabilités système.
- Informer les utilisateurs des systèmes concernés.
- La détection de vulnérabilités est effectuée automatiquement pour éviter toute négligence.
- Les rapports sont générés régulièrement via CRON pour mieux gérer ces vulnérabilités.
![Vuls-Motivation](img/vuls-motivation.png)
----
# Caractéristiques principales
- Recherche de vulnérabilités sur des serveurs Linux
- Supporte Ubuntu, Debian, CentOS, Amazon Linux, RHEL
- 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
- Supporte les logiciels inscrits au CPE
- Architecture sans agent
- L'utilisateur doit seulement mettre en place VULS sur une seule machine qui se connectera aux autres via SSH
- Génération automatique des fichiers de configuration
- Auto detection de serveurs via CIDR et génération de configuration
- Email et notification Slack possibles (supporte le Japonais)
- Les résultats d'un scan sont accessibles dans un shell via TUI Viewer terminal.
----
# Ce que Vuls ne fait pas
- Vuls ne met pas à jour les programmes affectés par les vulnérabilités découvertes.
----
# Hello Vuls
Ce tutoriel décrit la recherche de vulnérabilités sur une machine locale avec Vuls.
Voici les étapes à suivre.
1. Démrarrage d'Amazon Linux
1. Autoriser les connexions SSH depuis localhost
1. Installation des prérequis
1. Déploiement de go-cve-dictionary
1. Deploiement de Vuls
1. Configuration
1. Préparation
1. Scan
1. TUI(Terminal-Based User Interface)
## Step1. Démrarrage d'Amazon Linux
- Nous utilisons dans cette exemple une vieille AMI (amzn-ami-hvm-2015.09.1.x86_64-gp2 - ami-383c1956)
- Taille de l'instance : t2.medium
- La première fois, t2.medium et plus sont requis pour la récupération des CVE depuis NVD (2.3GB de mémoire utilisé)
- Une fois la récupération initiale des données NVD terminée vous pouvez passer sur une instance t2.nano.
- Ajoutez la configuration suivante au cloud-init, afin d'éviter une mise à jour automatique lors du premier démarrage.
- [Q: How do I disable the automatic installation of critical and important security updates on initial launch?](https://aws.amazon.com/amazon-linux-ami/faqs/?nc1=h_ls)
```
#cloud-config
repo_upgrade: none
```
## Step2. Paramètres SSH
Il est obligatoire que le serveur puisse se connecter à son propre serveur SSH
Générez une paire de clés SSH et ajoutez la clé publique dans le fichier authorized_keys
```bash
$ ssh-keygen -t rsa
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys
```
## Step3. Installation des prérequis
Vuls requiert l'installation des paquets suivants :
- sqlite
- git
- gcc
- go v1.6
- 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
$ mkdir $HOME/go
```
Ajoutez les lignes suivantes dans /etc/profile.d/goenv.sh
```bash
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
```
Ajoutons ces nouvelles variables denvironnement au shell
```bash
$ source /etc/profile.d/goenv.sh
```
## Step4. Déploiement de [go-cve-dictionary](https://github.com/kotakanbe/go-cve-dictionary)
go get
```bash
$ sudo mkdir /var/log/vuls
$ sudo chown ec2-user /var/log/vuls
$ sudo chmod 700 /var/log/vuls
$ go get github.com/kotakanbe/go-cve-dictionary
```
Démarrez go-cve-dictionary en mode serveur.
Lors de son premier démarrage go-cve-dictionary récupère la liste des vulnérabilités depuis NVD
Cette opération prend environ 10 minutes (sur AWS).
```bash
$ go-cve-dictionary server
... Fetching ...
$ ls -alh cve.sqlite3
-rw-r--r-- 1 ec2-user ec2-user 7.0M Mar 24 13:20 cve.sqlite3
```
Une fois les informations de vulnérabilités collectées redémarrez le mode serveur.
```bash
$ go-cve-dictionary server
[Mar 24 15:21:55] INFO Opening DB. datafile: /home/ec2-user/cve.sqlite3
[Mar 24 15:21:55] INFO Migrating DB
[Mar 24 15:21:56] INFO Starting HTTP Sever...
[Mar 24 15:21:56] INFO Listening on 127.0.0.1:1323
```
## Step5. Déploiement de Vuls
Ouvrez un second terminal, connectez vous à l'instance ec2 via SSH
go get
```
$ go get github.com/future-architect/vuls
```
## Step6. Configuration
Créez un fichier de configuration (TOML format).
```
$ cat config.toml
[servers]
[servers.172-31-4-82]
host = "172.31.4.82"
port = "22"
user = "ec2-user"
keyPath = "/home/ec2-user/.ssh/id_rsa"
```
## Step7. Configuration des serveurs cibles vuls
```
$ vuls prepare
```
## Step8. Scan
```
$ vuls scan
INFO[0000] Begin scanning (config: /home/ec2-user/config.toml)
... snip ...
172-31-4-82 (amazon 2015.09)
============================
CVE-2016-0494 10.0 Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle
Java SE 6u105, 7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to
affect confidentiality, integrity, and availability via unknown vectors related to
2D.
... snip ...
CVE-2016-0494
-------------
Score 10.0 (High)
Vector (AV:N/AC:L/Au:N/C:C/I:C/A:C)
Summary Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle Java SE 6u105,
7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to affect confidentiality,
integrity, and availability via unknown vectors related to 2D.
NVD https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0494
MITRE https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0494
CVE Details http://www.cvedetails.com/cve/CVE-2016-0494
CVSS 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)
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
```
## Step9. TUI
Les résultats de Vuls peuvent etre affichés dans un Shell via TUI (Terminal-Based User Interface).
```
$ vuls tui
```
![Vuls-TUI](img/hello-vuls-tui.png)
----
# Architecture
![Vuls-Architecture](img/vuls-architecture.png)
## go-cve-dictinary
- Collecte les informations de vulnérabilités depuis NVD, JVN(Japonais), et les envoie dans SQLite.
## Vuls
- Scan de vulnérabilités sur serveurs et création d'une liste contenant les CVE ID
- Pour des informations plus détaillés sur une CVE, envoie une requete HTTP à go-cve-dictinary
- Rapport à Slack et par Email
- L'administrateur système peut voir les résultats du dernier rapport dans le terminal
----
# Exemples d'utilisation
## Scan de tous les serverus
![Vuls-Usecase1](img/vuls-usecase-elb-rails-rds-all.png)
## Scan d'un seul serveur
web/app server in the same configuration under the load balancer
![Vuls-Usecase2](img/vuls-usecase-elb-rails-rds-single.png)
----
# OS supportés
| Distribution| Release |
|:------------|-------------------:|
| Ubuntu | 12, 14, 16|
| Debian | 7, 8|
| RHEL | 4, 5, 6, 7|
| CentOS | 5, 6, 7|
| Amazon Linux| All |
----
# Usage: Détection Automatique de Serveurs
La sous-commande Discovery permet de détecter les serveurs actifs dans un range d'IP CIDR, les résultas sont directement affichés dans le terminal en respectant le format du fichier de configuration (TOML format).
```
$ vuls discover -help
discover:
discover 192.168.0.0/24
```
## Exemple
```
$ vuls discover 172.31.4.0/24
# Create config.toml using below and then ./vuls --config=/path/to/config.toml
[slack]
hookURL = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
channel = "#channel-name"
#channel = "${servername}"
iconEmoji = ":ghost:"
authUser = "username"
notifyUsers = ["@username"]
[mail]
smtpAddr = "smtp.gmail.com"
smtpPort = 465
user = "username"
password = "password"
from = "from@address.com"
to = ["to@address.com"]
cc = ["cc@address.com"]
subjectPrefix = "[vuls]"
[default]
#port = "22"
#user = "username"
#password = "password"
#keyPath = "/home/username/.ssh/id_rsa"
#keyPassword = "password"
[servers]
[servers.172-31-4-82]
host = "172.31.4.82"
#port = "22"
#user = "root"
#password = "password"
#keyPath = "/home/username/.ssh/id_rsa"
#keyPassword = "password"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
```
Vous pouvez customiser votre configuration en utilisant ce modèle.
----
# Configuration
- Slack section
```
[slack]
hookURL = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
channel = "#channel-name"
#channel = "${servername}"
iconEmoji = ":ghost:"
authUser = "username"
notifyUsers = ["@username"]
```
- hookURL : Incomming webhook's URL
- channel : channel name.
If you set ${servername} to channel, the report will be sent to each channel.
In the following example, the report will be sent to the #server1 and #server2.
Be sure to create these channels before scanning.
```
[slack]
channel = "${servername}"
...snip...
[servers]
[servers.server1]
host = "172.31.4.82"
...snip...
[servers.server2]
host = "172.31.4.83"
...snip...
```
- iconEmoji: emoji
- authUser: username of the slack team
- notifyUsers: a list of Slack usernames to send Slack notifications.
If you set ["@foo", "@bar"] to notifyUsers, @foo @bar will be included in text.
So @foo, @bar can receive mobile push notifications on their smartphone.
- Mail section
```
[mail]
smtpAddr = "smtp.gmail.com"
smtpPort = 465
user = "username"
password = "password"
from = "from@address.com"
to = ["to@address.com"]
cc = ["cc@address.com"]
subjectPrefix = "[vuls]"
```
- Default section
```
[default]
#port = "22"
#user = "username"
#password = "password"
#keyPath = "/home/username/.ssh/id_rsa"
#keyPassword = "password"
```
Items of the default section will be used if not specified.
- servers section
```
[servers]
[servers.172-31-4-82]
host = "172.31.4.82"
#port = "22"
#user = "root"
#password = "password"
#keyPath = "/home/username/.ssh/id_rsa"
#keyPassword = "password"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
```
Vous pouvez remplacer les valeurs par défaut indiquées en modifiant la section default
Vuls supporte plusieurs méthodes d'authentification SSH :
- SSH agent
- SSH authentication par clés (avec mot de passe ou sans mot de passe)
- Authentification par mot de passe
----
# Utilisation : Prepare
La sous-commande prepare installe tous les paquets nécessaires sur chaque serveur.
| Distribution| Release | Requirements |
|:------------|-------------------:|:-------------|
| Ubuntu | 12, 14, 16| - |
| Debian | 7, 8| apptitude |
| CentOS | 5| yum-plugin-security, yum-changelog |
| CentOS | 6, 7| yum-plugin-security, yum-plugin-changelog |
| Amazon | All | - |
| RHEL | 4, 5, 6, 7 | - |
```
$ vuls prepare -help
prepare:
prepare [-config=/path/to/config.toml] [-debug]
-config string
/path/to/toml (default "$PWD/config.toml")
-debug
debug mode
-use-unattended-upgrades
[Deprecated] For Ubuntu, install unattended-upgrades
```
----
# Utilisation : Scan
```
$ vuls scan -help
scan:
scan
[-lang=en|ja]
[-config=/path/to/config.toml]
[-dbpath=/path/to/vuls.sqlite3]
[-cve-dictionary-url=http://127.0.0.1:1323]
[-cvss-over=7]
[-report-slack]
[-report-mail]
[-http-proxy=http://192.168.0.1:8080]
[-debug]
[-debug-sql]
-config string
/path/to/toml (default "$PWD/config.toml")
-cve-dictionary-url string
http://CVE.Dictionary (default "http://127.0.0.1:1323")
-cvss-over float
-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))
-dbpath string
/path/to/sqlite3 (default "$PWD/vuls.sqlite3")
-debug
debug mode
-debug-sql
SQL debug mode
-http-proxy string
http://proxy-url:port (default: empty)
-lang string
[en|ja] (default "en")
-report-mail
Email report
-report-slack
Slack report
-use-unattended-upgrades
[Deprecated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)
-use-yum-plugin-security
[Deprecated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)
```
## exemple
Lancez go-cve-dictionary en mode serveur avant de lancer un scan
```
$ go-cve-dictionary server
```
### Scan tous les serveurs identifiés dans le fichier de configuration
```
$ vuls scan --report-slack --report-mail --cvss-over=7
```
Via cette simple commande Vuls va : ..
- Scanner tous les serveurs identifiés dans le fichier de configuration
- Envoyer les résultas du scan à slack et par email
- Ne rapporter que les CVE dont la note CVSS est au dessus de 7
- Afficher les résultats du scan dans le terminal
### Scan de serveurs spécifiques
```
$ vuls scan server1 server2
```
Via cette simple commande Vuls va : ..
- Scanner seulement 2 serveurs. (server1, server2)
- Afficher les résultats du scan dans le terminal
----
# Utilisation : Recherche de vulnérabilités sur des paquets non compris dans l'OS
Il est possible de détecter des vulnérabilités sur des programmes que vous avez compilés, des lors que les libraries et frameworks ont été enregistré dans [CPE](https://nvd.nist.gov/cpe.cfm).
- Comment rechercher dans CPE via le nom du programme
- [NVD: Search Common Platform Enumerations (CPE)](https://web.nvd.nist.gov/view/cpe/search)
**Check CPE Naming Format: 2.2**
- Configuration
Pour détecter des vulnérabilités sur Ruby on Rails v4.2.1, cpeNames doit etre déclaré dans la section servers.
```
[servers]
[servers.172-31-4-82]
host = "172.31.4.82"
user = "ec2-user"
keyPath = "/home/username/.ssh/id_rsa"
cpeNames = [
"cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
]
```
# Utilisation : Mise à jour des données NVD.
```
$ go-cve-dictionary fetchnvd -h
fetchnvd:
fetchnvd
[-last2y]
[-dbpath=/path/to/cve.sqlite3]
[-debug]
[-debug-sql]
-dbpath string
/path/to/sqlite3 (default "$PWD/cve.sqlite3")
-debug
debug mode
-debug-sql
SQL debug mode
-last2y
Refresh NVD data in the last two years.
```
- Récupérer toutes les données jusqu'à aujourd'hui
```
$ go-cve-dictionary fetchnvd -entire
```
- Reçupérer les données des 2 denières années
```
$ go-cve-dictionary fetchnvd -last2y
```
----
# Misc
- HTTP Proxy Support
If your system is behind HTTP proxy, you have to specify --http-proxy option.
- How to Daemonize go-cve-dictionary
Use Systemd, Upstart or supervisord, daemontools...
- How to Enable Automatic-Update of Vunerability Data.
Use job scheduler like Cron (with -last2y option).
- How to cross compile
```bash
$ cd /path/to/your/local-git-reporsitory/vuls
$ GOOS=linux GOARCH=amd64 go build -o vuls.amd64
```
- Logging
Log wrote to under /var/log/vuls/
- Debug
Run with --debug, --sql-debug option.
- Ajusting Open File Limit
[Riak docs](http://docs.basho.com/riak/latest/ops/tuning/open-files-limit/) is awesome.
- Does Vuls accept ssh connections with fish-shell or old zsh as the login shell?
No, Vuls needs a user on the server for bash login. see also [#8](/../../issues/8)
- Windows
Use Microsoft Baseline Security Analyzer. [MBSA](https://technet.microsoft.com/en-us/security/cc184924.aspx)
----
# Data Source
- [NVD](https://nvd.nist.gov/)
- [JVN(Japanese)](http://jvndb.jvn.jp/apis/myjvn/)
# Authors
kotakanbe ([@kotakanbe](https://twitter.com/kotakanbe)) created vuls and [these fine people](https://github.com/future-architect/vuls/graphs/contributors) have contributed.
----
# Contribute
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request
----
# Change Log
Please see [CHANGELOG](https://github.com/future-architect/vuls/blob/master/CHANGELOG.md).
----
# Licence
Please see [LICENSE](https://github.com/future-architect/vuls/blob/master/LICENSE).
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/future-architect/vuls/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

241
README.md
View File

@@ -2,12 +2,15 @@
# Vuls: VULnerability Scanner
[![Slack](https://img.shields.io/badge/slack-join-blue.svg)](http://goo.gl/forms/xm5KFo35tu)
[![License](https://img.shields.io/github/license/future-architect/vuls.svg?style=flat-square)](https://github.com/future-architect/vuls/blob/master/LICENSE.txt)
Vulnerability scanner for Linux, agentless, written in golang.
We have a slack team. [Join slack team](http://goo.gl/forms/xm5KFo35tu)
[README in Japanese](https://github.com/future-architect/vuls/blob/master/README.ja.md)
[README in French](https://github.com/future-architect/vuls/blob/master/README.fr.md)
[![asciicast](https://asciinema.org/a/3y9zrf950agiko7klg8abvyck.png)](https://asciinema.org/a/3y9zrf950agiko7klg8abvyck)
@@ -24,7 +27,7 @@ To avoid downtime in production environment, it is common for system administrat
This leads to the following problems.
- System administrator will have to constantly watch out for any new vulnerabilities in NVD(National Vulnerability Database) and etc.
- It might be impossible for the system administrator to monitor all the software if there are a large number of software installed in server.
- It is expensive to perform anaylsis to determine the servers affected by new vulnerabilities. The possibility of overlooking a server or two during analysis is there.
- It is expensive to perform analysis to determine the servers affected by new vulnerabilities. The possibility of overlooking a server or two during analysis is there.
Vuls is a tool created to solve the problems listed above. It has the following characteristics.
@@ -78,17 +81,15 @@ This can be done in the following steps.
## Step1. Launch Amazon Linux
- We are using the old AMI (amzn-ami-hvm-2015.09.1.x86_64-gp2 - ami-383c1956) for this example
- Instance size: t2.medium
- For the first time, t2.medium and above is required for the data fetch from NVD
- You can switch to t2.nano after the initial data fetch.
- Add the following to the cloud-init, to avoid auto-update at the first launch.
- [Q: How do I disable the automatic installation of critical and important security updates on initial launch?](https://aws.amazon.com/amazon-linux-ami/faqs/?nc1=h_ls)
```
#cloud-config
repo_upgrade: none
```
- [Q: How do I disable the automatic installation of critical and important security updates on initial launch?](https://aws.amazon.com/amazon-linux-ami/faqs/?nc1=h_ls)
## Step2. SSH setting
This is required to ssh to itself.
@@ -97,13 +98,14 @@ Create a keypair then append public key to authorized_keys
```bash
$ ssh-keygen -t rsa
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys
```
## Step3. Install requirements
Vuls requires the following packages.
- sqlite
- SQLite3
- git
- gcc
- go v1.6
@@ -129,7 +131,7 @@ Set the OS environment variable to current shell
$ source /etc/profile.d/goenv.sh
```
## Step4. Deploy go-cve-dictionary
## Step4. Deploy [go-cve-dictionary](https://github.com/kotakanbe/go-cve-dictionary)
go get
@@ -140,18 +142,17 @@ $ sudo chmod 700 /var/log/vuls
$ go get github.com/kotakanbe/go-cve-dictionary
```
Start go-cve-dictionary as server mode.
For the first time, go-cve-dictionary fetches vulnerability data from NVD.
Fetch vulnerability data from NVD.
It takes about 10 minutes (on AWS).
```bash
$ go-cve-dictionary server
... Fetching ...
$ for i in {2002..2016}; do go-cve-dictionary fetchnvd -years $i; done
... snip ...
$ ls -alh cve.sqlite3
-rw-r--r-- 1 ec2-user ec2-user 7.0M Mar 24 13:20 cve.sqlite3
```
Now we successfully collected vulnerbility data, then start as server mode again.
Now we successfully collected vulnerbility data, then start as server.
```bash
$ go-cve-dictionary server
[Mar 24 15:21:55] INFO Opening DB. datafile: /home/ec2-user/cve.sqlite3
@@ -189,12 +190,13 @@ keyPath = "/home/ec2-user/.ssh/id_rsa"
```
$ vuls prepare
```
see [Usage: Prepare](https://github.com/future-architect/vuls#usage-prepare)
## Step8. Start Scanning
```
$ vuls scan
INFO[0000] Begin scannig (config: /home/ec2-user/config.toml)
INFO[0000] Begin scanning (config: /home/ec2-user/config.toml)
... snip ...
@@ -234,18 +236,26 @@ $ vuls tui
![Vuls-TUI](img/hello-vuls-tui.png)
----
# Hello Vuls in a docker container
see https://github.com/future-architect/vuls/tree/master/docker
----
# Architecture
![Vuls-Architecture](img/vuls-architecture.png)
## go-cve-dictinary
- Fetch vulnerbility information from NVD, JVN(Japanese), then insert into SQLite.
## [go-cve-dictinary](https://github.com/kotakanbe/go-cve-dictionary)
- Fetch vulnerability information from NVD, JVN(Japanese), then insert into SQLite3.
## Vuls
- Scan vulnerabilities on the servers and create a list of the CVE ID
- For more detailed information of the detected CVE, send HTTP request to go-cve-dictinary
- To scan Docker containers, Vuls connect via ssh to the Docker host and then `docker exec` to the containers. So, no need to run sshd daemon on the containers.
- Fetch more detailed information of the detected CVE from go-cve-dictionary
- Insert scan result into SQLite3
- Send a report by Slack, Email
- System operator can view the latest report by terminal
@@ -280,7 +290,7 @@ web/app server in the same configuration under the load balancer
# Usage: Automatic Server Discovery
Discovery subcommand discovers active servers specifed in CIDR range, then print the template of config file(TOML format) to terminal.
Discovery subcommand discovers active servers specified in CIDR range, then print the template of config file(TOML format) to terminal.
```
$ vuls discover -help
@@ -288,7 +298,7 @@ discover:
discover 192.168.0.0/24
```
## Exapmle
## Example
```
$ vuls discover 172.31.4.0/24
@@ -297,7 +307,7 @@ $ vuls discover 172.31.4.0/24
[slack]
hookURL = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
channel = "#channel-name"
#channel = "#{servername}"
#channel = "${servername}"
iconEmoji = ":ghost:"
authUser = "username"
notifyUsers = ["@username"]
@@ -315,9 +325,7 @@ subjectPrefix = "[vuls]"
[default]
#port = "22"
#user = "username"
#password = "password"
#keyPath = "/home/username/.ssh/id_rsa"
#keyPassword = "password"
[servers]
@@ -325,12 +333,11 @@ subjectPrefix = "[vuls]"
host = "172.31.4.82"
#port = "22"
#user = "root"
#password = "password"
#keyPath = "/home/username/.ssh/id_rsa"
#keyPassword = "password"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
#containers = ["${running}"]
```
You can customize your configuration using this template.
@@ -344,7 +351,7 @@ You can customize your configuration using this template.
[slack]
hookURL = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
channel = "#channel-name"
#channel = "#{servername}"
#channel = "${servername}"
iconEmoji = ":ghost:"
authUser = "username"
notifyUsers = ["@username"]
@@ -352,12 +359,12 @@ You can customize your configuration using this template.
- hookURL : Incomming webhook's URL
- channel : channel name.
If you set #{servername} to channel, the report will be sent to #servername channel.
If you set ${servername} to channel, the report will be sent to each channel.
In the following example, the report will be sent to the #server1 and #server2.
Be sure to create these channels before scanning.
```
[slack]
channel = "#{servername}"
channel = "${servername}"
...snip...
[servers]
@@ -390,16 +397,18 @@ You can customize your configuration using this template.
subjectPrefix = "[vuls]"
```
- Defualt section
- Default section
```
[default]
#port = "22"
#user = "username"
#password = "password"
#keyPath = "/home/username/.ssh/id_rsa"
#keyPassword = "password"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
#containers = ["${running}"]
```
Items of the defualt section will be used if not specified.
Items of the default section will be used if not specified.
- servers section
```
@@ -409,12 +418,11 @@ You can customize your configuration using this template.
host = "172.31.4.82"
#port = "22"
#user = "root"
#password = "password"
#keyPath = "/home/username/.ssh/id_rsa"
#keyPassword = "password"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
#containers = ["${running}"]
```
You can overwrite the default value specified in default section.
Vuls supports multiple SSH authentication methods.
@@ -431,7 +439,7 @@ Prepare subcommand installs required packages on each server.
| Distribution| Release | Requirements |
|:------------|-------------------:|:-------------|
| Ubuntu | 12, 14, 16| - |
| Debian | 7, 8| apptitude |
| Debian | 7, 8| aptitude |
| CentOS | 5| yum-plugin-security, yum-changelog |
| CentOS | 6, 7| yum-plugin-security, yum-plugin-changelog |
| Amazon | All | - |
@@ -440,15 +448,21 @@ Prepare subcommand installs required packages on each server.
```
$ vuls prepare -help
prepare:
prepare [-config=/path/to/config.toml] [-debug]
prepare
[-config=/path/to/config.toml] [-debug]
[-ask-sudo-password]
[-ask-key-password]
-ask-key-password
Ask ssh privatekey password before scanning
-ask-sudo-password
Ask sudo password of target servers before scanning
-config string
/path/to/toml (default "$PWD/config.toml")
-debug
debug mode
-use-unattended-upgrades
[Depricated] For Ubuntu, install unattended-upgrades
[Deprecated] For Ubuntu, install unattended-upgrades
```
----
@@ -456,6 +470,7 @@ prepare:
# Usage: Scan
```
$ vuls scan -help
scan:
scan
@@ -464,11 +479,18 @@ scan:
[-dbpath=/path/to/vuls.sqlite3]
[-cve-dictionary-url=http://127.0.0.1:1323]
[-cvss-over=7]
[-ignore-unscored-cves]
[-report-slack]
[-report-mail]
[-http-proxy=http://192.168.0.1:8080]
[-ask-sudo-password]
[-ask-key-password]
[-debug]
[-debug-sql]
-ask-key-password
Ask ssh privatekey password before scanning
-ask-sudo-password
Ask sudo password of target servers before scanning
-config string
/path/to/toml (default "$PWD/config.toml")
-cve-dictionary-url string
@@ -483,6 +505,8 @@ scan:
SQL debug mode
-http-proxy string
http://proxy-url:port (default: empty)
-ignore-unscored-cves
Don't report the unscored CVEs
-lang string
[en|ja] (default "en")
-report-mail
@@ -490,12 +514,27 @@ scan:
-report-slack
Slack report
-use-unattended-upgrades
[Depricated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)
[Deprecated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)
-use-yum-plugin-security
[Depricated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)
[Deprecated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)
```
## ask-key-password option
| SSH key password | -ask-key-password | |
|:-----------------|:-------------------|:----|
| empty password | - | |
| with password | required | or use ssh-agent |
## ask-sudo-password option
| sudo password on target servers | -ask-sudo-password | |
|:-----------------|:-------|:------|
| NOPASSWORD | - | defined as NOPASSWORD in /etc/sudoers on target servers |
| with password | required | . |
## example
Run go-cve-dictionary as server mode before scanning.
@@ -505,9 +544,10 @@ $ go-cve-dictionary server
### Scan all servers defined in config file
```
$ vuls scan --report-slack --report-mail --cvss-over=7
$ vuls scan --report-slack --report-mail --cvss-over=7 -ask-sudo-password -ask-key-password
```
With this sample command, it will ..
- Ask sudo password and ssh key passsword before scanning
- Scan all servers defined in config file
- Send scan results to slack and email
- Only Report CVEs that CVSS score is over 7
@@ -518,7 +558,9 @@ With this sample command, it will ..
$ vuls scan server1 server2
```
With this sample command, it will ..
- Scan only 2 servers. (server1, server2)
- Use SSH Key-Based authentication with empty password (without -ask-key-password option)
- Sudo with no password (without -ask-sudo-password option)
- Scan only 2 servers (server1, server2)
- Print scan result to terminal
----
@@ -531,6 +573,9 @@ It is possible to detect vulnerabilities something you compiled by yourself, the
- [NVD: Search Common Platform Enumerations (CPE)](https://web.nvd.nist.gov/view/cpe/search)
**Check CPE Naming Format: 2.2**
- [go-cpe-dictionary](https://github.com/kotakanbe/go-cpe-dictionary) is a good choice for geeks.
You can search a CPE name by the application name incremenally.
- Configuration
To detect the vulnerbility of Ruby on Rails v4.2.1, cpeNames needs to be set in the servers section.
```
@@ -544,6 +589,96 @@ To detect the vulnerbility of Ruby on Rails v4.2.1, cpeNames needs to be set in
"cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
]
```
# Usage: Scan Docker containers
It is common that keep Docker containers runnning without SSHd daemon.
see [Docker Blog:Why you don't need to run SSHd in your Docker containers](https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/)
Vuls scans Docker containers via `docker exec` instead of SSH.
For more details, see [Architecture section](https://github.com/future-architect/vuls#architecture)
- To scan all of running containers
"${running}" needs to be set in the containers item.
```
[servers]
[servers.172-31-4-82]
host = "172.31.4.82"
user = "ec2-user"
keyPath = "/home/username/.ssh/id_rsa"
containers = ["${running}"]
```
- To scan specific containers
The container ID or container name needs to be set in the containers item.
In the following example, only "container_name_a" and "4aa37a8b63b9" will be scanned.
Be sure to check these containers are running state before scanning.
If specified containers are exited, vuls gives up scanning with printing error message.
```
[servers]
[servers.172-31-4-82]
host = "172.31.4.82"
user = "ec2-user"
keyPath = "/home/username/.ssh/id_rsa"
containers = ["container_name_a", "4aa37a8b63b9"]
```
# Usage: TUI
## Display the latest scan results
```
$ vuls tui -h
tui:
tui [-dbpath=/path/to/vuls.sqlite3]
-dbpath string
/path/to/sqlite3 (default "$PWD/vuls.sqlite3")
-debug-sql
debug SQL
```
Key binding is bellow.
| key | |
|:-----------------|:-------|:------|
| TAB | move cursor among the panes |
| Arrow up/down | move cursor to up/down |
| Ctrl+j, Ctrl+k | move cursor to up/donw |
| Ctrl+u, Ctrl+d | page up/donw |
For details, see https://github.com/future-architect/vuls/blob/master/report/tui.go
## Display the previous scan results
- Display the list of scan results.
```
$ ./vuls history
2 2016-05-24 19:49 scanned 1 servers: amazon2
1 2016-05-24 19:48 scanned 2 servers: amazon1, romantic_goldberg
```
- Display the result of scanID 1
```
$ ./vuls tui 1
```
- Display the result of scanID 2
```
$ ./vuls tui 2
```
# Display the previous scan results using peco
```
$ ./vuls history | peco | ./vuls tui
```
[![asciicast](https://asciinema.org/a/emi7y7docxr60bq080z10t7v8.png)](https://asciinema.org/a/emi7y7docxr60bq080z10t7v8)
# Usage: Update NVD Data.
@@ -582,15 +717,24 @@ $ go-cve-dictionary fetchnvd -last2y
# Misc
- Unable to go get vuls
Update git to the latest version. Old version of git can't get some repositories.
see https://groups.google.com/forum/#!topic/mgo-users/rO1-gUDFo_g
- HTTP Proxy Support
If your system is behind HTTP proxy, you have to specify --http-proxy option.
- How to Daemonize go-cve-dictionary
Use Systemd, Upstart or supervisord, daemontools...
- How to update vulnerbility data automatically.
- How to Enable Automatic-Update of Vunerability Data.
Use job scheduler like Cron (with -last2y option).
- How to Enable Automatic-Scan.
Use job scheduler like Cron.
Set NOPASSWORD option in /etc/sudoers on target servers.
Use SSH Key-Based Authentication with empty password or ssh-agent.
- How to cross compile
```bash
$ cd /path/to/your/local-git-reporsitory/vuls
@@ -603,8 +747,21 @@ Log wrote to under /var/log/vuls/
- Debug
Run with --debug, --sql-debug option.
- Ajusting Open File Limit
[Riak docs](http://docs.basho.com/riak/latest/ops/tuning/open-files-limit/) is awesome.
- Does Vuls accept ssh connections with fish-shell or old zsh as the login shell?
No, Vuls needs a user on the server for bash login. see also [#8](/../../issues/8)
- Windows
Use Microsoft Baseline Secuirty Analyzer. [MBSA](https://technet.microsoft.com/en-us/security/cc184924.aspx)
Use Microsoft Baseline Security Analyzer. [MBSA](https://technet.microsoft.com/en-us/security/cc184924.aspx)
----
# Related Projects
- [k1LoW/ssh_config_to_vuls_config](https://github.com/k1LoW/ssh_config_to_vuls_config)
ssh_config to vuls config TOML format
----

21
commands/cmdutil.go Normal file
View File

@@ -0,0 +1,21 @@
package commands
import (
"fmt"
"github.com/howeyc/gopass"
)
func getPasswd(prompt string) (string, error) {
for {
fmt.Print(prompt)
pass, err := gopass.GetPasswdMasked()
if err != nil {
return "", fmt.Errorf("Failed to read password")
}
if 0 < len(pass) {
return string(pass[:]), nil
}
}
}

View File

@@ -39,7 +39,7 @@ type DiscoverCmd struct {
func (*DiscoverCmd) Name() string { return "discover" }
// Synopsis return synopsis
func (*DiscoverCmd) Synopsis() string { return "Host discovery in the CIDR." }
func (*DiscoverCmd) Synopsis() string { return "Host discovery in the CIDR" }
// Usage return usage
func (*DiscoverCmd) Usage() string {
@@ -77,7 +77,7 @@ func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface
}
if len(hosts) < 1 {
logrus.Errorf("Active hosts not found in %s.", cidr)
logrus.Errorf("Active hosts not found in %s", cidr)
return subcommands.ExitSuccess
} else if err := printConfigToml(hosts); err != nil {
logrus.Errorf("Failed to parse template. err: %s", err)
@@ -93,7 +93,7 @@ func printConfigToml(ips []string) (err error) {
[slack]
hookURL = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
channel = "#channel-name"
#channel = "#{servername}"
#channel = "${servername}"
iconEmoji = ":ghost:"
authUser = "username"
notifyUsers = ["@username"]
@@ -111,9 +111,11 @@ subjectPrefix = "[vuls]"
[default]
#port = "22"
#user = "username"
#password = "password"
#keyPath = "/home/username/.ssh/id_rsa"
#keyPassword = "password"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
#containers = ["${running}"]
[servers]
{{- $names:= .Names}}
@@ -122,12 +124,11 @@ subjectPrefix = "[vuls]"
host = "{{$ip}}"
#port = "22"
#user = "root"
#password = "password"
#keyPath = "/home/username/.ssh/id_rsa"
#keyPassword = "password"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
#containers = ["${running}"]
{{end}}
`

108
commands/history.go Normal file
View File

@@ -0,0 +1,108 @@
/* 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"
"fmt"
"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
}
// Name return subcommand name
func (*HistoryCmd) Name() string { return "history" }
// Synopsis return synopsis
func (*HistoryCmd) Synopsis() string {
return `List history of scanning.`
}
// Usage return usage
func (*HistoryCmd) Usage() string {
return `history:
history
[-dbpath=/path/to/vuls.sqlite3]
`
}
// SetFlags set flag
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")
}
// Execute execute
func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
c.Conf.DebugSQL = p.debugSQL
c.Conf.DBPath = p.dbpath
// _, err := scanHistories()
histories, err := scanHistories()
if err != nil {
logrus.Error("Failed to select scan histories: ", err)
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)
}
}
fmt.Printf("%-3d %s scanned %d servers: %s\n",
history.ID,
history.ScannedAt.Format(timeLayout),
len(history.ScanResults),
strings.Join(names, ", "),
)
}
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

@@ -20,6 +20,7 @@ package commands
import (
"flag"
"os"
"path/filepath"
"github.com/Sirupsen/logrus"
c "github.com/future-architect/vuls/config"
@@ -34,6 +35,9 @@ type PrepareCmd struct {
debug bool
configPath string
askSudoPassword bool
askKeyPassword bool
useUnattendedUpgrades bool
}
@@ -55,7 +59,11 @@ func (*PrepareCmd) Synopsis() string {
// Usage return usage
func (*PrepareCmd) Usage() string {
return `prepare:
prepare [-config=/path/to/config.toml] [-debug]
prepare
[-config=/path/to/config.toml]
[-ask-sudo-password]
[-ask-key-password]
[-debug]
`
}
@@ -65,27 +73,59 @@ func (p *PrepareCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&p.debug, "debug", false, "debug mode")
defaultConfPath := os.Getenv("PWD") + "/config.toml"
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,
"Ask sudo password of target servers before scanning",
)
f.BoolVar(
&p.useUnattendedUpgrades,
"use-unattended-upgrades",
false,
"[Depricated] For Ubuntu, install unattended-upgrades",
"[Deprecated] For Ubuntu, install unattended-upgrades",
)
}
// Execute execute
func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
logrus.Infof("Begin Preparing (config: %s)", p.configPath)
var keyPass, sudoPass 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 {
prompt := "sudo password: "
if sudoPass, err = getPasswd(prompt); err != nil {
logrus.Error(err)
return subcommands.ExitFailure
}
}
err := c.Load(p.configPath)
err = c.Load(p.configPath, keyPass, sudoPass)
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
@@ -121,7 +161,7 @@ func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{
logger.Info("Installing...")
if errs := scan.Prepare(); 0 < len(errs) {
for _, e := range errs {
logger.Errorf("Failed: %s.", e)
logger.Errorf("Failed: %s", e)
}
return subcommands.ExitFailure
}

View File

@@ -20,6 +20,7 @@ package commands
import (
"flag"
"os"
"path/filepath"
"github.com/Sirupsen/logrus"
c "github.com/future-architect/vuls/config"
@@ -42,22 +43,28 @@ type ScanCmd struct {
dbpath string
cveDictionaryURL string
cvssScoreOver float64
httpProxy string
useYumPluginSecurity bool
useUnattendedUpgrades bool
cvssScoreOver float64
ignoreUnscoredCves bool
httpProxy string
// reporting
reportSlack bool
reportMail bool
askSudoPassword bool
askKeyPassword bool
useYumPluginSecurity bool
useUnattendedUpgrades bool
}
// Name return subcommand name
func (*ScanCmd) Name() string { return "scan" }
// Synopsis return synopsis
func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities." }
func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities" }
// Usage return usage
func (*ScanCmd) Usage() string {
@@ -68,9 +75,12 @@ func (*ScanCmd) Usage() string {
[-dbpath=/path/to/vuls.sqlite3]
[-cve-dictionary-url=http://127.0.0.1:1323]
[-cvss-over=7]
[-ignore-unscored-cves]
[-report-slack]
[-report-mail]
[-http-proxy=http://192.168.0.1:8080]
[-ask-sudo-password]
[-ask-key-password]
[-debug]
[-debug-sql]
`
@@ -82,10 +92,12 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&p.debug, "debug", false, "debug mode")
f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
defaultConfPath := os.Getenv("PWD") + "/config.toml"
wd, _ := os.Getwd()
defaultConfPath := filepath.Join(wd, "config.toml")
f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
defaultDBPath := os.Getenv("PWD") + "/vuls.sqlite3"
defaultDBPath := filepath.Join(wd, "vuls.sqlite3")
f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3")
defaultURL := "http://127.0.0.1:1323"
@@ -101,6 +113,12 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
0,
"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
f.BoolVar(
&p.ignoreUnscoredCves,
"ignore-unscored-cves",
false,
"Don't report the unscored CVEs")
f.StringVar(
&p.httpProxy,
"http-proxy",
@@ -111,32 +129,62 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&p.reportSlack, "report-slack", false, "Slack report")
f.BoolVar(&p.reportMail, "report-mail", false, "Email report")
f.BoolVar(
&p.askKeyPassword,
"ask-key-password",
false,
"Ask ssh privatekey password before scanning",
)
f.BoolVar(
&p.askSudoPassword,
"ask-sudo-password",
false,
"Ask sudo password of target servers before scanning",
)
f.BoolVar(
&p.useYumPluginSecurity,
"use-yum-plugin-security",
false,
"[Depricated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)",
"[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,
"[Depricated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)",
"[Deprecated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)",
)
}
// Execute execute
func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
var keyPass, sudoPass 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 {
prompt := "sudo password: "
if sudoPass, err = getPasswd(prompt); err != nil {
logrus.Error(err)
return subcommands.ExitFailure
}
}
logrus.Infof("Begin scannig (config: %s)", p.configPath)
err := c.Load(p.configPath)
err = c.Load(p.configPath, keyPass, sudoPass)
if err != nil {
logrus.Errorf("Error loading %s, %s", p.configPath, err)
return subcommands.ExitUsageError
}
logrus.Infof("Start scanning (config: %s)", p.configPath)
target := make(map[string]c.ServerInfo)
for _, arg := range f.Args() {
found := false
@@ -177,6 +225,8 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
c.Conf.DBPath = p.dbpath
c.Conf.CveDictionaryURL = p.cveDictionaryURL
c.Conf.CvssScoreOver = p.cvssScoreOver
c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
c.Conf.HTTPProxy = p.httpProxy
c.Conf.UseYumPluginSecurity = p.useYumPluginSecurity
c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
@@ -192,17 +242,17 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
return subcommands.ExitFailure
}
Log.Info("Detecting OS... ")
Log.Info("Detecting Server OS... ")
err = scan.InitServers(Log)
if err != nil {
Log.Errorf("Failed to init servers. err: %s", err)
Log.Errorf("Failed to init servers. Check the configuration. err: %s", err)
return subcommands.ExitFailure
}
Log.Info("Scanning vulnerabilities... ")
if errs := scan.Scan(); 0 < len(errs) {
for _, e := range errs {
Log.Errorf("Failed to scan. err: %s.", e)
Log.Errorf("Failed to scan. err: %s", e)
}
return subcommands.ExitFailure
}

View File

@@ -20,11 +20,16 @@ package commands
import (
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/report"
"github.com/google/subcommands"
"github.com/labstack/gommon/log"
"golang.org/x/net/context"
)
@@ -39,7 +44,7 @@ type TuiCmd struct {
func (*TuiCmd) Name() string { return "tui" }
// Synopsis return synopsis
func (*TuiCmd) Synopsis() string { return "Run Tui view to anayze vulnerabilites." }
func (*TuiCmd) Synopsis() string { return "Run Tui view to anayze vulnerabilites" }
// Usage return usage
func (*TuiCmd) Usage() string {
@@ -54,7 +59,9 @@ func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
// f.StringVar(&p.lang, "lang", "en", "[en|ja]")
f.BoolVar(&p.debugSQL, "debug-sql", false, "debug SQL")
defaultDBPath := os.Getenv("PWD") + "/vuls.sqlite3"
wd, _ := os.Getwd()
defaultDBPath := filepath.Join(wd, "vuls.sqlite3")
f.StringVar(&p.dbpath, "dbpath", defaultDBPath,
fmt.Sprintf("/path/to/sqlite3 (default: %s)", defaultDBPath))
}
@@ -64,5 +71,24 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
c.Conf.Lang = "en"
c.Conf.DebugSQL = p.debugSQL
c.Conf.DBPath = p.dbpath
return report.RunTui()
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 {
bytes, err := ioutil.ReadAll(os.Stdin)
if err != nil {
log.Errorf("Failed to read stdin: %s", err)
return subcommands.ExitFailure
}
fields := strings.Fields(string(bytes))
if 0 < len(fields) {
historyID = fields[0]
}
}
return report.RunTui(historyID)
}

View File

@@ -41,9 +41,11 @@ type Config struct {
CveDictionaryURL string `valid:"url"`
CvssScoreOver float64
HTTPProxy string `valid:"url"`
DBPath string
CvssScoreOver float64
IgnoreUnscoredCves bool
HTTPProxy string `valid:"url"`
DBPath string
// CpeNames []string
// SummaryMode bool
UseYumPluginSecurity bool
@@ -188,8 +190,6 @@ func (c *SlackConf) Validate() (errs []error) {
errs = append(errs, err)
}
// TODO check if slack configration is valid
return
}
@@ -202,12 +202,33 @@ type ServerInfo struct {
Port string
KeyPath string
KeyPassword string
SudoOpt SudoOption
CpeNames []string
// DebugLog Color
LogMsgAnsiColor string
// Container Names or IDs
Containers []string
// userd internal
LogMsgAnsiColor string // DebugLog Color
SudoOpt SudoOption
Container Container
}
// IsContainer returns whether this ServerInfo is about container
func (s ServerInfo) IsContainer() bool {
return 0 < len(s.Container.ContainerID)
}
// SetContainer set container
func (s *ServerInfo) SetContainer(d Container) {
s.Container = d
}
// Container has Container information.
type Container struct {
ContainerID string
Name string
Type string
}
// SudoOption is flag of sudo option.

View File

@@ -24,6 +24,6 @@ type JSONLoader struct {
}
// Load load the configuraiton JSON file specified by path arg.
func (c JSONLoader) Load(path string) (err error) {
func (c JSONLoader) Load(path, sudoPass, keyPass string) (err error) {
return fmt.Errorf("Not implement yet")
}

View File

@@ -18,16 +18,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package config
// Load loads configuration
func Load(path string) error {
//TODO if path's suffix .toml
func Load(path, keyPass, sudoPass string) error {
var loader Loader
loader = TOMLLoader{}
return loader.Load(path)
return loader.Load(path, keyPass, sudoPass)
}
// Loader is interface of concrete loader
type Loader interface {
Load(string) error
Load(string, string, string) error
}

View File

@@ -31,10 +31,10 @@ type TOMLLoader struct {
}
// Load load the configuraiton TOML file specified by path arg.
func (c TOMLLoader) Load(pathToToml string) (err error) {
func (c TOMLLoader) Load(pathToToml, keyPass, sudoPass string) (err error) {
var conf Config
if _, err := toml.DecodeFile(pathToToml, &conf); err != nil {
log.Error("Load config failed.", err)
log.Error("Load config failed", err)
return err
}
@@ -45,14 +45,28 @@ func (c TOMLLoader) Load(pathToToml string) (err error) {
Conf.Default = d
servers := make(map[string]ServerInfo)
if keyPass != "" {
d.KeyPassword = keyPass
}
if sudoPass != "" {
d.Password = sudoPass
}
i := 0
for name, v := range conf.Servers {
if 0 < len(v.KeyPassword) || 0 < len(v.Password) {
log.Warn("[Deprecated] password and keypassword in config file are unsecure. Remove them immediately for a security reason. They will be removed in a future release.")
}
s := ServerInfo{ServerName: name}
s.User = v.User
if s.User == "" {
s.User = d.User
}
// s.Password = sudoPass
s.Password = v.Password
if s.Password == "" {
s.Password = d.Password
@@ -76,6 +90,7 @@ func (c TOMLLoader) Load(pathToToml string) (err error) {
}
}
// s.KeyPassword = keyPass
s.KeyPassword = v.KeyPassword
if s.KeyPassword == "" {
s.KeyPassword = d.KeyPassword
@@ -86,12 +101,17 @@ func (c TOMLLoader) Load(pathToToml string) (err error) {
s.CpeNames = d.CpeNames
}
s.LogMsgAnsiColor = Colors[i%len(conf.Servers)]
s.Containers = v.Containers
if len(s.Containers) == 0 {
s.Containers = d.Containers
}
s.LogMsgAnsiColor = Colors[i%len(Colors)]
i++
servers[name] = s
}
log.Debug("Config loaded.")
log.Debug("Config loaded")
log.Debugf("%s", pp.Sprintf("%v", servers))
Conf.Servers = servers
return

View File

@@ -127,19 +127,19 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDet
}
func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
var body string
var errs []error
var resp *http.Response
f := func() (err error) {
resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
if len(errs) > 0 || resp.StatusCode != 200 {
errChan <- fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
// resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
resp, body, errs = gorequest.New().Get(url).End()
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v", errs, url, resp)
}
return nil
}
notify := func(err error, t time.Duration) {
log.Warnf("Failed to get. retrying in %s seconds. err: %s", t, err)
log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", t, err)
}
err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
if err != nil {
@@ -219,12 +219,12 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
}
resp, body, errs = req.End()
if len(errs) > 0 || resp.StatusCode != 200 {
return fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
return fmt.Errorf("HTTP POST errors: %v, code: %d, url: %s", errs, resp.StatusCode, url)
}
return nil
}
notify := func(err error, t time.Duration) {
log.Warnf("Failed to get. retrying in %s seconds. err: %s", t, err)
log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %s", t, err)
}
err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
if err != nil {

View File

@@ -20,6 +20,7 @@ package db
import (
"fmt"
"sort"
"strconv"
"time"
"github.com/future-architect/vuls/config"
@@ -49,6 +50,7 @@ func MigrateDB() error {
&m.ScanHistory{},
&m.ScanResult{},
// &m.NWLink{},
&m.Container{},
&m.CveInfo{},
&m.CpeName{},
&m.PackageInfo{},
@@ -67,6 +69,10 @@ func MigrateDB() error {
// 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)
@@ -85,11 +91,11 @@ func MigrateDB() error {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&cve.CveDetail{}).
AddIndex("idx_cve_detail_cve_info_id", "cve_info_id").Error; err != nil {
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_detail_cveid", "cve_id").Error; err != nil {
AddIndex("idx_cve_details_cveid", "cve_id").Error; err != nil {
return fmt.Errorf(errMsg, err)
}
if err := db.Model(&cve.Nvd{}).
@@ -141,6 +147,10 @@ func Insert(results []m.ScanResult) error {
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
}
@@ -220,16 +230,29 @@ func resetGormIDs(infos []m.CveInfo) []m.CveInfo {
return infos
}
// SelectLatestScanHistory select latest scan history from DB
func SelectLatestScanHistory() (m.ScanHistory, error) {
scanHistory := m.ScanHistory{}
db.Order("scanned_at desc").First(&scanHistory)
// SelectScanHistory select scan history from DB
func SelectScanHistory(historyID string) (m.ScanHistory, error) {
var err error
if scanHistory.ID == 0 {
return m.ScanHistory{}, fmt.Errorf("No scanHistory records.")
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)
}
results := []m.ScanResult{}
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
@@ -238,10 +261,16 @@ func SelectLatestScanHistory() (m.ScanHistory, error) {
// 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
}
@@ -270,3 +299,26 @@ func selectCveInfos(result *m.ScanResult, fieldName string) []m.CveInfo {
}
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
}

14
docker/Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM golang:1.6
RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y git openssh-client gcc nmap
WORKDIR /app
RUN go get github.com/kotakanbe/go-cve-dictionary
RUN go get github.com/future-architect/vuls
COPY fetch.sh .
RUN /bin/bash /app/fetch.sh
COPY config.toml .
COPY run.sh .
ENTRYPOINT ["/bin/bash", "/app/run.sh"]
COPY id_rsa .
COPY id_rsa.pub .

7
docker/README.md Normal file
View File

@@ -0,0 +1,7 @@
# Before building the docker
Since it's not on docker hub because blablabla, you have to :
* Edit your [config.toml](https://github.com/future-architect/vuls#step6-config) to match your infrastructure
* generate a keypair dedicated to this docker : ```ssh-keygen -t rsa -b 4096 -C "your_email@example.com"```
* it's **highly** recommanded to use a restrained `authorized_keys` files with this key to be sure that it will be only usable from a single IP (after all it's a root executed software) : ```from="1.2.3.4,1.2.3.5" ssh-rsa [...] your_email@example.com```
* Deploy your ssh key on the targetted machines

1
docker/config.toml Normal file
View File

@@ -0,0 +1 @@

2
docker/fetch.sh Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
for i in {2002..2016}; do go-cve-dictionary fetchnvd -years $i ; done

1
docker/id_rsa Normal file
View File

@@ -0,0 +1 @@

1
docker/id_rsa.pub Normal file
View File

@@ -0,0 +1 @@

28
docker/run.sh Normal file
View File

@@ -0,0 +1,28 @@
#!/bin/bash
tries=0
function isopen {
tries=$1
nmap -Pn -T4 -p 1323 127.0.0.1|grep -iq open
if [ $? -ne 0 ]; then
if [ $tries -lt 5 ]; then
let tries++
startserver $tries
else
return 1
fi
else
return 0
fi
}
function startserver {
tries=$1
go-cve-dictionary server &
sleep 2
isopen $tries
}
startserver $tries
if [ $? -ne 1 ]; then
vuls scan -config /app/config.toml -report-slack
fi

View File

@@ -17,7 +17,7 @@
<node id="n0">
<data key="d6">
<y:GenericNode configuration="com.yworks.flowchart.cloud">
<y:Geometry height="50.0" width="80.0" x="269.4041252136233" y="446.4841308593749"/>
<y:Geometry height="50.0" width="80.0" x="1046.0141170024865" y="70.63541666666657"/>
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="38.0" y="23.0">
@@ -37,14 +37,14 @@
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="285.54366048177087" width="173.0" x="66.40412521362327" y="347.9090576171874"/>
<y:Geometry height="289.5891316731771" width="137.0" x="1274.5282340049735" y="-47.136413574218864"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="173.0" x="0.0" y="0.0">Vulnerbility Database</y:NodeLabel>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="159.0390625" x="-11.01953125" y="0.0">Vulnerbility Database</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="54" bottomF="53.63557942708337" left="30" leftF="29.90412521362373" right="28" rightF="28.09587478637627" top="27" topF="27.242065429687557"/>
<y:BorderInsets bottom="29" bottomF="28.96858723958337" left="9" leftF="9.35411262512207" right="13" rightF="12.64588737487793" top="27" topF="27.242065429687557"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
@@ -63,7 +63,7 @@
<node id="n1::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="70.0" width="85.0" x="111.308250427247" y="494.8171386718749"/>
<y:Geometry height="70.0" width="85.0" x="1298.8823466300955" y="128.4841308593749"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="63.279296875" x="10.8603515625" y="18.8671875">JVN
@@ -81,7 +81,7 @@
<node id="n1::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="70.0" width="85.0" x="111.308250427247" y="411.81713867187494"/>
<y:Geometry height="70.0" width="85.0" x="1298.8823466300955" y="16.771667480468693"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="29.69921875" x="27.650390625" y="25.93359375">NVD<y:LabelModel>
@@ -103,14 +103,14 @@
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="285.54366048177087" width="137.0" x="1209.345874786376" y="347.9090576171874"/>
<y:Geometry height="357.5982869466146" width="137.0" x="1274.5282340049735" y="285.984130859375"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="137.0" x="0.0" y="0.0">Linux Support</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="4" bottomF="4.059529622395871" left="5" leftF="4.85411262512207" right="8" rightF="8.14588737487793" top="0" topF="0.0"/>
<y:BorderInsets bottom="15" bottomF="14.801595052083258" left="5" leftF="4.85411262512207" right="8" rightF="8.14588737487793" top="11" topF="10.747945149739394"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
@@ -129,7 +129,7 @@
<node id="n2::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="71.484130859375" width="94.0" x="1229.1999874114981" y="463.7420654296874"/>
<y:Geometry height="71.484130859375" width="94.0" x="1294.3823466300955" y="443.21842447916663"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="64.158203125" x="14.9208984375" y="19.6092529296875">apptitude
@@ -147,7 +147,7 @@ changelog<y:LabelModel>
<node id="n2::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="71.484130859375" width="94.0" x="1229.1999874114981" y="384.5750732421874"/>
<y:Geometry height="71.484130859375" width="94.0" x="1294.3823466300955" y="333.3980916341144"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="64.158203125" x="14.9208984375" y="19.6092529296875">yum
@@ -165,7 +165,7 @@ changelog<y:LabelModel>
<node id="n2::n2">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="71.484130859375" width="94.0" x="1229.1999874114981" y="542.9090576171874"/>
<y:Geometry height="71.484130859375" width="94.0" x="1294.3823466300955" y="542.2966918945314"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="92.828125" x="0.5859375" y="19.6092529296875">RHSA (RedHat)
@@ -185,10 +185,10 @@ ALAS (Amazon)<y:LabelModel>
<node id="n3">
<data key="d6">
<y:GenericNode configuration="com.yworks.flowchart.cloud">
<y:Geometry height="50.0" width="80.0" x="1109.272931098937" y="399.1136678059895"/>
<y:Geometry height="50.0" width="56.554100036621094" x="1180.4405970573423" y="439.7832743326823"/>
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="38.0" y="23.0">
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="26.277050018310547" y="23.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
@@ -199,48 +199,20 @@ ALAS (Amazon)<y:LabelModel>
</y:GenericNode>
</data>
</node>
<node id="n4">
<data key="d6">
<y:SVGNode>
<y:Geometry height="121.666015625" width="137.0" x="942.2729310989371" y="363.2806599934895"/>
<y:Fill color="#CCCCFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="45.63671875" x="45.681640625" y="-30.475463867187386">servers<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.5" labelRatioY="0.5" nodeRatioX="0.16655736770072993" nodeRatioY="-0.5" offsetX="0.0" offsetY="-12.342651367187386" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="66.5" y="58.8330078125">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:SVGNodeProperties usingVisualBounds="false"/>
<y:SVGModel svgBoundsPolicy="0">
<y:SVGContent refid="1"/>
</y:SVGModel>
</y:SVGNode>
</data>
</node>
<node id="n5" yfiles.foldertype="group">
<node id="n4" yfiles.foldertype="group">
<data key="d4"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="285.54366048177087" width="220.24999999999977" x="662.2499999999998" y="347.9090576171874"/>
<y:Geometry height="365.54366048177087" width="251.74999999999977" x="641.4999999999995" y="285.984130859375"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="220.24999999999977" x="0.0" y="0.0">Vuls</y:NodeLabel>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="251.74999999999977" x="0.0" y="0.0">Vuls</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="12" bottomF="11.710652669270871" left="10" leftF="9.999999999999773" right="0" rightF="0.0" top="31" topF="30.57621256510413"/>
<y:BorderInsets bottom="14" bottomF="13.710652669270871" left="25" leftF="25.499999999999773" right="0" rightF="0.0" top="31" topF="30.57621256510413"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
@@ -255,11 +227,11 @@ ALAS (Amazon)<y:LabelModel>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n5:">
<node id="n5::n0">
<graph edgedefault="directed" id="n4:">
<node id="n4::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="50.0" width="80.0" x="787.4999999999995" y="495.1512858072915"/>
<y:Geometry height="50.0" width="80.0" x="798.2499999999993" y="566.4311319986981"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="42.595703125" x="18.7021484375" y="15.93359375">Report<y:LabelModel>
@@ -273,10 +245,10 @@ ALAS (Amazon)<y:LabelModel>
</y:ShapeNode>
</data>
</node>
<node id="n5::n1">
<node id="n4::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="50.0" width="80.0" x="787.4999999999995" y="556.7420654296874"/>
<y:Geometry height="50.0" width="80.0" x="690.2499999999993" y="572.817138671875"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="54.40234375" x="12.798828125" y="15.93359375">TUI View<y:LabelModel>
@@ -290,11 +262,11 @@ ALAS (Amazon)<y:LabelModel>
</y:ShapeNode>
</data>
</node>
<node id="n5::n2">
<node id="n4::n2">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="50.0" width="180.25" x="687.2499999999995" y="415.1512858072915"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:Geometry height="50.0" width="180.25" x="681.9999999999993" y="353.22635904947913"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="30.68359375" x="74.783203125" y="15.93359375">Scan<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
@@ -309,10 +281,10 @@ ALAS (Amazon)<y:LabelModel>
</node>
</graph>
</node>
<node id="n6">
<node id="n5">
<data key="d6">
<y:SVGNode>
<y:Geometry height="64.96826171875" width="56.554100036621094" x="1083.5729436874383" y="568.4844563802083"/>
<y:Geometry height="64.96826171875" width="56.554100036621094" x="743.8479499816888" y="734.5277913411459"/>
<y:Fill color="#CCCCFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="100.890625" x="-22.168262481689453" y="68.96826171875">System Operator<y:LabelModel>
@@ -324,15 +296,15 @@ ALAS (Amazon)<y:LabelModel>
</y:NodeLabel>
<y:SVGNodeProperties usingVisualBounds="true"/>
<y:SVGModel svgBoundsPolicy="0">
<y:SVGContent refid="2"/>
<y:SVGContent refid="1"/>
</y:SVGModel>
</y:SVGNode>
</data>
</node>
<node id="n7">
<node id="n6">
<data key="d6">
<y:GenericNode configuration="com.yworks.flowchart.dataBase">
<y:Geometry height="70.0" width="60.5" x="696.9999999999995" y="546.7420654296874"/>
<y:Geometry height="70.0" width="60.5" x="699.9999999999993" y="453.02174886067706"/>
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="28.25" y="33.0">
@@ -353,24 +325,24 @@ ALAS (Amazon)<y:LabelModel>
</y:GenericNode>
</data>
</node>
<node id="n8">
<node id="n7">
<data key="d6">
<y:SVGNode>
<y:Geometry height="37.0" width="109.57881927490234" x="991.1335277557366" y="532.4466756184895"/>
<y:Geometry height="37.0" width="109.57881927490234" x="889.737640380859" y="748.5119222005209"/>
<y:Fill color="#CCCCFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="sandwich" modelPosition="s" textColor="#000000" visible="true" width="4.0" x="52.78940963745117" y="41.0"/>
<y:SVGNodeProperties usingVisualBounds="true"/>
<y:SVGModel svgBoundsPolicy="0">
<y:SVGContent refid="3"/>
<y:SVGContent refid="2"/>
</y:SVGModel>
</y:SVGNode>
</data>
</node>
<node id="n9">
<node id="n8">
<data key="d6">
<y:GenericNode configuration="com.yworks.bpmn.Artifact.withShadow">
<y:Geometry height="24.0" width="35.0" x="943.5205974578851" y="538.9466756184895"/>
<y:Geometry height="24.0" width="35.0" x="811.8264548778523" y="681.5277913411459"/>
<y:Fill color="#FFFFFFE6" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="15.5" y="28.0">
@@ -390,20 +362,20 @@ ALAS (Amazon)<y:LabelModel>
</y:GenericNode>
</data>
</node>
<node id="n10" yfiles.foldertype="group">
<node id="n9" yfiles.foldertype="group">
<data key="d4"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="285.54366048177087" width="233.0" x="379.4041252136233" y="347.9090576171874"/>
<y:Geometry height="293.63460286458337" width="251.74999999999977" x="645.7499999999998" y="-51.181884765625114"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="233.0" x="0.0" y="0.0">go-cve-dictionary</y:NodeLabel>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="251.74999999999977" x="0.0" y="0.0">go-cve-dictionary</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="2" leftF="1.5" right="5" rightF="5.0" top="62" topF="61.9090576171875"/>
<y:BorderInsets bottom="29" bottomF="28.96858723958337" left="12" leftF="12.249999999999773" right="0" rightF="0.0" top="49" topF="49.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
@@ -418,11 +390,11 @@ ALAS (Amazon)<y:LabelModel>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n10:">
<node id="n10::n0">
<graph edgedefault="directed" id="n9:">
<node id="n9::n0">
<data key="d6">
<y:GenericNode configuration="com.yworks.flowchart.dataBase">
<y:Geometry height="70.0" width="60.5" x="447.15412521362305" y="548.4527180989583"/>
<y:Geometry height="70.0" width="60.5" x="747.1249999999995" y="34.484130859374886"/>
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="28.25" y="33.0">
@@ -443,10 +415,10 @@ ALAS (Amazon)<y:LabelModel>
</y:GenericNode>
</data>
</node>
<node id="n10::n1">
<node id="n9::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="50.0" width="101.0" x="491.4041252136233" y="446.4841308593749"/>
<y:Geometry height="50.0" width="101.0" x="672.9999999999995" y="148.4841308593749"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="73.943359375" x="13.5283203125" y="15.93359375">HTTP server<y:LabelModel>
@@ -460,10 +432,10 @@ ALAS (Amazon)<y:LabelModel>
</y:ShapeNode>
</data>
</node>
<node id="n10::n2">
<node id="n9::n2">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="50.0" width="80.0" x="395.9041252136233" y="446.4841308593749"/>
<y:Geometry height="50.0" width="80.0" x="802.4999999999995" y="148.4841308593749"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="46.796875" x="16.6015625" y="15.93359375">Fetcher<y:LabelModel>
@@ -479,28 +451,151 @@ ALAS (Amazon)<y:LabelModel>
</node>
</graph>
</node>
<edge id="n10::e0" source="n10::n1" target="n10::n0">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="none"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
<node id="n10" yfiles.foldertype="group">
<data key="d4"/>
<data key="d5"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="201.49428304036473" width="192.109991788863" x="964.6223485469814" y="296.7320760091144"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="192.109991788863" x="0.0" y="0.0">Docker Containers</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="32" leftF="32.0" right="45" rightF="45.109991788863" top="0" topF="0.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 5</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
</edge>
<edge id="e0" source="n10::n2" target="n0">
<graph edgedefault="directed" id="n10:">
<node id="n10::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="50.0" width="85.0" x="1011.6223485469814" y="433.22635904947913"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="71.91015625" x="6.544921875" y="15.93359375">DockerHost<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n10::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="50.0" width="85.0" x="1011.6223485469814" y="333.3980916341144"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="60.748046875" x="12.1259765625" y="8.8671875">Docker
Container<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<node id="n11" yfiles.foldertype="group">
<data key="d4"/>
<data key="d5"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="131.666015625" width="192.109991788863" x="964.6223485469814" y="516.3727416992189"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="192.109991788863" x="0.0" y="0.0">Linux Servers</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="17" bottomF="16.607625325520758" left="32" leftF="32.0" right="24" rightF="24.109991788863" top="0" topF="0.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 6</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n11:">
<node id="n11::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="50.0" width="85.0" x="1011.6223485469814" y="553.0387573242189"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="39.865234375" x="22.5673828125" y="15.93359375">Server<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n11::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="50.0" width="85.0" x="1032.6223485469814" y="566.4311319986981"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="39.865234375" x="22.5673828125" y="15.93359375">Server<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<edge id="e0" source="n9::n2" target="n0">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="none"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="106.240234375" x="-141.12006313536395" y="46.068162980709076">Fetch
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="106.240234375" x="98.64874426818972" y="-10.207796256635163">Fetch
Vulnerability data<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="62.2009754807092" distanceToCenter="true" position="left" ratio="41.499957391955974" segment="-1"/>
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="51.8582779043468" distanceToCenter="true" position="right" ratio="0.7727738017619232" segment="-1"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
@@ -514,17 +609,7 @@ Vulnerability data<y:LabelModel>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e2" source="n5::n2" target="n10::n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="34.626953125" x="-64.74898719787609" y="-33.01884181664411">HTTP<y:LabelModel>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="34.626953125" x="8.29332590103104" y="20.93360392252606">HTTP<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
@@ -536,67 +621,17 @@ Vulnerability data<y:LabelModel>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e3" source="n5::n1" target="n7">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="none"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e4" source="n0" target="n1">
<edge id="e2" source="n4::n2" target="n9::n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e5" source="n5::n0" target="n6">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e6" source="n4" target="n3">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n5::e0" source="n5::n2" target="n5::n0">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e7" source="n5::n2" target="n4">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.744140625" x="44.03690814971833" y="17.093924778052497">SSH<y:LabelModel>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="34.626953125" x="-70.78547462732604" y="-104.38485166353485">HTTP<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="1.0" segment="-1"/>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="left" ratio="0.6418953379495804" segment="-1"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
@@ -604,7 +639,137 @@ Vulnerability data<y:LabelModel>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e8" source="n6" target="n5::n1">
<edge id="e3" source="n0" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="34.626953125" x="65.78936398029236" y="21.58855026779011">HTTP<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e4" source="n4::n0" target="n5">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="31.802734375" x="-23.436084747315135" y="96.06095129235564">send<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="left" ratio="1.0" segment="-1"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n4::e0" source="n4::n2" target="n4::n0">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="56.201171875" x="39.557346848955035" y="107.80743708610783">Generate<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="left" ratio="0.7612118931433132" segment="-1"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e5" source="n5" target="n4::n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="109.7265625" x="-125.3179906251471" y="-50.63345557215132">Detail Information<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="58.40488430810431" distanceToCenter="true" position="left" ratio="0.3174763616620919" segment="-1"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e6" source="n4::n2" target="n11::n0">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="87.9098606499872" sy="24.54915453361525" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="27.07421875" x="63.50980239529861" y="100.92919596059016">SSH<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.6332970715271986" segment="-1"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e7" source="n4::n2" target="n10::n0">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="27.07421875" x="47.789244589009286" y="-10.21369683159321">SSH<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="17.840986205821807" distanceToCenter="true" position="left" ratio="0.3567784326754698" segment="-1"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n10::e0" source="n10::n0" target="n10::n1">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="77.576171875" x="-68.78805184364364" y="-33.974731445312614">docker exec<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="left" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e8" source="n10::n1" target="n3">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
@@ -614,22 +779,120 @@ Vulnerability data<y:LabelModel>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n10::e1" source="n10::n2" target="n10::n0">
<edge id="e9" source="n11::n0" target="n3">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="none"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e9" source="n5::n2" target="n7">
<edge id="n9::e0" source="n9::n2" target="n9::n0">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="none"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="37.10546875" x="-0.014769665799690301" y="-35.98834449405365">Insert<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n9::e1" source="n9::n0" target="n9::n1">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="37.9375" x="-65.17704859198193" y="14.912883039360338">Select<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e10" source="n4::n2" target="n6">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="70.275390625" x="-75.4169220517914" y="5.2924810128525905">Insert
Scan Result<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e11" source="n6" target="n4::n1">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="37.9375" x="-48.96875000000057" y="19.334788004557254">Select<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e12" source="n4::n0" target="n7">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e13" source="n7" target="n5">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="38.875" x="-43.904876708984716" y="20.93361409505212">Notify<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="left" ratio="0.0" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
@@ -638,162 +901,6 @@ Vulnerability data<y:LabelModel>
<data key="d7">
<y:Resources>
<y:Resource id="1">&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" width="68px" height="60px" viewBox="-0.435 -0.869 68 60" enable-background="new -0.435 -0.869 68 60"
xml:space="preserve"&gt;
&lt;defs&gt;
&lt;/defs&gt;
&lt;path fill="#666666" d="M52.462,30.881c-0.021,0-0.037,0.01-0.059,0.012c-0.021-0.002-0.037-0.012-0.059-0.012h-18.5v-7.555
c0-0.414-0.335-0.75-0.75-0.75c-0.414,0-0.75,0.336-0.75,0.75v7.555h-18.5c-0.02,0-0.037,0.01-0.057,0.012
c-0.02-0.002-0.037-0.012-0.057-0.012c-0.414,0-0.75,0.336-0.75,0.75v3.834c0,0.414,0.336,0.75,0.75,0.75s0.75-0.336,0.75-0.75
v-3.084H51.71v3.084c0,0.414,0.336,0.75,0.75,0.75s0.75-0.336,0.75-0.75v-3.834C53.212,31.217,52.876,30.881,52.462,30.881z"/&gt;
&lt;linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="130.7236" y1="-184.1631" x2="130.7236" y2="-191.9565" gradientTransform="matrix(1 0 0 -1 -97.6001 -158.6377)"&gt;
&lt;stop offset="0" style="stop-color:#9CD7FF"/&gt;
&lt;stop offset="1" style="stop-color:#3C89C9"/&gt;
&lt;/linearGradient&gt;
&lt;path fill="url(#SVGID_1_)" d="M36.296,29.976c-0.832,0-1.513-0.681-1.513-1.513v-1.424c0-0.832-0.681-1.513-1.513-1.513h-0.214
c-0.832,0-1.513,0.681-1.513,1.513v1.424c0,0.832-0.681,1.513-1.513,1.513h-2.499c-0.832,0-1.513,0.681-1.513,1.513v0.317
c0,0.832,0.681,1.513,1.513,1.513h11.187c0.832,0,1.513-0.681,1.513-1.513v-0.317c0-0.833-0.681-1.513-1.513-1.513H36.296z"/&gt;
&lt;linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="605.8877" y1="2040.6665" x2="593.1709" y2="2040.6665" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)"&gt;
&lt;stop offset="0" style="stop-color:#4D4D4D"/&gt;
&lt;stop offset="1" style="stop-color:#999999"/&gt;
&lt;/linearGradient&gt;
&lt;path fill="url(#SVGID_2_)" d="M20.205,57.452c0,0.519-3.619,0.752-6.627,0.752c-2.083,0-5.846-0.186-6.089-0.678
c0,0.238,0,0.806,0,0.89c0,0.389,2.573,0.661,6.084,0.661c3.511,0,6.632-0.344,6.632-0.729C20.205,58.264,20.205,57.7,20.205,57.452
z"/&gt;
&lt;path fill="#808080" d="M13.846,56.806c3.512,0,6.358,0.313,6.358,0.699s-2.846,0.763-6.358,0.763c-3.59,0-6.358-0.375-6.358-0.763
S10.335,56.806,13.846,56.806z"/&gt;
&lt;linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="600.833" y1="2037.4702" x2="598.1563" y2="2037.4702" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)"&gt;
&lt;stop offset="0" style="stop-color:#999999"/&gt;
&lt;stop offset="0.0417" style="stop-color:#8D8D8D"/&gt;
&lt;stop offset="0.1617" style="stop-color:#717171"/&gt;
&lt;stop offset="0.2821" style="stop-color:#5D5D5D"/&gt;
&lt;stop offset="0.4021" style="stop-color:#515151"/&gt;
&lt;stop offset="0.5212" style="stop-color:#4D4D4D"/&gt;
&lt;stop offset="0.6202" style="stop-color:#565656"/&gt;
&lt;stop offset="0.7817" style="stop-color:#6E6E6E"/&gt;
&lt;stop offset="0.9844" style="stop-color:#969696"/&gt;
&lt;stop offset="1" style="stop-color:#999999"/&gt;
&lt;/linearGradient&gt;
&lt;path fill="url(#SVGID_3_)" d="M15.215,57.657c0,0-0.792,0.053-1.339,0.053s-1.338-0.053-1.338-0.053v-5.231h2.677V57.657z"/&gt;
&lt;radialGradient id="SVGID_4_" cx="465.1113" cy="2023.4497" r="12.8975" gradientTransform="matrix(1.15 0 0 1 -526.6041 -1982.4023)" gradientUnits="userSpaceOnUse"&gt;
&lt;stop offset="0" style="stop-color:#F2F2F2"/&gt;
&lt;stop offset="1" style="stop-color:#666666"/&gt;
&lt;/radialGradient&gt;
&lt;path fill="url(#SVGID_4_)" d="M0.065,36.888c0-0.59,0.482-1.071,1.072-1.071H26.98c0.589,0,1.071,0.481,1.071,1.071v16.108
c0,0.589-0.482,1.07-1.071,1.07H1.137c-0.59,0.002-1.072-0.481-1.072-1.07V36.888z"/&gt;
&lt;path fill="none" stroke="#666666" stroke-width="0.1305" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M0.065,36.888c0-0.59,0.482-1.071,1.072-1.071H26.98c0.589,0,1.071,0.481,1.071,1.071v16.108c0,0.589-0.482,1.07-1.071,1.07H1.137
c-0.59,0.002-1.072-0.481-1.072-1.07V36.888z"/&gt;
&lt;radialGradient id="SVGID_5_" cx="439.1309" cy="2019.0845" r="28.5715" fx="461.6079" fy="2015.234" gradientTransform="matrix(1.1935 0 0 1 -509.6013 -1982.4023)" gradientUnits="userSpaceOnUse"&gt;
&lt;stop offset="0" style="stop-color:#4D4D4D"/&gt;
&lt;stop offset="1" style="stop-color:#999999"/&gt;
&lt;/radialGradient&gt;
&lt;path fill="url(#SVGID_5_)" d="M0.613,37.436c0-0.591,0.482-1.072,1.071-1.072h24.871c0.589,0,1.071,0.481,1.071,1.072v14.893
c0,0.59-0.482,1.072-1.071,1.072H1.685c-0.589,0-1.071-0.482-1.071-1.072V37.436z"/&gt;
&lt;radialGradient id="SVGID_6_" cx="440.0439" cy="2019.1304" r="18.3134" gradientTransform="matrix(1.1923 0 0 1 -510.0601 -1982.4023)" gradientUnits="userSpaceOnUse"&gt;
&lt;stop offset="0" style="stop-color:#9CD7FF"/&gt;
&lt;stop offset="1" style="stop-color:#3C89C9"/&gt;
&lt;/radialGradient&gt;
&lt;path fill="url(#SVGID_6_)" d="M0.917,37.679c0-0.59,0.482-1.071,1.072-1.071h24.262c0.589,0,1.071,0.481,1.071,1.071v14.406
c0,0.588-0.482,1.069-1.071,1.069H1.989c-0.59,0-1.072-0.481-1.072-1.069V37.679z"/&gt;
&lt;path opacity="0.24" fill="#F2F2F2" d="M0.917,49.11V37.679c0-0.59,0.482-1.071,1.072-1.071h24.262c0.589,0,1.071,0.481,1.071,1.071
v7.252l-12.407,2.646c-0.57,0.146-1.52,0.293-2.107,0.326L0.917,49.11z"/&gt;
&lt;linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="644.3887" y1="2040.6665" x2="631.6719" y2="2040.6665" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)"&gt;
&lt;stop offset="0" style="stop-color:#4D4D4D"/&gt;
&lt;stop offset="1" style="stop-color:#999999"/&gt;
&lt;/linearGradient&gt;
&lt;path fill="url(#SVGID_7_)" d="M58.706,57.452c0,0.518-3.621,0.752-6.627,0.752c-2.084,0-5.848-0.186-6.09-0.678
c0,0.237,0,0.805,0,0.889c0,0.389,2.572,0.662,6.084,0.662s6.633-0.344,6.633-0.729C58.706,58.263,58.706,57.7,58.706,57.452z"/&gt;
&lt;path fill="#808080" d="M52.347,56.805c3.512,0,6.357,0.313,6.357,0.699s-2.847,0.762-6.357,0.762c-3.59,0-6.357-0.373-6.357-0.762
C45.989,57.118,48.837,56.805,52.347,56.805z"/&gt;
&lt;linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="639.333" y1="2037.4683" x2="636.6553" y2="2037.4683" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)"&gt;
&lt;stop offset="0" style="stop-color:#999999"/&gt;
&lt;stop offset="0.0417" style="stop-color:#8D8D8D"/&gt;
&lt;stop offset="0.1617" style="stop-color:#717171"/&gt;
&lt;stop offset="0.2821" style="stop-color:#5D5D5D"/&gt;
&lt;stop offset="0.4021" style="stop-color:#515151"/&gt;
&lt;stop offset="0.5212" style="stop-color:#4D4D4D"/&gt;
&lt;stop offset="0.6202" style="stop-color:#565656"/&gt;
&lt;stop offset="0.7817" style="stop-color:#6E6E6E"/&gt;
&lt;stop offset="0.9844" style="stop-color:#969696"/&gt;
&lt;stop offset="1" style="stop-color:#999999"/&gt;
&lt;/linearGradient&gt;
&lt;path fill="url(#SVGID_8_)" d="M53.716,57.657c0,0-0.791,0.052-1.34,0.052c-0.547,0-1.338-0.052-1.338-0.052v-5.232h2.678V57.657z"
/&gt;
&lt;radialGradient id="SVGID_9_" cx="498.5898" cy="2023.4487" r="12.8975" gradientTransform="matrix(1.15 0 0 1 -526.6041 -1982.4023)" gradientUnits="userSpaceOnUse"&gt;
&lt;stop offset="0" style="stop-color:#F2F2F2"/&gt;
&lt;stop offset="1" style="stop-color:#666666"/&gt;
&lt;/radialGradient&gt;
&lt;path fill="url(#SVGID_9_)" d="M38.566,36.887c0-0.59,0.481-1.072,1.071-1.072h25.844c0.589,0,1.07,0.482,1.07,1.072v16.107
c0,0.59-0.481,1.072-1.07,1.072H39.638c-0.59,0-1.071-0.482-1.071-1.072V36.887z"/&gt;
&lt;path fill="none" stroke="#666666" stroke-width="0.1305" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M38.566,36.887c0-0.59,0.481-1.072,1.071-1.072h25.844c0.589,0,1.07,0.482,1.07,1.072v16.107c0,0.59-0.481,1.072-1.07,1.072H39.638
c-0.59,0-1.071-0.482-1.071-1.072V36.887z"/&gt;
&lt;radialGradient id="SVGID_10_" cx="471.3896" cy="2019.0845" r="28.5697" fx="493.8652" fy="2015.2343" gradientTransform="matrix(1.1935 0 0 1 -509.6013 -1982.4023)" gradientUnits="userSpaceOnUse"&gt;
&lt;stop offset="0" style="stop-color:#4D4D4D"/&gt;
&lt;stop offset="1" style="stop-color:#999999"/&gt;
&lt;/radialGradient&gt;
&lt;path fill="url(#SVGID_10_)" d="M39.114,37.434c0-0.59,0.482-1.072,1.071-1.072h24.87c0.589,0,1.07,0.482,1.07,1.072v14.895
c0,0.589-0.481,1.07-1.07,1.07h-24.87c-0.589,0-1.071-0.481-1.071-1.07V37.434z"/&gt;
&lt;radialGradient id="SVGID_11_" cx="472.334" cy="2019.1294" r="18.3139" gradientTransform="matrix(1.1923 0 0 1 -510.0601 -1982.4023)" gradientUnits="userSpaceOnUse"&gt;
&lt;stop offset="0" style="stop-color:#9CD7FF"/&gt;
&lt;stop offset="1" style="stop-color:#3C89C9"/&gt;
&lt;/radialGradient&gt;
&lt;path fill="url(#SVGID_11_)" d="M39.419,37.678c0-0.59,0.481-1.072,1.07-1.072h24.264c0.588,0,1.07,0.482,1.07,1.072v14.406
c0,0.588-0.482,1.07-1.07,1.07H40.489c-0.589,0-1.07-0.482-1.07-1.07V37.678z"/&gt;
&lt;path opacity="0.24" fill="#F2F2F2" d="M39.419,49.108v-11.43c0-0.59,0.481-1.072,1.07-1.072h24.264c0.588,0,1.07,0.482,1.07,1.072
v7.252l-12.408,2.645c-0.57,0.146-1.52,0.295-2.106,0.326L39.419,49.108z"/&gt;
&lt;linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="624.8936" y1="2004.9155" x2="612.1787" y2="2004.9155" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)"&gt;
&lt;stop offset="0" style="stop-color:#4D4D4D"/&gt;
&lt;stop offset="1" style="stop-color:#999999"/&gt;
&lt;/linearGradient&gt;
&lt;path fill="url(#SVGID_12_)" d="M39.212,21.701c0,0.518-3.621,0.752-6.626,0.752c-2.083,0-5.847-0.186-6.089-0.678
c0,0.238,0,0.805,0,0.889c0,0.389,2.573,0.662,6.084,0.662c3.51,0,6.631-0.344,6.631-0.729
C39.212,22.513,39.212,21.949,39.212,21.701z"/&gt;
&lt;path fill="#808080" d="M32.854,21.055c3.511,0,6.358,0.313,6.358,0.699c0,0.386-2.848,0.762-6.358,0.762
c-3.589,0-6.358-0.374-6.358-0.762C26.496,21.367,29.342,21.055,32.854,21.055z"/&gt;
&lt;linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="619.8379" y1="2001.7183" x2="617.1611" y2="2001.7183" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)"&gt;
&lt;stop offset="0" style="stop-color:#999999"/&gt;
&lt;stop offset="0.0417" style="stop-color:#8D8D8D"/&gt;
&lt;stop offset="0.1617" style="stop-color:#717171"/&gt;
&lt;stop offset="0.2821" style="stop-color:#5D5D5D"/&gt;
&lt;stop offset="0.4021" style="stop-color:#515151"/&gt;
&lt;stop offset="0.5212" style="stop-color:#4D4D4D"/&gt;
&lt;stop offset="0.6202" style="stop-color:#565656"/&gt;
&lt;stop offset="0.7817" style="stop-color:#6E6E6E"/&gt;
&lt;stop offset="0.9844" style="stop-color:#969696"/&gt;
&lt;stop offset="1" style="stop-color:#999999"/&gt;
&lt;/linearGradient&gt;
&lt;path fill="url(#SVGID_13_)" d="M34.222,21.906c0,0-0.791,0.052-1.338,0.052c-0.547,0-1.338-0.052-1.338-0.052v-5.232h2.677
L34.222,21.906L34.222,21.906z"/&gt;
&lt;radialGradient id="SVGID_14_" cx="481.6387" cy="1987.6978" r="12.8975" gradientTransform="matrix(1.15 0 0 1 -526.6041 -1982.4023)" gradientUnits="userSpaceOnUse"&gt;
&lt;stop offset="0" style="stop-color:#F2F2F2"/&gt;
&lt;stop offset="1" style="stop-color:#666666"/&gt;
&lt;/radialGradient&gt;
&lt;path fill="url(#SVGID_14_)" d="M19.072,1.137c0-0.59,0.482-1.072,1.071-1.072h25.843c0.589,0,1.071,0.482,1.071,1.072v16.108
c0,0.589-0.482,1.071-1.071,1.071H20.145c-0.589,0-1.071-0.482-1.071-1.071L19.072,1.137L19.072,1.137z"/&gt;
&lt;path fill="none" stroke="#666666" stroke-width="0.1305" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M19.072,1.137c0-0.59,0.482-1.072,1.071-1.072h25.843c0.589,0,1.071,0.482,1.071,1.072v16.108c0,0.589-0.482,1.071-1.071,1.071
H20.145c-0.589,0-1.071-0.482-1.071-1.071L19.072,1.137L19.072,1.137z"/&gt;
&lt;radialGradient id="SVGID_15_" cx="455.0566" cy="1983.3345" r="28.5689" fx="477.5316" fy="1979.4844" gradientTransform="matrix(1.1935 0 0 1 -509.6013 -1982.4023)" gradientUnits="userSpaceOnUse"&gt;
&lt;stop offset="0" style="stop-color:#4D4D4D"/&gt;
&lt;stop offset="1" style="stop-color:#999999"/&gt;
&lt;/radialGradient&gt;
&lt;path fill="url(#SVGID_15_)" d="M19.621,1.685c0-0.59,0.482-1.072,1.072-1.072h24.87c0.589,0,1.071,0.482,1.071,1.072v14.894
c0,0.589-0.482,1.071-1.071,1.071h-24.87c-0.589,0-1.072-0.482-1.072-1.071V1.685z"/&gt;
&lt;radialGradient id="SVGID_16_" cx="455.9854" cy="1983.3784" r="18.3134" gradientTransform="matrix(1.1923 0 0 1 -510.0601 -1982.4023)" gradientUnits="userSpaceOnUse"&gt;
&lt;stop offset="0" style="stop-color:#9CD7FF"/&gt;
&lt;stop offset="1" style="stop-color:#3C89C9"/&gt;
&lt;/radialGradient&gt;
&lt;path fill="url(#SVGID_16_)" d="M19.924,1.928c0-0.59,0.482-1.072,1.072-1.072h24.262c0.589,0,1.07,0.482,1.07,1.072v14.406
c0,0.588-0.481,1.07-1.07,1.07H20.997c-0.589,0-1.072-0.482-1.072-1.07V1.928z"/&gt;
&lt;path opacity="0.24" fill="#F2F2F2" d="M19.924,13.358V1.928c0-0.59,0.482-1.072,1.072-1.072h24.262c0.589,0,1.07,0.482,1.07,1.072
V9.18l-12.408,2.646c-0.569,0.146-1.519,0.294-2.106,0.326L19.924,13.358z"/&gt;
&lt;/svg&gt;
</y:Resource>
<y:Resource id="2">&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"&gt;
&lt;g&gt;
@@ -871,7 +978,7 @@ Vulnerability data<y:LabelModel>
&lt;/g&gt;
&lt;/svg&gt;
</y:Resource>
<y:Resource id="3">&lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&gt;
<y:Resource id="2">&lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&gt;
&lt;svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 73 KiB

10
main.go
View File

@@ -19,6 +19,7 @@ package main
import (
"flag"
"fmt"
"os"
"golang.org/x/net/context"
@@ -37,8 +38,17 @@ func main() {
subcommands.Register(&commands.TuiCmd{}, "tui")
subcommands.Register(&commands.ScanCmd{}, "scan")
subcommands.Register(&commands.PrepareCmd{}, "prepare")
subcommands.Register(&commands.HistoryCmd{}, "history")
var version = flag.Bool("v", false, "Show version")
flag.Parse()
if *version {
fmt.Printf("%s %s\n", Name, Version)
os.Exit(int(subcommands.ExitSuccess))
}
ctx := context.Background()
os.Exit(int(subcommands.Execute(ctx)))
}

View File

@@ -30,16 +30,34 @@ import (
// ScanHistory is the history of Scanning.
type ScanHistory struct {
gorm.Model
ScanResults []ScanResult
ScanResults ScanResults
ScannedAt time.Time
}
// ScanResults is slice of ScanResult.
type ScanResults []ScanResult
// Len implement Sort Interface
func (s ScanResults) Len() int {
return len(s)
}
// Swap implement Sort Interface
func (s ScanResults) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less implement Sort Interface
func (s ScanResults) Less(i, j int) bool {
if s[i].ServerName == s[j].ServerName {
return s[i].Container.ContainerID < s[i].Container.ContainerID
}
return s[i].ServerName < s[j].ServerName
}
// FilterByCvssOver is filter function.
func (results ScanResults) FilterByCvssOver() (filtered ScanResults) {
for _, result := range results {
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) {
@@ -61,12 +79,60 @@ type ScanResult struct {
// Hostname string
Family string
Release string
Container Container
// Fqdn string
// NWLinks []NWLink
KnownCves []CveInfo
UnknownCves []CveInfo
}
// 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,
)
}
return hostinfo
}
// 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,
)
}
return hostinfo
}
// CveSummary summarize the number of CVEs group by CVSSv2 Severity
func (r ScanResult) CveSummary() string {
var high, middle, low, unknown int
@@ -84,10 +150,13 @@ func (r ScanResult) CveSummary() string {
unknown++
}
}
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 Middle:%d Low:%d ?:%d)",
high+middle+low+unknown,
high, middle, low, unknown,
)
high+middle+low+unknown, high, middle, low, unknown)
}
// NWLink has network link information.
@@ -232,7 +301,6 @@ func (p PackageInfo) ToStringNewVersion() string {
}
// DistroAdvisory has Amazon Linux AMI Security Advisory information.
//TODO Rename to DistroAdvisory
type DistroAdvisory struct {
gorm.Model
CveInfoID uint
@@ -242,3 +310,12 @@ type DistroAdvisory struct {
Issued time.Time
Updated time.Time
}
// Container has Container information
type Container struct {
gorm.Model
ScanResultID uint
ContainerID string
Name string
}

View File

@@ -19,6 +19,8 @@ package report
import (
"os"
"path/filepath"
"runtime"
"github.com/Sirupsen/logrus"
"github.com/future-architect/vuls/models"
@@ -31,6 +33,9 @@ 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

View File

@@ -40,7 +40,7 @@ func (w MailWriter) Write(scanResults []models.ScanResult) (err error) {
subject := fmt.Sprintf("%s%s %s",
conf.Mail.SubjectPrefix,
s.ServerName,
s.ServerInfo(),
s.CveSummary(),
)
m.SetHeader("Subject", subject)

View File

@@ -103,18 +103,18 @@ func msgText(r models.ScanResult) string {
notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers)
}
hostinfo := fmt.Sprintf(
"*%s* (%s %s)",
r.ServerName,
r.Family,
r.Release,
)
return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, hostinfo, r.CveSummary())
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) {
scanResult.KnownCves = append(scanResult.KnownCves, scanResult.UnknownCves...)
cves := scanResult.KnownCves
if !config.Conf.IgnoreUnscoredCves {
cves = append(cves, scanResult.UnknownCves...)
}
scanResult.KnownCves = cves
for _, cveInfo := range scanResult.KnownCves {
cveID := cveInfo.CveDetail.CveID

View File

@@ -40,9 +40,9 @@ var currentCveInfo int
var currentDetailLimitY int
// RunTui execute main logic
func RunTui() subcommands.ExitStatus {
func RunTui(historyID string) subcommands.ExitStatus {
var err error
scanHistory, err = latestScanHistory()
scanHistory, err = selectScanHistory(historyID)
if err != nil {
log.Fatal(err)
return subcommands.ExitFailure
@@ -70,12 +70,12 @@ func RunTui() subcommands.ExitStatus {
return subcommands.ExitSuccess
}
func latestScanHistory() (latest models.ScanHistory, err error) {
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.SelectLatestScanHistory()
latest, err = db.SelectScanHistory(historyID)
return
}
@@ -410,7 +410,7 @@ func changeHost(g *gocui.Gui, v *gocui.View) error {
serverName := strings.TrimSpace(l)
for _, r := range scanHistory.ScanResults {
if serverName == r.ServerName {
if serverName == strings.TrimSpace(r.ServerInfoTui()) {
currentScanResult = r
break
}
@@ -509,14 +509,14 @@ func layout(g *gocui.Gui) error {
func setSideLayout(g *gocui.Gui) error {
_, maxY := g.Size()
if v, err := g.SetView("side", -1, -1, 30, maxY); err != nil {
if v, err := g.SetView("side", -1, -1, 40, maxY); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Highlight = true
for _, result := range scanHistory.ScanResults {
fmt.Fprintln(v, result.ServerName)
fmt.Fprintln(v, result.ServerInfoTui())
}
currentScanResult = scanHistory.ScanResults[0]
if err := g.SetCurrentView("side"); err != nil {
@@ -528,7 +528,7 @@ func setSideLayout(g *gocui.Gui) error {
func setSummaryLayout(g *gocui.Gui) error {
maxX, maxY := g.Size()
if v, err := g.SetView("summary", 30, -1, maxX, int(float64(maxY)*0.2)); err != nil {
if v, err := g.SetView("summary", 40, -1, maxX, int(float64(maxY)*0.2)); err != nil {
if err != gocui.ErrUnknownView {
return err
}
@@ -602,7 +602,6 @@ func summaryLines(data models.ScanResult) string {
}
stable.AddRow(icols...)
}
// ignore UnknownCves
return fmt.Sprintf("%s", stable)
}
@@ -617,7 +616,7 @@ func setDetailLayout(g *gocui.Gui) error {
_, oy := summaryView.Origin()
currentCveInfo = cy + oy
if v, err := g.SetView("detail", 30, int(float64(maxY)*0.2), maxX, maxY); err != nil {
if v, err := g.SetView("detail", 40, int(float64(maxY)*0.2), maxX, maxY); err != nil {
if err != gocui.ErrUnknownView {
return err
}
@@ -625,7 +624,6 @@ func setDetailLayout(g *gocui.Gui) error {
// currentScanResult.KnownCves[currentCveInfo],
// currentScanResult.Family)
//TODO error handling
text, err := detailLines()
if err != nil {
return err
@@ -654,6 +652,10 @@ type dataForTmpl struct {
}
func detailLines() (string, error) {
if len(currentScanResult.KnownCves) == 0 {
return "No vulnerable packages", nil
}
cveInfo := currentScanResult.KnownCves[currentCveInfo]
cveID := cveInfo.CveDetail.CveID

View File

@@ -28,18 +28,13 @@ import (
)
func toPlainText(scanResult models.ScanResult) (string, error) {
hostinfo := fmt.Sprintf(
"%s (%s %s)",
scanResult.ServerName,
scanResult.Family,
scanResult.Release,
)
serverInfo := scanResult.ServerInfo()
var buffer bytes.Buffer
for i := 0; i < len(hostinfo); i++ {
for i := 0; i < len(serverInfo); i++ {
buffer.WriteString("=")
}
header := fmt.Sprintf("%s\n%s", hostinfo, buffer.String())
header := fmt.Sprintf("%s\n%s", serverInfo, buffer.String())
if len(scanResult.KnownCves) == 0 && len(scanResult.UnknownCves) == 0 {
return fmt.Sprintf(`
@@ -53,7 +48,12 @@ No unsecure packages.
scoredReport, unscoredReport = toPlainTextDetails(scanResult, scanResult.Family)
scored := strings.Join(scoredReport, "\n\n")
unscored := strings.Join(unscoredReport, "\n\n")
unscored := ""
if !config.Conf.IgnoreUnscoredCves {
unscored = strings.Join(unscoredReport, "\n\n")
}
detail := fmt.Sprintf(`
%s
@@ -72,7 +72,12 @@ func ToPlainTextSummary(r models.ScanResult) string {
stable := uitable.New()
stable.MaxColWidth = 84
stable.Wrap = true
cves := append(r.KnownCves, r.UnknownCves...)
cves := r.KnownCves
if !config.Conf.IgnoreUnscoredCves {
cves = append(cves, r.UnknownCves...)
}
for _, d := range cves {
var scols []string

View File

@@ -174,7 +174,7 @@ func (o *debian) scanPackages() error {
var unsecurePacks []CvePacksInfo
if unsecurePacks, err = o.scanUnsecurePackages(packs); err != nil {
o.log.Errorf("Failed to scan valnerable packages")
o.log.Errorf("Failed to scan vulnerable packages")
return err
}
o.setUnsecurePackages(unsecurePacks)
@@ -226,7 +226,7 @@ func (o *debian) parseScanedPackagesLine(line string) (name, version string, err
func (o *debian) checkRequiredPackagesInstalled() error {
if o.Family == "debian" {
if r := o.ssh("test -f /usr/bin/aptitude", sudo); !r.isSuccess() {
if r := o.ssh("test -f /usr/bin/aptitude", noSudo); !r.isSuccess() {
msg := "aptitude is not installed"
o.log.Errorf(msg)
return fmt.Errorf(msg)
@@ -346,7 +346,7 @@ func (o *debian) fillCandidateVersion(packs []models.PackageInfo) ([]models.Pack
case err := <-errChan:
return nil, err
case <-timeout:
return nil, fmt.Errorf("Timeout fillCandidateVersion.")
return nil, fmt.Errorf("Timeout fillCandidateVersion")
}
}
return result, nil
@@ -500,7 +500,7 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
return nil, err
}
case <-timeout:
return nil, fmt.Errorf("Timeout scanPackageCveIds.")
return nil, fmt.Errorf("Timeout scanPackageCveIds")
}
}

View File

@@ -199,7 +199,7 @@ util-linux (2.26.2-6) unstable; urgency=medium`,
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.")
t.Errorf("Returning error is unexpected")
}
}
}
@@ -504,7 +504,7 @@ Calculating upgrade... Done
for _, tt := range tests {
actual, err := d.parseAptGetUpgrade(tt.in)
if err != nil {
t.Errorf("Returning error is unexpected.")
t.Errorf("Returning error is unexpected")
}
if len(tt.expected) != len(actual) {
t.Errorf("Result length is not as same as expected. expected: %d, actual: %d", len(tt.expected), len(actual))

View File

@@ -18,7 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package scan
import (
"fmt"
"sort"
"strings"
"github.com/Sirupsen/logrus"
"github.com/future-architect/vuls/config"
@@ -33,6 +35,8 @@ type linux struct {
Release string
osPackages
log *logrus.Entry
errs []error
}
func (l *linux) ssh(cmd string, sudo bool) sshResult {
@@ -43,7 +47,7 @@ func (l *linux) setServerInfo(c config.ServerInfo) {
l.ServerInfo = c
}
func (l *linux) getServerInfo() config.ServerInfo {
func (l linux) getServerInfo() config.ServerInfo {
return l.ServerInfo
}
@@ -52,6 +56,81 @@ func (l *linux) setDistributionInfo(fam, rel string) {
l.Release = rel
}
func (l *linux) getDistributionInfo() string {
return fmt.Sprintf("%s %s", l.Family, l.Release)
}
func (l linux) allContainers() (containers []config.Container, err error) {
switch l.ServerInfo.Container.Type {
case "", "docker":
stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}}'")
if err != nil {
return containers, err
}
return l.parseDockerPs(stdout)
default:
return containers, fmt.Errorf(
"Not supported yet: %s", l.ServerInfo.Container.Type)
}
}
func (l *linux) runningContainers() (containers []config.Container, err error) {
switch l.ServerInfo.Container.Type {
case "", "docker":
stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}}'")
if err != nil {
return containers, err
}
return l.parseDockerPs(stdout)
default:
return containers, fmt.Errorf(
"Not supported yet: %s", l.ServerInfo.Container.Type)
}
}
func (l *linux) exitedContainers() (containers []config.Container, err error) {
switch l.ServerInfo.Container.Type {
case "", "docker":
stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}}'")
if err != nil {
return containers, err
}
return l.parseDockerPs(stdout)
default:
return containers, fmt.Errorf(
"Not supported yet: %s", l.ServerInfo.Container.Type)
}
}
func (l *linux) dockerPs(option string) (string, error) {
cmd := fmt.Sprintf("docker ps %s", option)
r := l.ssh(cmd, noSudo)
if !r.isSuccess() {
return "", fmt.Errorf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
}
return r.Stdout, nil
}
func (l *linux) parseDockerPs(stdout string) (containers []config.Container, err error) {
lines := strings.Split(stdout, "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) == 0 {
break
}
if len(fields) != 2 {
return containers, fmt.Errorf("Unknown format: %s", line)
}
containers = append(containers, config.Container{
ContainerID: fields[0],
Name: fields[1],
})
}
return
}
func (l *linux) convertToModel() (models.ScanResult, error) {
var cves, unknownScoreCves []models.CveInfo
for _, p := range l.UnsecurePackages {
@@ -79,10 +158,16 @@ func (l *linux) convertToModel() (models.ScanResult, error) {
cves = append(cves, cve)
}
container := models.Container{
ContainerID: l.ServerInfo.Container.ContainerID,
Name: l.ServerInfo.Container.Name,
}
return models.ScanResult{
ServerName: l.ServerInfo.ServerName,
Family: l.Family,
Release: l.Release,
Container: container,
KnownCves: cves,
UnknownCves: unknownScoreCves,
}, nil
@@ -127,3 +212,11 @@ func (l *linux) scanVulnByCpeName() error {
l.setUnsecurePackages(unsecurePacks)
return nil
}
func (l *linux) setErrs(errs []error) {
l.errs = errs
}
func (l linux) getErrs() []error {
return l.errs
}

View File

@@ -16,3 +16,43 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package scan
import (
"reflect"
"testing"
"github.com/future-architect/vuls/config"
)
func TestParseDockerPs(t *testing.T) {
var test = struct {
in string
expected []config.Container
}{
`c7ca0992415a romantic_goldberg
f570ae647edc agitated_lovelace`,
[]config.Container{
{
ContainerID: "c7ca0992415a",
Name: "romantic_goldberg",
},
{
ContainerID: "f570ae647edc",
Name: "agitated_lovelace",
},
},
}
r := newRedhat(config.ServerInfo{})
actual, err := r.parseDockerPs(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])
}
}
}

View File

@@ -51,7 +51,7 @@ func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
red = newRedhat(c)
// set sudo option flag
c.SudoOpt = config.SudoOption{ExecBySudoSh: true}
c.SudoOpt = config.SudoOption{ExecBySudo: true}
red.setServerInfo(c)
if r := sshExec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
@@ -129,6 +129,7 @@ func (o *redhat) installYumPluginSecurity() error {
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(
@@ -139,7 +140,6 @@ func (o *redhat) installYumPluginSecurity() error {
}
func (o *redhat) installYumChangelog() error {
o.log.Info("Installing yum-plugin-security...")
if o.Family == "centos" {
var majorVersion int
@@ -160,17 +160,18 @@ func (o *redhat) installYumChangelog() error {
cmd := "rpm -q " + packName
if r := o.ssh(cmd, noSudo); r.isSuccess() {
o.log.Infof("Ignored: %s already installed.", packName)
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 install %s. status: %d, stdout: %s, stderr: %s",
packName, r.ExitStatus, r.Stdout, r.Stderr)
}
o.log.Infof("Installed: %s.", packName)
o.log.Infof("Installed: %s", packName)
}
return nil
}
@@ -228,7 +229,7 @@ func (o *redhat) scanPackages() error {
var unsecurePacks []CvePacksInfo
if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
o.log.Errorf("Failed to scan valnerable packages")
o.log.Errorf("Failed to scan vulnerable packages")
return err
}
o.setUnsecurePackages(unsecurePacks)
@@ -245,7 +246,7 @@ func (o *redhat) scanInstalledPackages() (installedPackages models.PackageInfoLi
for _, line := range lines {
if trimed := strings.TrimSpace(line); len(trimed) != 0 {
var packinfo models.PackageInfo
if packinfo, err = o.parseScanedPackagesLine(line); err != nil {
if packinfo, err = o.parseScannedPackagesLine(line); err != nil {
return
}
installedPackages = append(installedPackages, packinfo)
@@ -259,17 +260,17 @@ func (o *redhat) scanInstalledPackages() (installedPackages models.PackageInfoLi
r.ExitStatus, r.Stdout, r.Stderr)
}
func (o *redhat) parseScanedPackagesLine(line string) (pack models.PackageInfo, err error) {
re, _ := regexp.Compile(`^([^\t']+)\t([^\t]+)\t(.+)$`)
result := re.FindStringSubmatch(line)
if len(result) == 4 {
pack.Name = result[1]
pack.Version = result[2]
pack.Release = strings.TrimSpace(result[3])
} else {
err = fmt.Errorf("redhat: Failed to parse package line: %s", line)
func (o *redhat) parseScannedPackagesLine(line string) (models.PackageInfo, error) {
fields := strings.Fields(line)
if len(fields) != 3 {
return models.PackageInfo{},
fmt.Errorf("Failed to parse package line: %s", line)
}
return
return models.PackageInfo{
Name: fields[0],
Version: fields[1],
Release: fields[2],
}, nil
}
func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
@@ -286,7 +287,7 @@ func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
//TODO return whether already expired.
func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error) {
cmd := "yum check-update"
cmd := "LANG=en_US.UTF-8 yum --color=never check-update"
r := o.ssh(util.PrependProxyEnv(cmd), sudo)
if !r.isSuccess(0, 100) {
//returns an exit code of 100 if there are available updates.
@@ -300,7 +301,7 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error)
if err != nil {
return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
}
o.log.Debugf("%s", pp.Sprintf("%s", packInfoList))
o.log.Debugf("%s", pp.Sprintf("%v", packInfoList))
// Collect CVE-IDs in changelog
type PackInfoCveIDs struct {
@@ -311,7 +312,7 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error)
for i, packInfo := range packInfoList {
changelog, err := o.getChangelog(packInfo.Name)
if err != nil {
o.log.Errorf("Failed to collect CVE. err: %s", err)
o.log.Errorf("Failed to collect CVE IDs. err: %s", err)
return nil, err
}
@@ -409,16 +410,20 @@ func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.Package
continue
}
if needToParse {
if strings.HasPrefix(line, "Obsoleting") {
continue
}
candidate, err := o.parseYumCheckUpdateLine(line)
if err != nil {
return models.PackageInfoList{}, err
return results, err
}
installed, found := o.Packages.FindByName(candidate.Name)
if !found {
return models.PackageInfoList{}, fmt.Errorf(
"Failed to parse yum check update line: %s-%s-%s",
o.log.Warnf("Not found the package in rpm -qa. candidate: %s-%s-%s",
candidate.Name, candidate.Version, candidate.Release)
results = append(results, candidate)
continue
}
installed.NewVersion = candidate.NewVersion
installed.NewRelease = candidate.NewRelease
@@ -455,10 +460,15 @@ func (o *redhat) parseYumCheckUpdateLine(line string) (models.PackageInfo, error
}
func (o *redhat) getChangelog(packageNames string) (stdout string, err error) {
command := "echo N | "
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(" yum update --changelog %s | grep CVE", packageNames)
r := o.ssh(command, sudo)
@@ -485,7 +495,7 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
"yum updateinfo is not suppported on CentOS")
}
cmd := "yum repolist"
cmd := "yum --color=never repolist"
r := o.ssh(util.PrependProxyEnv(cmd), sudo)
if !r.isSuccess() {
return nil, fmt.Errorf(
@@ -494,7 +504,7 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
}
// get advisoryID(RHSA, ALAS) - package name,version
cmd = "yum updateinfo list available --security"
cmd = "yum --color=never updateinfo list available --security"
r = o.ssh(util.PrependProxyEnv(cmd), sudo)
if !r.isSuccess() {
return nil, fmt.Errorf(
@@ -504,7 +514,8 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
advIDPackNamesList, err := o.parseYumUpdateinfoListAvailable(r.Stdout)
// get package name, version, rel to be upgrade.
cmd = "yum check-update --security"
// cmd = "yum check-update --security"
cmd = "LANG=en_US.UTF-8 yum --color=never check-update"
r = o.ssh(util.PrependProxyEnv(cmd), sudo)
if !r.isSuccess(0, 100) {
//returns an exit code of 100 if there are available updates.
@@ -512,26 +523,17 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
}
vulnerablePackInfoList, err := o.parseYumCheckUpdateLines(r.Stdout)
updatable, err := o.parseYumCheckUpdateLines(r.Stdout)
if err != nil {
return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
}
o.log.Debugf("%s", pp.Sprintf("%s", vulnerablePackInfoList))
for i, packInfo := range vulnerablePackInfoList {
installedPack, found := o.Packages.FindByName(packInfo.Name)
if !found {
return nil, fmt.Errorf(
"Parsed package not found. packInfo: %#v", packInfo)
}
vulnerablePackInfoList[i].Version = installedPack.Version
vulnerablePackInfoList[i].Release = installedPack.Release
}
o.log.Debugf("%s", pp.Sprintf("%v", updatable))
dict := map[string][]models.PackageInfo{}
for _, advIDPackNames := range advIDPackNamesList {
packInfoList := models.PackageInfoList{}
for _, packName := range advIDPackNames.PackNames {
packInfo, found := vulnerablePackInfoList.FindByName(packName)
packInfo, found := updatable.FindByName(packName)
if !found {
return nil, fmt.Errorf(
"PackInfo not found. packInfo: %#v", packName)
@@ -543,8 +545,8 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
}
// get advisoryID(RHSA, ALAS) - CVE IDs
cmd = "yum updateinfo --security update"
r = o.ssh(util.PrependProxyEnv(cmd), noSudo)
cmd = "yum --color=never updateinfo --security update"
r = o.ssh(util.PrependProxyEnv(cmd), sudo)
if !r.isSuccess() {
return nil, fmt.Errorf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
@@ -859,3 +861,7 @@ func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacks
}
return result, nil
}
func (o *redhat) clone() osTypeInterface {
return o
}

View File

@@ -48,10 +48,18 @@ func TestParseScanedPackagesLineRedhat(t *testing.T) {
Release: "30.el6.11",
},
},
{
"Percona-Server-shared-56 5.6.19 rel67.0.el6",
models.PackageInfo{
Name: "Percona-Server-shared-56",
Version: "5.6.19",
Release: "rel67.0.el6",
},
},
}
for _, tt := range packagetests {
p, _ := r.parseScanedPackagesLine(tt.in)
p, _ := r.parseScannedPackagesLine(tt.in)
if p.Name != tt.pack.Name {
t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
}
@@ -556,7 +564,11 @@ Loading mirror speeds from cached hostfile
audit-libs.x86_64 2.3.7-5.el6 base
bash.x86_64 4.1.2-33.el6_7.1 updates
`
Obsoleting Packages
python-libs.i686 2.6.6-64.el6 rhui-REGION-rhel-server-releases
python-ordereddict.noarch 1.1-3.el6ev installed
`
r.Packages = []models.PackageInfo{
{
Name: "audit-libs",
@@ -568,6 +580,16 @@ bash.x86_64 4.1.2-33.el6_7.1 updates
Version: "4.1.1",
Release: "33",
},
{
Name: "python-libs",
Version: "2.6.0",
Release: "1.1-0",
},
{
Name: "python-ordereddict",
Version: "1.0",
Release: "1",
},
}
var tests = []struct {
in string
@@ -590,6 +612,20 @@ bash.x86_64 4.1.2-33.el6_7.1 updates
NewVersion: "4.1.2",
NewRelease: "33.el6_7.1",
},
{
Name: "python-libs",
Version: "2.6.0",
Release: "1.1-0",
NewVersion: "2.6.6",
NewRelease: "64.el6",
},
{
Name: "python-ordereddict",
Version: "1.0",
Release: "1",
NewVersion: "1.1",
NewRelease: "3.el6ev",
},
},
},
}
@@ -774,39 +810,6 @@ updateinfo list done`
}
}
func TestParseYumUpdateinfoToGetUpdateID(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var packagetests = []struct {
in string
pack models.PackageInfo
}{
{
"openssl 1.0.1e 30.el6.11",
models.PackageInfo{
Name: "openssl",
Version: "1.0.1e",
Release: "30.el6.11",
},
},
}
for _, tt := range packagetests {
p, _ := r.parseScanedPackagesLine(tt.in)
if p.Name != tt.pack.Name {
t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
}
if p.Version != tt.pack.Version {
t.Errorf("version: expected %s, actual %s", tt.pack.Version, p.Version)
}
if p.Release != tt.pack.Release {
t.Errorf("release: expected %s, actual %s", tt.pack.Release, p.Release)
}
}
}
func TestExtractPackNameVerRel(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var tests = []struct {

View File

@@ -7,7 +7,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/k0kubun/pp"
cve "github.com/kotakanbe/go-cve-dictionary/models"
)
@@ -21,11 +20,19 @@ type osTypeInterface interface {
setServerInfo(config.ServerInfo)
getServerInfo() config.ServerInfo
setDistributionInfo(string, string)
getDistributionInfo() string
checkRequiredPackagesInstalled() error
scanPackages() error
scanVulnByCpeName() error
install() error
convertToModel() (models.ScanResult, error)
runningContainers() ([]config.Container, error)
exitedContainers() ([]config.Container, error)
allContainers() ([]config.Container, error)
getErrs() []error
setErrs([]error)
}
// osPackages included by linux struct
@@ -94,50 +101,229 @@ func (s CvePacksList) Less(i, j int) bool {
return s[i].CveDetail.CvssScore("en") > s[j].CveDetail.CvssScore("en")
}
func detectOs(c config.ServerInfo) (osType osTypeInterface) {
func detectOS(c config.ServerInfo) (osType osTypeInterface) {
var itsMe bool
itsMe, osType = detectDebian(c)
if itsMe {
return
}
itsMe, osType = detectRedhat(c)
if itsMe {
return
}
osType.setErrs([]error{fmt.Errorf("Unknown OS Type")})
return
}
// InitServers detect the kind of OS distribution of target servers
func InitServers(localLogger *logrus.Entry) (err error) {
func InitServers(localLogger *logrus.Entry) error {
Log = localLogger
if servers, err = detectServersOS(); err != nil {
err = fmt.Errorf("Failed to detect OS")
} else {
Log.Debugf("%s", pp.Sprintf("%s", servers))
hosts, err := detectServerOSes()
if err != nil {
return fmt.Errorf("Failed to detect server OSes. err: %s", err)
}
return
servers = hosts
Log.Info("Detecting Container OS...")
containers, err := detectContainerOSes()
if err != nil {
return fmt.Errorf("Failed to detect Container OSes. err: %s", err)
}
servers = append(servers, containers...)
return nil
}
func detectServersOS() (osi []osTypeInterface, err error) {
func detectServerOSes() (oses []osTypeInterface, err error) {
osTypeChan := make(chan osTypeInterface, len(config.Conf.Servers))
defer close(osTypeChan)
for _, s := range config.Conf.Servers {
go func(s config.ServerInfo) {
osTypeChan <- detectOs(s)
//TODO handling Unknown OS
osTypeChan <- detectOS(s)
}(s)
}
timeout := time.After(60 * time.Second)
timeout := time.After(300 * time.Second)
for i := 0; i < len(config.Conf.Servers); i++ {
select {
case res := <-osTypeChan:
osi = append(osi, res)
if 0 < len(res.getErrs()) {
continue
}
Log.Infof("(%d/%d) Detected %s: %s",
i+1, len(config.Conf.Servers),
res.getServerInfo().ServerName,
res.getDistributionInfo())
oses = append(oses, res)
case <-timeout:
Log.Error("Timeout Occured while detecting OS.")
err = fmt.Errorf("Timeout!")
return
msg := "Timeout occurred while detecting"
Log.Error(msg)
for servername := range config.Conf.Servers {
found := false
for _, o := range oses {
if servername == o.getServerInfo().ServerName {
found = true
break
}
}
if !found {
Log.Errorf("Failed to detect. servername: %s", servername)
}
}
return oses, fmt.Errorf(msg)
}
}
errorOccurred := false
for _, osi := range oses {
if errs := osi.getErrs(); 0 < len(errs) {
errorOccurred = true
Log.Errorf("Some errors occurred on %s",
osi.getServerInfo().ServerName)
for _, err := range errs {
Log.Error(err)
}
}
}
if errorOccurred {
return oses, fmt.Errorf("Some errors occurred")
}
return
}
func detectContainerOSes() (oses []osTypeInterface, err error) {
osTypesChan := make(chan []osTypeInterface, len(servers))
defer close(osTypesChan)
for _, s := range servers {
go func(s osTypeInterface) {
osTypesChan <- detectContainerOSesOnServer(s)
}(s)
}
timeout := time.After(300 * time.Second)
for i := 0; i < len(config.Conf.Servers); i++ {
select {
case res := <-osTypesChan:
for _, osi := range res {
if 0 < len(osi.getErrs()) {
continue
}
sinfo := osi.getServerInfo()
Log.Infof("Detected %s/%s on %s: %s",
sinfo.Container.ContainerID, sinfo.Container.Name,
sinfo.ServerName, osi.getDistributionInfo())
}
oses = append(oses, res...)
case <-timeout:
msg := "Timeout occurred while detecting"
Log.Error(msg)
for servername := range config.Conf.Servers {
found := false
for _, o := range oses {
if servername == o.getServerInfo().ServerName {
found = true
break
}
}
if !found {
Log.Errorf("Failed to detect. servername: %s", servername)
}
}
return oses, fmt.Errorf(msg)
}
}
errorOccurred := false
for _, osi := range oses {
if errs := osi.getErrs(); 0 < len(errs) {
errorOccurred = true
Log.Errorf("Some errors occurred on %s",
osi.getServerInfo().ServerName)
for _, err := range errs {
Log.Error(err)
}
}
}
if errorOccurred {
return oses, fmt.Errorf("Some errors occurred")
}
return
}
func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeInterface) {
containerHostInfo := containerHost.getServerInfo()
if len(containerHostInfo.Containers) == 0 {
return
}
running, err := containerHost.runningContainers()
if err != nil {
containerHost.setErrs([]error{fmt.Errorf(
"Failed to get running containers on %s. err: %s",
containerHost.getServerInfo().ServerName, err)})
return append(oses, containerHost)
}
if containerHostInfo.Containers[0] == "${running}" {
for _, containerInfo := range running {
copied := containerHostInfo
copied.SetContainer(config.Container{
ContainerID: containerInfo.ContainerID,
Name: containerInfo.Name,
})
os := detectOS(copied)
oses = append(oses, os)
}
return oses
}
exitedContainers, err := containerHost.exitedContainers()
if err != nil {
containerHost.setErrs([]error{fmt.Errorf(
"Failed to get exited containers on %s. err: %s",
containerHost.getServerInfo().ServerName, err)})
return append(oses, containerHost)
}
var exited, unknown []string
for _, container := range containerHostInfo.Containers {
found := false
for _, c := range running {
if c.ContainerID == container || c.Name == container {
copied := containerHostInfo
copied.SetContainer(c)
os := detectOS(copied)
oses = append(oses, os)
found = true
break
}
}
if !found {
foundInExitedContainers := false
for _, c := range exitedContainers {
if c.ContainerID == container || c.Name == container {
exited = append(exited, container)
foundInExitedContainers = true
break
}
}
if !foundInExitedContainers {
unknown = append(unknown, container)
}
}
}
if 0 < len(exited) || 0 < len(unknown) {
containerHost.setErrs([]error{fmt.Errorf(
"Some containers on %s are exited or unknown. exited: %s, unknown: %s",
containerHost.getServerInfo().ServerName, exited, unknown)})
return append(oses, containerHost)
}
return oses
}
// Prepare installs requred packages to scan vulnerabilities.
func Prepare() []error {
return parallelSSHExec(func(o osTypeInterface) error {
@@ -151,7 +337,7 @@ func Prepare() []error {
// Scan scan
func Scan() []error {
if len(servers) == 0 {
return []error{fmt.Errorf("Not initialize yet.")}
return []error{fmt.Errorf("No server defined. Check the configuration")}
}
Log.Info("Check required packages for scanning...")
@@ -160,12 +346,12 @@ func Scan() []error {
return errs
}
Log.Info("Scanning vuluneable OS packages...")
Log.Info("Scanning vulnerable OS packages...")
if errs := scanPackages(); errs != nil {
return errs
}
Log.Info("Scanning vulnerable software specified in CPE...")
Log.Info("Scanning vulnerable software specified in the CPE...")
if errs := scanVulnByCpeName(); errs != nil {
return errs
}
@@ -201,7 +387,7 @@ 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)
return results, fmt.Errorf("Failed converting to model: %s", err)
}
results = append(results, r)
}

View File

@@ -94,11 +94,11 @@ func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []
if err != nil {
errs = append(errs, err)
} else {
logrus.Debug("Parallel SSH Success.")
logrus.Debug("Parallel SSH Success")
}
case <-time.After(time.Duration(timeout) * time.Second):
logrus.Errorf("Parallel SSH Timeout.")
errs = append(errs, fmt.Errorf("Timed out!"))
logrus.Errorf("Parallel SSH Timeout")
errs = append(errs, fmt.Errorf("Timed out"))
}
}
return
@@ -122,9 +122,9 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
} else {
logger = log[0]
}
c.SudoOpt.ExecBySudo = true
var err error
if sudo && c.User != "root" {
if sudo && c.User != "root" && !c.IsContainer() {
switch {
case c.SudoOpt.ExecBySudo:
cmd = fmt.Sprintf("echo %s | sudo -S %s", c.Password, cmd)
@@ -137,7 +137,15 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
// set pipefail option.
// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
logger.Debugf("Command: %s", strings.Replace(cmd, "\n", "", -1))
logger.Debugf("Command: %s",
strings.Replace(maskPassword(cmd, c.Password), "\n", "", -1))
if c.IsContainer() {
switch c.Container.Type {
case "", "docker":
cmd = fmt.Sprintf(`docker exec %s /bin/bash -c "%s"`, c.Container.ContainerID, cmd)
}
}
var client *ssh.Client
client, err = sshConnect(c)
@@ -189,7 +197,7 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
logger.Debugf(
"SSH executed. cmd: %s, status: %#v\nstdout: \n%s\nstderr: \n%s",
cmd, err, result.Stdout, result.Stderr)
maskPassword(cmd, c.Password), err, result.Stdout, result.Stderr)
return
}
@@ -225,7 +233,8 @@ func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
var auths = []ssh.AuthMethod{}
if auths, err = addKeyAuth(auths, c.KeyPath, c.KeyPassword); err != nil {
logrus.Fatalf("Faild to add keyAuth. err: %s", err)
logrus.Fatalf("Failed to add keyAuth. %s@%s:%s err: %s",
c.User, c.Host, c.Port, err)
}
if c.Password != "" {
@@ -237,12 +246,10 @@ func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
User: c.User,
Auth: auths,
}
// log.Debugf("config: %s", pp.Sprintf("%v", config))
notifyFunc := func(e error, t time.Duration) {
logrus.Warnf("Faild to ssh %s@%s:%s. err: %s, Retrying in %s...",
logrus.Warnf("Failed to ssh %s@%s:%s err: %s, Retrying in %s...",
c.User, c.Host, c.Port, e, t)
logrus.Debugf("sshConInfo: %s", pp.Sprintf("%v", c))
}
err = backoff.RetryNotify(func() error {
if client, err = ssh.Dial("tcp", c.Host+":"+c.Port, config); err != nil {
@@ -309,3 +316,8 @@ func parsePemBlock(block *pem.Block) (interface{}, error) {
return nil, fmt.Errorf("rtop: unsupported key type %q", block.Type)
}
}
// ref golang.org/x/crypto/ssh/keys.go#ParseRawPrivateKey.
func maskPassword(cmd, sudoPass string) string {
return strings.Replace(cmd, fmt.Sprintf("echo %s", sudoPass), "echo *****", -1)
}

View File

@@ -20,6 +20,8 @@ package util
import (
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/Sirupsen/logrus"
"github.com/rifflock/lfshook"
@@ -40,6 +42,9 @@ func NewCustomLogger(c config.ServerInfo) *logrus.Entry {
// File output
logDir := "/var/log/vuls"
if runtime.GOOS == "windows" {
logDir = filepath.Join(os.Getenv("APPDATA"), "vuls")
}
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)
@@ -48,11 +53,16 @@ func NewCustomLogger(c config.ServerInfo) *logrus.Entry {
whereami := "localhost"
if 0 < len(c.ServerName) {
whereami = fmt.Sprintf("%s:%s", c.ServerName, c.Port)
if 0 < len(c.Container.ContainerID) {
whereami = fmt.Sprintf(
"%s_%s", c.ServerName, c.Container.Name)
} else {
whereami = fmt.Sprintf("%s", c.ServerName)
}
}
if _, err := os.Stat(logDir); err == nil {
path := fmt.Sprintf("%s/%s.log", logDir, whereami)
path := filepath.Join(logDir, whereami)
log.Hooks.Add(lfshook.NewHook(lfshook.PathMap{
logrus.DebugLevel: path,
logrus.InfoLevel: path,

View File

@@ -17,8 +17,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
// Name.
// Name is Vuls
const Name string = "vuls"
// Version.
const Version string = "0.1.0"
// Version of Vuls
const Version string = "0.1.4"