Compare commits
233 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b264a564a | ||
|
|
227da93c13 | ||
|
|
f939041606 | ||
|
|
e5b1a0bef8 | ||
|
|
b9404d0880 | ||
|
|
d6f12868be | ||
|
|
b79e96f6cf | ||
|
|
b066cc819e | ||
|
|
4b669a0d49 | ||
|
|
5e9de5d91a | ||
|
|
da68b061e3 | ||
|
|
6c3802071f | ||
|
|
ad84f09bce | ||
|
|
04166632d3 | ||
|
|
376238b1ad | ||
|
|
4f0dbff059 | ||
|
|
f506e2b50a | ||
|
|
88d2fbf5e2 | ||
|
|
7fd8cc5449 | ||
|
|
d033463b34 | ||
|
|
740208cf74 | ||
|
|
0036c0b10e | ||
|
|
834c832390 | ||
|
|
5bc99dfd25 | ||
|
|
c92d2d064a | ||
|
|
a60c21323c | ||
|
|
34d6d6e709 | ||
|
|
f2ddafc718 | ||
|
|
267afdd15d | ||
|
|
48b7b82e33 | ||
|
|
84e5e5432e | ||
|
|
201e18eac2 | ||
|
|
3f3f0b1fec | ||
|
|
ca697c5038 | ||
|
|
5aeeb4e8b4 | ||
|
|
c285f9f587 | ||
|
|
d046608426 | ||
|
|
b91ed9cff5 | ||
|
|
185d85bfdd | ||
|
|
44b2c1464a | ||
|
|
a0762a0a6c | ||
|
|
2ad7660c09 | ||
|
|
d8b8c38182 | ||
|
|
1d50e5126a | ||
|
|
aa55e30358 | ||
|
|
f662de50db | ||
|
|
24c798ad3a | ||
|
|
0e304ae546 | ||
|
|
cd604cbfe7 | ||
|
|
b8e66d9df0 | ||
|
|
a2c738e57b | ||
|
|
ae16cd708c | ||
|
|
2ed0443f88 | ||
|
|
38f1c5075d | ||
|
|
55043a6348 | ||
|
|
1f6eb55b86 | ||
|
|
d9d8500484 | ||
|
|
0fca75c2db | ||
|
|
a7dcccbdf9 | ||
|
|
396eb5aec2 | ||
|
|
79d2076e09 | ||
|
|
693dca4ca2 | ||
|
|
4047076033 | ||
|
|
acb0b71f1b | ||
|
|
32d9352048 | ||
|
|
0246556f7c | ||
|
|
a17284681f | ||
|
|
adb66e3298 | ||
|
|
ad062d777d | ||
|
|
ffe1ff73a5 | ||
|
|
54f9202d74 | ||
|
|
ef3e173fb2 | ||
|
|
1aeec2ae51 | ||
|
|
1f50bfd801 | ||
|
|
d3466eabe5 | ||
|
|
8aff1af939 | ||
|
|
af35303432 | ||
|
|
0ef1a5a3ce | ||
|
|
e958bc8212 | ||
|
|
e0ca6e89d1 | ||
|
|
55d8ae124a | ||
|
|
5e28ec22e1 | ||
|
|
c3deb93489 | ||
|
|
a9aca94848 | ||
|
|
f3c06890dd | ||
|
|
d9d0e629fd | ||
|
|
17181405e3 | ||
|
|
c209564945 | ||
|
|
2da01db438 | ||
|
|
8c4913d411 | ||
|
|
e7ffc24844 | ||
|
|
259f23f6ee | ||
|
|
0de38b99c2 | ||
|
|
1044fb8574 | ||
|
|
e5bfa1bd6f | ||
|
|
a29b2a2ad9 | ||
|
|
b6899ce461 | ||
|
|
32c11af07c | ||
|
|
6ff55d24d0 | ||
|
|
055aacd7f6 | ||
|
|
5ecf58fd56 | ||
|
|
8a9106052f | ||
|
|
91264547c9 | ||
|
|
3190b877ae | ||
|
|
f8a8cc4676 | ||
|
|
93ee329315 | ||
|
|
b45163388d | ||
|
|
6029784f76 | ||
|
|
058ab55a6f | ||
|
|
1005d241b8 | ||
|
|
33b1ccba67 | ||
|
|
a5549fb500 | ||
|
|
b057ed3e77 | ||
|
|
1e88cc10e7 | ||
|
|
2f8634383e | ||
|
|
86f9e5ce96 | ||
|
|
9ae42d647c | ||
|
|
54d6217b93 | ||
|
|
150b1c2406 | ||
|
|
51b6f1b5f3 | ||
|
|
3eae14cef6 | ||
|
|
cc6dc1ca69 | ||
|
|
7f2361f58c | ||
|
|
7cb02d77ae | ||
|
|
52cc9b0cc0 | ||
|
|
d91bf61038 | ||
|
|
d5f81674f8 | ||
|
|
9381883835 | ||
|
|
f82e5a281d | ||
|
|
904e6241e4 | ||
|
|
ce39a3daf9 | ||
|
|
f2c7f74beb | ||
|
|
20db997fc2 | ||
|
|
7188e97444 | ||
|
|
6d528e741d | ||
|
|
d356e8370d | ||
|
|
5e336b5928 | ||
|
|
787ad0629b | ||
|
|
53e4adf24e | ||
|
|
6af811d63e | ||
|
|
359dab3380 | ||
|
|
97a8e6e965 | ||
|
|
8ea699aa08 | ||
|
|
7d924d2b0c | ||
|
|
3c85613ada | ||
|
|
c536d26db3 | ||
|
|
4350ff2692 | ||
|
|
0b9a1e7bb4 | ||
|
|
714ad18fa0 | ||
|
|
f81f785813 | ||
|
|
76c32af46f | ||
|
|
cd108263e1 | ||
|
|
093c47b59c | ||
|
|
56a40ec51a | ||
|
|
1337be2b84 | ||
|
|
eecd2c60f5 | ||
|
|
da071cb120 | ||
|
|
012cfa3cbe | ||
|
|
21180847dc | ||
|
|
9e9e538846 | ||
|
|
66025b1ae2 | ||
|
|
5999361358 | ||
|
|
e8699d1cb7 | ||
|
|
9292448e73 | ||
|
|
d7e156613d | ||
|
|
c3604aa66d | ||
|
|
49dd12fef3 | ||
|
|
5e037b1743 | ||
|
|
ebc79805ed | ||
|
|
c37e56e51d | ||
|
|
28a93c02e6 | ||
|
|
0996c58894 | ||
|
|
56ecf32565 | ||
|
|
416fb3c937 | ||
|
|
d48b8315c9 | ||
|
|
7c6d1eb585 | ||
|
|
fae04dce81 | ||
|
|
86a5433312 | ||
|
|
d9cf63a9fe | ||
|
|
88bf643363 | ||
|
|
e0b680b305 | ||
|
|
d6356408b8 | ||
|
|
4d28de17b4 | ||
|
|
fdd918d970 | ||
|
|
da16f9673e | ||
|
|
b02b7c9081 | ||
|
|
ea82149dbe | ||
|
|
9d64f039ab | ||
|
|
cd9cbd795b | ||
|
|
929d561de8 | ||
|
|
245abe5b6b | ||
|
|
768364fc77 | ||
|
|
60a3e9532a | ||
|
|
dcd6ba0a82 | ||
|
|
9f2dc2c6a3 | ||
|
|
7498a540d4 | ||
|
|
26ae01d960 | ||
|
|
f72781c30c | ||
|
|
21e957159d | ||
|
|
a66b425da0 | ||
|
|
804fffd009 | ||
|
|
ac77cc1f87 | ||
|
|
d0d360a6e7 | ||
|
|
9708533565 | ||
|
|
ac98b908e3 | ||
|
|
9bacd98577 | ||
|
|
d750205f31 | ||
|
|
b4d0aa7532 | ||
|
|
3e846233a3 | ||
|
|
1a943776c3 | ||
|
|
57ef45ebcd | ||
|
|
b64115f283 | ||
|
|
018eb29ce5 | ||
|
|
77c7d2fe26 | ||
|
|
336b72bbca | ||
|
|
0deb1032cd | ||
|
|
34c5644e63 | ||
|
|
1f80738bef | ||
|
|
66501663a0 | ||
|
|
f677939975 | ||
|
|
c465faeb6c | ||
|
|
6a6c7bf8a4 | ||
|
|
d19afe665f | ||
|
|
c62ca7c645 | ||
|
|
855b48f0c9 | ||
|
|
555e34d035 | ||
|
|
6b12ff35cd | ||
|
|
d9813e822f | ||
|
|
26273e7387 | ||
|
|
b52f0120ff | ||
|
|
76ade4c3b4 | ||
|
|
110d74a91e | ||
|
|
1819edf724 |
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,9 +1,12 @@
|
||||
vuls
|
||||
.vscode
|
||||
*.txt
|
||||
*.json
|
||||
*.sqlite3
|
||||
.gitmodules
|
||||
coverage.out
|
||||
issues/
|
||||
*.txt
|
||||
vendor/
|
||||
log/
|
||||
.gitmodules
|
||||
vuls
|
||||
*.sqlite3
|
||||
results/
|
||||
*config.toml
|
||||
|
||||
168
CHANGELOG.md
168
CHANGELOG.md
@@ -1,5 +1,169 @@
|
||||
# Change Log
|
||||
|
||||
0.1.0 (2013-03-23)
|
||||
## [v0.1.5](https://github.com/future-architect/vuls/tree/v0.1.5) (2016-08-16)
|
||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.4...v0.1.5)
|
||||
|
||||
Initial public release
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Enable to scan without running go-cve-dictionary as server mode [\#84](https://github.com/future-architect/vuls/issues/84)
|
||||
- Support high-speed scanning for CentOS [\#138](https://github.com/future-architect/vuls/pull/138) ([tai-ga](https://github.com/tai-ga))
|
||||
- Add configtest subcommand. skip un-ssh-able servers. [\#134](https://github.com/future-architect/vuls/pull/134) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Support -report-azure-blob option [\#130](https://github.com/future-architect/vuls/pull/130) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add optional key-values that will be outputted to JSON in config [\#117](https://github.com/future-architect/vuls/pull/117) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Change dir structure [\#115](https://github.com/future-architect/vuls/pull/115) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add some validation of loading config. user, host and port [\#113](https://github.com/future-architect/vuls/pull/113) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Support scanning with external ssh command [\#101](https://github.com/future-architect/vuls/pull/101) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Detect Platform and get instance-id of amazon ec2 [\#95](https://github.com/future-architect/vuls/pull/95) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add -report-s3 option [\#92](https://github.com/future-architect/vuls/pull/92) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Added FreeBSD support. [\#90](https://github.com/future-architect/vuls/pull/90) ([justyntemme](https://github.com/justyntemme))
|
||||
- Add glide files for vendoring [\#89](https://github.com/future-architect/vuls/pull/89) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix README, change -cvedbpath to -cve-dictionary-dbpath \#84 [\#85](https://github.com/future-architect/vuls/pull/85) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add option for it get cve detail from cve.sqlite3. [\#81](https://github.com/future-architect/vuls/pull/81) ([ymd38](https://github.com/ymd38))
|
||||
- Add -report-text option, Fix small bug of report in japanese [\#78](https://github.com/future-architect/vuls/pull/78) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add JSONWriter, Fix CVE sort order of report [\#77](https://github.com/future-architect/vuls/pull/77) ([kotakanbe](https://github.com/kotakanbe))
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Docker: Panic [\#76](https://github.com/future-architect/vuls/issues/76)
|
||||
- Fix apt command to scan correctly when system locale is not english [\#149](https://github.com/future-architect/vuls/pull/149) ([kit494way](https://github.com/kit494way))
|
||||
- Disable -ask-sudo-password for security reasons [\#148](https://github.com/future-architect/vuls/pull/148) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix no tty error while executing with -external-ssh option [\#143](https://github.com/future-architect/vuls/pull/143) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- wrong log packages [\#141](https://github.com/future-architect/vuls/pull/141) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
|
||||
- Fix platform detection. [\#137](https://github.com/future-architect/vuls/pull/137) ([Rompei](https://github.com/Rompei))
|
||||
- Fix nil pointer when scan with -cve-dictionary-dbpath and cpeNames [\#111](https://github.com/future-architect/vuls/pull/111) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Remove vulndb file before pkg audit [\#110](https://github.com/future-architect/vuls/pull/110) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add error handling when unable to connect via ssh. status code: 255 [\#108](https://github.com/future-architect/vuls/pull/108) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Enable to detect vulnerabilities on FreeBSD [\#98](https://github.com/future-architect/vuls/pull/98) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix unknown format err while check-update on RHEL6.5 [\#93](https://github.com/future-architect/vuls/pull/93) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
|
||||
- Fix type of SMTP Port of discovery command's output [\#88](https://github.com/future-architect/vuls/pull/88) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix error msg when go-cve-dictionary is unavailable \#84 [\#86](https://github.com/future-architect/vuls/pull/86) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix error handling to avoid nil pointer err on debian [\#83](https://github.com/future-architect/vuls/pull/83) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix nil pointer while doing apt-cache policy on ubuntu \#76 [\#82](https://github.com/future-architect/vuls/pull/82) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- fix log import url [\#79](https://github.com/future-architect/vuls/pull/79) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
|
||||
- Fix error handling of gorequest [\#75](https://github.com/future-architect/vuls/pull/75) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix freezing forever when no args specified in TUI mode [\#73](https://github.com/future-architect/vuls/pull/73) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- mv version.go version/version.go to run main.go without compile [\#71](https://github.com/future-architect/vuls/pull/71) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- SSh password authentication failed on FreeBSD [\#99](https://github.com/future-architect/vuls/issues/99)
|
||||
- BUG: -o pipefail is not work on FreeBSD's /bin/sh. because it isn't bash [\#91](https://github.com/future-architect/vuls/issues/91)
|
||||
- Use ~/.ssh/config [\#62](https://github.com/future-architect/vuls/issues/62)
|
||||
- SSH ciphers [\#37](https://github.com/future-architect/vuls/issues/37)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Update README \#138 [\#144](https://github.com/future-architect/vuls/pull/144) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix a typo [\#142](https://github.com/future-architect/vuls/pull/142) ([dtan4](https://github.com/dtan4))
|
||||
- Remove unnecessary step in readme of docker setup [\#140](https://github.com/future-architect/vuls/pull/140) ([mikkame](https://github.com/mikkame))
|
||||
- Update logo [\#139](https://github.com/future-architect/vuls/pull/139) ([chanomaru](https://github.com/chanomaru))
|
||||
- Update README.ja.md to fix wrong tips. [\#135](https://github.com/future-architect/vuls/pull/135) ([a2atsu](https://github.com/a2atsu))
|
||||
- add tips about NVD JVN issue [\#133](https://github.com/future-architect/vuls/pull/133) ([a2atsu](https://github.com/a2atsu))
|
||||
- Fix README wrong links [\#129](https://github.com/future-architect/vuls/pull/129) ([aomoriringo](https://github.com/aomoriringo))
|
||||
- Add logo [\#126](https://github.com/future-architect/vuls/pull/126) ([chanomaru](https://github.com/chanomaru))
|
||||
- Improve setup/docker [\#125](https://github.com/future-architect/vuls/pull/125) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix scan command help [\#124](https://github.com/future-architect/vuls/pull/124) ([aomoriringo](https://github.com/aomoriringo))
|
||||
- added dockernized-vuls with vulsrepo [\#121](https://github.com/future-architect/vuls/pull/121) ([hikachan](https://github.com/hikachan))
|
||||
- Fix detect platform on azure and degital ocean [\#119](https://github.com/future-architect/vuls/pull/119) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Remove json marshall-indent [\#118](https://github.com/future-architect/vuls/pull/118) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Improve Readme.ja [\#116](https://github.com/future-architect/vuls/pull/116) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add architecture diag to README.md [\#114](https://github.com/future-architect/vuls/pull/114) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Rename linux.go to base.go [\#100](https://github.com/future-architect/vuls/pull/100) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Update README.md [\#74](https://github.com/future-architect/vuls/pull/74) ([yoshi-taka](https://github.com/yoshi-taka))
|
||||
- Refactoring debian.go [\#72](https://github.com/future-architect/vuls/pull/72) ([kotakanbe](https://github.com/kotakanbe))
|
||||
|
||||
## [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)
|
||||
|
||||
**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)*
|
||||
9
Makefile
9
Makefile
@@ -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:
|
||||
|
||||
224
README.fr.md
Normal file
224
README.fr.md
Normal file
@@ -0,0 +1,224 @@
|
||||
|
||||
# Vuls: VULnerability Scanner
|
||||
|
||||
[](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 English](https://github.com/future-architect/vuls/blob/master/README.md)
|
||||
[README en Japonais](https://github.com/future-architect/vuls/blob/master/README.ja.md)
|
||||
|
||||
[](https://asciinema.org/a/3y9zrf950agiko7klg8abvyck)
|
||||
|
||||

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

|
||||
|
||||
----
|
||||
|
||||
# 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 d’environnement 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).
|
||||
|
||||
## 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 -cve-dictionary-dbpath=$PWD/cve.sqlite3
|
||||
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
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
----
|
||||
|
||||
For more information see [README in English](https://github.com/future-architect/vuls/blob/master/README.md)
|
||||
1098
README.ja.md
1098
README.ja.md
File diff suppressed because it is too large
Load Diff
656
README.md
656
README.md
@@ -2,12 +2,17 @@
|
||||
# Vuls: VULnerability Scanner
|
||||
|
||||
[](http://goo.gl/forms/xm5KFo35tu)
|
||||
[](https://github.com/future-architect/vuls/blob/master/LICENSE.txt)
|
||||
|
||||
Vulnerability scanner for Linux, agentless, written in golang.
|
||||
|
||||

|
||||
|
||||
Vulnerability scanner for Linux/FreeBSD, 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)
|
||||
|
||||
[](https://asciinema.org/a/3y9zrf950agiko7klg8abvyck)
|
||||
|
||||
@@ -22,16 +27,16 @@ We have a slack team. [Join slack team](http://goo.gl/forms/xm5KFo35tu)
|
||||
For a system administrator, having to perform security vulnerability analysis and software update on a daily basis can be a burden.
|
||||
To avoid downtime in production environment, it is common for system administrator to choose not to use the automatic update option provided by package manager and to perform update manually.
|
||||
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.
|
||||
- System administrator will have to constantly watch out for any new vulnerabilities in NVD(National Vulnerability Database) or similar databases.
|
||||
- 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.
|
||||
- Informs users of the vulnerabilities that are related to the system.
|
||||
- Informs users of the servers that are affected.
|
||||
- Vulnerability detection is done automatically to prevent any oversight.
|
||||
- Report is generated on regular basis using CRON etc. to manage vulnerability.
|
||||
- Report is generated on regular basis using CRON or other methods. to manage vulnerability.
|
||||
|
||||

|
||||
|
||||
@@ -39,8 +44,8 @@ Vuls is a tool created to solve the problems listed above. It has the following
|
||||
|
||||
# Main Features
|
||||
|
||||
- Scan for any vulnerabilities in Linux Server
|
||||
- Supports Ubuntu, Debian, CentOS, Amazon Linux, RHEL
|
||||
- Scan for any vulnerabilities in Linux/FreeBSD Server
|
||||
- Supports Ubuntu, Debian, CentOS, Amazon Linux, RHEL, FreeBSD
|
||||
- Cloud, on-premise, Docker
|
||||
- Scan middleware that are not included in OS package management
|
||||
- Scan middleware, programming language libraries and framework for vulnerability
|
||||
@@ -60,9 +65,26 @@ Vuls is a tool created to solve the problems listed above. It has the following
|
||||
|
||||
----
|
||||
|
||||
# Hello Vuls
|
||||
# Setup Vuls
|
||||
|
||||
This tutorial will let you scan the vulnerabilities on the localhost with vuls.
|
||||
There are 3 ways to setup Vuls.
|
||||
|
||||
- Docker container
|
||||
Dockernized-Vuls with vulsrepo UI in it.
|
||||
You can run install and run Vuls on your machine with only a few commands.
|
||||
see https://github.com/future-architect/vuls/tree/master/setup/docker
|
||||
|
||||
- Chef
|
||||
see https://github.com/sadayuki-matsuno/vuls-cookbook
|
||||
|
||||
- Manually
|
||||
Hello Vuls Tutorial shows how to setup vuls manually.
|
||||
|
||||
----
|
||||
|
||||
# Tutorial: Hello Vuls
|
||||
|
||||
This tutorial will let you scan the vulnerabilities on the localhost with Vuls.
|
||||
This can be done in the following steps.
|
||||
|
||||
1. Launch Amazon Linux
|
||||
@@ -78,17 +100,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,14 +117,18 @@ 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
|
||||
```
|
||||
|
||||
Vuls doesn't support SSH password authentication. So you have to use SSH key-based authentication.
|
||||
And also, SUDO with password is not supported for security reasons. So you have to define NOPASSWORD in /etc/sudoers on target servers.
|
||||
|
||||
## Step3. Install requirements
|
||||
|
||||
Vuls requires the following packages.
|
||||
|
||||
- sqlite
|
||||
- git
|
||||
- SQLite3
|
||||
- git v2
|
||||
- gcc
|
||||
- go v1.6
|
||||
- https://golang.org/doc/install
|
||||
@@ -129,7 +153,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,38 +164,37 @@ $ 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.
|
||||
If an error occurred while go get, check the following points.
|
||||
- Update Git
|
||||
- try [deploying with glide](https://github.com/future-architect/vuls/blob/master/README.md#deploy-with-glide).
|
||||
|
||||
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.
|
||||
```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. Deploy Vuls
|
||||
|
||||
## Step5. Deploy vuls
|
||||
|
||||
Launch a new terminal, SSH to the ec2 instance.
|
||||
Launch a new terminal and SSH to the ec2 instance.
|
||||
|
||||
go get
|
||||
```
|
||||
$ go get github.com/future-architect/vuls
|
||||
```
|
||||
|
||||
If an error occurred while go get, check the following points.
|
||||
- Update Git
|
||||
- try [deploying with glide](https://github.com/future-architect/vuls/blob/master/README.md#deploy-with-glide).
|
||||
|
||||
## Step6. Config
|
||||
|
||||
Create a config file(TOML format).
|
||||
Create a config file(TOML format).
|
||||
Then check the config.
|
||||
|
||||
```
|
||||
$ cat config.toml
|
||||
@@ -182,19 +205,26 @@ host = "172.31.4.82"
|
||||
port = "22"
|
||||
user = "ec2-user"
|
||||
keyPath = "/home/ec2-user/.ssh/id_rsa"
|
||||
|
||||
$ vuls configtest
|
||||
```
|
||||
|
||||
## Step7. Setting up target servers for vuls
|
||||
## Step7. Setting up target servers for Vuls
|
||||
|
||||
```
|
||||
$ 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)
|
||||
$ vuls scan -cve-dictionary-dbpath=$PWD/cve.sqlite3
|
||||
INFO[0000] Start scanning (config: /home/ec2-user/config.toml)
|
||||
INFO[0000] Start scanning
|
||||
INFO[0000] config: /home/ec2-user/config.toml
|
||||
INFO[0000] cve-dictionary: /home/ec2-user/cve.sqlite3
|
||||
|
||||
|
||||
... snip ...
|
||||
|
||||
@@ -234,20 +264,53 @@ $ vuls tui
|
||||

|
||||
|
||||
|
||||
----
|
||||
|
||||
# Setup Vuls in a Docker Container
|
||||
|
||||
see https://github.com/future-architect/vuls/tree/master/setup/docker
|
||||
|
||||
----
|
||||
|
||||
# Architecture
|
||||
|
||||

|
||||
|
||||
## 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 and 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
|
||||
- Send a report by Slack, Email
|
||||
- System operator can view the latest report by terminal
|
||||
- Scan vulnerabilities on the servers via SSH and create a list of the CVE ID
|
||||
- To scan Docker containers, Vuls connect via ssh to the Docker host and then `docker exec` to the containers. So, no need to run sshd daemon on the containers.
|
||||
- Fetch more detailed information of the detected CVE from go-cve-dictionary
|
||||
- Insert scan result into SQLite3
|
||||
- Send a report by Slack and Email
|
||||
- Show the latest report on your terminal
|
||||
|
||||

|
||||
|
||||
----
|
||||
# Performance Considerations
|
||||
|
||||
- on Ubuntu and Debian
|
||||
Vuls issues `apt-get changelog` for each upgradable packages and parse the changelog.
|
||||
`apt-get changelog` is slow and resource usage is heavy when there are many updatable packages on target server.
|
||||
|
||||
- on CentOS
|
||||
Vuls issues `yum update --changelog` to get changelogs of upgradable packages at once and parse the changelog.
|
||||
Scan speed is fast and resource usage is light.
|
||||
|
||||
- On Amazon, RHEL and FreeBSD
|
||||
High speed scan and resource usage is light because Vuls can get CVE IDs by using package manager(no need to parse a changelog).
|
||||
|
||||
| Distribution| Scan Speed | Resource Usage On Target Server |
|
||||
|:------------|:-------------------|:-------------|
|
||||
| Ubuntu | Slow | Heavy |
|
||||
| Debian | Slow | Heavy |
|
||||
| CentOS | Fast | Light |
|
||||
| Amazon | Fast | Light |
|
||||
| RHEL | Fast | Light |
|
||||
| FreeBSD | Fast | Light |
|
||||
|
||||
----
|
||||
|
||||
@@ -273,14 +336,15 @@ web/app server in the same configuration under the load balancer
|
||||
| Debian | 7, 8|
|
||||
| RHEL | 4, 5, 6, 7|
|
||||
| CentOS | 5, 6, 7|
|
||||
| Amazon Linux| All |
|
||||
| Amazon Linux| All|
|
||||
| FreeBSD | 10|
|
||||
|
||||
----
|
||||
|
||||
|
||||
# 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 display the template of config file(TOML format) to terminal.
|
||||
|
||||
```
|
||||
$ vuls discover -help
|
||||
@@ -288,7 +352,7 @@ discover:
|
||||
discover 192.168.0.0/24
|
||||
```
|
||||
|
||||
## Exapmle
|
||||
## Example
|
||||
|
||||
```
|
||||
$ vuls discover 172.31.4.0/24
|
||||
@@ -297,14 +361,14 @@ $ 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"]
|
||||
|
||||
[mail]
|
||||
smtpAddr = "smtp.gmail.com"
|
||||
smtpPort = 465
|
||||
smtpPort = "465"
|
||||
user = "username"
|
||||
password = "password"
|
||||
from = "from@address.com"
|
||||
@@ -315,9 +379,14 @@ 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}"]
|
||||
#optional = [
|
||||
# ["key", "value"],
|
||||
#]
|
||||
|
||||
[servers]
|
||||
|
||||
@@ -325,12 +394,14 @@ 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}"]
|
||||
#optional = [
|
||||
# ["key", "value"],
|
||||
#]
|
||||
```
|
||||
|
||||
You can customize your configuration using this template.
|
||||
@@ -344,7 +415,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 +423,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.
|
||||
In the following example, the report will be sent to the #server1 and #server2.
|
||||
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]
|
||||
@@ -374,14 +445,14 @@ You can customize your configuration using this template.
|
||||
- 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.
|
||||
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
|
||||
smtpPort = "465"
|
||||
user = "username"
|
||||
password = "password"
|
||||
from = "from@address.com"
|
||||
@@ -390,16 +461,21 @@ 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}"]
|
||||
#optional = [
|
||||
# ["key", "value"],
|
||||
#]
|
||||
```
|
||||
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,18 +485,68 @@ 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}"]
|
||||
#optional = [
|
||||
# ["key", "value"],
|
||||
#]
|
||||
```
|
||||
|
||||
You can overwrite the default value specified in default section.
|
||||
Vuls supports multiple SSH authentication methods.
|
||||
|
||||
- host: IP address or hostname of target server
|
||||
- port: SSH Port number
|
||||
- user: SSH username
|
||||
- keyPath: SSH private key path
|
||||
- cpeNames: see [Usage: Scan vulnerability of non-OS package](https://github.com/future-architect/vuls#usage-scan-vulnerability-of-non-os-package)
|
||||
- containers: see [Usage: Scan Docker containers](https://github.com/future-architect/vuls#usage-scan-docker-containers)
|
||||
- optional: Add additional information to JSON report.
|
||||
|
||||
Vuls supports two types of SSH. One is native go implementation. The other is external SSH command. For details, see [-ssh-external option](https://github.com/future-architect/vuls#-ssh-external-option)
|
||||
|
||||
Multiple SSH authentication methods are supported.
|
||||
- SSH agent
|
||||
- SSH public key authentication (with password, empty password)
|
||||
- Password authentication
|
||||
- SSH public key authentication (with password and empty password)
|
||||
Password authentication is not supported.
|
||||
|
||||
----
|
||||
|
||||
# Usage: Configtest
|
||||
|
||||
Configtest subcommand check if vuls is able to connect via ssh to servers/containers defined in the config.toml.
|
||||
```
|
||||
$ vuls configtest --help
|
||||
configtest:
|
||||
configtest
|
||||
[-config=/path/to/config.toml]
|
||||
[-ask-key-password]
|
||||
[-ssh-external]
|
||||
[-debug]
|
||||
|
||||
[SERVER]...
|
||||
-ask-key-password
|
||||
Ask ssh privatekey password before scanning
|
||||
-config string
|
||||
/path/to/toml (default "/Users/kotakanbe/go/src/github.com/future-architect/vuls/config.toml")
|
||||
-debug
|
||||
debug mode
|
||||
-ssh-external
|
||||
Use external ssh command. Default: Use the Go native implementation
|
||||
```
|
||||
|
||||
And also, configtest subcommand checks sudo settings on target servers whether Vuls is able to SUDO with nopassword via SSH.
|
||||
Example of /etc/sudoers on target servers
|
||||
- CentOS, Amazon Linux, RedHat Enterprise Linux
|
||||
```
|
||||
vuls ALL=(root) NOPASSWD: /usr/bin/yum
|
||||
```
|
||||
- Ubuntu, Debian
|
||||
```
|
||||
vuls ALL=(root) NOPASSWD: /usr/bin/apt-get, /usr/bin/apt-cache
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
@@ -431,24 +557,29 @@ 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 | - |
|
||||
| RHEL | 4, 5, 6, 7 | - |
|
||||
| FreeBSD | 10 | - |
|
||||
|
||||
|
||||
```
|
||||
$ vuls prepare -help
|
||||
prepare:
|
||||
prepare [-config=/path/to/config.toml] [-debug]
|
||||
prepare
|
||||
[-config=/path/to/config.toml] [-debug]
|
||||
[-ask-key-password]
|
||||
[SERVER]...
|
||||
|
||||
-ask-key-password
|
||||
Ask ssh privatekey password 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,21 +587,55 @@ prepare:
|
||||
# Usage: Scan
|
||||
|
||||
```
|
||||
|
||||
$ vuls scan -help
|
||||
scan:
|
||||
scan
|
||||
[-lang=en|ja]
|
||||
[-config=/path/to/config.toml]
|
||||
[-dbpath=/path/to/vuls.sqlite3]
|
||||
[--cve-dictionary-dbpath=/path/to/cve.sqlite3]
|
||||
[-cve-dictionary-url=http://127.0.0.1:1323]
|
||||
[-cvss-over=7]
|
||||
[-report-slack]
|
||||
[-ignore-unscored-cves]
|
||||
[-ssh-external]
|
||||
[-report-azure-blob]
|
||||
[-report-json]
|
||||
[-report-mail]
|
||||
[-report-s3]
|
||||
[-report-slack]
|
||||
[-report-text]
|
||||
[-http-proxy=http://192.168.0.1:8080]
|
||||
[-ask-key-password]
|
||||
[-debug]
|
||||
[-debug-sql]
|
||||
[-aws-profile=default]
|
||||
[-aws-region=us-west-2]
|
||||
[-aws-s3-bucket=bucket_name]
|
||||
[-azure-account=accout]
|
||||
[-azure-key=key]
|
||||
[-azure-container=container]
|
||||
[SERVER]...
|
||||
|
||||
|
||||
-ask-key-password
|
||||
Ask ssh privatekey password before scanning
|
||||
-aws-profile string
|
||||
AWS Profile to use (default "default")
|
||||
-aws-region string
|
||||
AWS Region to use (default "us-east-1")
|
||||
-aws-s3-bucket string
|
||||
S3 bucket name
|
||||
-azure-account string
|
||||
Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
|
||||
-azure-container string
|
||||
Azure storage container name
|
||||
-azure-key string
|
||||
Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
|
||||
-config string
|
||||
/path/to/toml (default "$PWD/config.toml")
|
||||
-cve-dictionary-dbpath string
|
||||
/path/to/sqlite3 (For get cve detail from cve.sqlite3)
|
||||
-cve-dictionary-url string
|
||||
http://CVE.Dictionary (default "http://127.0.0.1:1323")
|
||||
-cvss-over float
|
||||
@@ -483,54 +648,183 @@ 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-json
|
||||
Write report to JSON files ($PWD/results/current)
|
||||
-report-mail
|
||||
Email report
|
||||
Send report via Email
|
||||
-report-s3
|
||||
Write report to S3 (bucket/yyyyMMdd_HHmm)
|
||||
-report-slack
|
||||
Slack report
|
||||
Send report via Slack
|
||||
-report-text
|
||||
Write report to text files ($PWD/results/current)
|
||||
-ssh-external
|
||||
Use external ssh command. Default: Use the Go native implementation
|
||||
-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)
|
||||
|
||||
```
|
||||
|
||||
## example
|
||||
## -ssh-external option
|
||||
|
||||
Run go-cve-dictionary as server mode before scanning.
|
||||
Vuls supports different types of SSH.
|
||||
|
||||
By Defaut, using a native Go implementation from crypto/ssh.
|
||||
This is useful in situations where you may not have access to traditional UNIX tools.
|
||||
|
||||
To use external ssh command, specify this option.
|
||||
This is useful If you want to use ProxyCommand or chiper algorithm of SSH that is not supported by native go implementation.
|
||||
Don't forget to add below line to /etc/sudoers on the target servers. (username: vuls)
|
||||
```
|
||||
$ go-cve-dictionary server
|
||||
Defaults:vuls !requiretty
|
||||
```
|
||||
|
||||
### Scan all servers defined in config file
|
||||
|
||||
## -ask-key-password option
|
||||
|
||||
| SSH key password | -ask-key-password | |
|
||||
|:-----------------|:-------------------|:----|
|
||||
| empty password | - | |
|
||||
| with password | required | or use ssh-agent |
|
||||
|
||||
## -report-json , -report-text option
|
||||
|
||||
At the end of the scan, scan results will be available in the `$PWD/result/current/` directory.
|
||||
`all.(json|txt)` includes the scan results of all servres and `servername.(json|txt)` includes the scan result of the server.
|
||||
|
||||
## Example: 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-key-password \
|
||||
-cve-dictionary-dbpath=$PWD/cve.sqlite3
|
||||
```
|
||||
With this sample command, it will ..
|
||||
- Ask 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
|
||||
- Print scan result to terminal
|
||||
|
||||
### Scan specific servers
|
||||
## Example: Scan specific servers
|
||||
```
|
||||
$ vuls scan server1 server2
|
||||
$ vuls scan \
|
||||
-cve-dictionary-dbpath=$PWD/cve.sqlite3 \
|
||||
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)
|
||||
- Scan only 2 servers (server1, server2)
|
||||
- Print scan result to terminal
|
||||
|
||||
## Example: Put results in S3 bucket
|
||||
To put results in S3 bucket, configure following settings in AWS before scanning.
|
||||
- Create S3 bucket. see [Creating a Bucket](http://docs.aws.amazon.com/AmazonS3/latest/UG/CreatingaBucket.html)
|
||||
- Create access key. The access key must have read and write access to the AWS S3 bucket. see [Managing Access Keys for IAM Users](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html)
|
||||
- Configure the security credentials. see [Configuring the AWS Command Line Interface](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html)
|
||||
|
||||
```
|
||||
$ vuls scan \
|
||||
-cve-dictionary-dbpath=$PWD/cve.sqlite3 \
|
||||
-report-s3 \
|
||||
-aws-region=ap-northeast-1 \
|
||||
-aws-s3-bucket=vuls \
|
||||
-aws-profile=default
|
||||
```
|
||||
With this sample command, it will ..
|
||||
- Use SSH Key-Based authentication with empty password (without -ask-key-password option)
|
||||
- Scan all servers defined in config file
|
||||
- Put scan result(JSON) in S3 bucket. The bucket name is "vuls" in ap-northeast-1 and profile is "default"
|
||||
|
||||
## Example: Put results in Azure Blob storage
|
||||
|
||||
To put results in Azure Blob Storage, configure following settings in Azure before scanning.
|
||||
- Create a container
|
||||
|
||||
```
|
||||
$ vuls scan \
|
||||
-cve-dictionary-dbpath=$PWD/cve.sqlite3 \
|
||||
-report-azure-blob \
|
||||
-azure-container=vuls \
|
||||
-azure-account=test \
|
||||
-azure-key=access-key-string
|
||||
```
|
||||
With this sample command, it will ..
|
||||
- Use SSH Key-Based authentication with empty password (without -ask-key-password option)
|
||||
- Scan all servers defined in config file
|
||||
- Put scan result(JSON) in Azure Blob Storage. The container name is "vuls", storage account is "test" and accesskey is "access-key-string"
|
||||
|
||||
account and access key can be defined in environment variables.
|
||||
```
|
||||
$ export AZURE_STORAGE_ACCOUNT=test
|
||||
$ export AZURE_STORAGE_ACCESS_KEY=access-key-string
|
||||
$ vuls scan \
|
||||
-cve-dictionary-dbpath=$PWD/cve.sqlite3 \
|
||||
-report-azure-blob \
|
||||
-azure-container=vuls
|
||||
```
|
||||
|
||||
## Example: Add optional key-value pairs to JSON
|
||||
|
||||
Optional key-value can be outputted to JSON.
|
||||
The key-value in the default section will be overwritten by servers section's key-value.
|
||||
For instance, you can use this field for Azure ResourceGroup name, Azure VM Name and so on.
|
||||
|
||||
- config.toml
|
||||
```toml
|
||||
[default]
|
||||
optional = [
|
||||
["key1", "default_value"],
|
||||
["key3", "val3"],
|
||||
]
|
||||
|
||||
[servers.bsd]
|
||||
host = "192.168.11.11"
|
||||
user = "kanbe"
|
||||
optional = [
|
||||
["key1", "val1"],
|
||||
["key2", "val2"],
|
||||
]
|
||||
```
|
||||
|
||||
- bsd.json
|
||||
```json
|
||||
[
|
||||
{
|
||||
"ServerName": "bsd",
|
||||
"Family": "FreeBSD",
|
||||
"Release": "10.3-RELEASE",
|
||||
.... snip ...
|
||||
"Optional": [
|
||||
[ "key1", "val1" ],
|
||||
[ "key2", "val2" ],
|
||||
[ "key3", "val3" ]
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
# Usage: Scan vulnerability of non-OS package
|
||||
|
||||
It is possible to detect vulnerabilities something you compiled by yourself, the language libraries and the frameworks that have been registered in the [CPE](https://nvd.nist.gov/cpe.cfm).
|
||||
It is possible to detect vulnerabilities in non-OS packages, such as something you compiled by yourself, language libraries and frameworks, that have been registered in the [CPE](https://nvd.nist.gov/cpe.cfm).
|
||||
|
||||
- How to search CPE name by software name
|
||||
- [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,8 +838,110 @@ 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
|
||||
|
||||
# Usage: Update NVD Data.
|
||||
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 not running, Vuls gives up scanning with printing error message.
|
||||
```
|
||||
[servers]
|
||||
|
||||
[servers.172-31-4-82]
|
||||
host = "172.31.4.82"
|
||||
user = "ec2-user"
|
||||
keyPath = "/home/username/.ssh/id_rsa"
|
||||
containers = ["container_name_a", "4aa37a8b63b9"]
|
||||
```
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
[](https://asciinema.org/a/emi7y7docxr60bq080z10t7v8)
|
||||
|
||||
# Usage: go-cve-dictionary on different server
|
||||
|
||||
Run go-cve-dictionary as server mode before scanning on 192.168.10.1
|
||||
```
|
||||
$ go-cve-dictionary server -bind=192.168.10.1 -port=1323
|
||||
```
|
||||
|
||||
Run Vuls with -cve-dictionary-url option.
|
||||
|
||||
```
|
||||
$ vuls scan -cve-dictionary-url=http://192.168.0.1:1323
|
||||
```
|
||||
|
||||
# Usage: Update NVD Data
|
||||
|
||||
```
|
||||
$ go-cve-dictionary fetchnvd -h
|
||||
@@ -580,17 +976,70 @@ $ go-cve-dictionary fetchnvd -last2y
|
||||
|
||||
----
|
||||
|
||||
# Deploy With Glide
|
||||
|
||||
If an error occurred while go get, try deploying with glide.
|
||||
- Install [Glide](https://github.com/Masterminds/glide)
|
||||
- Deploy go-cve-dictionary
|
||||
```
|
||||
$ go get -d github.com/kotakanbe/go-cve-dictionary
|
||||
$ cd $GOPATH/src/github.com/kotakanbe/go-cve-dictionary
|
||||
$ glide install
|
||||
$ go install
|
||||
```
|
||||
- Deploy vuls
|
||||
```
|
||||
$ go get -d github.com/future-architect/vuls
|
||||
$ cd $GOPATH/src/github.com/future-architect/vuls
|
||||
$ glide install
|
||||
$ go install
|
||||
```
|
||||
- The binaries are created under $GOPARH/bin
|
||||
|
||||
----
|
||||
|
||||
# Update Vuls With Glide
|
||||
|
||||
- Update go-cve-dictionary
|
||||
```
|
||||
$ cd $GOPATH/src/github.com/kotakanbe/go-cve-dictionary
|
||||
$ git pull
|
||||
$ glide install
|
||||
$ go install
|
||||
```
|
||||
|
||||
- Update vuls
|
||||
```
|
||||
$ cd $GOPATH/src/github.com/future-architect/vuls
|
||||
$ git pull
|
||||
$ glide install
|
||||
$ go install
|
||||
```
|
||||
- The binaries are created under $GOPARH/bin
|
||||
- If the DB schema was changed, please specify new SQLite3 DB file.
|
||||
|
||||
---
|
||||
|
||||
# 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 no passphrase or ssh-agent.
|
||||
|
||||
- How to cross compile
|
||||
```bash
|
||||
$ cd /path/to/your/local-git-reporsitory/vuls
|
||||
@@ -598,13 +1047,32 @@ Use job scheduler like Cron (with -last2y option).
|
||||
```
|
||||
|
||||
- Logging
|
||||
Log wrote to under /var/log/vuls/
|
||||
Log is under /var/log/vuls/
|
||||
|
||||
- Debug
|
||||
Run with --debug, --sql-debug option.
|
||||
|
||||
- Adjusting 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
|
||||
|
||||
- [usiusi360/vulsrepo](https://github.com/usiusi360/vulsrepo)
|
||||
VulsRepo is visualized based on the json report output in vuls.
|
||||
Youtube
|
||||
[](https://www.youtube.com/watch?v=DIBPoik4owc)
|
||||
|
||||
|
||||
----
|
||||
|
||||
@@ -622,11 +1090,14 @@ kotakanbe ([@kotakanbe](https://twitter.com/kotakanbe)) created vuls and [these
|
||||
|
||||
# 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
|
||||
1. fork a repository: github.com/future-architect/vuls to github.com/you/repo
|
||||
2. get original code: go get github.com/future-architect/vuls
|
||||
3. work on original code
|
||||
4. add remote to your repo: git remote add myfork https://github.com/you/repo.git
|
||||
5. push your changes: git push myfork
|
||||
6. create a new Pull Request
|
||||
|
||||
- see [GitHub and Go: forking, pull requests, and go-getting](http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html)
|
||||
|
||||
----
|
||||
|
||||
@@ -640,6 +1111,3 @@ Please see [CHANGELOG](https://github.com/future-architect/vuls/blob/master/CHAN
|
||||
|
||||
Please see [LICENSE](https://github.com/future-architect/vuls/blob/master/LICENSE).
|
||||
|
||||
|
||||
[](https://bitdeli.com/free "Bitdeli Badge")
|
||||
|
||||
|
||||
21
commands/cmdutil.go
Normal file
21
commands/cmdutil.go
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
162
commands/configtest.go
Normal file
162
commands/configtest.go
Normal file
@@ -0,0 +1,162 @@
|
||||
/* 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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/google/subcommands"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/scan"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
// ConfigtestCmd is Subcommand
|
||||
type ConfigtestCmd struct {
|
||||
configPath string
|
||||
askKeyPassword bool
|
||||
sshExternal bool
|
||||
|
||||
debug bool
|
||||
}
|
||||
|
||||
// Name return subcommand name
|
||||
func (*ConfigtestCmd) Name() string { return "configtest" }
|
||||
|
||||
// Synopsis return synopsis
|
||||
func (*ConfigtestCmd) Synopsis() string { return "Test configuration" }
|
||||
|
||||
// Usage return usage
|
||||
func (*ConfigtestCmd) Usage() string {
|
||||
return `configtest:
|
||||
configtest
|
||||
[-config=/path/to/config.toml]
|
||||
[-ask-key-password]
|
||||
[-ssh-external]
|
||||
[-debug]
|
||||
|
||||
[SERVER]...
|
||||
`
|
||||
}
|
||||
|
||||
// SetFlags set flag
|
||||
func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
|
||||
wd, _ := os.Getwd()
|
||||
defaultConfPath := filepath.Join(wd, "config.toml")
|
||||
f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
|
||||
|
||||
f.BoolVar(&p.debug, "debug", false, "debug mode")
|
||||
|
||||
f.BoolVar(
|
||||
&p.askKeyPassword,
|
||||
"ask-key-password",
|
||||
false,
|
||||
"Ask ssh privatekey password before scanning",
|
||||
)
|
||||
|
||||
f.BoolVar(
|
||||
&p.sshExternal,
|
||||
"ssh-external",
|
||||
false,
|
||||
"Use external ssh command. Default: Use the Go native implementation")
|
||||
}
|
||||
|
||||
// Execute execute
|
||||
func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
|
||||
var keyPass string
|
||||
var err error
|
||||
if p.askKeyPassword {
|
||||
prompt := "SSH key password: "
|
||||
if keyPass, err = getPasswd(prompt); err != nil {
|
||||
logrus.Error(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
}
|
||||
|
||||
c.Conf.Debug = p.debug
|
||||
|
||||
err = c.Load(p.configPath, keyPass)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error loading %s, %s", p.configPath, err)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
var servernames []string
|
||||
if 0 < len(f.Args()) {
|
||||
servernames = f.Args()
|
||||
} else {
|
||||
stat, _ := os.Stdin.Stat()
|
||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
||||
bytes, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to read stdin: %s", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
fields := strings.Fields(string(bytes))
|
||||
if 0 < len(fields) {
|
||||
servernames = fields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target := make(map[string]c.ServerInfo)
|
||||
for _, arg := range servernames {
|
||||
found := false
|
||||
for servername, info := range c.Conf.Servers {
|
||||
if servername == arg {
|
||||
target[servername] = info
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
logrus.Errorf("%s is not in config", arg)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
}
|
||||
if 0 < len(servernames) {
|
||||
c.Conf.Servers = target
|
||||
}
|
||||
|
||||
// logger
|
||||
Log := util.NewCustomLogger(c.ServerInfo{})
|
||||
|
||||
Log.Info("Validating Config...")
|
||||
if !c.Conf.Validate() {
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
Log.Info("Detecting Server/Contianer OS... ")
|
||||
scan.InitServers(Log)
|
||||
|
||||
Log.Info("Checking sudo configuration... ")
|
||||
if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
|
||||
Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers. err: %s", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
scan.PrintSSHableServerNames()
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
@@ -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,14 +93,14 @@ 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"]
|
||||
|
||||
[mail]
|
||||
smtpAddr = "smtp.gmail.com"
|
||||
smtpPort = 465
|
||||
smtpPort = "465"
|
||||
user = "username"
|
||||
password = "password"
|
||||
from = "from@address.com"
|
||||
@@ -111,9 +111,14 @@ 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}"]
|
||||
#optional = [
|
||||
# ["key", "value"],
|
||||
#]
|
||||
|
||||
[servers]
|
||||
{{- $names:= .Names}}
|
||||
@@ -122,12 +127,14 @@ 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}"]
|
||||
#optional = [
|
||||
# ["key", "value"],
|
||||
#]
|
||||
{{end}}
|
||||
|
||||
`
|
||||
|
||||
108
commands/history.go
Normal file
108
commands/history.go
Normal 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
|
||||
}
|
||||
@@ -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,8 +59,12 @@ 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-key-password]
|
||||
[-debug]
|
||||
|
||||
[SERVER]...
|
||||
`
|
||||
}
|
||||
|
||||
@@ -65,27 +73,56 @@ 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,
|
||||
"[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASON. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication",
|
||||
)
|
||||
|
||||
f.BoolVar(
|
||||
&p.useUnattendedUpgrades,
|
||||
"use-unattended-upgrades",
|
||||
false,
|
||||
"[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 string
|
||||
var err error
|
||||
if p.askKeyPassword {
|
||||
prompt := "SSH key password: "
|
||||
if keyPass, err = getPasswd(prompt); err != nil {
|
||||
logrus.Error(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
}
|
||||
if p.askSudoPassword {
|
||||
logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication")
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
err := c.Load(p.configPath)
|
||||
err = c.Load(p.configPath, keyPass)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error loading %s, %s", p.configPath, err)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
logrus.Infof("Start Preparing (config: %s)", p.configPath)
|
||||
target := make(map[string]c.ServerInfo)
|
||||
for _, arg := range f.Args() {
|
||||
found := false
|
||||
@@ -112,16 +149,12 @@ func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{
|
||||
logger := util.NewCustomLogger(c.ServerInfo{})
|
||||
|
||||
logger.Info("Detecting OS... ")
|
||||
err = scan.InitServers(logger)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to init servers. err: %s", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
scan.InitServers(logger)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
257
commands/scan.go
257
commands/scan.go
@@ -19,7 +19,11 @@ package commands
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
c "github.com/future-architect/vuls/config"
|
||||
@@ -41,23 +45,43 @@ type ScanCmd struct {
|
||||
configPath string
|
||||
|
||||
dbpath string
|
||||
cvedbpath string
|
||||
cveDictionaryURL string
|
||||
cvssScoreOver float64
|
||||
httpProxy string
|
||||
|
||||
cvssScoreOver float64
|
||||
ignoreUnscoredCves bool
|
||||
|
||||
httpProxy string
|
||||
askSudoPassword bool
|
||||
askKeyPassword bool
|
||||
|
||||
// reporting
|
||||
reportSlack bool
|
||||
reportMail bool
|
||||
reportJSON bool
|
||||
reportText bool
|
||||
reportS3 bool
|
||||
reportAzureBlob bool
|
||||
|
||||
awsProfile string
|
||||
awsS3Bucket string
|
||||
awsRegion string
|
||||
|
||||
azureAccount string
|
||||
azureKey string
|
||||
azureContainer string
|
||||
|
||||
useYumPluginSecurity bool
|
||||
useUnattendedUpgrades bool
|
||||
|
||||
// reporting
|
||||
reportSlack bool
|
||||
reportMail bool
|
||||
sshExternal 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 {
|
||||
@@ -66,13 +90,29 @@ func (*ScanCmd) Usage() string {
|
||||
[-lang=en|ja]
|
||||
[-config=/path/to/config.toml]
|
||||
[-dbpath=/path/to/vuls.sqlite3]
|
||||
[-cve-dictionary-dbpath=/path/to/cve.sqlite3]
|
||||
[-cve-dictionary-url=http://127.0.0.1:1323]
|
||||
[-cvss-over=7]
|
||||
[-report-slack]
|
||||
[-ignore-unscored-cves]
|
||||
[-ssh-external]
|
||||
[-report-azure-blob]
|
||||
[-report-json]
|
||||
[-report-mail]
|
||||
[-report-s3]
|
||||
[-report-slack]
|
||||
[-report-text]
|
||||
[-http-proxy=http://192.168.0.1:8080]
|
||||
[-ask-key-password]
|
||||
[-debug]
|
||||
[-debug-sql]
|
||||
[-aws-profile=default]
|
||||
[-aws-region=us-west-2]
|
||||
[-aws-s3-bucket=bucket_name]
|
||||
[-azure-account=accout]
|
||||
[-azure-key=key]
|
||||
[-azure-container=container]
|
||||
|
||||
[SERVER]...
|
||||
`
|
||||
}
|
||||
|
||||
@@ -82,12 +122,20 @@ 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")
|
||||
|
||||
f.StringVar(
|
||||
&p.cvedbpath,
|
||||
"cve-dictionary-dbpath",
|
||||
"",
|
||||
"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
|
||||
|
||||
defaultURL := "http://127.0.0.1:1323"
|
||||
f.StringVar(
|
||||
&p.cveDictionaryURL,
|
||||
@@ -101,6 +149,18 @@ 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.BoolVar(
|
||||
&p.sshExternal,
|
||||
"ssh-external",
|
||||
false,
|
||||
"Use external ssh command. Default: Use the Go native implementation")
|
||||
|
||||
f.StringVar(
|
||||
&p.httpProxy,
|
||||
"http-proxy",
|
||||
@@ -108,37 +168,117 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
|
||||
"http://proxy-url:port (default: empty)",
|
||||
)
|
||||
|
||||
f.BoolVar(&p.reportSlack, "report-slack", false, "Slack report")
|
||||
f.BoolVar(&p.reportMail, "report-mail", false, "Email report")
|
||||
f.BoolVar(&p.reportSlack, "report-slack", false, "Send report via Slack")
|
||||
f.BoolVar(&p.reportMail, "report-mail", false, "Send report via Email")
|
||||
f.BoolVar(&p.reportJSON,
|
||||
"report-json",
|
||||
false,
|
||||
fmt.Sprintf("Write report to JSON files (%s/results/current)", wd),
|
||||
)
|
||||
f.BoolVar(&p.reportText,
|
||||
"report-text",
|
||||
false,
|
||||
fmt.Sprintf("Write report to text files (%s/results/current)", wd),
|
||||
)
|
||||
|
||||
f.BoolVar(&p.reportS3,
|
||||
"report-s3",
|
||||
false,
|
||||
"Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json)",
|
||||
)
|
||||
f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
|
||||
f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
|
||||
f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
|
||||
|
||||
f.BoolVar(&p.reportAzureBlob,
|
||||
"report-azure-blob",
|
||||
false,
|
||||
"Write report to S3 (container/yyyyMMdd_HHmm/servername.json)",
|
||||
)
|
||||
f.StringVar(&p.azureAccount, "azure-account", "", "Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified")
|
||||
f.StringVar(&p.azureKey, "azure-key", "", "Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified")
|
||||
f.StringVar(&p.azureContainer, "azure-container", "", "Azure storage container name")
|
||||
|
||||
f.BoolVar(
|
||||
&p.askKeyPassword,
|
||||
"ask-key-password",
|
||||
false,
|
||||
"Ask ssh privatekey password before scanning",
|
||||
)
|
||||
|
||||
f.BoolVar(
|
||||
&p.askSudoPassword,
|
||||
"ask-sudo-password",
|
||||
false,
|
||||
"[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication",
|
||||
)
|
||||
|
||||
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 string
|
||||
var err error
|
||||
if p.askKeyPassword {
|
||||
prompt := "SSH key password: "
|
||||
if keyPass, err = getPasswd(prompt); err != nil {
|
||||
logrus.Error(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
}
|
||||
if p.askSudoPassword {
|
||||
logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication")
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
logrus.Infof("Begin scannig (config: %s)", p.configPath)
|
||||
err := c.Load(p.configPath)
|
||||
err = c.Load(p.configPath, keyPass)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error loading %s, %s", p.configPath, err)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
logrus.Info("Start scanning")
|
||||
logrus.Infof("config: %s", p.configPath)
|
||||
if p.cvedbpath != "" {
|
||||
logrus.Infof("cve-dictionary: %s", p.cvedbpath)
|
||||
} else {
|
||||
logrus.Infof("cve-dictionary: %s", p.cveDictionaryURL)
|
||||
}
|
||||
|
||||
var servernames []string
|
||||
if 0 < len(f.Args()) {
|
||||
servernames = f.Args()
|
||||
} else {
|
||||
stat, _ := os.Stdin.Stat()
|
||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
||||
bytes, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to read stdin: %s", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
fields := strings.Fields(string(bytes))
|
||||
if 0 < len(fields) {
|
||||
servernames = fields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target := make(map[string]c.ServerInfo)
|
||||
for _, arg := range f.Args() {
|
||||
for _, arg := range servernames {
|
||||
found := false
|
||||
for servername, info := range c.Conf.Servers {
|
||||
if servername == arg {
|
||||
@@ -152,7 +292,7 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
}
|
||||
if 0 < len(f.Args()) {
|
||||
if 0 < len(servernames) {
|
||||
c.Conf.Servers = target
|
||||
}
|
||||
|
||||
@@ -165,7 +305,7 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
|
||||
// report
|
||||
reports := []report.ResultWriter{
|
||||
report.TextWriter{},
|
||||
report.StdoutWriter{},
|
||||
report.LogrusWriter{},
|
||||
}
|
||||
if p.reportSlack {
|
||||
@@ -174,9 +314,53 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
if p.reportMail {
|
||||
reports = append(reports, report.MailWriter{})
|
||||
}
|
||||
if p.reportJSON {
|
||||
reports = append(reports, report.JSONWriter{})
|
||||
}
|
||||
if p.reportText {
|
||||
reports = append(reports, report.TextFileWriter{})
|
||||
}
|
||||
if p.reportS3 {
|
||||
c.Conf.AwsRegion = p.awsRegion
|
||||
c.Conf.AwsProfile = p.awsProfile
|
||||
c.Conf.S3Bucket = p.awsS3Bucket
|
||||
if err := report.CheckIfBucketExists(); err != nil {
|
||||
Log.Errorf("Failed to access to the S3 bucket. err: %s", err)
|
||||
Log.Error("Ensure the bucket or check AWS config before scanning")
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
reports = append(reports, report.S3Writer{})
|
||||
}
|
||||
if p.reportAzureBlob {
|
||||
c.Conf.AzureAccount = p.azureAccount
|
||||
if c.Conf.AzureAccount == "" {
|
||||
c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
|
||||
}
|
||||
|
||||
c.Conf.AzureKey = p.azureKey
|
||||
if c.Conf.AzureKey == "" {
|
||||
c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
|
||||
}
|
||||
|
||||
c.Conf.AzureContainer = p.azureContainer
|
||||
if c.Conf.AzureContainer == "" {
|
||||
Log.Error("Azure storage container name is requied with --azure-container option")
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
if err := report.CheckIfAzureContainerExists(); err != nil {
|
||||
Log.Errorf("Failed to access to the Azure Blob container. err: %s", err)
|
||||
Log.Error("Ensure the container or check Azure config before scanning")
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
reports = append(reports, report.AzureBlobWriter{})
|
||||
}
|
||||
|
||||
c.Conf.DBPath = p.dbpath
|
||||
c.Conf.CveDBPath = p.cvedbpath
|
||||
c.Conf.CveDictionaryURL = p.cveDictionaryURL
|
||||
c.Conf.CvssScoreOver = p.cvssScoreOver
|
||||
c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
|
||||
c.Conf.SSHExternal = p.sshExternal
|
||||
c.Conf.HTTPProxy = p.httpProxy
|
||||
c.Conf.UseYumPluginSecurity = p.useYumPluginSecurity
|
||||
c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
|
||||
@@ -187,22 +371,27 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
}
|
||||
|
||||
if ok, err := cveapi.CveClient.CheckHealth(); !ok {
|
||||
Log.Errorf("CVE HTTP server is not running. %#v", cveapi.CveClient)
|
||||
Log.Fatal(err)
|
||||
Log.Errorf("CVE HTTP server is not running. err: %s", err)
|
||||
Log.Errorf("Run go-cve-dictionary as server mode or specify -cve-dictionary-dbpath option")
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
Log.Info("Detecting OS... ")
|
||||
err = scan.InitServers(Log)
|
||||
if err != nil {
|
||||
Log.Errorf("Failed to init servers. err: %s", err)
|
||||
Log.Info("Detecting Server/Contianer OS... ")
|
||||
scan.InitServers(Log)
|
||||
|
||||
Log.Info("Checking sudo configuration... ")
|
||||
if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
|
||||
Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers")
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
Log.Info("Detecting Platforms... ")
|
||||
scan.DetectPlatforms(Log)
|
||||
|
||||
Log.Info("Scanning vulnerabilities... ")
|
||||
if errs := scan.Scan(); 0 < len(errs) {
|
||||
for _, e := range errs {
|
||||
Log.Errorf("Failed to scan. err: %s.", e)
|
||||
Log.Errorf("Failed to scan. err: %s", e)
|
||||
}
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
@@ -213,15 +402,6 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
Log.Info("Reporting...")
|
||||
filtered := scanResults.FilterByCvssOver()
|
||||
for _, w := range reports {
|
||||
if err := w.Write(filtered); err != nil {
|
||||
Log.Fatalf("Failed to output report, err: %s", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
}
|
||||
|
||||
Log.Info("Insert to DB...")
|
||||
if err := db.OpenDB(); err != nil {
|
||||
Log.Errorf("Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
|
||||
@@ -237,5 +417,14 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
Log.Info("Reporting...")
|
||||
filtered := scanResults.FilterByCvssOver()
|
||||
for _, w := range reports {
|
||||
if err := w.Write(filtered); err != nil {
|
||||
Log.Fatalf("Failed to report, err: %s", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
}
|
||||
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
@@ -20,8 +20,13 @@ package commands
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/google/subcommands"
|
||||
@@ -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,27 @@ 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 {
|
||||
stat, _ := os.Stdin.Stat()
|
||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -41,9 +41,23 @@ type Config struct {
|
||||
|
||||
CveDictionaryURL string `valid:"url"`
|
||||
|
||||
CvssScoreOver float64
|
||||
HTTPProxy string `valid:"url"`
|
||||
DBPath string
|
||||
CvssScoreOver float64
|
||||
IgnoreUnscoredCves bool
|
||||
|
||||
SSHExternal bool
|
||||
|
||||
HTTPProxy string `valid:"url"`
|
||||
DBPath string
|
||||
CveDBPath string
|
||||
|
||||
AwsProfile string
|
||||
AwsRegion string
|
||||
S3Bucket string
|
||||
|
||||
AzureAccount string
|
||||
AzureKey string
|
||||
AzureContainer string
|
||||
|
||||
// CpeNames []string
|
||||
// SummaryMode bool
|
||||
UseYumPluginSecurity bool
|
||||
@@ -61,6 +75,13 @@ func (c Config) Validate() bool {
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.CveDBPath) != 0 {
|
||||
if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"SQLite3 DB(Cve Dictionary) path must be a *Absolute* file path. dbpath: %s", c.CveDBPath))
|
||||
}
|
||||
}
|
||||
|
||||
_, err := valid.ValidateStruct(c)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
@@ -188,8 +209,6 @@ func (c *SlackConf) Validate() (errs []error) {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// TODO check if slack configration is valid
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -197,25 +216,38 @@ func (c *SlackConf) Validate() (errs []error) {
|
||||
type ServerInfo struct {
|
||||
ServerName string
|
||||
User string
|
||||
Password string
|
||||
Host string
|
||||
Port string
|
||||
KeyPath string
|
||||
KeyPassword string
|
||||
SudoOpt SudoOption
|
||||
|
||||
CpeNames []string
|
||||
|
||||
// DebugLog Color
|
||||
LogMsgAnsiColor string
|
||||
// Container Names or IDs
|
||||
Containers []string
|
||||
|
||||
// Optional key-value set that will be outputted to JSON
|
||||
Optional [][]interface{}
|
||||
|
||||
// used internal
|
||||
LogMsgAnsiColor string // DebugLog Color
|
||||
Container Container
|
||||
Family string
|
||||
}
|
||||
|
||||
// SudoOption is flag of sudo option.
|
||||
type SudoOption struct {
|
||||
|
||||
// echo pass | sudo -S ls
|
||||
ExecBySudo bool
|
||||
|
||||
// echo pass | sudo sh -C 'ls'
|
||||
ExecBySudoSh bool
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -18,16 +18,13 @@ 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 string) error {
|
||||
var loader Loader
|
||||
loader = TOMLLoader{}
|
||||
|
||||
return loader.Load(path)
|
||||
return loader.Load(path, keyPass)
|
||||
}
|
||||
|
||||
// Loader is interface of concrete loader
|
||||
type Loader interface {
|
||||
Load(string) error
|
||||
Load(string, string) error
|
||||
}
|
||||
|
||||
@@ -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 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,24 +45,40 @@ func (c TOMLLoader) Load(pathToToml string) (err error) {
|
||||
Conf.Default = d
|
||||
servers := make(map[string]ServerInfo)
|
||||
|
||||
if keyPass != "" {
|
||||
d.KeyPassword = keyPass
|
||||
}
|
||||
|
||||
i := 0
|
||||
for name, v := range conf.Servers {
|
||||
s := ServerInfo{ServerName: name}
|
||||
s.User = v.User
|
||||
if s.User == "" {
|
||||
s.User = d.User
|
||||
|
||||
if 0 < len(v.KeyPassword) {
|
||||
log.Warn("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE.")
|
||||
}
|
||||
|
||||
s.Password = v.Password
|
||||
if s.Password == "" {
|
||||
s.Password = d.Password
|
||||
s := ServerInfo{ServerName: name}
|
||||
|
||||
switch {
|
||||
case v.User != "":
|
||||
s.User = v.User
|
||||
case d.User != "":
|
||||
s.User = d.User
|
||||
default:
|
||||
return fmt.Errorf("%s is invalid. User is empty", name)
|
||||
}
|
||||
|
||||
s.Host = v.Host
|
||||
if s.Host == "" {
|
||||
return fmt.Errorf("%s is invalid. host is empty", name)
|
||||
}
|
||||
|
||||
s.Port = v.Port
|
||||
if s.Port == "" {
|
||||
switch {
|
||||
case v.Port != "":
|
||||
s.Port = v.Port
|
||||
case d.Port != "":
|
||||
s.Port = d.Port
|
||||
default:
|
||||
s.Port = "22"
|
||||
}
|
||||
|
||||
s.KeyPath = v.KeyPath
|
||||
@@ -72,10 +88,11 @@ func (c TOMLLoader) Load(pathToToml string) (err error) {
|
||||
if s.KeyPath != "" {
|
||||
if _, err := os.Stat(s.KeyPath); err != nil {
|
||||
return fmt.Errorf(
|
||||
"config.toml is invalid. keypath: %s not exists", s.KeyPath)
|
||||
"%s is invalid. keypath: %s not exists", name, s.KeyPath)
|
||||
}
|
||||
}
|
||||
|
||||
// s.KeyPassword = keyPass
|
||||
s.KeyPassword = v.KeyPassword
|
||||
if s.KeyPassword == "" {
|
||||
s.KeyPassword = d.KeyPassword
|
||||
@@ -86,12 +103,31 @@ 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.Optional = v.Optional
|
||||
for _, dkv := range d.Optional {
|
||||
found := false
|
||||
for _, kv := range s.Optional {
|
||||
if dkv[0] == kv[0] {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
s.Optional = append(s.Optional, dkv)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -30,6 +30,8 @@ import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/util"
|
||||
cveconfig "github.com/kotakanbe/go-cve-dictionary/config"
|
||||
cvedb "github.com/kotakanbe/go-cve-dictionary/db"
|
||||
cve "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
)
|
||||
|
||||
@@ -46,17 +48,19 @@ func (api *cvedictClient) initialize() {
|
||||
}
|
||||
|
||||
func (api cvedictClient) CheckHealth() (ok bool, err error) {
|
||||
if config.Conf.CveDBPath != "" {
|
||||
log.Debugf("get cve-dictionary from sqlite3")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
api.initialize()
|
||||
url := fmt.Sprintf("%s/health", api.baseURL)
|
||||
var errs []error
|
||||
var resp *http.Response
|
||||
resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
|
||||
// resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
|
||||
if len(errs) > 0 || resp.StatusCode != 200 {
|
||||
return false, fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v",
|
||||
url,
|
||||
errs,
|
||||
)
|
||||
if len(errs) > 0 || resp == nil || resp.StatusCode != 200 {
|
||||
return false, fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v", url, errs)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -67,6 +71,10 @@ type response struct {
|
||||
}
|
||||
|
||||
func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDetails, err error) {
|
||||
if config.Conf.CveDBPath != "" {
|
||||
return api.FetchCveDetailsFromCveDB(cveIDs)
|
||||
}
|
||||
|
||||
api.baseURL = config.Conf.CveDictionaryURL
|
||||
reqChan := make(chan string, len(cveIDs))
|
||||
resChan := make(chan response, len(cveIDs))
|
||||
@@ -126,20 +134,43 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDet
|
||||
return
|
||||
}
|
||||
|
||||
func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
|
||||
func (api cvedictClient) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails cve.CveDetails, err error) {
|
||||
log.Debugf("open cve-dictionary db")
|
||||
cveconfig.Conf.DBPath = config.Conf.CveDBPath
|
||||
if err := cvedb.OpenDB(); err != nil {
|
||||
return []cve.CveDetail{},
|
||||
fmt.Errorf("Failed to open DB. err: %s", err)
|
||||
}
|
||||
for _, cveID := range cveIDs {
|
||||
cveDetail := cvedb.Get(cveID)
|
||||
if len(cveDetail.CveID) == 0 {
|
||||
cveDetails = append(cveDetails, cve.CveDetail{
|
||||
CveID: cveID,
|
||||
})
|
||||
} else {
|
||||
cveDetails = append(cveDetails, cveDetail)
|
||||
}
|
||||
}
|
||||
|
||||
// order by CVE ID desc
|
||||
sort.Sort(cveDetails)
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -196,8 +227,11 @@ type responseGetCveDetailByCpeName struct {
|
||||
}
|
||||
|
||||
func (api cvedictClient) FetchCveDetailsByCpeName(cpeName string) ([]cve.CveDetail, error) {
|
||||
api.baseURL = config.Conf.CveDictionaryURL
|
||||
if config.Conf.CveDBPath != "" {
|
||||
return api.FetchCveDetailsByCpeNameFromDB(cpeName)
|
||||
}
|
||||
|
||||
api.baseURL = config.Conf.CveDictionaryURL
|
||||
url, err := util.URLPathJoin(api.baseURL, "cpes")
|
||||
if err != nil {
|
||||
return []cve.CveDetail{}, err
|
||||
@@ -218,13 +252,13 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
|
||||
req = req.Send(fmt.Sprintf("%s=%s", key, query[key])).Type("json")
|
||||
}
|
||||
resp, body, errs = req.End()
|
||||
if len(errs) > 0 || resp.StatusCode != 200 {
|
||||
return fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
|
||||
if len(errs) > 0 || resp == nil || resp.StatusCode != 200 {
|
||||
return fmt.Errorf("HTTP POST error: %v, url: %s, resp: %v", errs, url, resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, t time.Duration) {
|
||||
log.Warnf("Failed to 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 {
|
||||
@@ -238,3 +272,13 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
|
||||
}
|
||||
return cveDetails, nil
|
||||
}
|
||||
|
||||
func (api cvedictClient) FetchCveDetailsByCpeNameFromDB(cpeName string) ([]cve.CveDetail, error) {
|
||||
log.Debugf("open cve-dictionary db")
|
||||
cveconfig.Conf.DBPath = config.Conf.CveDBPath
|
||||
if err := cvedb.OpenDB(); err != nil {
|
||||
return []cve.CveDetail{},
|
||||
fmt.Errorf("Failed to open DB. err: %s", err)
|
||||
}
|
||||
return cvedb.GetByCpeName(cpeName), nil
|
||||
}
|
||||
|
||||
70
db/db.go
70
db/db.go
@@ -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
|
||||
}
|
||||
|
||||
117
glide.lock
generated
Normal file
117
glide.lock
generated
Normal file
@@ -0,0 +1,117 @@
|
||||
hash: 9683c87b3cf998e7fac1b12c4a94bf2bd18cb5422e9108539811546e703a439a
|
||||
updated: 2016-07-12T16:20:45.462913061+09:00
|
||||
imports:
|
||||
- name: github.com/asaskevich/govalidator
|
||||
version: df81827fdd59d8b4fb93d8910b286ab7a3919520
|
||||
- name: github.com/aws/aws-sdk-go
|
||||
version: 90dec2183a5f5458ee79cbaf4b8e9ab910bc81a6
|
||||
subpackages:
|
||||
- aws
|
||||
- aws/credentials
|
||||
- aws/session
|
||||
- service/s3
|
||||
- aws/awserr
|
||||
- aws/client
|
||||
- aws/corehandlers
|
||||
- aws/defaults
|
||||
- aws/request
|
||||
- private/endpoints
|
||||
- aws/awsutil
|
||||
- aws/client/metadata
|
||||
- aws/signer/v4
|
||||
- private/protocol
|
||||
- private/protocol/restxml
|
||||
- private/waiter
|
||||
- aws/credentials/ec2rolecreds
|
||||
- aws/ec2metadata
|
||||
- private/protocol/rest
|
||||
- private/protocol/query
|
||||
- private/protocol/xml/xmlutil
|
||||
- private/protocol/query/queryutil
|
||||
- name: github.com/Azure/azure-sdk-for-go
|
||||
version: 58a13e378daf3b06e65925397185684b16321111
|
||||
subpackages:
|
||||
- storage
|
||||
- name: github.com/BurntSushi/toml
|
||||
version: ffaa107fbd880f6d18cd6fec9b511668dcad8639
|
||||
- name: github.com/cenkalti/backoff
|
||||
version: cdf48bbc1eb78d1349cbda326a4a037f7ba565c6
|
||||
- name: github.com/cheggaaa/pb
|
||||
version: 04b234c80d661c663dbcebd52fc7218fdacc6d0c
|
||||
- name: github.com/go-ini/ini
|
||||
version: cf53f9204df4fbdd7ec4164b57fa6184ba168292
|
||||
- name: github.com/google/subcommands
|
||||
version: 1c7173745a6001f67d8d96ab4e178284c77f7759
|
||||
- name: github.com/gosuri/uitable
|
||||
version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42
|
||||
subpackages:
|
||||
- util/strutil
|
||||
- util/wordwrap
|
||||
- name: github.com/howeyc/gopass
|
||||
version: 66487b23f2880ba32e185121d2cd51a338ea069a
|
||||
- name: github.com/jinzhu/gorm
|
||||
version: 613c0655691abb7691b70c5fda80a716d9e20b1b
|
||||
- name: github.com/jinzhu/inflection
|
||||
version: 8f4d3a0d04ce0b7c0cf3126fb98524246d00d102
|
||||
- name: github.com/jmespath/go-jmespath
|
||||
version: 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74
|
||||
- name: github.com/jroimartin/gocui
|
||||
version: 2dcda558bf18ec07c7065bf1eaf071b5305f7c0c
|
||||
- name: github.com/k0kubun/pp
|
||||
version: f5dce6ed0ccf6c350f1679964ff6b61f3d6d2033
|
||||
- name: github.com/kotakanbe/go-cve-dictionary
|
||||
version: 1a336b8ac785badfe89a175ee926d39574901232
|
||||
subpackages:
|
||||
- config
|
||||
- db
|
||||
- models
|
||||
- log
|
||||
- jvn
|
||||
- nvd
|
||||
- name: github.com/kotakanbe/go-pingscanner
|
||||
version: 58e188a3e4f6ab1a6371e33421e4502e26fa1e80
|
||||
- name: github.com/kotakanbe/logrus-prefixed-formatter
|
||||
version: f4f7d41649cf1e75e736884da8d05324aa76ea25
|
||||
- name: github.com/mattn/go-colorable
|
||||
version: 9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59
|
||||
- name: github.com/mattn/go-isatty
|
||||
version: 56b76bdf51f7708750eac80fa38b952bb9f32639
|
||||
- name: github.com/mattn/go-runewidth
|
||||
version: d6bea18f789704b5f83375793155289da36a3c7f
|
||||
- name: github.com/mattn/go-sqlite3
|
||||
version: 38ee283dabf11c9cbdb968eebd79b1fa7acbabe6
|
||||
- name: github.com/mgutz/ansi
|
||||
version: c286dcecd19ff979eeb73ea444e479b903f2cfcb
|
||||
- name: github.com/moul/http2curl
|
||||
version: b1479103caacaa39319f75e7f57fc545287fca0d
|
||||
- name: github.com/nsf/termbox-go
|
||||
version: c45773466a30b680355d6494cc8826113c93cd0f
|
||||
- name: github.com/parnurzeal/gorequest
|
||||
version: 6e8ad4ebdee4bec2934ed5afaaa1c7b877832a17
|
||||
- name: github.com/rifflock/lfshook
|
||||
version: 05a24e24fa8d3a2eca8c2baf23aa2d5a2c51490c
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: f3cfb454f4c209e6668c95216c4744b8fddb2356
|
||||
- name: golang.org/x/crypto
|
||||
version: c2f4947f41766b144bb09066e919466da5eddeae
|
||||
subpackages:
|
||||
- ssh
|
||||
- ssh/agent
|
||||
- ssh/terminal
|
||||
- curve25519
|
||||
- ed25519
|
||||
- ed25519/internal/edwards25519
|
||||
- name: golang.org/x/net
|
||||
version: f841c39de738b1d0df95b5a7187744f0e03d8112
|
||||
subpackages:
|
||||
- context
|
||||
- publicsuffix
|
||||
- name: golang.org/x/sys
|
||||
version: a408501be4d17ee978c04a618e7a1b22af058c0e
|
||||
subpackages:
|
||||
- unix
|
||||
- name: gopkg.in/alexcesaro/quotedprintable.v3
|
||||
version: 2caba252f4dc53eaf6b553000885530023f54623
|
||||
- name: gopkg.in/gomail.v2
|
||||
version: 81ebce5c23dfd25c6c67194b37d3dd3f338c98b1
|
||||
devImports: []
|
||||
39
glide.yaml
Normal file
39
glide.yaml
Normal file
@@ -0,0 +1,39 @@
|
||||
package: github.com/future-architect/vuls
|
||||
import:
|
||||
- package: github.com/Azure/azure-sdk-for-go
|
||||
subpackages:
|
||||
- storage
|
||||
- package: github.com/BurntSushi/toml
|
||||
- package: github.com/Sirupsen/logrus
|
||||
- package: github.com/asaskevich/govalidator
|
||||
- package: github.com/aws/aws-sdk-go
|
||||
subpackages:
|
||||
- aws
|
||||
- aws/credentials
|
||||
- aws/session
|
||||
- service/s3
|
||||
- package: github.com/cenkalti/backoff
|
||||
- package: github.com/google/subcommands
|
||||
- package: github.com/gosuri/uitable
|
||||
- package: github.com/howeyc/gopass
|
||||
- package: github.com/jinzhu/gorm
|
||||
- package: github.com/jroimartin/gocui
|
||||
- package: github.com/k0kubun/pp
|
||||
- package: github.com/kotakanbe/go-cve-dictionary
|
||||
subpackages:
|
||||
- config
|
||||
- db
|
||||
- models
|
||||
- package: github.com/kotakanbe/go-pingscanner
|
||||
- package: github.com/kotakanbe/logrus-prefixed-formatter
|
||||
- package: github.com/mattn/go-sqlite3
|
||||
- package: github.com/parnurzeal/gorequest
|
||||
- package: github.com/rifflock/lfshook
|
||||
- package: golang.org/x/crypto
|
||||
subpackages:
|
||||
- ssh
|
||||
- ssh/agent
|
||||
- package: golang.org/x/net
|
||||
subpackages:
|
||||
- context
|
||||
- package: gopkg.in/gomail.v2
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 77 KiB |
446
img/vuls-scan-flow.graphml
Normal file
446
img/vuls-scan-flow.graphml
Normal file
@@ -0,0 +1,446 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
||||
<!--Created by yEd 3.14.2-->
|
||||
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
||||
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
||||
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
||||
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
||||
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
||||
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
||||
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
||||
<key for="graphml" id="d7" yfiles.type="resources"/>
|
||||
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
||||
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
||||
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
||||
<graph edgedefault="directed" id="G">
|
||||
<data key="d0"/>
|
||||
<node id="n0">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.process">
|
||||
<y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="0.0"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="83.482421875" x="92.2587890625" y="18.93359375">Detect the OS<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n1">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.decision">
|
||||
<y:Geometry height="40.0" width="80.0" x="403.6849206349206" y="206.44247787610618"/>
|
||||
<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="18.0">
|
||||
<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n2">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.process">
|
||||
<y:Geometry height="90.44247787610618" width="268.0" x="309.6849206349206" y="86.0"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="60.53125" modelName="custom" textColor="#000000" visible="true" width="170.763671875" x="48.61816406250006" y="14.95561393805309">Get installed packages
|
||||
Debian/Ubuntu: dpkg-query
|
||||
Amazon/RHEL/CentOS: rpm
|
||||
FreeBSD: pkg<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n3">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.process">
|
||||
<y:Geometry height="56.0" width="268.0" x="10.0" y="287.8409153761062"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="260.83984375" x="3.580078125" y="11.8671875">Get upgradable packages
|
||||
Debian/Ubuntu: apt-get upgrade --dry-run<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n4">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.loopLimit">
|
||||
<y:Geometry height="51.10998735777497" width="137.19216182048035" x="75.40391908975982" y="376.28592169721867"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="131.751953125" x="2.7201043477401754" y="9.422181178887513">foreach
|
||||
upgradable packages<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="5.551115123125783E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n5">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.process">
|
||||
<y:Geometry height="56.0" width="268.0" x="10.0" y="459.8409153761062"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="213.619140625" x="27.1904296875" y="11.8671875">Parse changelog and get CVE IDs
|
||||
Debian/Ubuntu: aptitude changelog<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n6">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.loopLimitEnd">
|
||||
<y:Geometry height="50.0" width="137.0" x="75.5" y="545.8409153761062"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="55.24609375" x="40.876953125" y="15.93359375">end loop<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n7">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.process">
|
||||
<y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="625.8409153761062"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="194.904296875" x="36.5478515625" y="18.93359375">Select the CVE detail information<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n8">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.process">
|
||||
<y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="287.8409153761062"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" modelName="custom" textColor="#000000" visible="true" width="232.744140625" x="17.6279296875" y="4.80078125">Get CVE IDs by using package manager
|
||||
Amazon/RHEL: yum plugin security
|
||||
FreeBSD: pkg audit<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n9">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.dataBase">
|
||||
<y:Geometry height="64.1719342604298" width="111.96965865992411" x="687.3850119398792" y="807.0697396491782"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="48.56640625" x="31.701626204962054" y="23.019560880214726">Vuls DB<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="-8.881784197001252E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n10">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.dataBase">
|
||||
<y:Geometry height="65.22882427307195" width="136.83944374209864" x="411.5802781289507" y="687.385587863464"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="117.970703125" x="9.434370308549205" y="23.548005886535975">CVE DB (NVD / JVN)<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-8.326672684688674E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n11">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.process">
|
||||
<y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="716.4553275126422"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="126.396484375" x="70.8017578125" y="11.8671875">Insert results into DB
|
||||
Reporting<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n12">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.process">
|
||||
<y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="287.8409153761062"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="271.369140625" x="-1.6845703124999432" y="11.8671875">Get all changelogs by using package manager
|
||||
CentOS: yum update --changelog<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n13">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.process">
|
||||
<y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="373.8409153761062"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="205.52734375" x="31.236328125000057" y="18.93359375">Parse changelogs and get CVE IDs <y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<edge id="e0" source="n2" target="n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="45.22123893805309" tx="0.0" ty="-20.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="e1" source="n1" target="n3">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="-40.0" sy="0.0" tx="0.0" ty="-28.0">
|
||||
<y:Point x="144.0" y="226.44247787610618"/>
|
||||
</y:Path>
|
||||
<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="46.697265625" x="-56.79057374984495" y="-34.26562148912808">Debian
|
||||
Ubuntu<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="right" ratio="0.02215389573439544" 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="e2" source="n3" target="n4">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="28.0" tx="0.0" ty="-25.554993678887485"/>
|
||||
<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="e3" source="n4" target="n5">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="25.554993678887485" tx="0.0" ty="-28.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="e4" source="n5" target="n6">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="28.0" tx="0.0" ty="-25.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="n6" target="n7">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="68.5" sy="0.0" tx="0.0" ty="-28.0">
|
||||
<y:Point x="743.3698412698412" y="570.8409153761062"/>
|
||||
</y:Path>
|
||||
<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="n1" target="n8">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="40.0" sy="0.0" tx="0.0" ty="-28.0">
|
||||
<y:Point x="743.3698412698412" y="226.44247787610618"/>
|
||||
</y:Path>
|
||||
<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="46.3984375" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="51.806640625" x="10.125014629061297" y="-48.39843398912805">Amazon
|
||||
RHEL
|
||||
FreeBSD<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="left" ratio="0.022401276994204813" 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="e7" source="n8" target="n7">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.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="e8" source="n0" target="n2">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="28.0" tx="0.0" ty="-45.22123893805309"/>
|
||||
<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="e9" source="n7" target="n11">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.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="e10" source="n7" target="n10">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="-134.01566143419018" sy="6.159084623893818" tx="0.0" ty="-29.333162136535975">
|
||||
<y:Point x="480.0" y="660.0"/>
|
||||
</y:Path>
|
||||
<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="e11" source="n11" target="n9">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.86721713021484"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="none"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e12" source="n1" target="n12">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="20.0" tx="0.0" ty="-28.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="46.708984375" x="-53.35447755843876" y="11.632816010871807">CentOS<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="e13" source="n12" target="n13">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e14" source="n13" target="n7">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="134.00000000000006" sy="0.0" tx="0.0" ty="-28.0">
|
||||
<y:Point x="743.3698412698412" y="401.8409153761062"/>
|
||||
</y:Path>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
</graph>
|
||||
<data key="d7">
|
||||
<y:Resources/>
|
||||
</data>
|
||||
</graphml>
|
||||
BIN
img/vuls-scan-flow.png
Normal file
BIN
img/vuls-scan-flow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
BIN
img/vuls_icon.png
Normal file
BIN
img/vuls_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
img/vuls_logo.png
Normal file
BIN
img/vuls_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
BIN
img/vuls_logo_large.png
Normal file
BIN
img/vuls_logo_large.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
12
main.go
12
main.go
@@ -19,11 +19,13 @@ package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/future-architect/vuls/commands"
|
||||
"github.com/future-architect/vuls/version"
|
||||
"github.com/google/subcommands"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
@@ -37,8 +39,18 @@ func main() {
|
||||
subcommands.Register(&commands.TuiCmd{}, "tui")
|
||||
subcommands.Register(&commands.ScanCmd{}, "scan")
|
||||
subcommands.Register(&commands.PrepareCmd{}, "prepare")
|
||||
subcommands.Register(&commands.HistoryCmd{}, "history")
|
||||
subcommands.Register(&commands.ConfigtestCmd{}, "configtest")
|
||||
|
||||
var v = flag.Bool("v", false, "Show version")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *v {
|
||||
fmt.Printf("%s %s\n", version.Name, version.Version)
|
||||
os.Exit(int(subcommands.ExitSuccess))
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
os.Exit(int(subcommands.Execute(ctx)))
|
||||
}
|
||||
|
||||
133
models/models.go
133
models/models.go
@@ -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) {
|
||||
@@ -54,17 +72,69 @@ func (results ScanResults) FilterByCvssOver() (filtered ScanResults) {
|
||||
|
||||
// ScanResult has the result of scanned CVE information.
|
||||
type ScanResult struct {
|
||||
gorm.Model
|
||||
ScanHistoryID uint
|
||||
gorm.Model `json:"-"`
|
||||
ScanHistoryID uint `json:"-"`
|
||||
|
||||
ServerName string // TOML Section key
|
||||
// Hostname string
|
||||
Family string
|
||||
Release string
|
||||
|
||||
Container Container
|
||||
|
||||
Platform Platform
|
||||
|
||||
// Fqdn string
|
||||
// NWLinks []NWLink
|
||||
KnownCves []CveInfo
|
||||
UnknownCves []CveInfo
|
||||
|
||||
Optional [][]interface{} `gorm:"-"`
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -84,16 +154,19 @@ 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.
|
||||
type NWLink struct {
|
||||
gorm.Model
|
||||
ScanResultID uint
|
||||
gorm.Model `json:"-"`
|
||||
ScanResultID uint `json:"-"`
|
||||
|
||||
IPAddress string
|
||||
Netmask string
|
||||
@@ -114,13 +187,16 @@ func (c CveInfos) Swap(i, j int) {
|
||||
|
||||
func (c CveInfos) Less(i, j int) bool {
|
||||
lang := config.Conf.Lang
|
||||
if c[i].CveDetail.CvssScore(lang) == c[j].CveDetail.CvssScore(lang) {
|
||||
return c[i].CveDetail.CveID < c[j].CveDetail.CveID
|
||||
}
|
||||
return c[i].CveDetail.CvssScore(lang) > c[j].CveDetail.CvssScore(lang)
|
||||
}
|
||||
|
||||
// CveInfo has Cve Information.
|
||||
type CveInfo struct {
|
||||
gorm.Model
|
||||
ScanResultID uint
|
||||
gorm.Model `json:"-"`
|
||||
ScanResultID uint `json:"-"`
|
||||
|
||||
CveDetail cve.CveDetail
|
||||
Packages []PackageInfo
|
||||
@@ -130,8 +206,8 @@ type CveInfo struct {
|
||||
|
||||
// CpeName has CPE name
|
||||
type CpeName struct {
|
||||
gorm.Model
|
||||
CveInfoID uint
|
||||
gorm.Model `json:"-"`
|
||||
CveInfoID uint `json:"-"`
|
||||
|
||||
Name string
|
||||
}
|
||||
@@ -196,8 +272,8 @@ func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found boo
|
||||
|
||||
// PackageInfo has installed packages.
|
||||
type PackageInfo struct {
|
||||
gorm.Model
|
||||
CveInfoID uint
|
||||
gorm.Model `json:"-"`
|
||||
CveInfoID uint `json:"-"`
|
||||
|
||||
Name string
|
||||
Version string
|
||||
@@ -231,14 +307,31 @@ func (p PackageInfo) ToStringNewVersion() string {
|
||||
return str
|
||||
}
|
||||
|
||||
// DistroAdvisory has Amazon Linux AMI Security Advisory information.
|
||||
//TODO Rename to DistroAdvisory
|
||||
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
|
||||
type DistroAdvisory struct {
|
||||
gorm.Model
|
||||
CveInfoID uint
|
||||
gorm.Model `json:"-"`
|
||||
CveInfoID uint `json:"-"`
|
||||
|
||||
AdvisoryID string
|
||||
Severity string
|
||||
Issued time.Time
|
||||
Updated time.Time
|
||||
}
|
||||
|
||||
// Container has Container information
|
||||
type Container struct {
|
||||
gorm.Model `json:"-"`
|
||||
ScanResultID uint `json:"-"`
|
||||
|
||||
ContainerID string
|
||||
Name string
|
||||
}
|
||||
|
||||
// Platform has platform information
|
||||
type Platform struct {
|
||||
gorm.Model `json:"-"`
|
||||
ScanResultID uint `json:"-"`
|
||||
|
||||
Name string // aws or azure or gcp or other...
|
||||
InstanceID string
|
||||
}
|
||||
|
||||
140
report/azureblob.go
Normal file
140
report/azureblob.go
Normal file
@@ -0,0 +1,140 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/storage"
|
||||
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
// AzureBlobWriter writes results to AzureBlob
|
||||
type AzureBlobWriter struct{}
|
||||
|
||||
// CheckIfAzureContainerExists check the existence of Azure storage container
|
||||
func CheckIfAzureContainerExists() error {
|
||||
cli, err := getBlobClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ok, err := cli.ContainerExists(c.Conf.AzureContainer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("Container not found. Container: %s", c.Conf.AzureContainer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBlobClient() (storage.BlobStorageClient, error) {
|
||||
api, err := storage.NewBasicClient(c.Conf.AzureAccount, c.Conf.AzureKey)
|
||||
if err != nil {
|
||||
return storage.BlobStorageClient{}, err
|
||||
}
|
||||
return api.GetBlobService(), nil
|
||||
}
|
||||
|
||||
// Write results to Azure Blob storage
|
||||
func (w AzureBlobWriter) Write(scanResults []models.ScanResult) (err error) {
|
||||
reqChan := make(chan models.ScanResult, len(scanResults))
|
||||
resChan := make(chan bool)
|
||||
errChan := make(chan error, len(scanResults))
|
||||
defer close(resChan)
|
||||
defer close(errChan)
|
||||
defer close(reqChan)
|
||||
|
||||
timeout := time.After(10 * 60 * time.Second)
|
||||
concurrency := 10
|
||||
tasks := util.GenWorkers(concurrency)
|
||||
|
||||
go func() {
|
||||
for _, r := range scanResults {
|
||||
reqChan <- r
|
||||
}
|
||||
}()
|
||||
|
||||
for range scanResults {
|
||||
tasks <- func() {
|
||||
select {
|
||||
case sresult := <-reqChan:
|
||||
func(r models.ScanResult) {
|
||||
err := w.upload(r)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
resChan <- true
|
||||
}(sresult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errs := []error{}
|
||||
for i := 0; i < len(scanResults); i++ {
|
||||
select {
|
||||
case <-resChan:
|
||||
case err := <-errChan:
|
||||
errs = append(errs, err)
|
||||
case <-timeout:
|
||||
errs = append(errs, fmt.Errorf("Timeout while uploading to azure Blob"))
|
||||
}
|
||||
}
|
||||
|
||||
if 0 < len(errs) {
|
||||
return fmt.Errorf("Failed to upload json to Azure Blob: %v", errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w AzureBlobWriter) upload(res models.ScanResult) (err error) {
|
||||
cli, err := getBlobClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timestr := time.Now().Format("20060102_1504")
|
||||
name := ""
|
||||
if res.Container.ContainerID == "" {
|
||||
name = fmt.Sprintf("%s/%s.json", timestr, res.ServerName)
|
||||
} else {
|
||||
name = fmt.Sprintf("%s/%s_%s.json", timestr, res.ServerName, res.Container.Name)
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Marshal to JSON: %s", err)
|
||||
}
|
||||
|
||||
if err = cli.CreateBlockBlobFromReader(
|
||||
c.Conf.AzureContainer,
|
||||
name,
|
||||
uint64(len(jsonBytes)),
|
||||
bytes.NewReader(jsonBytes),
|
||||
map[string]string{},
|
||||
); err != nil {
|
||||
return fmt.Errorf("%s/%s, %s",
|
||||
c.Conf.AzureContainer, name, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -20,18 +20,43 @@ package report
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
// JSONWriter writes report as JSON format
|
||||
// JSONWriter writes results to file.
|
||||
type JSONWriter struct{}
|
||||
|
||||
func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
|
||||
var j []byte
|
||||
if j, err = json.MarshalIndent(scanResults, "", " "); err != nil {
|
||||
return
|
||||
|
||||
path, err := ensureResultDir()
|
||||
|
||||
var jsonBytes []byte
|
||||
if jsonBytes, err = json.Marshal(scanResults); err != nil {
|
||||
return fmt.Errorf("Failed to Marshal to JSON: %s", err)
|
||||
}
|
||||
all := filepath.Join(path, "all.json")
|
||||
if err := ioutil.WriteFile(all, jsonBytes, 0644); err != nil {
|
||||
return fmt.Errorf("Failed to write JSON. path: %s, err: %s", all, err)
|
||||
}
|
||||
|
||||
for _, r := range scanResults {
|
||||
jsonPath := ""
|
||||
if r.Container.ContainerID == "" {
|
||||
jsonPath = filepath.Join(path, fmt.Sprintf("%s.json", r.ServerName))
|
||||
} else {
|
||||
jsonPath = filepath.Join(path,
|
||||
fmt.Sprintf("%s_%s.json", r.ServerName, r.Container.Name))
|
||||
}
|
||||
|
||||
if jsonBytes, err = json.Marshal(r); err != nil {
|
||||
return fmt.Errorf("Failed to Marshal to JSON: %s", err)
|
||||
}
|
||||
if err := ioutil.WriteFile(jsonPath, jsonBytes, 0644); err != nil {
|
||||
return fmt.Errorf("Failed to write JSON. path: %s, err: %s", jsonPath, err)
|
||||
}
|
||||
}
|
||||
fmt.Println(string(j))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
112
report/s3.go
Normal file
112
report/s3.go
Normal file
@@ -0,0 +1,112 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
// CheckIfBucketExists check the existence of S3 bucket
|
||||
func CheckIfBucketExists() error {
|
||||
svc := getS3()
|
||||
result, err := svc.ListBuckets(&s3.ListBucketsInput{})
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"Failed to list buckets. err: %s, profile: %s, region: %s",
|
||||
err, c.Conf.AwsProfile, c.Conf.AwsRegion)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, bucket := range result.Buckets {
|
||||
if *bucket.Name == c.Conf.S3Bucket {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf(
|
||||
"Failed to find the buckets. profile: %s, region: %s, bukdet: %s",
|
||||
c.Conf.AwsProfile, c.Conf.AwsRegion, c.Conf.S3Bucket)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// S3Writer writes results to S3
|
||||
type S3Writer struct{}
|
||||
|
||||
func getS3() *s3.S3 {
|
||||
return s3.New(session.New(&aws.Config{
|
||||
Region: aws.String(c.Conf.AwsRegion),
|
||||
Credentials: credentials.NewSharedCredentials("", c.Conf.AwsProfile),
|
||||
}))
|
||||
}
|
||||
|
||||
// Write results to S3
|
||||
func (w S3Writer) Write(scanResults []models.ScanResult) (err error) {
|
||||
|
||||
var jsonBytes []byte
|
||||
if jsonBytes, err = json.Marshal(scanResults); err != nil {
|
||||
return fmt.Errorf("Failed to Marshal to JSON: %s", err)
|
||||
}
|
||||
|
||||
// http://docs.aws.amazon.com/sdk-for-go/latest/v1/developerguide/common-examples.title.html
|
||||
svc := getS3()
|
||||
timestr := time.Now().Format("20060102_1504")
|
||||
key := fmt.Sprintf("%s/%s", timestr, "all.json")
|
||||
_, err = svc.PutObject(&s3.PutObjectInput{
|
||||
Bucket: &c.Conf.S3Bucket,
|
||||
Key: &key,
|
||||
Body: bytes.NewReader(jsonBytes),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to upload data to %s/%s, %s", c.Conf.S3Bucket, key, err)
|
||||
}
|
||||
|
||||
for _, r := range scanResults {
|
||||
key := ""
|
||||
if r.Container.ContainerID == "" {
|
||||
key = fmt.Sprintf("%s/%s.json", timestr, r.ServerName)
|
||||
} else {
|
||||
key = fmt.Sprintf("%s/%s_%s.json", timestr, r.ServerName, r.Container.Name)
|
||||
}
|
||||
|
||||
if jsonBytes, err = json.Marshal(r); err != nil {
|
||||
return fmt.Errorf("Failed to Marshal to JSON: %s", err)
|
||||
}
|
||||
_, err = svc.PutObject(&s3.PutObjectInput{
|
||||
Bucket: &c.Conf.S3Bucket,
|
||||
Key: &key,
|
||||
Body: bytes.NewReader(jsonBytes),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to upload data to %s/%s, %s", c.Conf.S3Bucket, key, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -103,19 +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...)
|
||||
for _, cveInfo := range scanResult.KnownCves {
|
||||
cves := scanResult.KnownCves
|
||||
if !config.Conf.IgnoreUnscoredCves {
|
||||
cves = append(cves, scanResult.UnknownCves...)
|
||||
}
|
||||
|
||||
for _, cveInfo := range cves {
|
||||
cveID := cveInfo.CveDetail.CveID
|
||||
|
||||
curentPackages := []string{}
|
||||
@@ -176,16 +175,15 @@ func attachmentText(cveInfo models.CveInfo, osFamily string) string {
|
||||
|
||||
switch {
|
||||
case config.Conf.Lang == "ja" &&
|
||||
cveInfo.CveDetail.Jvn.ID != 0 &&
|
||||
0 < cveInfo.CveDetail.CvssScore("ja"):
|
||||
0 < cveInfo.CveDetail.Jvn.CvssScore():
|
||||
|
||||
jvn := cveInfo.CveDetail.Jvn
|
||||
return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
|
||||
cveInfo.CveDetail.CvssScore(config.Conf.Lang),
|
||||
jvn.Severity,
|
||||
fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, jvn.Vector),
|
||||
jvn.Vector,
|
||||
jvn.Title,
|
||||
jvn.CvssSeverity(),
|
||||
fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, jvn.CvssVector()),
|
||||
jvn.CvssVector(),
|
||||
jvn.CveTitle(),
|
||||
linkText,
|
||||
)
|
||||
|
||||
@@ -193,15 +191,15 @@ func attachmentText(cveInfo models.CveInfo, osFamily string) string {
|
||||
nvd := cveInfo.CveDetail.Nvd
|
||||
return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
|
||||
cveInfo.CveDetail.CvssScore(config.Conf.Lang),
|
||||
nvd.Severity(),
|
||||
nvd.CvssSeverity(),
|
||||
fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, nvd.CvssVector()),
|
||||
nvd.CvssVector(),
|
||||
nvd.Summary,
|
||||
nvd.CveSummary(),
|
||||
linkText,
|
||||
)
|
||||
default:
|
||||
nvd := cveInfo.CveDetail.Nvd
|
||||
return fmt.Sprintf("?\n%s\n%s", nvd.Summary, linkText)
|
||||
return fmt.Sprintf("?\n%s\n%s", nvd.CveSummary(), linkText)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ import (
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
// TextWriter write to stdout
|
||||
type TextWriter struct{}
|
||||
// StdoutWriter write to stdout
|
||||
type StdoutWriter struct{}
|
||||
|
||||
func (w TextWriter) Write(scanResults []models.ScanResult) error {
|
||||
func (w StdoutWriter) Write(scanResults []models.ScanResult) error {
|
||||
for _, s := range scanResults {
|
||||
text, err := toPlainText(s)
|
||||
if err != nil {
|
||||
|
||||
63
report/textfile.go
Normal file
63
report/textfile.go
Normal file
@@ -0,0 +1,63 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
// TextFileWriter writes results to file.
|
||||
type TextFileWriter struct{}
|
||||
|
||||
func (w TextFileWriter) Write(scanResults []models.ScanResult) (err error) {
|
||||
|
||||
path, err := ensureResultDir()
|
||||
|
||||
all := []string{}
|
||||
for _, r := range scanResults {
|
||||
textFilePath := ""
|
||||
if r.Container.ContainerID == "" {
|
||||
textFilePath = filepath.Join(path, fmt.Sprintf("%s.txt", r.ServerName))
|
||||
} else {
|
||||
textFilePath = filepath.Join(path,
|
||||
fmt.Sprintf("%s_%s.txt", r.ServerName, r.Container.Name))
|
||||
}
|
||||
text, err := toPlainText(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
all = append(all, text)
|
||||
b := []byte(text)
|
||||
if err := ioutil.WriteFile(textFilePath, b, 0644); err != nil {
|
||||
return fmt.Errorf("Failed to write text files. path: %s, err: %s", textFilePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
text := strings.Join(all, "\n\n")
|
||||
b := []byte(text)
|
||||
allPath := filepath.Join(path, "all.txt")
|
||||
if err := ioutil.WriteFile(allPath, b, 0644); err != nil {
|
||||
return fmt.Errorf("Failed to write text files. path: %s, err: %s", allPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -564,19 +564,19 @@ func summaryLines(data models.ScanResult) string {
|
||||
// packs = append(packs, pack.Name)
|
||||
// }
|
||||
if config.Conf.Lang == "ja" && 0 < d.CveDetail.Jvn.CvssScore() {
|
||||
summary := d.CveDetail.Jvn.Title
|
||||
summary := d.CveDetail.Jvn.CveTitle()
|
||||
cols = []string{
|
||||
fmt.Sprintf(indexFormat, i+1),
|
||||
d.CveDetail.CveID,
|
||||
fmt.Sprintf("| %-4.1f(%s)",
|
||||
d.CveDetail.CvssScore(config.Conf.Lang),
|
||||
d.CveDetail.Jvn.Severity,
|
||||
d.CveDetail.Jvn.CvssSeverity(),
|
||||
),
|
||||
// strings.Join(packs, ","),
|
||||
summary,
|
||||
}
|
||||
} else {
|
||||
summary := d.CveDetail.Nvd.Summary
|
||||
summary := d.CveDetail.Nvd.CveSummary()
|
||||
|
||||
var cvssScore string
|
||||
if d.CveDetail.CvssScore("en") <= 0 {
|
||||
@@ -584,7 +584,7 @@ func summaryLines(data models.ScanResult) string {
|
||||
} else {
|
||||
cvssScore = fmt.Sprintf("| %-4.1f(%s)",
|
||||
d.CveDetail.CvssScore(config.Conf.Lang),
|
||||
d.CveDetail.Nvd.Severity(),
|
||||
d.CveDetail.Nvd.CvssSeverity(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -668,16 +670,16 @@ func detailLines() (string, error) {
|
||||
case config.Conf.Lang == "ja" &&
|
||||
0 < cveInfo.CveDetail.Jvn.CvssScore():
|
||||
jvn := cveInfo.CveDetail.Jvn
|
||||
cvssSeverity = jvn.Severity
|
||||
cvssVector = jvn.Vector
|
||||
summary = fmt.Sprintf("%s\n%s", jvn.Title, jvn.Summary)
|
||||
refs = jvn.References
|
||||
cvssSeverity = jvn.CvssSeverity()
|
||||
cvssVector = jvn.CvssVector()
|
||||
summary = fmt.Sprintf("%s\n%s", jvn.CveTitle(), jvn.CveSummary())
|
||||
refs = jvn.VulnSiteReferences()
|
||||
default:
|
||||
nvd := cveInfo.CveDetail.Nvd
|
||||
cvssSeverity = nvd.Severity()
|
||||
cvssSeverity = nvd.CvssSeverity()
|
||||
cvssVector = nvd.CvssVector()
|
||||
summary = nvd.Summary
|
||||
refs = nvd.References
|
||||
summary = nvd.CveSummary()
|
||||
refs = nvd.VulnSiteReferences()
|
||||
}
|
||||
|
||||
links := []string{
|
||||
|
||||
@@ -20,26 +20,52 @@ package report
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/gosuri/uitable"
|
||||
)
|
||||
|
||||
func ensureResultDir() (path string, err error) {
|
||||
if resultDirPath != "" {
|
||||
return resultDirPath, nil
|
||||
}
|
||||
|
||||
const timeLayout = "20060102_1504"
|
||||
timedir := time.Now().Format(timeLayout)
|
||||
wd, _ := os.Getwd()
|
||||
dir := filepath.Join(wd, "results", timedir)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return "", fmt.Errorf("Failed to create dir: %s", err)
|
||||
}
|
||||
|
||||
symlinkPath := filepath.Join(wd, "results", "current")
|
||||
if _, err := os.Stat(symlinkPath); err == nil {
|
||||
if err := os.Remove(symlinkPath); err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"Failed to remove symlink. path: %s, err: %s", symlinkPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Symlink(dir, symlinkPath); err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"Failed to create symlink: path: %s, err: %s", symlinkPath, err)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
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 +79,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,30 +103,35 @@ 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
|
||||
|
||||
switch {
|
||||
case config.Conf.Lang == "ja" &&
|
||||
d.CveDetail.Jvn.ID != 0 &&
|
||||
0 < d.CveDetail.CvssScore("ja"):
|
||||
0 < d.CveDetail.Jvn.CvssScore():
|
||||
|
||||
summary := d.CveDetail.Jvn.Title
|
||||
summary := d.CveDetail.Jvn.CveTitle()
|
||||
scols = []string{
|
||||
d.CveDetail.CveID,
|
||||
fmt.Sprintf("%-4.1f (%s)",
|
||||
d.CveDetail.CvssScore(config.Conf.Lang),
|
||||
d.CveDetail.Jvn.Severity,
|
||||
d.CveDetail.Jvn.CvssSeverity(),
|
||||
),
|
||||
summary,
|
||||
}
|
||||
case 0 < d.CveDetail.CvssScore("en"):
|
||||
summary := d.CveDetail.Nvd.Summary
|
||||
summary := d.CveDetail.Nvd.CveSummary()
|
||||
scols = []string{
|
||||
d.CveDetail.CveID,
|
||||
fmt.Sprintf("%-4.1f",
|
||||
fmt.Sprintf("%-4.1f (%s)",
|
||||
d.CveDetail.CvssScore(config.Conf.Lang),
|
||||
d.CveDetail.Nvd.CvssSeverity(),
|
||||
),
|
||||
summary,
|
||||
}
|
||||
@@ -103,7 +139,7 @@ func ToPlainTextSummary(r models.ScanResult) string {
|
||||
scols = []string{
|
||||
d.CveDetail.CveID,
|
||||
"?",
|
||||
d.CveDetail.Nvd.Summary,
|
||||
d.CveDetail.Nvd.CveSummary(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,12 +152,11 @@ func ToPlainTextSummary(r models.ScanResult) string {
|
||||
return fmt.Sprintf("%s", stable)
|
||||
}
|
||||
|
||||
//TODO Distro Advisory
|
||||
func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
|
||||
for _, cve := range data.KnownCves {
|
||||
switch config.Conf.Lang {
|
||||
case "en":
|
||||
if cve.CveDetail.Nvd.ID != 0 {
|
||||
if 0 < cve.CveDetail.Nvd.CvssScore() {
|
||||
scoredReport = append(
|
||||
scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
|
||||
} else {
|
||||
@@ -129,10 +164,10 @@ func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport,
|
||||
scoredReport, toPlainTextUnknownCve(cve, osFamily))
|
||||
}
|
||||
case "ja":
|
||||
if cve.CveDetail.Jvn.ID != 0 {
|
||||
if 0 < cve.CveDetail.Jvn.CvssScore() {
|
||||
scoredReport = append(
|
||||
scoredReport, toPlainTextDetailsLangJa(cve, osFamily))
|
||||
} else if cve.CveDetail.Nvd.ID != 0 {
|
||||
} else if 0 < cve.CveDetail.Nvd.CvssScore() {
|
||||
scoredReport = append(
|
||||
scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
|
||||
} else {
|
||||
@@ -185,14 +220,14 @@ func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
|
||||
dtable.AddRow("Score",
|
||||
fmt.Sprintf("%4.1f (%s)",
|
||||
cveDetail.Jvn.CvssScore(),
|
||||
jvn.Severity,
|
||||
jvn.CvssSeverity(),
|
||||
))
|
||||
} else {
|
||||
dtable.AddRow("Score", "?")
|
||||
}
|
||||
dtable.AddRow("Vector", jvn.Vector)
|
||||
dtable.AddRow("Title", jvn.Title)
|
||||
dtable.AddRow("Description", jvn.Summary)
|
||||
dtable.AddRow("Vector", jvn.CvssVector())
|
||||
dtable.AddRow("Title", jvn.CveTitle())
|
||||
dtable.AddRow("Description", jvn.CveSummary())
|
||||
|
||||
dtable.AddRow("JVN", jvn.Link())
|
||||
dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
|
||||
@@ -227,14 +262,14 @@ func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
|
||||
dtable.AddRow("Score",
|
||||
fmt.Sprintf("%4.1f (%s)",
|
||||
cveDetail.Nvd.CvssScore(),
|
||||
nvd.Severity(),
|
||||
nvd.CvssSeverity(),
|
||||
))
|
||||
} else {
|
||||
dtable.AddRow("Score", "?")
|
||||
}
|
||||
|
||||
dtable.AddRow("Vector", nvd.CvssVector())
|
||||
dtable.AddRow("Summary", nvd.Summary)
|
||||
dtable.AddRow("Summary", nvd.CveSummary())
|
||||
dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
|
||||
dtable.AddRow("MITRE", fmt.Sprintf("%s%s", mitreBaseURL, cveID))
|
||||
dtable.AddRow("CVE Details", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
|
||||
@@ -306,6 +341,15 @@ func distroLinks(cveInfo models.CveInfo, osFamily string) []distroLink {
|
||||
},
|
||||
// TODO Debian dsa
|
||||
}
|
||||
case "FreeBSD":
|
||||
links := []distroLink{}
|
||||
for _, advisory := range cveInfo.DistroAdvisories {
|
||||
links = append(links, distroLink{
|
||||
"FreeBSD-VuXML",
|
||||
fmt.Sprintf(freeBSDVuXMLBaseURL, advisory.AdvisoryID),
|
||||
})
|
||||
}
|
||||
return links
|
||||
default:
|
||||
return []distroLink{}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,13 @@ const (
|
||||
|
||||
ubuntuSecurityBaseURL = "http://people.ubuntu.com/~ubuntu-security/cve"
|
||||
debianTrackerBaseURL = "https://security-tracker.debian.org/tracker"
|
||||
|
||||
freeBSDVuXMLBaseURL = "https://vuxml.freebsd.org/freebsd/%s.html"
|
||||
)
|
||||
|
||||
// ResultWriter Interface
|
||||
type ResultWriter interface {
|
||||
Write([]models.ScanResult) error
|
||||
}
|
||||
|
||||
var resultDirPath string
|
||||
|
||||
297
scan/base.go
Normal file
297
scan/base.go
Normal file
@@ -0,0 +1,297 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
type base struct {
|
||||
ServerInfo config.ServerInfo
|
||||
|
||||
Family string
|
||||
Release string
|
||||
Platform models.Platform
|
||||
osPackages
|
||||
|
||||
log *logrus.Entry
|
||||
errs []error
|
||||
}
|
||||
|
||||
func (l *base) ssh(cmd string, sudo bool) sshResult {
|
||||
return sshExec(l.ServerInfo, cmd, sudo, l.log)
|
||||
}
|
||||
|
||||
func (l *base) setServerInfo(c config.ServerInfo) {
|
||||
l.ServerInfo = c
|
||||
}
|
||||
|
||||
func (l base) getServerInfo() config.ServerInfo {
|
||||
return l.ServerInfo
|
||||
}
|
||||
|
||||
func (l *base) setDistributionInfo(fam, rel string) {
|
||||
l.Family = fam
|
||||
l.Release = rel
|
||||
}
|
||||
|
||||
func (l base) getDistributionInfo() string {
|
||||
return fmt.Sprintf("%s %s", l.Family, l.Release)
|
||||
}
|
||||
|
||||
func (l *base) setPlatform(p models.Platform) {
|
||||
l.Platform = p
|
||||
}
|
||||
|
||||
func (l base) getPlatform() models.Platform {
|
||||
return l.Platform
|
||||
}
|
||||
|
||||
func (l base) 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 *base) 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 *base) 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 *base) 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 SSH: %s", r)
|
||||
}
|
||||
return r.Stdout, nil
|
||||
}
|
||||
|
||||
func (l *base) parseDockerPs(stdout string) (containers []config.Container, err error) {
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
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 *base) detectPlatform() error {
|
||||
ok, instanceID, err := l.detectRunningOnAws()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
l.setPlatform(models.Platform{
|
||||
Name: "aws",
|
||||
InstanceID: instanceID,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
//TODO Azure, GCP...
|
||||
l.setPlatform(models.Platform{
|
||||
Name: "other",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l base) detectRunningOnAws() (ok bool, instanceID string, err error) {
|
||||
if r := l.ssh("type curl", noSudo); r.isSuccess() {
|
||||
cmd := "curl --max-time 1 --retry 3 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id"
|
||||
r := l.ssh(cmd, noSudo)
|
||||
if r.isSuccess() {
|
||||
id := strings.TrimSpace(r.Stdout)
|
||||
|
||||
if id == "not found" {
|
||||
// status: 0, stdout: "not found" on degitalocean or Azure
|
||||
return false, "", nil
|
||||
}
|
||||
return true, id, nil
|
||||
}
|
||||
|
||||
switch r.ExitStatus {
|
||||
case 28, 7:
|
||||
// Not running on AWS
|
||||
// 7 Failed to connect to host.
|
||||
// 28 operation timeout.
|
||||
return false, "", nil
|
||||
}
|
||||
}
|
||||
|
||||
if r := l.ssh("type wget", noSudo); r.isSuccess() {
|
||||
cmd := "wget --tries=3 --timeout=1 --no-proxy -q -O - http://169.254.169.254/latest/meta-data/instance-id"
|
||||
r := l.ssh(cmd, noSudo)
|
||||
if r.isSuccess() {
|
||||
id := strings.TrimSpace(r.Stdout)
|
||||
return true, id, nil
|
||||
}
|
||||
|
||||
switch r.ExitStatus {
|
||||
case 4, 8:
|
||||
// Not running on AWS
|
||||
// 4 Network failure
|
||||
// 8 Server issued an error response.
|
||||
return false, "", nil
|
||||
}
|
||||
}
|
||||
return false, "", fmt.Errorf(
|
||||
"Failed to curl or wget to AWS instance metadata on %s. container: %s",
|
||||
l.ServerInfo.ServerName, l.ServerInfo.Container.Name)
|
||||
}
|
||||
|
||||
func (l *base) convertToModel() (models.ScanResult, error) {
|
||||
var scoredCves, unscoredCves models.CveInfos
|
||||
for _, p := range l.UnsecurePackages {
|
||||
if p.CveDetail.CvssScore(config.Conf.Lang) <= 0 {
|
||||
unscoredCves = append(unscoredCves, models.CveInfo{
|
||||
CveDetail: p.CveDetail,
|
||||
Packages: p.Packs,
|
||||
DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
cpenames := []models.CpeName{}
|
||||
for _, cpename := range p.CpeNames {
|
||||
cpenames = append(cpenames,
|
||||
models.CpeName{Name: cpename})
|
||||
}
|
||||
|
||||
cve := models.CveInfo{
|
||||
CveDetail: p.CveDetail,
|
||||
Packages: p.Packs,
|
||||
DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
|
||||
CpeNames: cpenames,
|
||||
}
|
||||
scoredCves = append(scoredCves, cve)
|
||||
}
|
||||
|
||||
container := models.Container{
|
||||
ContainerID: l.ServerInfo.Container.ContainerID,
|
||||
Name: l.ServerInfo.Container.Name,
|
||||
}
|
||||
|
||||
sort.Sort(scoredCves)
|
||||
sort.Sort(unscoredCves)
|
||||
|
||||
return models.ScanResult{
|
||||
ServerName: l.ServerInfo.ServerName,
|
||||
Family: l.Family,
|
||||
Release: l.Release,
|
||||
Container: container,
|
||||
Platform: l.Platform,
|
||||
KnownCves: scoredCves,
|
||||
UnknownCves: unscoredCves,
|
||||
Optional: l.ServerInfo.Optional,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
|
||||
func (l *base) scanVulnByCpeName() error {
|
||||
unsecurePacks := CvePacksList{}
|
||||
|
||||
serverInfo := l.getServerInfo()
|
||||
cpeNames := serverInfo.CpeNames
|
||||
|
||||
// remove duplicate
|
||||
set := map[string]CvePacksInfo{}
|
||||
|
||||
for _, name := range cpeNames {
|
||||
details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, detail := range details {
|
||||
if val, ok := set[detail.CveID]; ok {
|
||||
names := val.CpeNames
|
||||
names = append(names, name)
|
||||
val.CpeNames = names
|
||||
set[detail.CveID] = val
|
||||
} else {
|
||||
set[detail.CveID] = CvePacksInfo{
|
||||
CveID: detail.CveID,
|
||||
CveDetail: detail,
|
||||
CpeNames: []string{name},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key := range set {
|
||||
unsecurePacks = append(unsecurePacks, set[key])
|
||||
}
|
||||
unsecurePacks = append(unsecurePacks, l.UnsecurePackages...)
|
||||
l.setUnsecurePackages(unsecurePacks)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *base) setErrs(errs []error) {
|
||||
l.errs = errs
|
||||
}
|
||||
|
||||
func (l base) getErrs() []error {
|
||||
return l.errs
|
||||
}
|
||||
58
scan/base_test.go
Normal file
58
scan/base_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"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])
|
||||
}
|
||||
}
|
||||
}
|
||||
143
scan/debian.go
143
scan/debian.go
@@ -20,7 +20,6 @@ package scan
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -33,7 +32,7 @@ import (
|
||||
|
||||
// inherit OsTypeInterface
|
||||
type debian struct {
|
||||
linux
|
||||
base
|
||||
}
|
||||
|
||||
// NewDebian is constructor
|
||||
@@ -45,17 +44,20 @@ func newDebian(c config.ServerInfo) *debian {
|
||||
|
||||
// Ubuntu, Debian
|
||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/debian.rb
|
||||
func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface) {
|
||||
|
||||
func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err error) {
|
||||
deb = newDebian(c)
|
||||
|
||||
// set sudo option flag
|
||||
c.SudoOpt = config.SudoOption{ExecBySudo: true}
|
||||
deb.setServerInfo(c)
|
||||
|
||||
if r := sshExec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
|
||||
Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
|
||||
return false, deb
|
||||
if r.Error != nil {
|
||||
return false, deb, r.Error
|
||||
}
|
||||
if r.ExitStatus == 255 {
|
||||
return false, deb, fmt.Errorf(
|
||||
"Unable to connect via SSH. Check SSH settings. %s", r)
|
||||
}
|
||||
Log.Debugf("Not Debian like Linux. %s", r)
|
||||
return false, deb, nil
|
||||
}
|
||||
|
||||
if r := sshExec(c, "lsb_release -ir", noSudo); r.isSuccess() {
|
||||
@@ -70,13 +72,12 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface) {
|
||||
if len(result) == 0 {
|
||||
deb.setDistributionInfo("debian/ubuntu", "unknown")
|
||||
Log.Warnf(
|
||||
"Unknown Debian/Ubuntu version. lsb_release -ir: %s, Host: %s:%s",
|
||||
r.Stdout, c.Host, c.Port)
|
||||
"Unknown Debian/Ubuntu version. lsb_release -ir: %s", r)
|
||||
} else {
|
||||
distro := strings.ToLower(trim(result[1]))
|
||||
deb.setDistributionInfo(distro, trim(result[2]))
|
||||
}
|
||||
return true, deb
|
||||
return true, deb, nil
|
||||
}
|
||||
|
||||
if r := sshExec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
|
||||
@@ -90,39 +91,47 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface) {
|
||||
result := re.FindStringSubmatch(trim(r.Stdout))
|
||||
if len(result) == 0 {
|
||||
Log.Warnf(
|
||||
"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s, Host: %s:%s",
|
||||
r.Stdout, c.Host, c.Port)
|
||||
"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s", r)
|
||||
deb.setDistributionInfo("debian/ubuntu", "unknown")
|
||||
} else {
|
||||
distro := strings.ToLower(trim(result[1]))
|
||||
deb.setDistributionInfo(distro, trim(result[2]))
|
||||
}
|
||||
return true, deb
|
||||
return true, deb, nil
|
||||
}
|
||||
|
||||
// Debian
|
||||
cmd := "cat /etc/debian_version"
|
||||
if r := sshExec(c, cmd, noSudo); r.isSuccess() {
|
||||
deb.setDistributionInfo("debian", trim(r.Stdout))
|
||||
return true, deb
|
||||
return true, deb, nil
|
||||
}
|
||||
|
||||
Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
|
||||
return false, deb
|
||||
Log.Debugf("Not Debian like Linux: %s", c.ServerName)
|
||||
return false, deb, nil
|
||||
}
|
||||
|
||||
func trim(str string) string {
|
||||
return strings.TrimSpace(str)
|
||||
}
|
||||
|
||||
func (o *debian) checkIfSudoNoPasswd() error {
|
||||
r := o.ssh("apt-get -v", sudo)
|
||||
if !r.isSuccess() {
|
||||
o.log.Errorf("sudo error on %s", r)
|
||||
return fmt.Errorf("Failed to sudo: %s", r)
|
||||
}
|
||||
o.log.Infof("sudo ... OK")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *debian) install() error {
|
||||
|
||||
// apt-get update
|
||||
o.log.Infof("apt-get update...")
|
||||
cmd := util.PrependProxyEnv("apt-get update")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
msg := fmt.Sprintf("Failed to SSH: %s", r)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
@@ -131,8 +140,7 @@ func (o *debian) install() error {
|
||||
// install aptitude
|
||||
cmd = util.PrependProxyEnv("apt-get install --force-yes -y aptitude")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
msg := fmt.Sprintf("Failed to SSH: %s", r)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
@@ -153,8 +161,7 @@ func (o *debian) install() error {
|
||||
cmd = util.PrependProxyEnv(
|
||||
"apt-get install --force-yes -y unattended-upgrades")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
msg := fmt.Sprintf("Failed to SSH: %s", r)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
@@ -174,7 +181,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)
|
||||
@@ -184,9 +191,7 @@ func (o *debian) scanPackages() error {
|
||||
func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error) {
|
||||
r := o.ssh("dpkg-query -W", noSudo)
|
||||
if !r.isSuccess() {
|
||||
return packs, fmt.Errorf(
|
||||
"Failed to scan packages. status: %d, stdout:%s, stderr: %s",
|
||||
r.ExitStatus, r.Stdout, r.Stderr)
|
||||
return packs, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
|
||||
// e.g.
|
||||
@@ -195,7 +200,7 @@ func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error)
|
||||
lines := strings.Split(r.Stdout, "\n")
|
||||
for _, line := range lines {
|
||||
if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
|
||||
name, version, err := o.parseScanedPackagesLine(trimmed)
|
||||
name, version, err := o.parseScannedPackagesLine(trimmed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Debian: Failed to parse package line: %s", line)
|
||||
@@ -209,7 +214,7 @@ func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *debian) parseScanedPackagesLine(line string) (name, version string, err error) {
|
||||
func (o *debian) parseScannedPackagesLine(line string) (name, version string, err error) {
|
||||
re, _ := regexp.Compile(`^([^\t']+)\t(.+)$`)
|
||||
result := re.FindStringSubmatch(line)
|
||||
if len(result) == 3 {
|
||||
@@ -226,8 +231,8 @@ 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() {
|
||||
msg := "aptitude is not installed"
|
||||
if r := o.ssh("test -f /usr/bin/aptitude", noSudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("aptitude is not installed: %s", r)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
@@ -238,7 +243,7 @@ func (o *debian) checkRequiredPackagesInstalled() error {
|
||||
}
|
||||
|
||||
if r := o.ssh("type unattended-upgrade", noSudo); !r.isSuccess() {
|
||||
msg := "unattended-upgrade is not installed"
|
||||
msg := fmt.Sprintf("unattended-upgrade is not installed: %s", r)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
@@ -250,10 +255,7 @@ func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInf
|
||||
// cmd := prependProxyEnv(conf.HTTPProxy, "apt-get update | cat; echo 1")
|
||||
cmd := util.PrependProxyEnv("apt-get update")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr,
|
||||
)
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
|
||||
var upgradablePackNames []string
|
||||
@@ -283,7 +285,7 @@ func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInf
|
||||
|
||||
unsecurePacks, err = o.fillCandidateVersion(unsecurePacks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
|
||||
}
|
||||
|
||||
// Collect CVE information of upgradable packages
|
||||
@@ -317,12 +319,10 @@ func (o *debian) fillCandidateVersion(packs []models.PackageInfo) ([]models.Pack
|
||||
select {
|
||||
case pack := <-reqChan:
|
||||
func(p models.PackageInfo) {
|
||||
cmd := fmt.Sprintf("apt-cache policy %s", p.Name)
|
||||
cmd := fmt.Sprintf("LANG=en_US.UTF-8 apt-cache policy %s", p.Name)
|
||||
r := o.ssh(cmd, sudo)
|
||||
if !r.isSuccess() {
|
||||
errChan <- fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
errChan <- fmt.Errorf("Failed to SSH: %s.", r)
|
||||
return
|
||||
}
|
||||
ver, err := o.parseAptCachePolicy(r.Stdout, p.Name)
|
||||
@@ -336,6 +336,7 @@ func (o *debian) fillCandidateVersion(packs []models.PackageInfo) ([]models.Pack
|
||||
}
|
||||
}
|
||||
|
||||
errs := []error{}
|
||||
result := []models.PackageInfo{}
|
||||
for i := 0; i < len(packs); i++ {
|
||||
select {
|
||||
@@ -344,11 +345,14 @@ func (o *debian) fillCandidateVersion(packs []models.PackageInfo) ([]models.Pack
|
||||
o.log.Infof("(%d/%d) Upgradable: %s-%s -> %s",
|
||||
i+1, len(packs), pack.Name, pack.Version, pack.NewVersion)
|
||||
case err := <-errChan:
|
||||
return nil, err
|
||||
errs = append(errs, err)
|
||||
case <-timeout:
|
||||
return nil, fmt.Errorf("Timeout fillCandidateVersion.")
|
||||
return nil, fmt.Errorf("Timeout fillCandidateVersion")
|
||||
}
|
||||
}
|
||||
if 0 < len(errs) {
|
||||
return nil, fmt.Errorf("%v", errs)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -389,7 +393,7 @@ func (o *debian) GetUnsecurePackNamesUsingUnattendedUpgrades() (packNames []stri
|
||||
}
|
||||
|
||||
func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
|
||||
cmd := util.PrependProxyEnv("apt-get upgrade --dry-run")
|
||||
cmd := util.PrependProxyEnv("LANG=en_US.UTF-8 apt-get upgrade --dry-run")
|
||||
r := o.ssh(cmd, sudo)
|
||||
if r.isSuccess(0, 1) {
|
||||
return o.parseAptGetUpgrade(r.Stdout)
|
||||
@@ -464,7 +468,6 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
|
||||
}()
|
||||
|
||||
timeout := time.After(30 * 60 * time.Second)
|
||||
|
||||
concurrency := 10
|
||||
tasks := util.GenWorkers(concurrency)
|
||||
for range unsecurePacks {
|
||||
@@ -472,47 +475,51 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
|
||||
select {
|
||||
case pack := <-reqChan:
|
||||
func(p models.PackageInfo) {
|
||||
if cveIds, err := o.scanPackageCveIds(p); err != nil {
|
||||
if cveIDs, err := o.scanPackageCveIDs(p); err != nil {
|
||||
errChan <- err
|
||||
} else {
|
||||
resChan <- struct {
|
||||
models.PackageInfo
|
||||
strarray
|
||||
}{p, cveIds}
|
||||
}{p, cveIDs}
|
||||
}
|
||||
}(pack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errs := []error{}
|
||||
for i := 0; i < len(unsecurePacks); i++ {
|
||||
select {
|
||||
case pair := <-resChan:
|
||||
pack := pair.PackageInfo
|
||||
cveIds := pair.strarray
|
||||
for _, cveID := range cveIds {
|
||||
cveIDs := pair.strarray
|
||||
for _, cveID := range cveIDs {
|
||||
cvePackages[cveID] = appendPackIfMissing(cvePackages[cveID], pack)
|
||||
}
|
||||
o.log.Infof("(%d/%d) Scanned %s-%s : %s",
|
||||
i+1, len(unsecurePacks), pair.Name, pair.PackageInfo.Version, cveIds)
|
||||
i+1, len(unsecurePacks), pair.Name, pair.PackageInfo.Version, cveIDs)
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
errs = append(errs, err)
|
||||
case <-timeout:
|
||||
return nil, fmt.Errorf("Timeout scanPackageCveIds.")
|
||||
//TODO append to errs
|
||||
return nil, fmt.Errorf("Timeout scanPackageCveIDs")
|
||||
}
|
||||
}
|
||||
|
||||
var cveIds []string
|
||||
for k := range cvePackages {
|
||||
cveIds = append(cveIds, k)
|
||||
if 0 < len(errs) {
|
||||
return nil, fmt.Errorf("%v", errs)
|
||||
}
|
||||
|
||||
o.log.Debugf("%d Cves are found. cves: %v", len(cveIds), cveIds)
|
||||
var cveIDs []string
|
||||
for k := range cvePackages {
|
||||
cveIDs = append(cveIDs, k)
|
||||
}
|
||||
|
||||
o.log.Debugf("%d Cves are found. cves: %v", len(cveIDs), cveIDs)
|
||||
|
||||
o.log.Info("Fetching CVE details...")
|
||||
cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIds)
|
||||
cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -526,11 +533,10 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
|
||||
// CvssScore: cinfo.CvssScore(conf.Lang),
|
||||
})
|
||||
}
|
||||
sort.Sort(CvePacksList(cvePacksList))
|
||||
return
|
||||
}
|
||||
|
||||
func (o *debian) scanPackageCveIds(pack models.PackageInfo) (cveIds []string, err error) {
|
||||
func (o *debian) scanPackageCveIDs(pack models.PackageInfo) ([]string, error) {
|
||||
cmd := ""
|
||||
switch o.Family {
|
||||
case "ubuntu":
|
||||
@@ -542,19 +548,12 @@ func (o *debian) scanPackageCveIds(pack models.PackageInfo) (cveIds []string, er
|
||||
|
||||
r := o.ssh(cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
o.log.Warnf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
o.log.Warnf("Failed to SSH: %s", r)
|
||||
// Ignore this Error.
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
cveIds, err = o.getCveIDParsingChangelog(r.Stdout, pack.Name, pack.Version)
|
||||
if err != nil {
|
||||
trimUbuntu := strings.Split(pack.Version, "ubuntu")[0]
|
||||
return o.getCveIDParsingChangelog(r.Stdout, pack.Name, trimUbuntu)
|
||||
}
|
||||
return
|
||||
return o.getCveIDParsingChangelog(r.Stdout, pack.Name, pack.Version)
|
||||
}
|
||||
|
||||
func (o *debian) getCveIDParsingChangelog(changelog string,
|
||||
@@ -581,7 +580,7 @@ func (o *debian) getCveIDParsingChangelog(changelog string,
|
||||
}
|
||||
|
||||
//TODO report as unable to parse changelog.
|
||||
o.log.Warn(err)
|
||||
o.log.Error(err)
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
"github.com/k0kubun/pp"
|
||||
)
|
||||
|
||||
func TestParseScanedPackagesLineDebian(t *testing.T) {
|
||||
func TestParseScannedPackagesLineDebian(t *testing.T) {
|
||||
|
||||
var packagetests = []struct {
|
||||
in string
|
||||
@@ -43,7 +43,7 @@ func TestParseScanedPackagesLineDebian(t *testing.T) {
|
||||
|
||||
d := newDebian(config.ServerInfo{})
|
||||
for _, tt := range packagetests {
|
||||
n, v, _ := d.parseScanedPackagesLine(tt.in)
|
||||
n, v, _ := d.parseScannedPackagesLine(tt.in)
|
||||
if n != tt.name {
|
||||
t.Errorf("name: expected %s, actual %s", tt.name, n)
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
238
scan/freebsd.go
Normal file
238
scan/freebsd.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
// inherit OsTypeInterface
|
||||
type bsd struct {
|
||||
base
|
||||
}
|
||||
|
||||
// NewBSD constructor
|
||||
func newBsd(c config.ServerInfo) *bsd {
|
||||
d := &bsd{}
|
||||
d.log = util.NewCustomLogger(c)
|
||||
return d
|
||||
}
|
||||
|
||||
//https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/freebsd.rb
|
||||
func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) {
|
||||
bsd = newBsd(c)
|
||||
c.Family = "FreeBSD"
|
||||
if r := sshExec(c, "uname", noSudo); r.isSuccess() {
|
||||
if strings.Contains(r.Stdout, "FreeBSD") == true {
|
||||
if b := sshExec(c, "uname -r", noSudo); b.isSuccess() {
|
||||
bsd.setDistributionInfo("FreeBSD", strings.TrimSpace(b.Stdout))
|
||||
bsd.setServerInfo(c)
|
||||
return true, bsd
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.Debugf("Not FreeBSD. servernam: %s", c.ServerName)
|
||||
return false, bsd
|
||||
}
|
||||
|
||||
func (o *bsd) checkIfSudoNoPasswd() error {
|
||||
// FreeBSD doesn't need root privilege
|
||||
o.log.Infof("sudo ... OK")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *bsd) install() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *bsd) checkRequiredPackagesInstalled() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *bsd) scanPackages() error {
|
||||
var err error
|
||||
var packs []models.PackageInfo
|
||||
if packs, err = o.scanInstalledPackages(); err != nil {
|
||||
o.log.Errorf("Failed to scan installed packages")
|
||||
return err
|
||||
}
|
||||
o.setPackages(packs)
|
||||
|
||||
var unsecurePacks []CvePacksInfo
|
||||
if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
|
||||
o.log.Errorf("Failed to scan vulnerable packages")
|
||||
return err
|
||||
}
|
||||
o.setUnsecurePackages(unsecurePacks)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *bsd) scanInstalledPackages() ([]models.PackageInfo, error) {
|
||||
cmd := util.PrependProxyEnv("pkg version -v")
|
||||
r := o.ssh(cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
return o.parsePkgVersion(r.Stdout), nil
|
||||
}
|
||||
|
||||
func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
|
||||
const vulndbPath = "/tmp/vuln.db"
|
||||
cmd := "rm -f " + vulndbPath
|
||||
r := o.ssh(cmd, noSudo)
|
||||
if !r.isSuccess(0) {
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
|
||||
cmd = util.PrependProxyEnv("pkg audit -F -r -f " + vulndbPath)
|
||||
r = o.ssh(cmd, noSudo)
|
||||
if !r.isSuccess(0, 1) {
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
if r.ExitStatus == 0 {
|
||||
// no vulnerabilities
|
||||
return []CvePacksInfo{}, nil
|
||||
}
|
||||
|
||||
var packAdtRslt []pkgAuditResult
|
||||
blocks := o.splitIntoBlocks(r.Stdout)
|
||||
for _, b := range blocks {
|
||||
name, cveIDs, vulnID := o.parseBlock(b)
|
||||
if len(cveIDs) == 0 {
|
||||
continue
|
||||
}
|
||||
pack, found := o.Packages.FindByName(name)
|
||||
if !found {
|
||||
return nil, fmt.Errorf("Vulnerable package: %s is not found", name)
|
||||
}
|
||||
packAdtRslt = append(packAdtRslt, pkgAuditResult{
|
||||
pack: pack,
|
||||
vulnIDCveIDs: vulnIDCveIDs{
|
||||
vulnID: vulnID,
|
||||
cveIDs: cveIDs,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// { CVE ID: []pkgAuditResult }
|
||||
cveIDAdtMap := make(map[string][]pkgAuditResult)
|
||||
for _, p := range packAdtRslt {
|
||||
for _, cid := range p.vulnIDCveIDs.cveIDs {
|
||||
cveIDAdtMap[cid] = append(cveIDAdtMap[cid], p)
|
||||
}
|
||||
}
|
||||
|
||||
cveIDs := []string{}
|
||||
for k := range cveIDAdtMap {
|
||||
cveIDs = append(cveIDs, k)
|
||||
}
|
||||
|
||||
cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.log.Info("Done")
|
||||
|
||||
for _, d := range cveDetails {
|
||||
packs := []models.PackageInfo{}
|
||||
for _, r := range cveIDAdtMap[d.CveID] {
|
||||
packs = append(packs, r.pack)
|
||||
}
|
||||
|
||||
disAdvs := []models.DistroAdvisory{}
|
||||
for _, r := range cveIDAdtMap[d.CveID] {
|
||||
disAdvs = append(disAdvs, models.DistroAdvisory{
|
||||
AdvisoryID: r.vulnIDCveIDs.vulnID,
|
||||
})
|
||||
}
|
||||
|
||||
cvePacksList = append(cvePacksList, CvePacksInfo{
|
||||
CveID: d.CveID,
|
||||
CveDetail: d,
|
||||
Packs: packs,
|
||||
DistroAdvisories: disAdvs,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *bsd) parsePkgVersion(stdout string) (packs []models.PackageInfo) {
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, l := range lines {
|
||||
fields := strings.Fields(l)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
packVer := fields[0]
|
||||
splitted := strings.Split(packVer, "-")
|
||||
ver := splitted[len(splitted)-1]
|
||||
name := strings.Join(splitted[:len(splitted)-1], "-")
|
||||
|
||||
switch fields[1] {
|
||||
case "?", "=":
|
||||
packs = append(packs, models.PackageInfo{
|
||||
Name: name,
|
||||
Version: ver,
|
||||
})
|
||||
case "<":
|
||||
candidate := strings.TrimSuffix(fields[6], ")")
|
||||
packs = append(packs, models.PackageInfo{
|
||||
Name: name,
|
||||
Version: ver,
|
||||
NewVersion: candidate,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type vulnIDCveIDs struct {
|
||||
vulnID string
|
||||
cveIDs []string
|
||||
}
|
||||
|
||||
type pkgAuditResult struct {
|
||||
pack models.PackageInfo
|
||||
vulnIDCveIDs vulnIDCveIDs
|
||||
}
|
||||
|
||||
func (o *bsd) splitIntoBlocks(stdout string) (blocks []string) {
|
||||
lines := strings.Split(stdout, "\n")
|
||||
block := []string{}
|
||||
for _, l := range lines {
|
||||
if len(strings.TrimSpace(l)) == 0 {
|
||||
if 0 < len(block) {
|
||||
blocks = append(blocks, strings.Join(block, "\n"))
|
||||
block = []string{}
|
||||
}
|
||||
continue
|
||||
}
|
||||
block = append(block, strings.TrimSpace(l))
|
||||
}
|
||||
if 0 < len(block) {
|
||||
blocks = append(blocks, strings.Join(block, "\n"))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *bsd) parseBlock(block string) (packName string, cveIDs []string, vulnID string) {
|
||||
lines := strings.Split(block, "\n")
|
||||
for _, l := range lines {
|
||||
if strings.HasSuffix(l, " is vulnerable:") {
|
||||
packVer := strings.Fields(l)[0]
|
||||
splitted := strings.Split(packVer, "-")
|
||||
packName = strings.Join(splitted[:len(splitted)-1], "-")
|
||||
} else if strings.HasPrefix(l, "CVE:") {
|
||||
cveIDs = append(cveIDs, strings.Fields(l)[1])
|
||||
} else if strings.HasPrefix(l, "WWW:") {
|
||||
splitted := strings.Split(l, "/")
|
||||
vulnID = strings.TrimSuffix(splitted[len(splitted)-1], ".html")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
155
scan/freebsd_test.go
Normal file
155
scan/freebsd_test.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/k0kubun/pp"
|
||||
)
|
||||
|
||||
func TestParsePkgVersion(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in string
|
||||
expected []models.PackageInfo
|
||||
}{
|
||||
{
|
||||
`Updating FreeBSD repository catalogue...
|
||||
FreeBSD repository is up-to-date.
|
||||
All repositories are up-to-date.
|
||||
bash-4.2.45 < needs updating (remote has 4.3.42_1)
|
||||
gettext-0.18.3.1 < needs updating (remote has 0.19.7)
|
||||
tcl84-8.4.20_2,1 = up-to-date with remote
|
||||
teTeX-base-3.0_25 ? orphaned: print/teTeX-base`,
|
||||
|
||||
[]models.PackageInfo{
|
||||
{
|
||||
Name: "bash",
|
||||
Version: "4.2.45",
|
||||
NewVersion: "4.3.42_1",
|
||||
},
|
||||
{
|
||||
Name: "gettext",
|
||||
Version: "0.18.3.1",
|
||||
NewVersion: "0.19.7",
|
||||
},
|
||||
{
|
||||
Name: "tcl84",
|
||||
Version: "8.4.20_2,1",
|
||||
},
|
||||
{
|
||||
Name: "teTeX-base",
|
||||
Version: "3.0_25",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
d := newBsd(config.ServerInfo{})
|
||||
for _, tt := range tests {
|
||||
actual := d.parsePkgVersion(tt.in)
|
||||
if !reflect.DeepEqual(tt.expected, actual) {
|
||||
e := pp.Sprintf("%v", tt.expected)
|
||||
a := pp.Sprintf("%v", actual)
|
||||
t.Errorf("expected %s, actual %s", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitIntoBlocks(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
`
|
||||
block1
|
||||
|
||||
block2
|
||||
block2
|
||||
block2
|
||||
|
||||
block3
|
||||
block3`,
|
||||
[]string{
|
||||
`block1`,
|
||||
"block2\nblock2\nblock2",
|
||||
"block3\nblock3",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
d := newBsd(config.ServerInfo{})
|
||||
for _, tt := range tests {
|
||||
actual := d.splitIntoBlocks(tt.in)
|
||||
if !reflect.DeepEqual(tt.expected, actual) {
|
||||
e := pp.Sprintf("%v", tt.expected)
|
||||
a := pp.Sprintf("%v", actual)
|
||||
t.Errorf("expected %s, actual %s", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParseBlock(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in string
|
||||
name string
|
||||
cveIDs []string
|
||||
vulnID string
|
||||
}{
|
||||
{
|
||||
|
||||
in: `vulnxml file up-to-date
|
||||
bind96-9.6.3.2.ESV.R10_2 is vulnerable:
|
||||
bind -- denial of service vulnerability
|
||||
CVE: CVE-2014-0591
|
||||
WWW: https://vuxml.FreeBSD.org/freebsd/cb252f01-7c43-11e3-b0a6-005056a37f68.html`,
|
||||
name: "bind96",
|
||||
cveIDs: []string{"CVE-2014-0591"},
|
||||
vulnID: "cb252f01-7c43-11e3-b0a6-005056a37f68",
|
||||
},
|
||||
{
|
||||
in: `bind96-9.6.3.2.ESV.R10_2 is vulnerable:
|
||||
bind -- denial of service vulnerability
|
||||
CVE: CVE-2014-8680
|
||||
CVE: CVE-2014-8500
|
||||
WWW: https://vuxml.FreeBSD.org/freebsd/ab3e98d9-8175-11e4-907d-d050992ecde8.html`,
|
||||
name: "bind96",
|
||||
cveIDs: []string{"CVE-2014-8680", "CVE-2014-8500"},
|
||||
vulnID: "ab3e98d9-8175-11e4-907d-d050992ecde8",
|
||||
},
|
||||
{
|
||||
in: `hoge-hoge-9.6.3.2.ESV.R10_2 is vulnerable:
|
||||
bind -- denial of service vulnerability
|
||||
CVE: CVE-2014-8680
|
||||
CVE: CVE-2014-8500
|
||||
WWW: https://vuxml.FreeBSD.org/freebsd/ab3e98d9-8175-11e4-907d-d050992ecde8.html`,
|
||||
name: "hoge-hoge",
|
||||
cveIDs: []string{"CVE-2014-8680", "CVE-2014-8500"},
|
||||
vulnID: "ab3e98d9-8175-11e4-907d-d050992ecde8",
|
||||
},
|
||||
{
|
||||
in: `1 problem(s) in the installed packages found.`,
|
||||
cveIDs: []string{},
|
||||
vulnID: "",
|
||||
},
|
||||
}
|
||||
|
||||
d := newBsd(config.ServerInfo{})
|
||||
for _, tt := range tests {
|
||||
aName, aCveIDs, aVunlnID := d.parseBlock(tt.in)
|
||||
if tt.name != aName {
|
||||
t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVunlnID)
|
||||
}
|
||||
for i := range tt.cveIDs {
|
||||
if tt.cveIDs[i] != aCveIDs[i] {
|
||||
t.Errorf("expected cveID: %s, actual %s", tt.cveIDs[i], aCveIDs[i])
|
||||
}
|
||||
}
|
||||
if tt.vulnID != aVunlnID {
|
||||
t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVunlnID)
|
||||
}
|
||||
}
|
||||
}
|
||||
129
scan/linux.go
129
scan/linux.go
@@ -1,129 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
type linux struct {
|
||||
ServerInfo config.ServerInfo
|
||||
|
||||
Family string
|
||||
Release string
|
||||
osPackages
|
||||
log *logrus.Entry
|
||||
}
|
||||
|
||||
func (l *linux) ssh(cmd string, sudo bool) sshResult {
|
||||
return sshExec(l.ServerInfo, cmd, sudo, l.log)
|
||||
}
|
||||
|
||||
func (l *linux) setServerInfo(c config.ServerInfo) {
|
||||
l.ServerInfo = c
|
||||
}
|
||||
|
||||
func (l *linux) getServerInfo() config.ServerInfo {
|
||||
return l.ServerInfo
|
||||
}
|
||||
|
||||
func (l *linux) setDistributionInfo(fam, rel string) {
|
||||
l.Family = fam
|
||||
l.Release = rel
|
||||
}
|
||||
|
||||
func (l *linux) convertToModel() (models.ScanResult, error) {
|
||||
var cves, unknownScoreCves []models.CveInfo
|
||||
for _, p := range l.UnsecurePackages {
|
||||
if p.CveDetail.CvssScore(config.Conf.Lang) < 0 {
|
||||
unknownScoreCves = append(unknownScoreCves, models.CveInfo{
|
||||
CveDetail: p.CveDetail,
|
||||
Packages: p.Packs,
|
||||
DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
cpenames := []models.CpeName{}
|
||||
for _, cpename := range p.CpeNames {
|
||||
cpenames = append(cpenames,
|
||||
models.CpeName{Name: cpename})
|
||||
}
|
||||
|
||||
cve := models.CveInfo{
|
||||
CveDetail: p.CveDetail,
|
||||
Packages: p.Packs,
|
||||
DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
|
||||
CpeNames: cpenames,
|
||||
}
|
||||
cves = append(cves, cve)
|
||||
}
|
||||
|
||||
return models.ScanResult{
|
||||
ServerName: l.ServerInfo.ServerName,
|
||||
Family: l.Family,
|
||||
Release: l.Release,
|
||||
KnownCves: cves,
|
||||
UnknownCves: unknownScoreCves,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
|
||||
func (l *linux) scanVulnByCpeName() error {
|
||||
unsecurePacks := CvePacksList{}
|
||||
|
||||
serverInfo := l.getServerInfo()
|
||||
cpeNames := serverInfo.CpeNames
|
||||
|
||||
// remove duplicate
|
||||
set := map[string]CvePacksInfo{}
|
||||
|
||||
for _, name := range cpeNames {
|
||||
details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, detail := range details {
|
||||
if val, ok := set[detail.CveID]; ok {
|
||||
names := val.CpeNames
|
||||
names = append(names, name)
|
||||
val.CpeNames = names
|
||||
set[detail.CveID] = val
|
||||
} else {
|
||||
set[detail.CveID] = CvePacksInfo{
|
||||
CveID: detail.CveID,
|
||||
CveDetail: detail,
|
||||
CpeNames: []string{name},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key := range set {
|
||||
unsecurePacks = append(unsecurePacks, set[key])
|
||||
}
|
||||
unsecurePacks = append(unsecurePacks, l.UnsecurePackages...)
|
||||
sort.Sort(CvePacksList(unsecurePacks))
|
||||
l.setUnsecurePackages(unsecurePacks)
|
||||
return nil
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
289
scan/redhat.go
289
scan/redhat.go
@@ -35,7 +35,7 @@ import (
|
||||
|
||||
// inherit OsTypeInterface
|
||||
type redhat struct {
|
||||
linux
|
||||
base
|
||||
}
|
||||
|
||||
// NewRedhat is constructor
|
||||
@@ -47,16 +47,12 @@ func newRedhat(c config.ServerInfo) *redhat {
|
||||
|
||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/redhat.rb
|
||||
func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
|
||||
|
||||
red = newRedhat(c)
|
||||
|
||||
// set sudo option flag
|
||||
c.SudoOpt = config.SudoOption{ExecBySudoSh: true}
|
||||
red.setServerInfo(c)
|
||||
|
||||
if r := sshExec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
|
||||
red.setDistributionInfo("fedora", "unknown")
|
||||
Log.Warn("Fedora not tested yet. Host: %s:%s", c.Host, c.Port)
|
||||
Log.Warn("Fedora not tested yet: %s", r)
|
||||
return true, red
|
||||
}
|
||||
|
||||
@@ -69,9 +65,7 @@ func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
|
||||
re, _ := regexp.Compile(`(.*) release (\d[\d.]*)`)
|
||||
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
|
||||
if len(result) != 3 {
|
||||
Log.Warn(
|
||||
"Failed to parse RedHat/CentOS version. stdout: %s, Host: %s:%s",
|
||||
r.Stdout, c.Host, c.Port)
|
||||
Log.Warn("Failed to parse RedHat/CentOS version: %s", r)
|
||||
return true, red
|
||||
}
|
||||
|
||||
@@ -100,10 +94,20 @@ func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
|
||||
return true, red
|
||||
}
|
||||
|
||||
Log.Debugf("Not RedHat like Linux. Host: %s:%s", c.Host, c.Port)
|
||||
Log.Debugf("Not RedHat like Linux. servername: %s", c.ServerName)
|
||||
return false, red
|
||||
}
|
||||
|
||||
func (o *redhat) checkIfSudoNoPasswd() error {
|
||||
r := o.ssh("yum --version", sudo)
|
||||
if !r.isSuccess() {
|
||||
o.log.Errorf("sudo error on %s", r)
|
||||
return fmt.Errorf("Failed to sudo: %s", r)
|
||||
}
|
||||
o.log.Infof("sudo ... OK")
|
||||
return nil
|
||||
}
|
||||
|
||||
// CentOS 5 ... yum-plugin-security, yum-changelog
|
||||
// CentOS 6 ... yum-plugin-security, yum-plugin-changelog
|
||||
// CentOS 7 ... yum-plugin-security, yum-plugin-changelog
|
||||
@@ -129,17 +133,15 @@ 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(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
return fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) installYumChangelog() error {
|
||||
o.log.Info("Installing yum-plugin-security...")
|
||||
|
||||
if o.Family == "centos" {
|
||||
var majorVersion int
|
||||
@@ -160,17 +162,16 @@ 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)
|
||||
return fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
o.log.Infof("Installed: %s.", packName)
|
||||
o.log.Infof("Installed: %s", packName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -179,8 +180,8 @@ func (o *redhat) checkRequiredPackagesInstalled() error {
|
||||
if config.Conf.UseYumPluginSecurity {
|
||||
// check if yum-plugin-security is installed.
|
||||
// Amazon Linux, REHL can execute 'yum updateinfo --security updates' without yum-plugin-security
|
||||
cmd := "rpm -q yum-plugin-security"
|
||||
if o.Family == "centos" {
|
||||
cmd := "rpm -q yum-plugin-security"
|
||||
if r := o.ssh(cmd, noSudo); !r.isSuccess() {
|
||||
msg := "yum-plugin-security is not installed"
|
||||
o.log.Errorf(msg)
|
||||
@@ -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,13 +287,11 @@ 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.
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
|
||||
// get Updateble package name, installed, candidate version.
|
||||
@@ -300,20 +299,29 @@ 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 {
|
||||
PackInfo models.PackageInfo
|
||||
CveIDs []string
|
||||
}
|
||||
|
||||
// { packageName: changelog-lines }
|
||||
var rpm2changelog map[string]*string
|
||||
allChangelog, err := o.getAllChangelog(packInfoList)
|
||||
if err != nil {
|
||||
o.log.Errorf("Failed to getAllchangelog. err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
rpm2changelog, err = o.parseAllChangelog(allChangelog)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parseAllChangelog. err: %s", err)
|
||||
}
|
||||
|
||||
var results []PackInfoCveIDs
|
||||
for i, packInfo := range packInfoList {
|
||||
changelog, err := o.getChangelog(packInfo.Name)
|
||||
if err != nil {
|
||||
o.log.Errorf("Failed to collect CVE. err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
changelog := o.getChangelogCVELines(rpm2changelog, packInfo)
|
||||
|
||||
// Collect unique set of CVE-ID in each changelog
|
||||
uniqueCveIDMap := make(map[string]bool)
|
||||
@@ -394,7 +402,6 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error)
|
||||
// CvssScore: cinfo.CvssScore(conf.Lang),
|
||||
})
|
||||
}
|
||||
sort.Sort(CvePacksList(cvePacksList))
|
||||
return cvePacksList, nil
|
||||
}
|
||||
|
||||
@@ -409,16 +416,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
|
||||
@@ -445,7 +456,7 @@ func (o *redhat) parseYumCheckUpdateLine(line string) (models.PackageInfo, error
|
||||
if len(fields) != 2 {
|
||||
return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line)
|
||||
}
|
||||
version := fields[0]
|
||||
version := o.regexpReplace(fields[0], `^[0-9]+:`, "")
|
||||
release := fields[1]
|
||||
return models.PackageInfo{
|
||||
Name: packName,
|
||||
@@ -454,12 +465,127 @@ func (o *redhat) parseYumCheckUpdateLine(line string) (models.PackageInfo, error
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *redhat) getChangelog(packageNames string) (stdout string, err error) {
|
||||
command := "echo N | "
|
||||
func (o *redhat) mkPstring() *string {
|
||||
str := ""
|
||||
return &str
|
||||
}
|
||||
|
||||
func (o *redhat) regexpReplace(src string, pat string, rep string) string {
|
||||
r := regexp.MustCompile(pat)
|
||||
return r.ReplaceAllString(src, rep)
|
||||
}
|
||||
|
||||
func (o *redhat) getChangelogCVELines(rpm2changelog map[string]*string, packInfo models.PackageInfo) string {
|
||||
rpm := fmt.Sprintf("%s-%s-%s", packInfo.Name, packInfo.NewVersion, packInfo.NewRelease)
|
||||
retLine := ""
|
||||
if rpm2changelog[rpm] != nil {
|
||||
lines := strings.Split(*rpm2changelog[rpm], "\n")
|
||||
for _, line := range lines {
|
||||
match, _ := regexp.MatchString("CVE-[0-9]+-[0-9]+", line)
|
||||
if match {
|
||||
retLine += fmt.Sprintf("%s\n", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
return retLine
|
||||
}
|
||||
|
||||
func (o *redhat) parseAllChangelog(allChangelog string) (map[string]*string, error) {
|
||||
var majorVersion int
|
||||
if 0 < len(o.Release) && o.Family == "centos" {
|
||||
majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
|
||||
} else {
|
||||
return nil, fmt.Errorf(
|
||||
"Not implemented yet. family: %s, release: %s",
|
||||
o.Family, o.Release)
|
||||
}
|
||||
|
||||
orglines := strings.Split(allChangelog, "\n")
|
||||
tmpline := ""
|
||||
var lines []string
|
||||
var prev, now bool
|
||||
var err error
|
||||
for i := range orglines {
|
||||
if majorVersion == 5 {
|
||||
/* for CentOS5 (yum-util < 1.1.20) */
|
||||
prev = false
|
||||
now = false
|
||||
if i > 0 {
|
||||
prev, err = o.isRpmPackageNameLine(orglines[i-1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
now, err = o.isRpmPackageNameLine(orglines[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if prev && now {
|
||||
tmpline = fmt.Sprintf("%s, %s", tmpline, orglines[i])
|
||||
continue
|
||||
}
|
||||
if !prev && now {
|
||||
tmpline = fmt.Sprintf("%s%s", tmpline, orglines[i])
|
||||
continue
|
||||
}
|
||||
if tmpline != "" {
|
||||
lines = append(lines, fmt.Sprintf("%s", tmpline))
|
||||
tmpline = ""
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf("%s", orglines[i]))
|
||||
} else {
|
||||
/* for CentOS6,7 (yum-util >= 1.1.20) */
|
||||
line := orglines[i]
|
||||
line = o.regexpReplace(line, `^ChangeLog for: `, "")
|
||||
line = o.regexpReplace(line, `^\*\*\sNo\sChangeLog\sfor:.*`, "")
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
|
||||
rpm2changelog := make(map[string]*string)
|
||||
writePointer := o.mkPstring()
|
||||
for _, line := range lines {
|
||||
match, err := o.isRpmPackageNameLine(line)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if match {
|
||||
rpms := strings.Split(line, ",")
|
||||
pNewString := o.mkPstring()
|
||||
writePointer = pNewString
|
||||
for _, rpm := range rpms {
|
||||
rpm = strings.TrimSpace(rpm)
|
||||
rpm = o.regexpReplace(rpm, `^[0-9]+:`, "")
|
||||
rpm = o.regexpReplace(rpm, `\.(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)$`, "")
|
||||
rpm2changelog[rpm] = pNewString
|
||||
}
|
||||
} else {
|
||||
stop, _ := regexp.MatchString("^Dependencies Resolved", line)
|
||||
if stop {
|
||||
return rpm2changelog, nil
|
||||
}
|
||||
*writePointer += fmt.Sprintf("%s\n", line)
|
||||
}
|
||||
}
|
||||
return rpm2changelog, nil
|
||||
}
|
||||
|
||||
func (o *redhat) getAllChangelog(packInfoList models.PackageInfoList) (stdout string, err error) {
|
||||
packageNames := ""
|
||||
for _, packInfo := range packInfoList {
|
||||
packageNames += fmt.Sprintf("%s ", packInfo.Name)
|
||||
}
|
||||
|
||||
command := ""
|
||||
if o.ServerInfo.User == "root" {
|
||||
command = "echo N | "
|
||||
}
|
||||
if 0 < len(config.Conf.HTTPProxy) {
|
||||
command += util.ProxyEnv()
|
||||
}
|
||||
command += fmt.Sprintf(" yum update --changelog %s | grep CVE", packageNames)
|
||||
|
||||
// yum update --changelog doesn't have --color option.
|
||||
command += fmt.Sprintf(" LANG=en_US.UTF-8 yum update --changelog %s", packageNames)
|
||||
|
||||
r := o.ssh(command, sudo)
|
||||
if !r.isSuccess(0, 1) {
|
||||
@@ -485,53 +611,39 @@ 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(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
|
||||
// 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(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
advIDPackNamesList, err := o.parseYumUpdateinfoListAvailable(r.Stdout)
|
||||
|
||||
// get package name, version, rel to be upgrade.
|
||||
cmd = "yum check-update --security"
|
||||
// cmd = "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.
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
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,12 +655,10 @@ 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",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
advisoryCveIDsList, err := o.parseYumUpdateinfo(r.Stdout)
|
||||
if err != nil {
|
||||
@@ -711,6 +821,23 @@ func (o *redhat) isHorizontalRule(line string) (bool, error) {
|
||||
return regexp.MatchString("^=+$", line)
|
||||
}
|
||||
|
||||
func (o *redhat) isRpmPackageNameLine(line string) (bool, error) {
|
||||
s := strings.TrimPrefix(line, "ChangeLog for: ")
|
||||
ss := strings.Split(s, ", ")
|
||||
if len(ss) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
for _, s := range ss {
|
||||
s = strings.TrimRight(s, " \r\n")
|
||||
ok, err := regexp.MatchString(
|
||||
`^[^ ]+\.(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)$`, s)
|
||||
if !ok {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// see test case
|
||||
func (o *redhat) parseYumUpdateinfoHeaderCentOS(line string) (packs []models.PackageInfo, err error) {
|
||||
pkgs := strings.Split(strings.TrimSpace(line), ",")
|
||||
@@ -859,3 +986,7 @@ func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacks
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (o *redhat) clone() osTypeInterface {
|
||||
return o
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -294,6 +302,54 @@ func TestIsDescriptionLine(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRpmPackageNameLine(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
"stunnel-4.15-2.el5.2.i386",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"iproute-2.6.18-15.el5.i386",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"1:yum-updatesd-0.9-6.el5_10.noarch",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"glibc-2.12-1.192.el6.x86_64",
|
||||
true,
|
||||
},
|
||||
{
|
||||
" glibc-2.12-1.192.el6.x86_64",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"glibc-2.12-1.192.el6.x86_64, iproute-2.6.18-15.el5.i386",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"k6 hoge.i386",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"triathlon",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
found, err := r.isRpmPackageNameLine(tt.in)
|
||||
if tt.found != found {
|
||||
t.Errorf("[%d] line: %s, expected %t, actual %t, err %v", i, tt.in, tt.found, found, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoToGetSeverity(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
@@ -556,7 +612,12 @@ 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
|
||||
bind-utils.x86_64 30:9.3.6-25.P1.el5_11.8 updates
|
||||
`
|
||||
|
||||
r.Packages = []models.PackageInfo{
|
||||
{
|
||||
Name: "audit-libs",
|
||||
@@ -568,6 +629,21 @@ 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",
|
||||
},
|
||||
{
|
||||
Name: "bind-utils",
|
||||
Version: "1.0",
|
||||
Release: "1",
|
||||
},
|
||||
}
|
||||
var tests = []struct {
|
||||
in string
|
||||
@@ -590,6 +666,27 @@ 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",
|
||||
},
|
||||
{
|
||||
Name: "bind-utils",
|
||||
Version: "1.0",
|
||||
Release: "1",
|
||||
NewVersion: "9.3.6",
|
||||
NewRelease: "25.P1.el5_11.8",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -612,23 +709,23 @@ bash.x86_64 4.1.2-33.el6_7.1 updates
|
||||
|
||||
func TestParseYumCheckUpdateLinesAmazon(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r.Family = "amzon"
|
||||
r.Family = "amazon"
|
||||
stdout := `Loaded plugins: priorities, update-motd, upgrade-helper
|
||||
34 package(s) needed for security, out of 71 available
|
||||
|
||||
bind-libs.x86_64 32:9.8.2-0.37.rc1.45.amzn1 amzn-main
|
||||
java-1.7.0-openjdk.x86_64 1:1.7.0.95-2.6.4.0.65.amzn1 amzn-main
|
||||
java-1.7.0-openjdk.x86_64 1.7.0.95-2.6.4.0.65.amzn1 amzn-main
|
||||
if-not-architecture 100-200 amzn-main
|
||||
`
|
||||
r.Packages = []models.PackageInfo{
|
||||
{
|
||||
Name: "bind-libs",
|
||||
Version: "32:9.8.0",
|
||||
Version: "9.8.0",
|
||||
Release: "0.33.rc1.45.amzn1",
|
||||
},
|
||||
{
|
||||
Name: "java-1.7.0-openjdk",
|
||||
Version: "1:1.7.0.0",
|
||||
Version: "1.7.0.0",
|
||||
Release: "2.6.4.0.0.amzn1",
|
||||
},
|
||||
{
|
||||
@@ -646,16 +743,16 @@ if-not-architecture 100-200 amzn-main
|
||||
models.PackageInfoList{
|
||||
{
|
||||
Name: "bind-libs",
|
||||
Version: "32:9.8.0",
|
||||
Version: "9.8.0",
|
||||
Release: "0.33.rc1.45.amzn1",
|
||||
NewVersion: "32:9.8.2",
|
||||
NewVersion: "9.8.2",
|
||||
NewRelease: "0.37.rc1.45.amzn1",
|
||||
},
|
||||
{
|
||||
Name: "java-1.7.0-openjdk",
|
||||
Version: "1:1.7.0.0",
|
||||
Version: "1.7.0.0",
|
||||
Release: "2.6.4.0.0.amzn1",
|
||||
NewVersion: "1:1.7.0.95",
|
||||
NewVersion: "1.7.0.95",
|
||||
NewRelease: "2.6.4.0.65.amzn1",
|
||||
},
|
||||
{
|
||||
@@ -774,39 +871,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 {
|
||||
@@ -841,3 +905,304 @@ func TestExtractPackNameVerRel(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const (
|
||||
/* for CentOS6,7 (yum-util >= 1.1.20) */
|
||||
stdoutCentos6 = `---> Package libaio.x86_64 0:0.3.107-10.el6 will be installed
|
||||
--> Finished Dependency Resolution
|
||||
|
||||
Changes in packages about to be updated:
|
||||
|
||||
ChangeLog for: binutils-2.20.51.0.2-5.44.el6.x86_64
|
||||
* Mon Dec 7 21:00:00 2015 Nick Clifton <nickc@redhat.com> - 2.20.51.0.2-5.44
|
||||
- Backport upstream RELRO fixes. (#1227839)
|
||||
|
||||
** No ChangeLog for: chkconfig-1.3.49.5-1.el6.x86_64
|
||||
|
||||
ChangeLog for: coreutils-8.4-43.el6.x86_64, coreutils-libs-8.4-43.el6.x86_64
|
||||
* Wed Feb 10 21:00:00 2016 Ondrej Vasik <ovasik@redhat.com> - 8.4-43
|
||||
- sed should actually be /bin/sed (related #1222140)
|
||||
|
||||
* Wed Jan 6 21:00:00 2016 Ondrej Vasik <ovasik@redhat.com> - 8.4-41
|
||||
- colorls.sh,colorls.csh - call utilities with complete path (#1222140)
|
||||
- mkdir, mkfifo, mknod - respect default umask/acls when
|
||||
COREUTILS_CHILD_DEFAULT_ACLS envvar is set (to match rhel 7 behaviour,
|
||||
|
||||
ChangeLog for: centos-release-6-8.el6.centos.12.3.x86_64
|
||||
* Wed May 18 21:00:00 2016 Johnny Hughes <johnny@centos.org> 6-8.el6.centos.12.3
|
||||
- CentOS-6.8 Released
|
||||
- TESTSTRING CVE-0000-0000
|
||||
|
||||
ChangeLog for: 12:dhclient-4.1.1-51.P1.el6.centos.x86_64, 12:dhcp-common-4.1.1-51.P1.el6.centos.x86_64
|
||||
* Tue May 10 21:00:00 2016 Johnny Hughes <johnny@centos.org> - 12:4.1.1-51.P1
|
||||
- created patch 1000 for CentOS Branding
|
||||
- replaced vvendor variable with CentOS in the SPEC file
|
||||
- TESTSTRING CVE-1111-1111
|
||||
|
||||
* Mon Jan 11 21:00:00 2016 Jiri Popelka <jpopelka@redhat.com> - 12:4.1.1-51.P1
|
||||
- send unicast request/release via correct interface (#1297445)
|
||||
|
||||
* Thu Dec 3 21:00:00 2015 Jiri Popelka <jpopelka@redhat.com> - 12:4.1.1-50.P1
|
||||
- Lease table overflow crash. (#1133917)
|
||||
- Add ignore-client-uids option. (#1196768)
|
||||
- dhclient-script: it's OK if the arping reply comes from our system. (#1204095)
|
||||
- VLAN ID is only bottom 12-bits of TCI. (#1259552)
|
||||
- dhclient: Make sure link-local address is ready in stateless mode. (#1263466)
|
||||
- dhclient-script: make_resolv_conf(): Keep old nameservers
|
||||
if server sends domain-name/search, but no nameservers. (#1269595)
|
||||
|
||||
ChangeLog for: file-5.04-30.el6.x86_64, file-libs-5.04-30.el6.x86_64
|
||||
* Tue Feb 16 21:00:00 2016 Jan Kaluza <jkaluza@redhat.com> 5.04-30
|
||||
- fix CVE-2014-3538 (unrestricted regular expression matching)
|
||||
|
||||
* Tue Jan 5 21:00:00 2016 Jan Kaluza <jkaluza@redhat.com> 5.04-29
|
||||
- fix #1284826 - try to read ELF header to detect corrupted one
|
||||
|
||||
* Wed Dec 16 21:00:00 2015 Jan Kaluza <jkaluza@redhat.com> 5.04-28
|
||||
- fix #1263987 - fix bugs found by coverity in the patch
|
||||
|
||||
* Thu Nov 26 21:00:00 2015 Jan Kaluza <jkaluza@redhat.com> 5.04-27
|
||||
- fix CVE-2014-3587 (incomplete fix for CVE-2012-1571)
|
||||
- fix CVE-2014-3710 (out-of-bounds read in elf note headers)
|
||||
- fix CVE-2014-8116 (multiple DoS issues (resource consumption))
|
||||
- fix CVE-2014-8117 (denial of service issue (resource consumption))
|
||||
- fix CVE-2014-9620 (limit the number of ELF notes processed)
|
||||
- fix CVE-2014-9653 (malformed elf file causes access to uninitialized memory)
|
||||
|
||||
|
||||
Dependencies Resolved
|
||||
|
||||
`
|
||||
/* for CentOS5 (yum-util < 1.1.20) */
|
||||
stdoutCentos5 = `---> Package portmap.i386 0:4.0-65.2.2.1 set to be updated
|
||||
--> Finished Dependency Resolution
|
||||
|
||||
Changes in packages about to be updated:
|
||||
|
||||
libuser-0.54.7-3.el5.i386
|
||||
nss_db-2.2-38.el5_11.i386
|
||||
* Thu Nov 20 23:00:00 2014 Nalin Dahyabhai <nalin@redhat.com> - 2.2-38
|
||||
- build without strict aliasing (internal build tooling)
|
||||
|
||||
* Sat Nov 15 23:00:00 2014 Nalin Dahyabhai <nalin@redhat.com> - 2.2-37
|
||||
- pull in fix for a memory leak in nss_db (#1163493)
|
||||
|
||||
acpid-1.0.4-12.el5.i386
|
||||
* Thu Oct 6 00:00:00 2011 Jiri Skala <jskala@redhat.com> - 1.0.4-12
|
||||
- Resolves: #729769 - acpid dumping useless info to log
|
||||
|
||||
nash-5.1.19.6-82.el5.i386, mkinitrd-5.1.19.6-82.el5.i386
|
||||
* Tue Apr 15 00:00:00 2014 Brian C. Lane <bcl@redhat.com> 5.1.19.6-82
|
||||
- Use ! instead of / when searching sysfs for ccis device
|
||||
Resolves: rhbz#988020
|
||||
- Always include ahci module (except on s390) (bcl)
|
||||
Resolves: rhbz#978245
|
||||
- Prompt to recreate default initrd (karsten)
|
||||
Resolves: rhbz#472764
|
||||
|
||||
util-linux-2.13-0.59.el5_8.i386
|
||||
* Wed Oct 17 00:00:00 2012 Karel Zak <kzak@redhat.com> 2.13-0.59.el5_8
|
||||
- fix #865791 - fdisk fails to partition disk not in use
|
||||
|
||||
* Wed Dec 21 23:00:00 2011 Karel Zak <kzak@redhat.com> 2.13-0.59
|
||||
- fix #768382 - CVE-2011-1675 CVE-2011-1677 util-linux various flaws
|
||||
|
||||
* Wed Oct 26 00:00:00 2011 Karel Zak <kzak@redhat.com> 2.13-0.58
|
||||
- fix #677452 - util-linux fails to build with gettext-0.17
|
||||
|
||||
30:bind-utils-9.3.6-25.P1.el5_11.8.i386, 30:bind-libs-9.3.6-25.P1.el5_11.8.i386
|
||||
* Mon Mar 14 23:00:00 2016 Tomas Hozza <thozza@redhat.com> - 30:9.3.6-25.P1.8
|
||||
- Fix issue with patch for CVE-2016-1285 and CVE-2016-1286 found by test suite
|
||||
|
||||
* Wed Mar 9 23:00:00 2016 Tomas Hozza <thozza@redhat.com> - 30:9.3.6-25.P1.7
|
||||
- Fix CVE-2016-1285 and CVE-2016-1286
|
||||
|
||||
* Mon Jan 18 23:00:00 2016 Tomas Hozza <thozza@redhat.com> - 30:9.3.6-25.P1.6
|
||||
- Fix CVE-2015-8704
|
||||
|
||||
* Thu Sep 3 00:00:00 2015 Tomas Hozza <thozza@redhat.com> - 30:9.3.6-25.P1.5
|
||||
- Fix CVE-2015-8000
|
||||
|
||||
|
||||
Dependencies Resolved
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
func TestGetChangelogCVELines(t *testing.T) {
|
||||
var testsCentos6 = []struct {
|
||||
in models.PackageInfo
|
||||
out string
|
||||
}{
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "binutils",
|
||||
NewVersion: "2.20.51.0.2",
|
||||
NewRelease: "5.44.el6",
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "centos-release",
|
||||
NewVersion: "6",
|
||||
NewRelease: "8.el6.centos.12.3",
|
||||
},
|
||||
`- TESTSTRING CVE-0000-0000
|
||||
`,
|
||||
},
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "dhclient",
|
||||
NewVersion: "4.1.1",
|
||||
NewRelease: "51.P1.el6.centos",
|
||||
},
|
||||
`- TESTSTRING CVE-1111-1111
|
||||
`,
|
||||
},
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "dhcp-common",
|
||||
NewVersion: "4.1.1",
|
||||
NewRelease: "51.P1.el6.centos",
|
||||
},
|
||||
`- TESTSTRING CVE-1111-1111
|
||||
`,
|
||||
},
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "coreutils-libs",
|
||||
NewVersion: "8.4",
|
||||
NewRelease: "43.el6",
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "file",
|
||||
NewVersion: "5.04",
|
||||
NewRelease: "30.el6",
|
||||
},
|
||||
`- fix CVE-2014-3538 (unrestricted regular expression matching)
|
||||
- fix CVE-2014-3587 (incomplete fix for CVE-2012-1571)
|
||||
- fix CVE-2014-3710 (out-of-bounds read in elf note headers)
|
||||
- fix CVE-2014-8116 (multiple DoS issues (resource consumption))
|
||||
- fix CVE-2014-8117 (denial of service issue (resource consumption))
|
||||
- fix CVE-2014-9620 (limit the number of ELF notes processed)
|
||||
- fix CVE-2014-9653 (malformed elf file causes access to uninitialized memory)
|
||||
`,
|
||||
},
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "file-libs",
|
||||
NewVersion: "5.04",
|
||||
NewRelease: "30.el6",
|
||||
},
|
||||
`- fix CVE-2014-3538 (unrestricted regular expression matching)
|
||||
- fix CVE-2014-3587 (incomplete fix for CVE-2012-1571)
|
||||
- fix CVE-2014-3710 (out-of-bounds read in elf note headers)
|
||||
- fix CVE-2014-8116 (multiple DoS issues (resource consumption))
|
||||
- fix CVE-2014-8117 (denial of service issue (resource consumption))
|
||||
- fix CVE-2014-9620 (limit the number of ELF notes processed)
|
||||
- fix CVE-2014-9653 (malformed elf file causes access to uninitialized memory)
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r.Family = "centos"
|
||||
r.Release = "6.7"
|
||||
for _, tt := range testsCentos6 {
|
||||
rpm2changelog, err := r.parseAllChangelog(stdoutCentos6)
|
||||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
changelog := r.getChangelogCVELines(rpm2changelog, tt.in)
|
||||
if tt.out != changelog {
|
||||
t.Errorf("line: expected %s, actual %s, tt: %#v", tt.out, changelog, tt)
|
||||
}
|
||||
}
|
||||
|
||||
var testsCentos5 = []struct {
|
||||
in models.PackageInfo
|
||||
out string
|
||||
}{
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "libuser",
|
||||
NewVersion: "0.54.7",
|
||||
NewRelease: "3.el5",
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "nss_db",
|
||||
NewVersion: "2.2",
|
||||
NewRelease: "38.el5_11",
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "acpid",
|
||||
NewVersion: "1.0.4",
|
||||
NewRelease: "82.el5",
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "mkinitrd",
|
||||
NewVersion: "5.1.19.6",
|
||||
NewRelease: "82.el5",
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "util-linux",
|
||||
NewVersion: "2.13",
|
||||
NewRelease: "0.59.el5_8",
|
||||
},
|
||||
`- fix #768382 - CVE-2011-1675 CVE-2011-1677 util-linux various flaws
|
||||
`,
|
||||
},
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "bind-libs",
|
||||
NewVersion: "9.3.6",
|
||||
NewRelease: "25.P1.el5_11.8",
|
||||
},
|
||||
`- Fix issue with patch for CVE-2016-1285 and CVE-2016-1286 found by test suite
|
||||
- Fix CVE-2016-1285 and CVE-2016-1286
|
||||
- Fix CVE-2015-8704
|
||||
- Fix CVE-2015-8000
|
||||
`,
|
||||
},
|
||||
{
|
||||
models.PackageInfo{
|
||||
Name: "bind-utils",
|
||||
NewVersion: "9.3.6",
|
||||
NewRelease: "25.P1.el5_11.8",
|
||||
},
|
||||
`- Fix issue with patch for CVE-2016-1285 and CVE-2016-1286 found by test suite
|
||||
- Fix CVE-2016-1285 and CVE-2016-1286
|
||||
- Fix CVE-2015-8704
|
||||
- Fix CVE-2015-8000
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
r.Release = "5.6"
|
||||
for _, tt := range testsCentos5 {
|
||||
rpm2changelog, err := r.parseAllChangelog(stdoutCentos5)
|
||||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
changelog := r.getChangelogCVELines(rpm2changelog, tt.in)
|
||||
if tt.out != changelog {
|
||||
t.Errorf("line: expected %s, actual %s, tt: %#v", tt.out, changelog, tt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -16,16 +15,30 @@ var Log *logrus.Entry
|
||||
|
||||
var servers []osTypeInterface
|
||||
|
||||
// Base Interface of redhat, debian
|
||||
// Base Interface of redhat, debian, freebsd
|
||||
type osTypeInterface interface {
|
||||
setServerInfo(config.ServerInfo)
|
||||
getServerInfo() config.ServerInfo
|
||||
|
||||
setDistributionInfo(string, string)
|
||||
getDistributionInfo() string
|
||||
|
||||
checkIfSudoNoPasswd() error
|
||||
detectPlatform() error
|
||||
getPlatform() models.Platform
|
||||
|
||||
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
|
||||
@@ -52,10 +65,9 @@ type CvePacksList []CvePacksInfo
|
||||
type CvePacksInfo struct {
|
||||
CveID string
|
||||
CveDetail cve.CveDetail
|
||||
Packs []models.PackageInfo
|
||||
DistroAdvisories []models.DistroAdvisory // for Aamazon, RHEL
|
||||
Packs models.PackageInfoList
|
||||
DistroAdvisories []models.DistroAdvisory // for Aamazon, RHEL, FreeBSD
|
||||
CpeNames []string
|
||||
// CvssScore float64
|
||||
}
|
||||
|
||||
// FindByCveID find by CVEID
|
||||
@@ -91,53 +103,298 @@ func (s CvePacksList) Swap(i, j int) {
|
||||
|
||||
// Less implement Sort Interface
|
||||
func (s CvePacksList) Less(i, j int) bool {
|
||||
return s[i].CveDetail.CvssScore("en") > s[j].CveDetail.CvssScore("en")
|
||||
return s[i].CveDetail.CvssScore(config.Conf.Lang) >
|
||||
s[j].CveDetail.CvssScore(config.Conf.Lang)
|
||||
}
|
||||
|
||||
func detectOs(c config.ServerInfo) (osType osTypeInterface) {
|
||||
func detectOS(c config.ServerInfo) (osType osTypeInterface) {
|
||||
var itsMe bool
|
||||
itsMe, osType = detectDebian(c)
|
||||
if itsMe {
|
||||
var fatalErr error
|
||||
|
||||
itsMe, osType, fatalErr = detectDebian(c)
|
||||
if fatalErr != nil {
|
||||
osType.setServerInfo(c)
|
||||
osType.setErrs([]error{fatalErr})
|
||||
return
|
||||
} else if itsMe {
|
||||
Log.Debugf("Debian like Linux. Host: %s:%s", c.Host, c.Port)
|
||||
return
|
||||
}
|
||||
itsMe, osType = detectRedhat(c)
|
||||
|
||||
if itsMe, osType = detectRedhat(c); itsMe {
|
||||
Log.Debugf("Redhat like Linux. Host: %s:%s", c.Host, c.Port)
|
||||
return
|
||||
}
|
||||
if itsMe, osType = detectFreebsd(c); itsMe {
|
||||
Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port)
|
||||
return
|
||||
}
|
||||
osType.setServerInfo(c)
|
||||
osType.setErrs([]error{fmt.Errorf("Unknown OS Type")})
|
||||
return
|
||||
}
|
||||
|
||||
// PrintSSHableServerNames print SSH-able servernames
|
||||
func PrintSSHableServerNames() {
|
||||
Log.Info("SSH-able servers are below...")
|
||||
for _, s := range servers {
|
||||
if s.getServerInfo().IsContainer() {
|
||||
fmt.Printf("%s@%s ",
|
||||
s.getServerInfo().Container.Name,
|
||||
s.getServerInfo().ServerName,
|
||||
)
|
||||
} else {
|
||||
fmt.Printf("%s ", s.getServerInfo().ServerName)
|
||||
}
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
// InitServers detect the kind of OS distribution of target servers
|
||||
func InitServers(localLogger *logrus.Entry) (err error) {
|
||||
func InitServers(localLogger *logrus.Entry) {
|
||||
Log = localLogger
|
||||
if servers, err = detectServersOS(); err != nil {
|
||||
err = fmt.Errorf("Failed to detect OS")
|
||||
} else {
|
||||
Log.Debugf("%s", pp.Sprintf("%s", servers))
|
||||
}
|
||||
return
|
||||
servers = detectServerOSes()
|
||||
containers := detectContainerOSes()
|
||||
servers = append(servers, containers...)
|
||||
}
|
||||
|
||||
func detectServersOS() (osi []osTypeInterface, err error) {
|
||||
func detectServerOSes() (sshAbleOses []osTypeInterface) {
|
||||
Log.Info("Detecting OS of servers... ")
|
||||
osTypeChan := make(chan osTypeInterface, len(config.Conf.Servers))
|
||||
defer close(osTypeChan)
|
||||
for _, s := range config.Conf.Servers {
|
||||
go func(s config.ServerInfo) {
|
||||
osTypeChan <- detectOs(s)
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
Log.Debugf("Panic: %s on %s", p, s.ServerName)
|
||||
}
|
||||
}()
|
||||
osTypeChan <- detectOS(s)
|
||||
}(s)
|
||||
}
|
||||
|
||||
timeout := time.After(60 * time.Second)
|
||||
var oses []osTypeInterface
|
||||
timeout := time.After(30 * time.Second)
|
||||
for i := 0; i < len(config.Conf.Servers); i++ {
|
||||
select {
|
||||
case res := <-osTypeChan:
|
||||
osi = append(osi, res)
|
||||
oses = append(oses, res)
|
||||
if 0 < len(res.getErrs()) {
|
||||
Log.Errorf("(%d/%d) Failed: %s, err: %s",
|
||||
i+1, len(config.Conf.Servers),
|
||||
res.getServerInfo().ServerName,
|
||||
res.getErrs())
|
||||
} else {
|
||||
Log.Infof("(%d/%d) Detected: %s: %s",
|
||||
i+1, len(config.Conf.Servers),
|
||||
res.getServerInfo().ServerName,
|
||||
res.getDistributionInfo())
|
||||
}
|
||||
case <-timeout:
|
||||
Log.Error("Timeout Occured while detecting OS.")
|
||||
err = fmt.Errorf("Timeout!")
|
||||
return
|
||||
msg := "Timed out while detecting servers"
|
||||
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("(%d/%d) Timed out: %s",
|
||||
i+1, len(config.Conf.Servers),
|
||||
servername)
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, o := range oses {
|
||||
if len(o.getErrs()) == 0 {
|
||||
sshAbleOses = append(sshAbleOses, o)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func detectContainerOSes() (actives []osTypeInterface) {
|
||||
Log.Info("Detecting OS of containers... ")
|
||||
osTypesChan := make(chan []osTypeInterface, len(servers))
|
||||
defer close(osTypesChan)
|
||||
for _, s := range servers {
|
||||
go func(s osTypeInterface) {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
Log.Debugf("Panic: %s on %s",
|
||||
p, s.getServerInfo().ServerName)
|
||||
}
|
||||
}()
|
||||
osTypesChan <- detectContainerOSesOnServer(s)
|
||||
}(s)
|
||||
}
|
||||
|
||||
var oses []osTypeInterface
|
||||
timeout := time.After(30 * time.Second)
|
||||
for i := 0; i < len(servers); i++ {
|
||||
select {
|
||||
case res := <-osTypesChan:
|
||||
for _, osi := range res {
|
||||
sinfo := osi.getServerInfo()
|
||||
if 0 < len(osi.getErrs()) {
|
||||
Log.Errorf("Failed: %s err: %s", sinfo.ServerName, osi.getErrs())
|
||||
continue
|
||||
}
|
||||
oses = append(oses, res...)
|
||||
Log.Infof("Detected: %s@%s: %s",
|
||||
sinfo.Container.Name, sinfo.ServerName, osi.getDistributionInfo())
|
||||
}
|
||||
case <-timeout:
|
||||
msg := "Timed out while detecting containers"
|
||||
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("Timed out: %s", servername)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, o := range oses {
|
||||
if len(o.getErrs()) == 0 {
|
||||
actives = append(actives, o)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeInterface) {
|
||||
containerHostInfo := containerHost.getServerInfo()
|
||||
if len(containerHostInfo.Containers) == 0 {
|
||||
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
|
||||
}
|
||||
|
||||
// CheckIfSudoNoPasswd checks whether vuls can sudo with nopassword via SSH
|
||||
func CheckIfSudoNoPasswd(localLogger *logrus.Entry) error {
|
||||
timeoutSec := 1 * 15
|
||||
errs := parallelSSHExec(func(o osTypeInterface) error {
|
||||
return o.checkIfSudoNoPasswd()
|
||||
}, timeoutSec)
|
||||
|
||||
if 0 < len(errs) {
|
||||
return fmt.Errorf(fmt.Sprintf("%s", errs))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetectPlatforms detects the platform of each servers.
|
||||
func DetectPlatforms(localLogger *logrus.Entry) {
|
||||
errs := detectPlatforms()
|
||||
if 0 < len(errs) {
|
||||
// Only logging
|
||||
Log.Warnf("Failed to detect platforms. err: %v", errs)
|
||||
}
|
||||
for i, s := range servers {
|
||||
if s.getServerInfo().IsContainer() {
|
||||
Log.Infof("(%d/%d) %s on %s is running on %s",
|
||||
i+1, len(servers),
|
||||
s.getServerInfo().Container.Name,
|
||||
s.getServerInfo().ServerName,
|
||||
s.getPlatform().Name,
|
||||
)
|
||||
|
||||
} else {
|
||||
Log.Infof("(%d/%d) %s is running on %s",
|
||||
i+1, len(servers),
|
||||
s.getServerInfo().ServerName,
|
||||
s.getPlatform().Name,
|
||||
)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func detectPlatforms() []error {
|
||||
timeoutSec := 1 * 60
|
||||
return parallelSSHExec(func(o osTypeInterface) error {
|
||||
return o.detectPlatform()
|
||||
}, timeoutSec)
|
||||
}
|
||||
|
||||
// Prepare installs requred packages to scan vulnerabilities.
|
||||
func Prepare() []error {
|
||||
return parallelSSHExec(func(o osTypeInterface) error {
|
||||
@@ -151,7 +408,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 +417,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
|
||||
}
|
||||
@@ -180,7 +437,7 @@ func checkRequiredPackagesInstalled() []error {
|
||||
}
|
||||
|
||||
func scanPackages() []error {
|
||||
timeoutSec := 30 * 60
|
||||
timeoutSec := 120 * 60
|
||||
return parallelSSHExec(func(o osTypeInterface) error {
|
||||
return o.scanPackages()
|
||||
}, timeoutSec)
|
||||
@@ -201,7 +458,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)
|
||||
}
|
||||
|
||||
245
scan/sshutil.go
245
scan/sshutil.go
@@ -25,7 +25,10 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
@@ -34,18 +37,30 @@ import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
conf "github.com/future-architect/vuls/config"
|
||||
"github.com/k0kubun/pp"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
type sshResult struct {
|
||||
Servername string
|
||||
Host string
|
||||
Port string
|
||||
Cmd string
|
||||
Stdout string
|
||||
Stderr string
|
||||
ExitStatus int
|
||||
Error error
|
||||
}
|
||||
|
||||
func (s sshResult) String() string {
|
||||
return fmt.Sprintf(
|
||||
"SSHResult: servername: %s, cmd: %s, exitstatus: %d, stdout: %s, stderr: %s, err: %s",
|
||||
s.Servername, s.Cmd, s.ExitStatus, s.Stdout, s.Stderr, s.Error)
|
||||
}
|
||||
|
||||
func (s sshResult) isSuccess(expectedStatusCodes ...int) bool {
|
||||
if s.Error != nil {
|
||||
return false
|
||||
}
|
||||
if len(expectedStatusCodes) == 0 {
|
||||
return s.ExitStatus == 0
|
||||
}
|
||||
@@ -64,10 +79,19 @@ const sudo = true
|
||||
const noSudo = false
|
||||
|
||||
func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []error) {
|
||||
resChan := make(chan string, len(servers))
|
||||
errChan := make(chan error, len(servers))
|
||||
defer close(errChan)
|
||||
defer close(resChan)
|
||||
|
||||
for _, s := range servers {
|
||||
go func(s osTypeInterface) {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
logrus.Debugf("Panic: %s on %s",
|
||||
p, s.getServerInfo().ServerName)
|
||||
}
|
||||
}()
|
||||
if err := fn(s); err != nil {
|
||||
errChan <- fmt.Errorf("%s@%s:%s: %s",
|
||||
s.getServerInfo().User,
|
||||
@@ -76,7 +100,7 @@ func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []
|
||||
err,
|
||||
)
|
||||
} else {
|
||||
errChan <- nil
|
||||
resChan <- s.getServerInfo().ServerName
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
@@ -88,66 +112,78 @@ func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []
|
||||
timeout = timeoutSec[0]
|
||||
}
|
||||
|
||||
var snames []string
|
||||
isTimedout := false
|
||||
for i := 0; i < len(servers); i++ {
|
||||
select {
|
||||
case s := <-resChan:
|
||||
snames = append(snames, s)
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
logrus.Debug("Parallel SSH Success.")
|
||||
}
|
||||
errs = append(errs, err)
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
logrus.Errorf("Parallel SSH Timeout.")
|
||||
errs = append(errs, fmt.Errorf("Timed out!"))
|
||||
isTimedout = true
|
||||
}
|
||||
}
|
||||
|
||||
// collect timed out servernames
|
||||
var timedoutSnames []string
|
||||
if isTimedout {
|
||||
for _, s := range servers {
|
||||
name := s.getServerInfo().ServerName
|
||||
found := false
|
||||
for _, t := range snames {
|
||||
if name == t {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
timedoutSnames = append(timedoutSnames, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if isTimedout {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"Timed out: %s", timedoutSnames))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result sshResult) {
|
||||
// Setup Logger
|
||||
var logger *logrus.Entry
|
||||
if len(log) == 0 {
|
||||
level := logrus.InfoLevel
|
||||
if conf.Conf.Debug == true {
|
||||
level = logrus.DebugLevel
|
||||
}
|
||||
l := &logrus.Logger{
|
||||
Out: os.Stderr,
|
||||
Formatter: new(logrus.TextFormatter),
|
||||
Hooks: make(logrus.LevelHooks),
|
||||
Level: level,
|
||||
}
|
||||
logger = logrus.NewEntry(l)
|
||||
if isSSHExecNative() {
|
||||
result = sshExecNative(c, cmd, sudo)
|
||||
} else {
|
||||
logger = log[0]
|
||||
result = sshExecExternal(c, cmd, sudo)
|
||||
}
|
||||
|
||||
var err error
|
||||
if sudo && c.User != "root" {
|
||||
switch {
|
||||
case c.SudoOpt.ExecBySudo:
|
||||
cmd = fmt.Sprintf("echo %s | sudo -S %s", c.Password, cmd)
|
||||
case c.SudoOpt.ExecBySudoSh:
|
||||
cmd = fmt.Sprintf("echo %s | sudo sh -c '%s'", c.Password, cmd)
|
||||
default:
|
||||
logger.Panicf("sudoOpt is invalid. SudoOpt: %v", c.SudoOpt)
|
||||
}
|
||||
}
|
||||
// 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 := getSSHLogger(log...)
|
||||
logger.Debug(result)
|
||||
return
|
||||
}
|
||||
|
||||
func isSSHExecNative() bool {
|
||||
return runtime.GOOS == "windows" || !conf.Conf.SSHExternal
|
||||
}
|
||||
|
||||
func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult) {
|
||||
result.Servername = c.ServerName
|
||||
result.Host = c.Host
|
||||
result.Port = c.Port
|
||||
|
||||
var client *ssh.Client
|
||||
client, err = sshConnect(c)
|
||||
var err error
|
||||
if client, err = sshConnect(c); err != nil {
|
||||
result.Error = err
|
||||
result.ExitStatus = 999
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var session *ssh.Session
|
||||
if session, err = client.NewSession(); err != nil {
|
||||
logger.Errorf("Failed to new session. err: %s, c: %s",
|
||||
err,
|
||||
pp.Sprintf("%v", c))
|
||||
result.Error = fmt.Errorf(
|
||||
"Failed to create a new session. servername: %s, err: %s",
|
||||
c.ServerName, err)
|
||||
result.ExitStatus = 999
|
||||
return
|
||||
}
|
||||
@@ -159,11 +195,10 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
|
||||
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
|
||||
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
||||
}
|
||||
if err = session.RequestPty("xterm", 400, 120, modes); err != nil {
|
||||
logger.Errorf("Failed to request for pseudo terminal. err: %s, c: %s",
|
||||
err,
|
||||
pp.Sprintf("%v", c))
|
||||
|
||||
if err = session.RequestPty("xterm", 400, 256, modes); err != nil {
|
||||
result.Error = fmt.Errorf(
|
||||
"Failed to request for pseudo terminal. servername: %s, err: %s",
|
||||
c.ServerName, err)
|
||||
result.ExitStatus = 999
|
||||
return
|
||||
}
|
||||
@@ -172,6 +207,7 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
|
||||
session.Stdout = &stdoutBuf
|
||||
session.Stderr = &stderrBuf
|
||||
|
||||
cmd = decolateCmd(c, cmd, sudo)
|
||||
if err := session.Run(cmd); err != nil {
|
||||
if exitErr, ok := err.(*ssh.ExitError); ok {
|
||||
result.ExitStatus = exitErr.ExitStatus()
|
||||
@@ -184,16 +220,103 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
|
||||
|
||||
result.Stdout = stdoutBuf.String()
|
||||
result.Stderr = stderrBuf.String()
|
||||
result.Cmd = strings.Replace(cmd, "\n", "", -1)
|
||||
return
|
||||
}
|
||||
|
||||
func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result sshResult) {
|
||||
sshBinaryPath, err := exec.LookPath("ssh")
|
||||
if err != nil {
|
||||
return sshExecNative(c, cmd, sudo)
|
||||
}
|
||||
|
||||
defaultSSHArgs := []string{
|
||||
"-t",
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
"-o", "UserKnownHostsFile=/dev/null",
|
||||
"-o", "LogLevel=quiet",
|
||||
"-o", "ConnectionAttempts=3",
|
||||
"-o", "ConnectTimeout=10",
|
||||
"-o", "ControlMaster=no",
|
||||
"-o", "ControlPath=none",
|
||||
|
||||
// TODO ssh session multiplexing
|
||||
// "-o", "ControlMaster=auto",
|
||||
// "-o", `ControlPath=~/.ssh/controlmaster-%r-%h.%p`,
|
||||
// "-o", "Controlpersist=30m",
|
||||
}
|
||||
args := append(defaultSSHArgs, fmt.Sprintf("%s@%s", c.User, c.Host))
|
||||
args = append(args, "-p", c.Port)
|
||||
|
||||
// if conf.Conf.Debug {
|
||||
// args = append(args, "-v")
|
||||
// }
|
||||
|
||||
if 0 < len(c.KeyPath) {
|
||||
args = append(args, "-i", c.KeyPath)
|
||||
args = append(args, "-o", "PasswordAuthentication=no")
|
||||
}
|
||||
|
||||
cmd = decolateCmd(c, cmd, sudo)
|
||||
// cmd = fmt.Sprintf("stty cols 256; set -o pipefail; %s", cmd)
|
||||
|
||||
args = append(args, cmd)
|
||||
execCmd := exec.Command(sshBinaryPath, args...)
|
||||
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
execCmd.Stdout = &stdoutBuf
|
||||
execCmd.Stderr = &stderrBuf
|
||||
if err := execCmd.Run(); err != nil {
|
||||
if e, ok := err.(*exec.ExitError); ok {
|
||||
if s, ok := e.Sys().(syscall.WaitStatus); ok {
|
||||
result.ExitStatus = s.ExitStatus()
|
||||
} else {
|
||||
result.ExitStatus = 998
|
||||
}
|
||||
} else {
|
||||
result.ExitStatus = 999
|
||||
}
|
||||
} else {
|
||||
result.ExitStatus = 0
|
||||
}
|
||||
|
||||
result.Stdout = stdoutBuf.String()
|
||||
result.Stderr = stderrBuf.String()
|
||||
result.Servername = c.ServerName
|
||||
result.Host = c.Host
|
||||
result.Port = c.Port
|
||||
|
||||
logger.Debugf(
|
||||
"SSH executed. cmd: %s, status: %#v\nstdout: \n%s\nstderr: \n%s",
|
||||
cmd, err, result.Stdout, result.Stderr)
|
||||
|
||||
result.Cmd = fmt.Sprintf("%s %s", sshBinaryPath, strings.Join(args, " "))
|
||||
return
|
||||
}
|
||||
|
||||
func getSSHLogger(log ...*logrus.Entry) *logrus.Entry {
|
||||
if len(log) == 0 {
|
||||
return util.NewCustomLogger(conf.ServerInfo{})
|
||||
}
|
||||
return log[0]
|
||||
}
|
||||
|
||||
func decolateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
|
||||
if sudo && c.User != "root" && !c.IsContainer() {
|
||||
cmd = fmt.Sprintf("sudo -S %s", cmd)
|
||||
}
|
||||
|
||||
if c.Family != "FreeBSD" {
|
||||
// set pipefail option. Bash only
|
||||
// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
|
||||
cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
|
||||
}
|
||||
|
||||
if c.IsContainer() {
|
||||
switch c.Container.Type {
|
||||
case "", "docker":
|
||||
cmd = fmt.Sprintf(`docker exec %s /bin/bash -c "%s"`, c.Container.ContainerID, cmd)
|
||||
}
|
||||
}
|
||||
// cmd = fmt.Sprintf("set -x; %s", cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func getAgentAuth() (auth ssh.AuthMethod, ok bool) {
|
||||
if sock := os.Getenv("SSH_AUTH_SOCK"); len(sock) > 0 {
|
||||
if agconn, err := net.Dial("unix", sock); err == nil {
|
||||
@@ -218,18 +341,13 @@ func tryAgentConnect(c conf.ServerInfo) *ssh.Client {
|
||||
}
|
||||
|
||||
func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
|
||||
|
||||
if client = tryAgentConnect(c); client != nil {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
var auths = []ssh.AuthMethod{}
|
||||
if auths, err = addKeyAuth(auths, c.KeyPath, c.KeyPassword); err != nil {
|
||||
logrus.Fatalf("Faild to add keyAuth. err: %s", err)
|
||||
}
|
||||
|
||||
if c.Password != "" {
|
||||
auths = append(auths, ssh.Password(c.Password))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// http://blog.ralch.com/tutorial/golang-ssh-connection/
|
||||
@@ -237,12 +355,11 @@ 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...",
|
||||
c.User, c.Host, c.Port, e, t)
|
||||
logrus.Debugf("sshConInfo: %s", pp.Sprintf("%v", c))
|
||||
logger := getSSHLogger()
|
||||
logger.Debugf("Failed to Dial to %s, err: %s, Retrying in %s...",
|
||||
c.ServerName, e, t)
|
||||
}
|
||||
err = backoff.RetryNotify(func() error {
|
||||
if client, err = ssh.Dial("tcp", c.Host+":"+c.Port, config); err != nil {
|
||||
@@ -306,6 +423,6 @@ func parsePemBlock(block *pem.Block) (interface{}, error) {
|
||||
case "DSA PRIVATE KEY":
|
||||
return ssh.ParseDSAPrivateKey(block.Bytes)
|
||||
default:
|
||||
return nil, fmt.Errorf("rtop: unsupported key type %q", block.Type)
|
||||
return nil, fmt.Errorf("Unsupported key type %q", block.Type)
|
||||
}
|
||||
}
|
||||
|
||||
101
setup/docker/README.ja.md
Normal file
101
setup/docker/README.ja.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Vuls on Docker
|
||||
|
||||
## What's Vuls-On-Docker
|
||||
|
||||
- 数個のコマンドを実行するだけでVulsとvulsrepoのセットアップが出来るスクリプト
|
||||
- Dockerコンテナ上にVulsと[vulsrepo](https://github.com/usiusi360/vulsrepo)をセットアップ可能
|
||||
- スキャン結果をvulsrepoでブラウザで分析可能
|
||||
- 脆弱性データベースの更新が可能
|
||||
- モジュールのアップデートが可能
|
||||
|
||||
## Setting up your machine
|
||||
|
||||
1. [Install Docker](https://docs.docker.com/engine/installation/)
|
||||
2. [Install Docker-Compose](https://docs.docker.com/compose/install/)
|
||||
3. 実行前に以下のコマンドが実行可能なことを確認する
|
||||
|
||||
```
|
||||
$ docker version
|
||||
$ docker-compose version
|
||||
```
|
||||
|
||||
4. Vulsをgit clone
|
||||
```
|
||||
mkdir work
|
||||
cd work
|
||||
git clone https://github.com/future-architect/vuls.git
|
||||
cd vuls/setup/docker
|
||||
```
|
||||
|
||||
## Start A Vuls Container
|
||||
|
||||
- 以下のコマンドを実行してコンテナをビルドする
|
||||
|
||||
```
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
## Setting up Vuls
|
||||
|
||||
1. スキャン対象サーバのSSH秘密鍵を保存(vuls/setup/docker/conf/)する
|
||||
2. config.toml(vuls/docker/conf/config.toml) を環境に合わせて作成する
|
||||
|
||||
```
|
||||
[servers]
|
||||
|
||||
[servers.172-31-4-82]
|
||||
host = "172.31.4.82"
|
||||
user = "ec2-user"
|
||||
keyPath = "conf/id_rsa"
|
||||
```
|
||||
|
||||
## Fetch Vulnerability database
|
||||
|
||||
- NVDから脆弱性データベースを取得する
|
||||
```
|
||||
$ docker exec -t vuls scripts/fetch_nvd_all.sh
|
||||
```
|
||||
|
||||
- レポートを日本語化する場合は、JVNから脆弱性データを取得する
|
||||
```
|
||||
$ docker exec -t vuls scripts/fetch_jvn_all.sh
|
||||
```
|
||||
|
||||
## Scan servers with Vuls-On-Docker
|
||||
|
||||
- スキャンを実行する
|
||||
|
||||
```
|
||||
$ docker exec -t vuls vuls prepare -config=conf/config.toml
|
||||
$ docker exec -t vuls scripts/scan_for_vulsrepo.sh
|
||||
```
|
||||
|
||||
## See the results in a browser
|
||||
|
||||
```
|
||||
http://${Vuls_Host}/vulsrepo/
|
||||
```
|
||||
|
||||
# Update modules
|
||||
|
||||
- vuls, go-cve-dictionary, vulsrepoのモジュールをアップデートする
|
||||
```
|
||||
$ docker exec -t vuls scripts/update_modules.sh
|
||||
```
|
||||
|
||||
# Update Vulnerability database
|
||||
|
||||
- NVDの過去2年分の脆弱性データベースを更新する
|
||||
```
|
||||
$ docker exec -t vuls scripts/fetch_nvd_last2y.sh
|
||||
```
|
||||
|
||||
- JVNの過去1ヶ月分の脆弱性データベースを更新する
|
||||
```
|
||||
$ docker exec -t vuls scripts/fetch_jvn_month.sh
|
||||
```
|
||||
|
||||
- JVNの過去1週間分の脆弱性データベースを更新する
|
||||
```
|
||||
$ docker exec -t vuls scripts/fetch_jvn_week.sh
|
||||
```
|
||||
87
setup/docker/README.md
Normal file
87
setup/docker/README.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Vuls on Docker
|
||||
|
||||
## What's Vuls-On-Docker
|
||||
|
||||
- This is a dockernized-Vuls with vulsrepo UI in it.
|
||||
- It's designed to reduce the cost of installation and the dependencies that vuls requires.
|
||||
- You can run install and run Vuls on your machine with only a few commands.
|
||||
- The result can be viewed with a browser
|
||||
|
||||
## Setting up your machine
|
||||
|
||||
1. [Install Docker](https://docs.docker.com/engine/installation/)
|
||||
2. [Install Docker-Compose](https://docs.docker.com/compose/install/)
|
||||
3. Make sure that you can run the following commands before you move on.
|
||||
|
||||
```
|
||||
$ docker version
|
||||
$ docker-compose version
|
||||
```
|
||||
|
||||
4. git clone vuls
|
||||
```
|
||||
mkdir work
|
||||
cd work
|
||||
git clone https://github.com/future-architect/vuls.git
|
||||
cd vuls/setup/docker
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Start A Vuls Container
|
||||
|
||||
- Execute the following command to build and run a Vuls Container
|
||||
|
||||
```
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
## Setting up Vuls
|
||||
|
||||
1. Locate ssh-keys of targer servers in (vuls/docker/conf/)
|
||||
2. Create and ajust config.toml(vuls/docker/conf/config.toml) to your environment
|
||||
|
||||
```
|
||||
[servers]
|
||||
|
||||
[servers.172-31-4-82]
|
||||
host = "172.31.4.82"
|
||||
user = "ec2-user"
|
||||
keyPath = "conf/id_rsa"
|
||||
```
|
||||
|
||||
## Fetch Vulnerability database
|
||||
|
||||
- Fetch Vulnerability database from NVD
|
||||
```
|
||||
$ docker exec -t vuls scripts/fetch_nvd_all.sh
|
||||
```
|
||||
|
||||
## Scan servers with Vuls-On-Docker
|
||||
|
||||
- Use the embedded script to scan servers for vulsrepo(or run whatever with docker exec)
|
||||
|
||||
```
|
||||
$ docker exec -t vuls vuls prepare -config=conf/config.toml
|
||||
$ docker exec -t vuls scripts/scan_for_vulsrepo.sh
|
||||
```
|
||||
|
||||
## See the results in a browser
|
||||
|
||||
```
|
||||
http://${Vuls_Host}/vulsrepo/
|
||||
```
|
||||
|
||||
# Update modules
|
||||
|
||||
- update vuls, go-cve-dictionary, vulsrepo
|
||||
```
|
||||
$ docker exec -t vuls scripts/update_modules.sh
|
||||
```
|
||||
|
||||
# Update Vulnerability database
|
||||
|
||||
- Fetch Vulnerability database from NVD
|
||||
```
|
||||
$ docker exec -t vuls scripts/fetch_nvd_last2y.sh
|
||||
```
|
||||
0
setup/docker/conf/.gitkeep
Normal file
0
setup/docker/conf/.gitkeep
Normal file
11
setup/docker/docker-compose.yml
Normal file
11
setup/docker/docker-compose.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
version: '2'
|
||||
services:
|
||||
vuls:
|
||||
container_name: vuls
|
||||
build: ./dockerfile
|
||||
image: vuls-docker:0.1
|
||||
volumes:
|
||||
- ./conf:/opt/vuls/conf
|
||||
ports:
|
||||
- "80:80"
|
||||
|
||||
89
setup/docker/dockerfile/Dockerfile
Normal file
89
setup/docker/dockerfile/Dockerfile
Normal file
@@ -0,0 +1,89 @@
|
||||
FROM buildpack-deps:jessie-scm
|
||||
|
||||
# golang Install
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
g++ \
|
||||
gcc \
|
||||
libc6-dev \
|
||||
make \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GOLANG_VERSION 1.6.2
|
||||
ENV GOLANG_DOWNLOAD_URL https://golang.org/dl/go$GOLANG_VERSION.linux-amd64.tar.gz
|
||||
ENV GOLANG_DOWNLOAD_SHA256 e40c36ae71756198478624ed1bb4ce17597b3c19d243f3f0899bb5740d56212a
|
||||
|
||||
RUN curl -fsSL "$GOLANG_DOWNLOAD_URL" -o golang.tar.gz \
|
||||
&& echo "$GOLANG_DOWNLOAD_SHA256 golang.tar.gz" | sha256sum -c - \
|
||||
&& tar -C /usr/local -xzf golang.tar.gz \
|
||||
&& rm golang.tar.gz
|
||||
|
||||
ENV GOPATH /go
|
||||
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
|
||||
|
||||
RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"
|
||||
|
||||
# glide install
|
||||
ENV GLIDE_VERSION 0.10.2
|
||||
ENV GLIDE_DOWNLOAD_URL https://github.com/Masterminds/glide/releases/download/$GLIDE_VERSION/glide-$GLIDE_VERSION-linux-amd64.tar.gz
|
||||
RUN curl -fsSL "$GLIDE_DOWNLOAD_URL" -o glide.tar.gz \
|
||||
&& mkdir /usr/local/glide \
|
||||
&& tar -C /usr/local/glide -xzf glide.tar.gz \
|
||||
&& ln -s /usr/local/glide/linux-amd64/glide /usr/local/bin/ \
|
||||
&& rm glide.tar.gz
|
||||
|
||||
# nginx Install
|
||||
RUN apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62 \
|
||||
&& echo "deb http://nginx.org/packages/mainline/debian/ jessie nginx" >> /etc/apt/sources.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
ca-certificates \
|
||||
nginx \
|
||||
nginx-module-xslt \
|
||||
nginx-module-geoip \
|
||||
nginx-module-image-filter \
|
||||
nginx-module-perl \
|
||||
nginx-module-njs \
|
||||
gettext-base \
|
||||
wget \
|
||||
unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
|
||||
&& ln -sf /dev/stderr /var/log/nginx/error.log
|
||||
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
#Vuls Install
|
||||
ENV VULS_ROOT /opt/vuls
|
||||
RUN mkdir -p /var/log/vuls ${VULS_ROOT}/conf /root/.ssh/
|
||||
RUN chmod 700 -R /var/log/vuls $VULS_ROOT
|
||||
# RUN go get github.com/kotakanbe/go-cve-dictionary
|
||||
# RUN go get github.com/future-architect/vuls
|
||||
|
||||
RUN go get -v -d github.com/kotakanbe/go-cve-dictionary \
|
||||
&& cd $GOPATH/src/github.com/kotakanbe/go-cve-dictionary \
|
||||
&& glide install \
|
||||
&& go install
|
||||
|
||||
RUN go get -v -d github.com/future-architect/vuls \
|
||||
&& cd $GOPATH/src/github.com/future-architect/vuls \
|
||||
&& glide install \
|
||||
&& go install
|
||||
|
||||
# Copy custom Scripts
|
||||
COPY ./scripts/ ${VULS_ROOT}/scripts
|
||||
RUN chmod 755 ${VULS_ROOT}/scripts/*
|
||||
|
||||
|
||||
#Vulrepo Install
|
||||
RUN git clone https://github.com/usiusi360/vulsrepo /tmp/vulsrepo
|
||||
RUN mkdir /usr/share/nginx/html/vulsrepo/
|
||||
RUN cp -rp /tmp/vulsrepo/src/* /usr/share/nginx/html/vulsrepo
|
||||
RUN rm -rf /tmp/vulsrepo
|
||||
|
||||
#Home
|
||||
WORKDIR /opt/vuls
|
||||
EXPOSE 80 443
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
32
setup/docker/dockerfile/nginx.conf
Normal file
32
setup/docker/dockerfile/nginx.conf
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
user root;
|
||||
worker_processes 1;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
#gzip on;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
6
setup/docker/dockerfile/scripts/fetch_jvn_all.sh
Normal file
6
setup/docker/dockerfile/scripts/fetch_jvn_all.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
VULS_ROOT=/opt/vuls
|
||||
#VULS_CONF=${VULS_ROOT}/conf
|
||||
cd $VULS_ROOT
|
||||
go-cve-dictionary fetchjvn -entire
|
||||
|
||||
6
setup/docker/dockerfile/scripts/fetch_jvn_month.sh
Normal file
6
setup/docker/dockerfile/scripts/fetch_jvn_month.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
VULS_ROOT=/opt/vuls
|
||||
#VULS_CONF=${VULS_ROOT}/conf
|
||||
cd $VULS_ROOT
|
||||
go-cve-dictionary fetchjvn -month
|
||||
|
||||
6
setup/docker/dockerfile/scripts/fetch_jvn_week.sh
Normal file
6
setup/docker/dockerfile/scripts/fetch_jvn_week.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
VULS_ROOT=/opt/vuls
|
||||
#VULS_CONF=${VULS_ROOT}/conf
|
||||
cd $VULS_ROOT
|
||||
go-cve-dictionary fetchjvn -week
|
||||
|
||||
6
setup/docker/dockerfile/scripts/fetch_nvd_all.sh
Normal file
6
setup/docker/dockerfile/scripts/fetch_nvd_all.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
VULS_ROOT=/opt/vuls
|
||||
#VULS_CONF=${VULS_ROOT}/conf
|
||||
cd $VULS_ROOT
|
||||
for i in {2002..2016}; do go-cve-dictionary fetchnvd -years $i; done
|
||||
|
||||
6
setup/docker/dockerfile/scripts/fetch_nvd_last2y.sh
Normal file
6
setup/docker/dockerfile/scripts/fetch_nvd_last2y.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
VULS_ROOT=/opt/vuls
|
||||
#VULS_CONF=${VULS_ROOT}/conf
|
||||
cd $VULS_ROOT
|
||||
go-cve-dictionary fetchnvd -last2y
|
||||
|
||||
7
setup/docker/dockerfile/scripts/scan_for_vulsrepo.sh
Normal file
7
setup/docker/dockerfile/scripts/scan_for_vulsrepo.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
VULS_ROOT=/opt/vuls
|
||||
VULS_CONF=${VULS_ROOT}/conf
|
||||
NGINX_VULSREPO_ROOT=/usr/share/nginx/html/vulsrepo
|
||||
cd $VULS_ROOT
|
||||
vuls scan -report-json --cve-dictionary-dbpath=${VULS_ROOT}/cve.sqlite3 -config=${VULS_CONF}/config.toml
|
||||
ln -sf ${VULS_ROOT}/results/current ${NGINX_VULSREPO_ROOT}/current
|
||||
17
setup/docker/dockerfile/scripts/update_modules.sh
Normal file
17
setup/docker/dockerfile/scripts/update_modules.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd $GOPATH/src/github.com/future-architect/vuls
|
||||
git pull origin master
|
||||
glide install
|
||||
go install
|
||||
|
||||
|
||||
cd $GOPATH/src/github.com/kotakanbe/go-cve-dictionary
|
||||
git pull origin master
|
||||
glide install
|
||||
go install
|
||||
|
||||
git clone https://github.com/usiusi360/vulsrepo /tmp/vulsrepo
|
||||
cp -rp /tmp/vulsrepo/src/* /usr/share/nginx/html/vulsrepo
|
||||
rm -rf /tmp/vulsrepo
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -31,6 +31,12 @@ func GenWorkers(num int) chan<- func() {
|
||||
tasks := make(chan func())
|
||||
for i := 0; i < num; i++ {
|
||||
go func() {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
log := NewCustomLogger(config.ServerInfo{})
|
||||
log.Debugf("Panic: %s")
|
||||
}
|
||||
}()
|
||||
for f := range tasks {
|
||||
f()
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@ 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 main
|
||||
package version
|
||||
|
||||
// Name.
|
||||
// Name is Vuls
|
||||
const Name string = "vuls"
|
||||
|
||||
// Version.
|
||||
const Version string = "0.1.0"
|
||||
// Version of Vuls
|
||||
const Version string = "0.1.5"
|
||||
Reference in New Issue
Block a user