Compare commits
	
		
			303 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					b0d9c0b550 | ||
| 
						 | 
					9255132f9b | ||
| 
						 | 
					d5c0092fa3 | ||
| 
						 | 
					c7019debb9 | ||
| 
						 | 
					7131270cad | ||
| 
						 | 
					af5a1204bc | ||
| 
						 | 
					58afcfc49a | ||
| 
						 | 
					986762ca85 | ||
| 
						 | 
					6342cf79f5 | ||
| 
						 | 
					5fbf67f971 | ||
| 
						 | 
					e441e5a696 | ||
| 
						 | 
					d201efb029 | ||
| 
						 | 
					25960126c7 | ||
| 
						 | 
					63d5a6f584 | ||
| 
						 | 
					2030951a8f | ||
| 
						 | 
					cd841462cd | ||
| 
						 | 
					735aa835a6 | ||
| 
						 | 
					92e213ca32 | ||
| 
						 | 
					d077c29716 | ||
| 
						 | 
					d6eba48a50 | ||
| 
						 | 
					2a1608d1d2 | ||
| 
						 | 
					cc7d3dc2aa | ||
| 
						 | 
					a5c4c682f5 | ||
| 
						 | 
					688cfd6872 | ||
| 
						 | 
					7e268dbae1 | ||
| 
						 | 
					ce6a4231ef | ||
| 
						 | 
					e1de8ab626 | ||
| 
						 | 
					0058eaf357 | ||
| 
						 | 
					732d95098a | ||
| 
						 | 
					52f0943207 | ||
| 
						 | 
					41f99f2b65 | ||
| 
						 | 
					1f9e5c6263 | ||
| 
						 | 
					2f3eddd2ab | ||
| 
						 | 
					619a0ee700 | ||
| 
						 | 
					b1b5c2c9a0 | ||
| 
						 | 
					a86035c0bf | ||
| 
						 | 
					c66b0f4db4 | ||
| 
						 | 
					a4cf4bd314 | ||
| 
						 | 
					f1cd9383c1 | ||
| 
						 | 
					6fa57abe10 | ||
| 
						 | 
					6e77c714b5 | ||
| 
						 | 
					fbab020e6e | ||
| 
						 | 
					5581a5cce7 | ||
| 
						 | 
					b4be11775e | ||
| 
						 | 
					b079f5e52e | ||
| 
						 | 
					f9bf470a37 | ||
| 
						 | 
					9d783dd2ab | ||
| 
						 | 
					1b9aafbbaf | ||
| 
						 | 
					1d3ee6a241 | ||
| 
						 | 
					2f9c3071a6 | ||
| 
						 | 
					4b0be4f115 | ||
| 
						 | 
					1419c7c8c6 | ||
| 
						 | 
					851cecdd73 | ||
| 
						 | 
					753da3aad7 | ||
| 
						 | 
					65c10d6d8e | ||
| 
						 | 
					1b8b423131 | ||
| 
						 | 
					55b1264c7d | ||
| 
						 | 
					902a1888d4 | ||
| 
						 | 
					98151f7d0e | ||
| 
						 | 
					a6f0c559f8 | ||
| 
						 | 
					e7ec5b841d | ||
| 
						 | 
					d6f72ac0f3 | ||
| 
						 | 
					7e3a10025a | ||
| 
						 | 
					e16ec15226 | ||
| 
						 | 
					6935b56c9d | ||
| 
						 | 
					0e3a0b64e7 | ||
| 
						 | 
					74e6aee236 | ||
| 
						 | 
					db0602b7b8 | ||
| 
						 | 
					c9b7c3f179 | ||
| 
						 | 
					5bd9f4afb4 | ||
| 
						 | 
					9d2ba5912e | ||
| 
						 | 
					9986c4a6f3 | ||
| 
						 | 
					df2c9697ef | ||
| 
						 | 
					ab0388e882 | ||
| 
						 | 
					c05d8a36eb | ||
| 
						 | 
					492753d905 | ||
| 
						 | 
					6e08bd23f4 | ||
| 
						 | 
					a687c97808 | ||
| 
						 | 
					c6864289cb | ||
| 
						 | 
					97d85258c5 | ||
| 
						 | 
					bee25f5aa2 | ||
| 
						 | 
					386b97d2be | ||
| 
						 | 
					00660485b7 | ||
| 
						 | 
					1e8f24dedb | ||
| 
						 | 
					2be190f863 | ||
| 
						 | 
					ec7c6e6c85 | ||
| 
						 | 
					c52bc53fd8 | ||
| 
						 | 
					981631503a | ||
| 
						 | 
					48de3a6a4f | ||
| 
						 | 
					d1983a6978 | ||
| 
						 | 
					f821a26aec | ||
| 
						 | 
					3380e905de | ||
| 
						 | 
					b5c2718756 | ||
| 
						 | 
					a03a803b89 | ||
| 
						 | 
					e743177ae6 | ||
| 
						 | 
					6e12c69953 | ||
| 
						 | 
					019ab77466 | ||
| 
						 | 
					1730caf124 | ||
| 
						 | 
					59d1533795 | ||
| 
						 | 
					a6278ab7ea | ||
| 
						 | 
					42a6004c7d | ||
| 
						 | 
					6084c1b1d3 | ||
| 
						 | 
					c96fbc1dba | ||
| 
						 | 
					5546a8b093 | ||
| 
						 | 
					6b76b38dcd | ||
| 
						 | 
					941e50b460 | ||
| 
						 | 
					5a10e5c9ff | ||
| 
						 | 
					883fe13756 | ||
| 
						 | 
					2e7c34cf9f | ||
| 
						 | 
					9216efbd2f | ||
| 
						 | 
					6c8100e5b6 | ||
| 
						 | 
					e7ef50bedf | ||
| 
						 | 
					386ca3565a | ||
| 
						 | 
					2d854cd64d | ||
| 
						 | 
					49b4b8be22 | ||
| 
						 | 
					db975ebfee | ||
| 
						 | 
					d60a41139b | ||
| 
						 | 
					f62d869d27 | ||
| 
						 | 
					6cbe3cdb93 | ||
| 
						 | 
					b13e7b9da4 | ||
| 
						 | 
					8fe34c8474 | ||
| 
						 | 
					bef29be50f | ||
| 
						 | 
					20275a1063 | ||
| 
						 | 
					910385b084 | ||
| 
						 | 
					8e779374a7 | ||
| 
						 | 
					44fc6f728e | ||
| 
						 | 
					1f62dcf22a | ||
| 
						 | 
					0416c3b561 | ||
| 
						 | 
					a6912cae76 | ||
| 
						 | 
					63dfe8a952 | ||
| 
						 | 
					62d1b761bd | ||
| 
						 | 
					082b10a15b | ||
| 
						 | 
					1a6bcd82b0 | ||
| 
						 | 
					6ecd70220b | ||
| 
						 | 
					e9f55f5772 | ||
| 
						 | 
					155cadf901 | ||
| 
						 | 
					cb29289167 | ||
| 
						 | 
					e4db9d1d91 | ||
| 
						 | 
					7b2e2cb817 | ||
| 
						 | 
					c717f8d15d | ||
| 
						 | 
					8db147acab | ||
| 
						 | 
					e6de7aa9ca | ||
| 
						 | 
					46f96740a2 | ||
| 
						 | 
					8f9fb5c262 | ||
| 
						 | 
					171d6d6684 | ||
| 
						 | 
					f648b5ad0a | ||
| 
						 | 
					ef21376f0a | ||
| 
						 | 
					58958d68d8 | ||
| 
						 | 
					a06b565ee9 | ||
| 
						 | 
					a7db27ce5a | ||
| 
						 | 
					cda69dc7f0 | ||
| 
						 | 
					39f9594548 | ||
| 
						 | 
					6d82ad32a9 | ||
| 
						 | 
					cfcd8bf223 | ||
| 
						 | 
					8149ad00b5 | ||
| 
						 | 
					2310522806 | ||
| 
						 | 
					e40ef656d6 | ||
| 
						 | 
					e060d40a32 | ||
| 
						 | 
					a522218c4e | ||
| 
						 | 
					820455399c | ||
| 
						 | 
					959d612534 | ||
| 
						 | 
					cd81e6eab2 | ||
| 
						 | 
					e6ec6920ad | ||
| 
						 | 
					18a92fa1ca | ||
| 
						 | 
					f95af9897b | ||
| 
						 | 
					b61adcb1fd | ||
| 
						 | 
					1bbf320755 | ||
| 
						 | 
					159f26171c | ||
| 
						 | 
					8ac00f6c0d | ||
| 
						 | 
					ce2daf2493 | ||
| 
						 | 
					f014f8fd59 | ||
| 
						 | 
					f50a39a9e2 | ||
| 
						 | 
					e0d8147104 | ||
| 
						 | 
					c5cfac62da | ||
| 
						 | 
					83469ce5cc | ||
| 
						 | 
					7cd7b4a9a2 | ||
| 
						 | 
					7681b277cf | ||
| 
						 | 
					406efa96c0 | ||
| 
						 | 
					9a7a30c0bc | ||
| 
						 | 
					64bdfa0e80 | ||
| 
						 | 
					067089973c | ||
| 
						 | 
					85e6d753c7 | ||
| 
						 | 
					4094984642 | ||
| 
						 | 
					85c0009a43 | ||
| 
						 | 
					234e312ee2 | ||
| 
						 | 
					ce3ca64678 | ||
| 
						 | 
					b042a600c3 | ||
| 
						 | 
					686e9f07a9 | ||
| 
						 | 
					bb6725372b | ||
| 
						 | 
					6f012fc9c5 | ||
| 
						 | 
					4c82458481 | ||
| 
						 | 
					a0ac863998 | ||
| 
						 | 
					d23ef838f8 | ||
| 
						 | 
					f81ac197f5 | ||
| 
						 | 
					652b37e630 | ||
| 
						 | 
					c57e430393 | ||
| 
						 | 
					fff6047df9 | ||
| 
						 | 
					1e2b93d55b | ||
| 
						 | 
					66b27a7795 | ||
| 
						 | 
					63f0a272c4 | ||
| 
						 | 
					8d2180cf5a | ||
| 
						 | 
					1986f7e4dd | ||
| 
						 | 
					21beb396b4 | ||
| 
						 | 
					cb5a6f38d6 | ||
| 
						 | 
					67e4aaede0 | ||
| 
						 | 
					b42805d00c | ||
| 
						 | 
					95d6888c87 | ||
| 
						 | 
					549b315a65 | ||
| 
						 | 
					5b80b16684 | ||
| 
						 | 
					0cd0a4bf2b | ||
| 
						 | 
					b5cf06cad8 | ||
| 
						 | 
					b964d19d82 | ||
| 
						 | 
					cf7990d444 | ||
| 
						 | 
					738ccf7dbb | ||
| 
						 | 
					fc2ea48c1d | ||
| 
						 | 
					3af93b93d7 | ||
| 
						 | 
					f386c3be92 | ||
| 
						 | 
					239d910dbe | ||
| 
						 | 
					48929deabd | ||
| 
						 | 
					79523de1db | ||
| 
						 | 
					fbfc14dfeb | ||
| 
						 | 
					a8dc886f89 | ||
| 
						 | 
					cfc9e064b9 | ||
| 
						 | 
					e72fa3362a | ||
| 
						 | 
					26364421e8 | ||
| 
						 | 
					4a07974b54 | ||
| 
						 | 
					eaddc7f2ba | ||
| 
						 | 
					85056aaa00 | ||
| 
						 | 
					c077c740fa | ||
| 
						 | 
					c2eab87a3f | ||
| 
						 | 
					ea582d2d2e | ||
| 
						 | 
					2f89a24100 | ||
| 
						 | 
					73ebb94f67 | ||
| 
						 | 
					95bf387ecc | ||
| 
						 | 
					f17a8452f9 | ||
| 
						 | 
					920ffe1f33 | ||
| 
						 | 
					093bcb7477 | ||
| 
						 | 
					c06b3ec9eb | ||
| 
						 | 
					ac6fe6f9fc | ||
| 
						 | 
					2dffdaac42 | ||
| 
						 | 
					cb445c9504 | ||
| 
						 | 
					e3fc3aa9d1 | ||
| 
						 | 
					97c3f5d642 | ||
| 
						 | 
					0a52fc9a56 | ||
| 
						 | 
					c831339b0d | ||
| 
						 | 
					058ccf575f | ||
| 
						 | 
					92be12bc2f | ||
| 
						 | 
					1aa2f4b5b1 | ||
| 
						 | 
					bba9431985 | ||
| 
						 | 
					3c39f1e737 | ||
| 
						 | 
					e6f4d07a87 | ||
| 
						 | 
					e43358a0d2 | ||
| 
						 | 
					f0644e8a9d | ||
| 
						 | 
					11b010b281 | ||
| 
						 | 
					c751029127 | ||
| 
						 | 
					fb70d1b2f0 | ||
| 
						 | 
					3d68783b7f | ||
| 
						 | 
					0d77853912 | ||
| 
						 | 
					ea1b5dd8f7 | ||
| 
						 | 
					2dcb7d5ce1 | ||
| 
						 | 
					99cab34527 | ||
| 
						 | 
					f5eeed0bc2 | ||
| 
						 | 
					1b85e56961 | ||
| 
						 | 
					8a8ac5fd22 | ||
| 
						 | 
					00c0354a8e | ||
| 
						 | 
					a2a6973ba1 | ||
| 
						 | 
					dd1d3a05fa | ||
| 
						 | 
					2afe2d2640 | ||
| 
						 | 
					29678f9b59 | ||
| 
						 | 
					77edb251bb | ||
| 
						 | 
					29151fa267 | ||
| 
						 | 
					b3f13790bd | ||
| 
						 | 
					38857c3356 | ||
| 
						 | 
					d75990d9fd | ||
| 
						 | 
					ed063f6534 | ||
| 
						 | 
					c8a9bdc517 | ||
| 
						 | 
					595729cdf8 | ||
| 
						 | 
					6119f79748 | ||
| 
						 | 
					d4fb46c9ba | ||
| 
						 | 
					c41301afca | ||
| 
						 | 
					50fd80830e | ||
| 
						 | 
					1c203b4272 | ||
| 
						 | 
					c545e9045d | ||
| 
						 | 
					2721dc0647 | ||
| 
						 | 
					51d13f4234 | ||
| 
						 | 
					a60a5d6eab | ||
| 
						 | 
					5959235425 | ||
| 
						 | 
					d8e6d4e5fc | ||
| 
						 | 
					7dfc9815b3 | ||
| 
						 | 
					0c53b187a4 | ||
| 
						 | 
					42dadfed8f | ||
| 
						 | 
					a46c603c77 | ||
| 
						 | 
					ad0020d9a6 | ||
| 
						 | 
					a224f0bfd4 | ||
| 
						 | 
					d8dc3650d3 | ||
| 
						 | 
					30f7527f10 | ||
| 
						 | 
					b1f5bdd8b2 | ||
| 
						 | 
					c8e7c8b9fa | ||
| 
						 | 
					30bf3223f8 | ||
| 
						 | 
					886710ec30 | ||
| 
						 | 
					510dc8d828 | ||
| 
						 | 
					5ff7b2aab4 | ||
| 
						 | 
					1e33536205 | 
							
								
								
									
										36
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
 | 
			
		||||
# Environment
 | 
			
		||||
 | 
			
		||||
## Vuls
 | 
			
		||||
 | 
			
		||||
Hash : ____
 | 
			
		||||
 | 
			
		||||
To check the commit hash of HEAD
 | 
			
		||||
$ vuls -v
 | 
			
		||||
 | 
			
		||||
or
 | 
			
		||||
$ cd $GOPATH/src/github.com/future-architect/vuls 
 | 
			
		||||
$ git rev-parse --short HEAD 
 | 
			
		||||
 | 
			
		||||
## OS
 | 
			
		||||
- Target Server: Write here
 | 
			
		||||
- Vuls Server: Write here
 | 
			
		||||
 | 
			
		||||
## Go
 | 
			
		||||
- Go version: here
 | 
			
		||||
 | 
			
		||||
# Current Output
 | 
			
		||||
 | 
			
		||||
Please re-run the command using ```-debug``` and provide the output below.
 | 
			
		||||
 | 
			
		||||
# Addition Details
 | 
			
		||||
 | 
			
		||||
Can you also please fill in each of the remaining sections.
 | 
			
		||||
 | 
			
		||||
## Expected Behavior
 | 
			
		||||
 | 
			
		||||
## Actual Behavior
 | 
			
		||||
 | 
			
		||||
## Steps to reproduce the behaviour
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										24
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
## What did you implement:
 | 
			
		||||
 | 
			
		||||
Closes #XXXXX
 | 
			
		||||
 | 
			
		||||
## How did you implement it:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## How can we verify it:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Todos:
 | 
			
		||||
You don't have to satisfy all of the following.
 | 
			
		||||
 | 
			
		||||
- [ ] Write tests
 | 
			
		||||
- [ ] Write documentation
 | 
			
		||||
- [ ] Check that there aren't other open pull requests for the same issue/feature
 | 
			
		||||
- [ ] Format your source code by `make fmt`
 | 
			
		||||
- [ ] Pass the test by `make test`
 | 
			
		||||
- [ ] Provide verification config / commands
 | 
			
		||||
- [ ] Enable "Allow edits from maintainers" for this PR
 | 
			
		||||
- [ ] Update the messages below
 | 
			
		||||
 | 
			
		||||
***Is this ready for review?:*** NO  
 | 
			
		||||
***Is it a breaking change?:*** NO
 | 
			
		||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -2,7 +2,9 @@ vuls
 | 
			
		||||
.vscode
 | 
			
		||||
*.txt
 | 
			
		||||
*.json
 | 
			
		||||
*.sqlite3
 | 
			
		||||
*.sqlite3*
 | 
			
		||||
*.db
 | 
			
		||||
tags
 | 
			
		||||
.gitmodules
 | 
			
		||||
coverage.out
 | 
			
		||||
issues/
 | 
			
		||||
@@ -10,3 +12,4 @@ vendor/
 | 
			
		||||
log/
 | 
			
		||||
results/
 | 
			
		||||
*config.toml
 | 
			
		||||
!setup/docker/*
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
language: go
 | 
			
		||||
 | 
			
		||||
go:
 | 
			
		||||
  - 1.7
 | 
			
		||||
  - 1.8
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										270
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										270
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,5 +1,273 @@
 | 
			
		||||
# Change Log
 | 
			
		||||
 | 
			
		||||
## [v0.3.0](https://github.com/future-architect/vuls/tree/v0.3.0) (2017-03-24)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.2.0...v0.3.0)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- Changelog parsing fails when package maintainers aren't consistent regarding versions [\#327](https://github.com/future-architect/vuls/issues/327)
 | 
			
		||||
- Docker scan doesn't report image name [\#325](https://github.com/future-architect/vuls/issues/325)
 | 
			
		||||
- vuls report -to-email only one E-Mail [\#295](https://github.com/future-architect/vuls/issues/295)
 | 
			
		||||
- Support RHEL5 [\#286](https://github.com/future-architect/vuls/issues/286)
 | 
			
		||||
- Continue scanning even when some hosts have tech issues? [\#264](https://github.com/future-architect/vuls/issues/264)
 | 
			
		||||
- Normalization of JSON output [\#259](https://github.com/future-architect/vuls/issues/259)
 | 
			
		||||
- Add report subcommand, change scan subcommand options [\#239](https://github.com/future-architect/vuls/issues/239)
 | 
			
		||||
- scan localhost? [\#210](https://github.com/future-architect/vuls/issues/210)
 | 
			
		||||
- Can Vuls show details about updateable packages [\#341](https://github.com/future-architect/vuls/issues/341)
 | 
			
		||||
- Scan all containers except [\#285](https://github.com/future-architect/vuls/issues/285)
 | 
			
		||||
- Notify the difference from the previous scan result [\#255](https://github.com/future-architect/vuls/issues/255)
 | 
			
		||||
- EC2RoleCreds support? [\#250](https://github.com/future-architect/vuls/issues/250)
 | 
			
		||||
- Output confidence score of detection accuracy and detection method to JSON or Reporting [\#350](https://github.com/future-architect/vuls/pull/350) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Avoid null slice being null in JSON [\#345](https://github.com/future-architect/vuls/pull/345) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add -format-one-email option [\#331](https://github.com/future-architect/vuls/pull/331) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Support Raspbian [\#330](https://github.com/future-architect/vuls/pull/330) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Add leniancy to the version matching for debian to account for versio… [\#328](https://github.com/future-architect/vuls/pull/328) ([jsulinski](https://github.com/jsulinski))
 | 
			
		||||
- Add image information for docker containers [\#326](https://github.com/future-architect/vuls/pull/326) ([jsulinski](https://github.com/jsulinski))
 | 
			
		||||
- Continue scanning even when some hosts have tech issues [\#309](https://github.com/future-architect/vuls/pull/309) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add -log-dir option [\#301](https://github.com/future-architect/vuls/pull/301) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Use --assumeno option [\#300](https://github.com/future-architect/vuls/pull/300) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Add local scan mode\(Scan without SSH when target server is localhost\) [\#291](https://github.com/future-architect/vuls/pull/291) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Support RHEL5 [\#289](https://github.com/future-architect/vuls/pull/289) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add LXD support [\#288](https://github.com/future-architect/vuls/pull/288) ([jiazio](https://github.com/jiazio))
 | 
			
		||||
- Add timeout option to configtest [\#400](https://github.com/future-architect/vuls/pull/400) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Notify the difference from the previous scan result [\#392](https://github.com/future-architect/vuls/pull/392) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Add Oracle Linux support [\#386](https://github.com/future-architect/vuls/pull/386) ([Djelibeybi](https://github.com/Djelibeybi))
 | 
			
		||||
- Change container scan format in config.toml [\#381](https://github.com/future-architect/vuls/pull/381) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Obsolete CentOS5 support [\#378](https://github.com/future-architect/vuls/pull/378) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Deprecate prepare subcommand to minimize the root authority defined by /etc/sudoers [\#375](https://github.com/future-architect/vuls/pull/375) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Support IAM role for report to S3. [\#370](https://github.com/future-architect/vuls/pull/370) ([ohsawa0515](https://github.com/ohsawa0515))
 | 
			
		||||
- Add .travis.yml [\#363](https://github.com/future-architect/vuls/pull/363) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Output changelog in report, TUI and JSON for Ubuntu/Debian/CentOS [\#356](https://github.com/future-architect/vuls/pull/356) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Debian scans failing in docker [\#323](https://github.com/future-architect/vuls/issues/323)
 | 
			
		||||
- Local CVE DB is still checked, even if a CVE Dictionary URL is defined [\#316](https://github.com/future-architect/vuls/issues/316)
 | 
			
		||||
- vuls needs gmake. [\#313](https://github.com/future-architect/vuls/issues/313)
 | 
			
		||||
- patch request for FreeBSD [\#312](https://github.com/future-architect/vuls/issues/312)
 | 
			
		||||
- Report: failed to read from json \(Docker\) [\#294](https://github.com/future-architect/vuls/issues/294)
 | 
			
		||||
- -report-mail option does not output required mail header [\#282](https://github.com/future-architect/vuls/issues/282)
 | 
			
		||||
- PackInfo not found error when vuls scan. [\#281](https://github.com/future-architect/vuls/issues/281)
 | 
			
		||||
- Normalize character set [\#279](https://github.com/future-architect/vuls/issues/279)
 | 
			
		||||
- The number of Updatable Packages is different from the number of yum check-update [\#373](https://github.com/future-architect/vuls/issues/373)
 | 
			
		||||
- sudo is needed when exec yum check-update on RHEL7 [\#371](https://github.com/future-architect/vuls/issues/371)
 | 
			
		||||
- `123-3ubuntu4` should be marked as ChangelogLenientMatch [\#362](https://github.com/future-architect/vuls/issues/362)
 | 
			
		||||
- CentOS  multi package invalid result [\#360](https://github.com/future-architect/vuls/issues/360)
 | 
			
		||||
- Parse error after check-update. \(Unknown format\) [\#359](https://github.com/future-architect/vuls/issues/359)
 | 
			
		||||
- Fix candidate to confidence. [\#354](https://github.com/future-architect/vuls/pull/354) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Bug fix: not send e-mail to cc address [\#346](https://github.com/future-architect/vuls/pull/346) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Change the command used for os detection from uname to freebsd-version [\#340](https://github.com/future-architect/vuls/pull/340) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix error handling of detectOS [\#337](https://github.com/future-architect/vuls/pull/337) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix infinite retry at size overrun error in Slack report [\#329](https://github.com/future-architect/vuls/pull/329) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- aptitude changelog defaults to using more, which is not interactive a… [\#324](https://github.com/future-architect/vuls/pull/324) ([jsulinski](https://github.com/jsulinski))
 | 
			
		||||
- Do not use sudo when echo [\#322](https://github.com/future-architect/vuls/pull/322) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Reduce privilege requirements for commands that don't need sudo on Ubuntu/Debian [\#319](https://github.com/future-architect/vuls/pull/319) ([jsulinski](https://github.com/jsulinski))
 | 
			
		||||
- Don't check for a CVE DB when CVE Dictionary URL is defined [\#317](https://github.com/future-architect/vuls/pull/317) ([jsulinski](https://github.com/jsulinski))
 | 
			
		||||
- Fix typo contianer -\> container [\#314](https://github.com/future-architect/vuls/pull/314) ([justyns](https://github.com/justyns))
 | 
			
		||||
- Fix the changelog cache logic for ubuntu/debian [\#305](https://github.com/future-architect/vuls/pull/305) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix yum updateinfo options [\#304](https://github.com/future-architect/vuls/pull/304) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Update glide.lock to fix create-log-dir error. [\#303](https://github.com/future-architect/vuls/pull/303) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix a bug in logging \(file output\) at scan command [\#302](https://github.com/future-architect/vuls/pull/302) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add -pipe flag \#294 [\#299](https://github.com/future-architect/vuls/pull/299) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix RHEL5 scan stopped halfway [\#293](https://github.com/future-architect/vuls/pull/293) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix amazon linux scan stopped halfway [\#292](https://github.com/future-architect/vuls/pull/292) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix nil-ponter in TUI [\#388](https://github.com/future-architect/vuls/pull/388) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix Bug of Mysql Backend [\#384](https://github.com/future-architect/vuls/pull/384) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix scan confidence on Ubuntu/Debian/Raspbian \#362 [\#379](https://github.com/future-architect/vuls/pull/379) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix updatalbe packages count \#373 [\#374](https://github.com/future-architect/vuls/pull/374) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- sudo yum check-update on RHEL [\#372](https://github.com/future-architect/vuls/pull/372) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Change ssh option from -t to -tt [\#369](https://github.com/future-architect/vuls/pull/369) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Increase the width of RequestPty [\#364](https://github.com/future-architect/vuls/pull/364) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
 | 
			
		||||
**Closed issues:**
 | 
			
		||||
 | 
			
		||||
-  vuls configtest --debugがsudoのチェックで止まってしまう [\#395](https://github.com/future-architect/vuls/issues/395)
 | 
			
		||||
- Add support for Oracle Linux [\#385](https://github.com/future-architect/vuls/issues/385)
 | 
			
		||||
- error on install - Ubuntu 16.04 [\#376](https://github.com/future-architect/vuls/issues/376)
 | 
			
		||||
- Unknown OS Type [\#335](https://github.com/future-architect/vuls/issues/335)
 | 
			
		||||
- mac os 10.12.3 make install error [\#334](https://github.com/future-architect/vuls/issues/334)
 | 
			
		||||
- assumeYes doesn't work because there is no else condition [\#320](https://github.com/future-architect/vuls/issues/320)
 | 
			
		||||
- Debian scan uses sudo where unnecessary [\#318](https://github.com/future-architect/vuls/issues/318)
 | 
			
		||||
- Add FreeBSD 11 to supported OS on documents. [\#311](https://github.com/future-architect/vuls/issues/311)
 | 
			
		||||
- docker fetchnvd failing [\#274](https://github.com/future-architect/vuls/issues/274)
 | 
			
		||||
- Latest version of labstack echo breaks installation [\#268](https://github.com/future-architect/vuls/issues/268)
 | 
			
		||||
- fetchnvd Fails using example loop [\#267](https://github.com/future-architect/vuls/issues/267)
 | 
			
		||||
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- fix typo in README.ja.md [\#394](https://github.com/future-architect/vuls/pull/394) ([lv7777](https://github.com/lv7777))
 | 
			
		||||
- Update Tutorial in README [\#387](https://github.com/future-architect/vuls/pull/387) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix README [\#383](https://github.com/future-architect/vuls/pull/383) ([usiusi360](https://github.com/usiusi360))
 | 
			
		||||
- s/dictinary/dictionary typo [\#382](https://github.com/future-architect/vuls/pull/382) ([beuno](https://github.com/beuno))
 | 
			
		||||
- Fix Japanese typo [\#377](https://github.com/future-architect/vuls/pull/377) ([IMAI-Yuji](https://github.com/IMAI-Yuji))
 | 
			
		||||
- Improve kanji character [\#351](https://github.com/future-architect/vuls/pull/351) ([hasegawa-tomoki](https://github.com/hasegawa-tomoki))
 | 
			
		||||
- Add PULL\_REQUEST\_TEMPLATE.md [\#348](https://github.com/future-architect/vuls/pull/348) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Update README [\#347](https://github.com/future-architect/vuls/pull/347) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Fix test case [\#344](https://github.com/future-architect/vuls/pull/344) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix typo [\#343](https://github.com/future-architect/vuls/pull/343) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Rename Makefile to GNUmakefile \#313 [\#339](https://github.com/future-architect/vuls/pull/339) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Update README [\#338](https://github.com/future-architect/vuls/pull/338) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- add error handling [\#332](https://github.com/future-architect/vuls/pull/332) ([kazuminn](https://github.com/kazuminn))
 | 
			
		||||
- Update readme [\#308](https://github.com/future-architect/vuls/pull/308) ([lapthorn](https://github.com/lapthorn))
 | 
			
		||||
- Update glide.lock to fix import error [\#306](https://github.com/future-architect/vuls/pull/306) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Check whether echo is executable with nopasswd [\#298](https://github.com/future-architect/vuls/pull/298) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Update docker README [\#297](https://github.com/future-architect/vuls/pull/297) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- update readme [\#296](https://github.com/future-architect/vuls/pull/296) ([galigalikun](https://github.com/galigalikun))
 | 
			
		||||
- remove unused import line. [\#358](https://github.com/future-architect/vuls/pull/358) ([ymomoi](https://github.com/ymomoi))
 | 
			
		||||
 | 
			
		||||
## [v0.2.0](https://github.com/future-architect/vuls/tree/v0.2.0) (2017-01-10)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.7...v0.2.0)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- Add report subcommand, change scan options. \#239 [\#270](https://github.com/future-architect/vuls/pull/270) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add --assume-yes to prepare \#260 [\#266](https://github.com/future-architect/vuls/pull/266) ([Code0x58](https://github.com/Code0x58))
 | 
			
		||||
- Use RFC3339 timestamps in the results [\#265](https://github.com/future-architect/vuls/pull/265) ([Code0x58](https://github.com/Code0x58))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- vuls prepare failed to centos7 [\#275](https://github.com/future-architect/vuls/issues/275)
 | 
			
		||||
- Failed to scan on RHEL5 [\#94](https://github.com/future-architect/vuls/issues/94)
 | 
			
		||||
- Fix container os detection [\#287](https://github.com/future-architect/vuls/pull/287) ([jiazio](https://github.com/jiazio))
 | 
			
		||||
- Add date header to report mail. [\#283](https://github.com/future-architect/vuls/pull/283) ([ymomoi](https://github.com/ymomoi))
 | 
			
		||||
- Add Content-Type header to report/mail.go . [\#280](https://github.com/future-architect/vuls/pull/280) ([hogehogehugahuga](https://github.com/hogehogehugahuga))
 | 
			
		||||
- Keep output of "vuls scan -report-\*" to be same every times [\#272](https://github.com/future-architect/vuls/pull/272) ([yoheimuta](https://github.com/yoheimuta))
 | 
			
		||||
- Fix JSON-dir regex pattern \#265 [\#271](https://github.com/future-architect/vuls/pull/271) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Stop quietly ignoring `--ssh-external` on Windows [\#263](https://github.com/future-architect/vuls/pull/263) ([Code0x58](https://github.com/Code0x58))
 | 
			
		||||
- Fix non-interactive `apt-get install` \#251 [\#253](https://github.com/future-architect/vuls/pull/253) ([Code0x58](https://github.com/Code0x58))
 | 
			
		||||
 | 
			
		||||
**Closed issues:**
 | 
			
		||||
 | 
			
		||||
- gocui.NewGui now takes a parameter [\#261](https://github.com/future-architect/vuls/issues/261)
 | 
			
		||||
- Add a `--yes` flag to bypass interactive prompt for `vuls prepare` [\#260](https://github.com/future-architect/vuls/issues/260)
 | 
			
		||||
- `vuls prepare` doesn't work on Debian host due to apt-get confirmation prompt [\#251](https://github.com/future-architect/vuls/issues/251)
 | 
			
		||||
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- Fix gocui.NewGui after signature change \#261 [\#262](https://github.com/future-architect/vuls/pull/262) ([Code0x58](https://github.com/Code0x58))
 | 
			
		||||
- Replace inconsistent tabs with spaces [\#254](https://github.com/future-architect/vuls/pull/254) ([Code0x58](https://github.com/Code0x58))
 | 
			
		||||
- Fix README [\#249](https://github.com/future-architect/vuls/pull/249) ([usiusi360](https://github.com/usiusi360))
 | 
			
		||||
 | 
			
		||||
## [v0.1.7](https://github.com/future-architect/vuls/tree/v0.1.7) (2016-11-08)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.6...v0.1.7)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- Enable to scan only docker container, without docker host [\#122](https://github.com/future-architect/vuls/issues/122)
 | 
			
		||||
- Add -skip-broken option \[CentOS only\] \#245 [\#248](https://github.com/future-architect/vuls/pull/248) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Display unknown CVEs to TUI [\#244](https://github.com/future-architect/vuls/pull/244) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add the XML output [\#240](https://github.com/future-architect/vuls/pull/240) ([gleentea](https://github.com/gleentea))
 | 
			
		||||
- add '-ssh-external' option to prepare subcommand [\#234](https://github.com/future-architect/vuls/pull/234) ([mykstmhr](https://github.com/mykstmhr))
 | 
			
		||||
- Integrate OWASP Dependency Check [\#232](https://github.com/future-architect/vuls/pull/232) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add support for reading CVE data from MySQL. [\#225](https://github.com/future-architect/vuls/pull/225) ([oswell](https://github.com/oswell))
 | 
			
		||||
- Remove base docker image, -v shows commit hash [\#223](https://github.com/future-architect/vuls/pull/223) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Support ignore CveIDs in config [\#222](https://github.com/future-architect/vuls/pull/222) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Confirm before installing dependencies on prepare [\#219](https://github.com/future-architect/vuls/pull/219) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Remove all.json [\#218](https://github.com/future-architect/vuls/pull/218) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add GitHub issue template [\#217](https://github.com/future-architect/vuls/pull/217) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Improve makefile, -version shows git hash, fix README [\#216](https://github.com/future-architect/vuls/pull/216) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- change e-mail package from gomail to net/smtp [\#211](https://github.com/future-architect/vuls/pull/211) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Add only-containers option to scan subcommand \#122 [\#190](https://github.com/future-architect/vuls/pull/190) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix -results-dir option of scan subcommand [\#185](https://github.com/future-architect/vuls/pull/185) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Show error when no scannable servers are detected. [\#177](https://github.com/future-architect/vuls/pull/177) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add sudo check to prepare subcommand [\#176](https://github.com/future-architect/vuls/pull/176) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Supports yum --enablerepo option \(supports only base,updates for now\) [\#147](https://github.com/future-architect/vuls/pull/147) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Debian 8.6 \(jessie\) scan does not show vulnerable packages [\#235](https://github.com/future-architect/vuls/issues/235)
 | 
			
		||||
- panic: runtime error: index out of range - ubuntu 16.04 + vuls history [\#180](https://github.com/future-architect/vuls/issues/180)
 | 
			
		||||
- Moved golang.org/x/net/context to context [\#243](https://github.com/future-architect/vuls/pull/243) ([yoheimuta](https://github.com/yoheimuta))
 | 
			
		||||
- Fix changelog cache bug on Ubuntu and Debian \#235 [\#238](https://github.com/future-architect/vuls/pull/238) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- add '-ssh-external' option to prepare subcommand [\#234](https://github.com/future-architect/vuls/pull/234) ([mykstmhr](https://github.com/mykstmhr))
 | 
			
		||||
- Fixed error for the latest version of gocui [\#231](https://github.com/future-architect/vuls/pull/231) ([ymd38](https://github.com/ymd38))
 | 
			
		||||
- Handle the refactored gocui SetCurrentView method. [\#229](https://github.com/future-architect/vuls/pull/229) ([oswell](https://github.com/oswell))
 | 
			
		||||
- Fix locale env var LANG to LANGUAGE [\#215](https://github.com/future-architect/vuls/pull/215) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fixed bug with parsing update line on CentOS/RHEL [\#206](https://github.com/future-architect/vuls/pull/206) ([andyone](https://github.com/andyone))
 | 
			
		||||
- Fix defer cache.DB.close [\#201](https://github.com/future-architect/vuls/pull/201) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix a help message of -report-azure-blob option [\#195](https://github.com/future-architect/vuls/pull/195) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix error handling in tui [\#193](https://github.com/future-architect/vuls/pull/193) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix not working changelog cache on Container [\#189](https://github.com/future-architect/vuls/pull/189) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix release version detection on FreeBSD [\#184](https://github.com/future-architect/vuls/pull/184) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix defer cahce.DB.close\(\) [\#183](https://github.com/future-architect/vuls/pull/183) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix a mode of files/dir \(report, log\) [\#182](https://github.com/future-architect/vuls/pull/182) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix a error when no json dirs are found under results \#180 [\#181](https://github.com/future-architect/vuls/pull/181) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- ssh-external option of configtest is not working \#178 [\#179](https://github.com/future-architect/vuls/pull/179) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
**Closed issues:**
 | 
			
		||||
 | 
			
		||||
- --enable-repos of yum option [\#246](https://github.com/future-architect/vuls/issues/246)
 | 
			
		||||
- --skip-broken at yum option [\#245](https://github.com/future-architect/vuls/issues/245)
 | 
			
		||||
- Recent changes to gobui cause build failures [\#228](https://github.com/future-architect/vuls/issues/228)
 | 
			
		||||
- https://hub.docker.com/r/vuls/go-cve-dictionary/ is empty [\#208](https://github.com/future-architect/vuls/issues/208)
 | 
			
		||||
- Not able to install gomail fails [\#202](https://github.com/future-architect/vuls/issues/202)
 | 
			
		||||
- No results file created - vuls tui failed [\#199](https://github.com/future-architect/vuls/issues/199)
 | 
			
		||||
- Wrong file permissions for results/\*.json in official Docker container [\#197](https://github.com/future-architect/vuls/issues/197)
 | 
			
		||||
- Failed: Unknown OS Type [\#196](https://github.com/future-architect/vuls/issues/196)
 | 
			
		||||
- Segmentation fault with configtest [\#192](https://github.com/future-architect/vuls/issues/192)
 | 
			
		||||
- Failed to scan. err: No server defined. Check the configuration [\#187](https://github.com/future-architect/vuls/issues/187)
 | 
			
		||||
- vuls configtest -ssh-external doesnt work [\#178](https://github.com/future-architect/vuls/issues/178)
 | 
			
		||||
- apt-get update: time out [\#175](https://github.com/future-architect/vuls/issues/175)
 | 
			
		||||
- scanning on Centos6, but vuls recognizes debian. [\#174](https://github.com/future-architect/vuls/issues/174)
 | 
			
		||||
- Fix READMEja  \#164  [\#173](https://github.com/future-architect/vuls/issues/173)
 | 
			
		||||
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- Update README \#225 [\#242](https://github.com/future-architect/vuls/pull/242) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix readme [\#241](https://github.com/future-architect/vuls/pull/241) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Fix README \#234 [\#237](https://github.com/future-architect/vuls/pull/237) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Update glide files [\#236](https://github.com/future-architect/vuls/pull/236) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix README [\#226](https://github.com/future-architect/vuls/pull/226) ([usiusi360](https://github.com/usiusi360))
 | 
			
		||||
- fix some misspelling. [\#221](https://github.com/future-architect/vuls/pull/221) ([ymomoi](https://github.com/ymomoi))
 | 
			
		||||
- fix docker readme [\#214](https://github.com/future-architect/vuls/pull/214) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Fix ja document about typo [\#213](https://github.com/future-architect/vuls/pull/213) ([shokohara](https://github.com/shokohara))
 | 
			
		||||
- fix readme [\#212](https://github.com/future-architect/vuls/pull/212) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- fix README [\#207](https://github.com/future-architect/vuls/pull/207) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- fix typo [\#204](https://github.com/future-architect/vuls/pull/204) ([usiusi360](https://github.com/usiusi360))
 | 
			
		||||
- fix gitignore [\#191](https://github.com/future-architect/vuls/pull/191) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Update glide.lock [\#188](https://github.com/future-architect/vuls/pull/188) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix path in setup/docker/README [\#186](https://github.com/future-architect/vuls/pull/186) ([dladuke](https://github.com/dladuke))
 | 
			
		||||
- Vuls and vulsrepo are now separated [\#163](https://github.com/future-architect/vuls/pull/163) ([hikachan](https://github.com/hikachan))
 | 
			
		||||
 | 
			
		||||
## [v0.1.6](https://github.com/future-architect/vuls/tree/v0.1.6) (2016-09-12)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.5...v0.1.6)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- High speed scan on Ubuntu/Debian [\#172](https://github.com/future-architect/vuls/pull/172) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Support CWE\(Common Weakness Enumeration\) [\#169](https://github.com/future-architect/vuls/pull/169) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Enable to scan without sudo on amazon linux [\#167](https://github.com/future-architect/vuls/pull/167) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Remove deprecated options -use-unattended-upgrades,-use-yum-plugin-security [\#161](https://github.com/future-architect/vuls/pull/161) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- delete sqlite3 [\#152](https://github.com/future-architect/vuls/pull/152) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Failed to setup vuls docker [\#170](https://github.com/future-architect/vuls/issues/170)
 | 
			
		||||
- yum check-update error occurred when no reboot after kernel updating [\#165](https://github.com/future-architect/vuls/issues/165)
 | 
			
		||||
- error thrown from 'docker build .' [\#157](https://github.com/future-architect/vuls/issues/157)
 | 
			
		||||
- CVE-ID is truncated to 4 digits [\#153](https://github.com/future-architect/vuls/issues/153)
 | 
			
		||||
- 'yum update --changelog' stalled in 'vuls scan'. if ssh user is not 'root'. [\#150](https://github.com/future-architect/vuls/issues/150)
 | 
			
		||||
- Panic on packet scan [\#131](https://github.com/future-architect/vuls/issues/131)
 | 
			
		||||
- Update glide.lock \#170 [\#171](https://github.com/future-architect/vuls/pull/171) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix detecting a platform on Azure [\#168](https://github.com/future-architect/vuls/pull/168) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix parse error for yum check-update \#165 [\#166](https://github.com/future-architect/vuls/pull/166) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix bug: Vuls on Docker [\#159](https://github.com/future-architect/vuls/pull/159) ([tjinjin](https://github.com/tjinjin))
 | 
			
		||||
- Fix CVE-ID is truncated to 4 digits [\#155](https://github.com/future-architect/vuls/pull/155) ([usiusi360](https://github.com/usiusi360))
 | 
			
		||||
- Fix yum update --changelog stalled when non-root ssh user on CentOS \#150 [\#151](https://github.com/future-architect/vuls/pull/151) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
**Closed issues:**
 | 
			
		||||
 | 
			
		||||
- Support su for root privilege escalation [\#44](https://github.com/future-architect/vuls/issues/44)
 | 
			
		||||
- Support FreeBSD [\#34](https://github.com/future-architect/vuls/issues/34)
 | 
			
		||||
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- Change scripts for data fetching from jvn [\#164](https://github.com/future-architect/vuls/pull/164) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix: setup vulsrepo [\#162](https://github.com/future-architect/vuls/pull/162) ([tjinjin](https://github.com/tjinjin))
 | 
			
		||||
- Fix-docker-vulsrepo-install [\#160](https://github.com/future-architect/vuls/pull/160) ([usiusi360](https://github.com/usiusi360))
 | 
			
		||||
- Reduce regular expression compilation [\#158](https://github.com/future-architect/vuls/pull/158) ([itchyny](https://github.com/itchyny))
 | 
			
		||||
- Add testcases for \#153 [\#156](https://github.com/future-architect/vuls/pull/156) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
## [v0.1.5](https://github.com/future-architect/vuls/tree/v0.1.5) (2016-08-16)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.4...v0.1.5)
 | 
			
		||||
 | 
			
		||||
@@ -141,7 +409,7 @@
 | 
			
		||||
- Maximum 6 nodes available to scan [\#12](https://github.com/future-architect/vuls/issues/12)
 | 
			
		||||
- panic: runtime error: index out of range [\#5](https://github.com/future-architect/vuls/issues/5)
 | 
			
		||||
- Fix sudo option on RedHat like Linux and change some messages. [\#20](https://github.com/future-architect/vuls/pull/20) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Typo fix and updated readme [\#19](https://github.com/future-architect/vuls/pull/19) ([Euan-Kerr](https://github.com/Euan-Kerr))
 | 
			
		||||
- Typo fix and updated readme [\#19](https://github.com/future-architect/vuls/pull/19) ([EuanKerr](https://github.com/EuanKerr))
 | 
			
		||||
- remove a period at the end of error messages. [\#18](https://github.com/future-architect/vuls/pull/18) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix error while yum updateinfo --security update on rhel@aws [\#17](https://github.com/future-architect/vuls/pull/17) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fixed typos [\#15](https://github.com/future-architect/vuls/pull/15) ([radarhere](https://github.com/radarhere))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,9 @@
 | 
			
		||||
.PHONY: \
 | 
			
		||||
	glide \
 | 
			
		||||
	deps \
 | 
			
		||||
	update \
 | 
			
		||||
	build \
 | 
			
		||||
	install \
 | 
			
		||||
	all \
 | 
			
		||||
	vendor \
 | 
			
		||||
	lint \
 | 
			
		||||
@@ -11,13 +16,29 @@
 | 
			
		||||
	clean
 | 
			
		||||
 | 
			
		||||
SRCS = $(shell git ls-files '*.go')
 | 
			
		||||
PKGS = ./. ./db ./config ./models ./report ./cveapi ./scan ./util ./commands
 | 
			
		||||
PKGS = ./. ./config ./models ./report ./cveapi ./scan ./util ./commands ./cache
 | 
			
		||||
VERSION := $(shell git describe --tags --abbrev=0)
 | 
			
		||||
REVISION := $(shell git rev-parse --short HEAD)
 | 
			
		||||
LDFLAGS := -X 'main.version=$(VERSION)' \
 | 
			
		||||
	-X 'main.revision=$(REVISION)'
 | 
			
		||||
 | 
			
		||||
all: test
 | 
			
		||||
all: glide deps build test
 | 
			
		||||
 | 
			
		||||
glide:
 | 
			
		||||
	go get github.com/Masterminds/glide
 | 
			
		||||
 | 
			
		||||
deps: glide
 | 
			
		||||
	glide install
 | 
			
		||||
 | 
			
		||||
update: glide
 | 
			
		||||
	glide update
 | 
			
		||||
 | 
			
		||||
build: main.go deps
 | 
			
		||||
	go build -ldflags "$(LDFLAGS)" -o vuls $<
 | 
			
		||||
 | 
			
		||||
install: main.go deps
 | 
			
		||||
	go install -ldflags "$(LDFLAGS)"
 | 
			
		||||
 | 
			
		||||
#  vendor:
 | 
			
		||||
#          @ go get -v github.com/mjibson/party
 | 
			
		||||
#          party -d external -c -u
 | 
			
		||||
 | 
			
		||||
lint:
 | 
			
		||||
	@ go get -v github.com/golang/lint/golint
 | 
			
		||||
@@ -36,7 +57,7 @@ fmtcheck:
 | 
			
		||||
pretest: lint vet fmtcheck
 | 
			
		||||
 | 
			
		||||
test: pretest
 | 
			
		||||
	$(foreach pkg,$(PKGS),go test -v $(pkg) || exit;)
 | 
			
		||||
	$(foreach pkg,$(PKGS),go test -cover -v $(pkg) || exit;)
 | 
			
		||||
 | 
			
		||||
unused :
 | 
			
		||||
	$(foreach pkg,$(PKGS),unused $(pkg);)
 | 
			
		||||
							
								
								
									
										10
									
								
								README.fr.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.fr.md
									
									
									
									
									
								
							@@ -40,7 +40,7 @@ Vuls est un outil crée pour palier aux problèmes listés ci-dessus. Voici ses
 | 
			
		||||
# Caractéristiques principales
 | 
			
		||||
 | 
			
		||||
- Recherche de vulnérabilités sur des serveurs Linux
 | 
			
		||||
    - Supporte Ubuntu, Debian, CentOS, Amazon Linux, RHEL
 | 
			
		||||
    - Supporte Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Raspbian
 | 
			
		||||
    - Cloud, auto-hébergement, Docker
 | 
			
		||||
- Scan d'intergiciels non inclus dans le gestionnaire de paquets de l'OS
 | 
			
		||||
    - Scan d'intergiciels, de libraries de language de programmation et framework pour des vulnérabilités
 | 
			
		||||
@@ -107,14 +107,14 @@ Vuls requiert l'installation des paquets suivants :
 | 
			
		||||
- sqlite
 | 
			
		||||
- git
 | 
			
		||||
- gcc
 | 
			
		||||
- go v1.6
 | 
			
		||||
- go v1.7.1 or later
 | 
			
		||||
    - https://golang.org/doc/install
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
$ ssh ec2-user@52.100.100.100  -i ~/.ssh/private.pem
 | 
			
		||||
$ sudo yum -y install sqlite git gcc
 | 
			
		||||
$ wget https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz
 | 
			
		||||
$ sudo tar -C /usr/local -xzf go1.6.linux-amd64.tar.gz
 | 
			
		||||
$ wget https://storage.googleapis.com/golang/go1.7.1.linux-amd64.tar.gz
 | 
			
		||||
$ sudo tar -C /usr/local -xzf go1.7.1.linux-amd64.tar.gz
 | 
			
		||||
$ mkdir $HOME/go
 | 
			
		||||
```
 | 
			
		||||
Ajoutez les lignes suivantes dans /etc/profile.d/goenv.sh
 | 
			
		||||
@@ -201,7 +201,7 @@ Summary         Unspecified vulnerability in the Java SE and Java SE Embedded co
 | 
			
		||||
NVD             https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0494
 | 
			
		||||
MITRE           https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0494
 | 
			
		||||
CVE Details     http://www.cvedetails.com/cve/CVE-2016-0494
 | 
			
		||||
CVSS Claculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0494&vector=(AV:N/AC:L/Au:N/C:C/I:C/A:C)
 | 
			
		||||
CVSS Calculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0494&vector=(AV:N/AC:L/Au:N/C:C/I:C/A:C)
 | 
			
		||||
RHEL-CVE        https://access.redhat.com/security/cve/CVE-2016-0494
 | 
			
		||||
ALAS-2016-643   https://alas.aws.amazon.com/ALAS-2016-643.html
 | 
			
		||||
Package/CPE     java-1.7.0-openjdk-1.7.0.91-2.6.2.2.63.amzn1 -> java-1.7.0-openjdk-1:1.7.0.95-2.6.4.0.65.amzn1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1265
									
								
								README.ja.md
									
									
									
									
									
								
							
							
						
						
									
										1265
									
								
								README.ja.md
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										191
									
								
								cache/bolt.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								cache/bolt.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,191 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package cache
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/boltdb/bolt"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Bolt holds a pointer of bolt.DB
 | 
			
		||||
// boltdb is used to store a cache of Changelogs of Ubuntu/Debian
 | 
			
		||||
type Bolt struct {
 | 
			
		||||
	Path string
 | 
			
		||||
	Log  *logrus.Entry
 | 
			
		||||
	db   *bolt.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetupBolt opens a boltdb and creates a meta bucket if not exists.
 | 
			
		||||
func SetupBolt(path string, l *logrus.Entry) error {
 | 
			
		||||
	l.Infof("Open boltDB: %s", path)
 | 
			
		||||
	db, err := bolt.Open(path, 0600, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b := Bolt{
 | 
			
		||||
		Path: path,
 | 
			
		||||
		Log:  l,
 | 
			
		||||
		db:   db,
 | 
			
		||||
	}
 | 
			
		||||
	if err = b.createBucketIfNotExists(metabucket); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	DB = b
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close a db.
 | 
			
		||||
func (b Bolt) Close() error {
 | 
			
		||||
	if b.db == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return b.db.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  CreateBucketIfNotExists creates a buket that is specified by arg.
 | 
			
		||||
func (b *Bolt) createBucketIfNotExists(name string) error {
 | 
			
		||||
	return b.db.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		_, err := tx.CreateBucketIfNotExists([]byte(name))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to create bucket: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMeta gets a Meta Information os the servername to boltdb.
 | 
			
		||||
func (b Bolt) GetMeta(serverName string) (meta Meta, found bool, err error) {
 | 
			
		||||
	err = b.db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		v := bkt.Get([]byte(serverName))
 | 
			
		||||
		if len(v) == 0 {
 | 
			
		||||
			found = false
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if e := json.Unmarshal(v, &meta); e != nil {
 | 
			
		||||
			return e
 | 
			
		||||
		}
 | 
			
		||||
		found = true
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RefreshMeta gets a Meta Information os the servername to boltdb.
 | 
			
		||||
func (b Bolt) RefreshMeta(meta Meta) error {
 | 
			
		||||
	meta.CreatedAt = time.Now()
 | 
			
		||||
	jsonBytes, err := json.Marshal(meta)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to marshal to JSON: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return b.db.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		if err := bkt.Put([]byte(meta.Name), jsonBytes); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		b.Log.Debugf("Refreshed Meta: %s", meta.Name)
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnsureBuckets puts a Meta information and create a buket that holds changelogs.
 | 
			
		||||
func (b Bolt) EnsureBuckets(meta Meta) error {
 | 
			
		||||
	jsonBytes, err := json.Marshal(meta)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to marshal to JSON: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return b.db.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		b.Log.Debugf("Put to meta: %s", meta.Name)
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		if err := bkt.Put([]byte(meta.Name), jsonBytes); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// re-create a bucket (bucket name: servername)
 | 
			
		||||
		bkt = tx.Bucket([]byte(meta.Name))
 | 
			
		||||
		if bkt != nil {
 | 
			
		||||
			b.Log.Debugf("Delete bucket: %s", meta.Name)
 | 
			
		||||
			if err := tx.DeleteBucket([]byte(meta.Name)); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			b.Log.Debugf("Bucket deleted: %s", meta.Name)
 | 
			
		||||
		}
 | 
			
		||||
		b.Log.Debugf("Create bucket: %s", meta.Name)
 | 
			
		||||
		if _, err := tx.CreateBucket([]byte(meta.Name)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		b.Log.Debugf("Bucket created: %s", meta.Name)
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrettyPrint is for debug
 | 
			
		||||
func (b Bolt) PrettyPrint(meta Meta) error {
 | 
			
		||||
	return b.db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		v := bkt.Get([]byte(meta.Name))
 | 
			
		||||
		b.Log.Debugf("Meta: key:%s, value:%s", meta.Name, v)
 | 
			
		||||
 | 
			
		||||
		bkt = tx.Bucket([]byte(meta.Name))
 | 
			
		||||
		c := bkt.Cursor()
 | 
			
		||||
		for k, v := c.First(); k != nil; k, v = c.Next() {
 | 
			
		||||
			b.Log.Debugf("key:%s, len: %d, %s...",
 | 
			
		||||
				k, len(v), util.Truncate(string(v), 30))
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetChangelog get the changelgo of specified packName from the Bucket
 | 
			
		||||
func (b Bolt) GetChangelog(servername, packName string) (changelog string, err error) {
 | 
			
		||||
	err = b.db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(servername))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			return fmt.Errorf("Faild to get Bucket: %s", servername)
 | 
			
		||||
		}
 | 
			
		||||
		v := bkt.Get([]byte(packName))
 | 
			
		||||
		if v == nil {
 | 
			
		||||
			changelog = ""
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		changelog = string(v)
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PutChangelog put the changelgo of specified packName into the Bucket
 | 
			
		||||
func (b Bolt) PutChangelog(servername, packName, changelog string) error {
 | 
			
		||||
	return b.db.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(servername))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			return fmt.Errorf("Faild to get Bucket: %s", servername)
 | 
			
		||||
		}
 | 
			
		||||
		if err := bkt.Put([]byte(packName), []byte(changelog)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										137
									
								
								cache/bolt_test.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								cache/bolt_test.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package cache
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/boltdb/bolt"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const path = "/tmp/vuls-test-cache-11111111.db"
 | 
			
		||||
const servername = "server1"
 | 
			
		||||
 | 
			
		||||
var meta = Meta{
 | 
			
		||||
	Name: servername,
 | 
			
		||||
	Distro: config.Distro{
 | 
			
		||||
		Family:  "ubuntu",
 | 
			
		||||
		Release: "16.04",
 | 
			
		||||
	},
 | 
			
		||||
	Packs: []models.PackageInfo{
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "apt",
 | 
			
		||||
			Version: "1",
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetupBolt(t *testing.T) {
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	err := SetupBolt(path, log)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(path)
 | 
			
		||||
 | 
			
		||||
	if err := DB.Close(); err != nil {
 | 
			
		||||
		t.Errorf("Failed to close bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check if meta bucket exists
 | 
			
		||||
	db, err := bolt.Open(path, 0600, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to open bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			t.Errorf("Meta bucket nof found")
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEnsureBuckets(t *testing.T) {
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	if err := SetupBolt(path, log); err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := DB.EnsureBuckets(meta); err != nil {
 | 
			
		||||
		t.Errorf("Failed to ensure buckets: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(path)
 | 
			
		||||
 | 
			
		||||
	m, found, err := DB.GetMeta(servername)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to get meta: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		t.Errorf("Not Found in meta")
 | 
			
		||||
	}
 | 
			
		||||
	if meta.Name != m.Name || meta.Distro != m.Distro {
 | 
			
		||||
		t.Errorf("expected %v, actual %v", meta, m)
 | 
			
		||||
	}
 | 
			
		||||
	if !reflect.DeepEqual(meta.Packs, m.Packs) {
 | 
			
		||||
		t.Errorf("expected %v, actual %v", meta.Packs, m.Packs)
 | 
			
		||||
	}
 | 
			
		||||
	if err := DB.Close(); err != nil {
 | 
			
		||||
		t.Errorf("Failed to close bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db, err := bolt.Open(path, 0600, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to open bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(servername))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			t.Errorf("Meta bucket nof found")
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPutGetChangelog(t *testing.T) {
 | 
			
		||||
	clog := "changelog-text"
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	if err := SetupBolt(path, log); err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(path)
 | 
			
		||||
 | 
			
		||||
	if err := DB.EnsureBuckets(meta); err != nil {
 | 
			
		||||
		t.Errorf("Failed to ensure buckets: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := DB.PutChangelog(servername, "apt", clog); err != nil {
 | 
			
		||||
		t.Errorf("Failed to put changelog: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if actual, err := DB.GetChangelog(servername, "apt"); err != nil {
 | 
			
		||||
		t.Errorf("Failed to get changelog: %s", err)
 | 
			
		||||
	} else {
 | 
			
		||||
		if actual != clog {
 | 
			
		||||
			t.Errorf("changelog is not same. e: %s, a: %s", clog, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								cache/db.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								cache/db.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package cache
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DB has a cache instance
 | 
			
		||||
var DB Cache
 | 
			
		||||
 | 
			
		||||
const metabucket = "changelog-meta"
 | 
			
		||||
 | 
			
		||||
// Cache is a interface of cache
 | 
			
		||||
type Cache interface {
 | 
			
		||||
	Close() error
 | 
			
		||||
	GetMeta(string) (Meta, bool, error)
 | 
			
		||||
	RefreshMeta(Meta) error
 | 
			
		||||
	EnsureBuckets(Meta) error
 | 
			
		||||
	PrettyPrint(Meta) error
 | 
			
		||||
	GetChangelog(string, string) (string, error)
 | 
			
		||||
	PutChangelog(string, string, string) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Meta holds a server name, distro information of the scanned server and
 | 
			
		||||
// package information that was collected at the last scan.
 | 
			
		||||
type Meta struct {
 | 
			
		||||
	Name      string
 | 
			
		||||
	Distro    config.Distro
 | 
			
		||||
	Packs     []models.PackageInfo
 | 
			
		||||
	CreatedAt time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindPack search a PackageInfo
 | 
			
		||||
func (m Meta) FindPack(name string) (pack models.PackageInfo, found bool) {
 | 
			
		||||
	for _, p := range m.Packs {
 | 
			
		||||
		if name == p.Name {
 | 
			
		||||
			return p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return pack, false
 | 
			
		||||
}
 | 
			
		||||
@@ -18,15 +18,12 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/scan"
 | 
			
		||||
@@ -36,8 +33,11 @@ import (
 | 
			
		||||
// ConfigtestCmd is Subcommand
 | 
			
		||||
type ConfigtestCmd struct {
 | 
			
		||||
	configPath     string
 | 
			
		||||
	logDir         string
 | 
			
		||||
	askKeyPassword bool
 | 
			
		||||
	sshExternal    bool
 | 
			
		||||
	httpProxy      string
 | 
			
		||||
	timeoutSec     int
 | 
			
		||||
 | 
			
		||||
	debug bool
 | 
			
		||||
}
 | 
			
		||||
@@ -52,12 +52,15 @@ func (*ConfigtestCmd) Synopsis() string { return "Test configuration" }
 | 
			
		||||
func (*ConfigtestCmd) Usage() string {
 | 
			
		||||
	return `configtest:
 | 
			
		||||
	configtest
 | 
			
		||||
		        [-config=/path/to/config.toml]
 | 
			
		||||
	        	[-ask-key-password]
 | 
			
		||||
	        	[-ssh-external]
 | 
			
		||||
		        [-debug]
 | 
			
		||||
			[-config=/path/to/config.toml]
 | 
			
		||||
			[-log-dir=/path/to/log]
 | 
			
		||||
			[-ask-key-password]
 | 
			
		||||
			[-timeout=300]
 | 
			
		||||
			[-ssh-external]
 | 
			
		||||
			[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
			[-debug]
 | 
			
		||||
 | 
			
		||||
		        [SERVER]...
 | 
			
		||||
			[SERVER]...
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -67,8 +70,13 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	f.IntVar(&p.timeoutSec, "timeout", 5*60, "Timeout(Sec)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
@@ -76,6 +84,13 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.httpProxy,
 | 
			
		||||
		"http-proxy",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://proxy-url:port (default: empty)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.sshExternal,
 | 
			
		||||
		"ssh-external",
 | 
			
		||||
@@ -85,41 +100,34 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	// Setup Logger
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.LogDir = p.logDir
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			util.Log.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		util.Log.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		util.Log.Errorf("If you update Vuls and get this error, there may be incompatible changes in config.toml")
 | 
			
		||||
		util.Log.Errorf("Please check README: https://github.com/future-architect/vuls#configuration")
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
	c.Conf.SSHExternal = p.sshExternal
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
 | 
			
		||||
	var servernames []string
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		servernames = f.Args()
 | 
			
		||||
	} else {
 | 
			
		||||
		stat, _ := os.Stdin.Stat()
 | 
			
		||||
		if (stat.Mode() & os.ModeCharDevice) == 0 {
 | 
			
		||||
			bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			fields := strings.Fields(string(bytes))
 | 
			
		||||
			if 0 < len(fields) {
 | 
			
		||||
				servernames = fields
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
@@ -133,7 +141,7 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			logrus.Errorf("%s is not in config", arg)
 | 
			
		||||
			util.Log.Errorf("%s is not in config", arg)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -141,22 +149,23 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// logger
 | 
			
		||||
	Log := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	Log.Info("Validating Config...")
 | 
			
		||||
	if !c.Conf.Validate() {
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnConfigtest() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting Server/Contianer OS... ")
 | 
			
		||||
	scan.InitServers(Log)
 | 
			
		||||
 | 
			
		||||
	Log.Info("Checking sudo configuration... ")
 | 
			
		||||
	if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers. err: %s", err)
 | 
			
		||||
	util.Log.Info("Detecting Server/Container OS... ")
 | 
			
		||||
	if err := scan.InitServers(); err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to init servers: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking dependendies...")
 | 
			
		||||
	scan.CheckDependencies(p.timeoutSec)
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking sudo settings...")
 | 
			
		||||
	scan.CheckIfSudoNoPasswd(p.timeoutSec)
 | 
			
		||||
 | 
			
		||||
	scan.PrintSSHableServerNames()
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
@@ -25,7 +26,6 @@ import (
 | 
			
		||||
	"text/template"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	ps "github.com/kotakanbe/go-pingscanner"
 | 
			
		||||
@@ -98,9 +98,9 @@ iconEmoji    = ":ghost:"
 | 
			
		||||
authUser     = "username"
 | 
			
		||||
notifyUsers  = ["@username"]
 | 
			
		||||
 | 
			
		||||
[mail]
 | 
			
		||||
[email]
 | 
			
		||||
smtpAddr      = "smtp.gmail.com"
 | 
			
		||||
smtpPort      = "465"
 | 
			
		||||
smtpPort      = "587"
 | 
			
		||||
user          = "username"
 | 
			
		||||
password      = "password"
 | 
			
		||||
from          = "from@address.com"
 | 
			
		||||
@@ -115,10 +115,13 @@ subjectPrefix = "[vuls]"
 | 
			
		||||
#cpeNames = [
 | 
			
		||||
#  "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
#]
 | 
			
		||||
#containers = ["${running}"]
 | 
			
		||||
#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml"
 | 
			
		||||
#ignoreCves = ["CVE-2014-6271"]
 | 
			
		||||
#optional = [
 | 
			
		||||
#    ["key", "value"],
 | 
			
		||||
#]
 | 
			
		||||
#containers = ["${running}"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[servers]
 | 
			
		||||
{{- $names:=  .Names}}
 | 
			
		||||
@@ -131,10 +134,17 @@ host         = "{{$ip}}"
 | 
			
		||||
#cpeNames = [
 | 
			
		||||
#  "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
#]
 | 
			
		||||
#containers = ["${running}"]
 | 
			
		||||
#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml"
 | 
			
		||||
#ignoreCves = ["CVE-2014-0160"]
 | 
			
		||||
#optional = [
 | 
			
		||||
#    ["key", "value"],
 | 
			
		||||
#]
 | 
			
		||||
#[servers.{{index $names $i}}.containers]
 | 
			
		||||
#type = "docker" #or "lxd" defualt: docker
 | 
			
		||||
#includes = ["${running}"]
 | 
			
		||||
#excludes = ["container_name_a", "4aa37a8b63b9"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
 
 | 
			
		||||
@@ -18,27 +18,23 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/db"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HistoryCmd is Subcommand of list scanned results
 | 
			
		||||
type HistoryCmd struct {
 | 
			
		||||
	debug    bool
 | 
			
		||||
	debugSQL bool
 | 
			
		||||
 | 
			
		||||
	dbpath string
 | 
			
		||||
	debug      bool
 | 
			
		||||
	debugSQL   bool
 | 
			
		||||
	resultsDir string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
@@ -53,7 +49,7 @@ func (*HistoryCmd) Synopsis() string {
 | 
			
		||||
func (*HistoryCmd) Usage() string {
 | 
			
		||||
	return `history:
 | 
			
		||||
	history
 | 
			
		||||
		[-dbpath=/path/to/vuls.sqlite3]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
	`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -62,47 +58,41 @@ func (p *HistoryCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultDBPath := filepath.Join(wd, "vuls.sqlite3")
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3")
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.DBPath = p.dbpath
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
 | 
			
		||||
	//  _, err := scanHistories()
 | 
			
		||||
	histories, err := scanHistories()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Error("Failed to select scan histories: ", err)
 | 
			
		||||
	var err error
 | 
			
		||||
	var dirs jsonDirs
 | 
			
		||||
	if dirs, err = lsValidJSONDirs(); err != nil {
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	const timeLayout = "2006-01-02 15:04"
 | 
			
		||||
	for _, history := range histories {
 | 
			
		||||
		names := []string{}
 | 
			
		||||
		for _, result := range history.ScanResults {
 | 
			
		||||
			if 0 < len(result.Container.ContainerID) {
 | 
			
		||||
				names = append(names, result.Container.Name)
 | 
			
		||||
			} else {
 | 
			
		||||
				names = append(names, result.ServerName)
 | 
			
		||||
			}
 | 
			
		||||
	for _, d := range dirs {
 | 
			
		||||
		var files []os.FileInfo
 | 
			
		||||
		if files, err = ioutil.ReadDir(d); err != nil {
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Printf("%-3d %s scanned %d servers: %s\n",
 | 
			
		||||
			history.ID,
 | 
			
		||||
			history.ScannedAt.Format(timeLayout),
 | 
			
		||||
			len(history.ScanResults),
 | 
			
		||||
			strings.Join(names, ", "),
 | 
			
		||||
		var hosts []string
 | 
			
		||||
		for _, f := range files {
 | 
			
		||||
			if filepath.Ext(f.Name()) != ".json" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			fileBase := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
 | 
			
		||||
			hosts = append(hosts, fileBase)
 | 
			
		||||
		}
 | 
			
		||||
		splitPath := strings.Split(d, string(os.PathSeparator))
 | 
			
		||||
		timeStr := splitPath[len(splitPath)-1]
 | 
			
		||||
		fmt.Printf("%s %d servers: %s\n",
 | 
			
		||||
			timeStr,
 | 
			
		||||
			len(hosts),
 | 
			
		||||
			strings.Join(hosts, ", "),
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanHistories() (histories []models.ScanHistory, err error) {
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		return histories, fmt.Errorf(
 | 
			
		||||
			"Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	histories, err = db.SelectScanHistories()
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,164 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/scan"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// PrepareCmd is Subcommand of host discovery mode
 | 
			
		||||
type PrepareCmd struct {
 | 
			
		||||
	debug      bool
 | 
			
		||||
	configPath string
 | 
			
		||||
 | 
			
		||||
	askSudoPassword bool
 | 
			
		||||
	askKeyPassword  bool
 | 
			
		||||
 | 
			
		||||
	useUnattendedUpgrades bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*PrepareCmd) Name() string { return "prepare" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*PrepareCmd) Synopsis() string {
 | 
			
		||||
	//  return "Install packages Ubuntu: unattended-upgrade, CentOS: yum-plugin-security)"
 | 
			
		||||
	return `Install required packages to scan.
 | 
			
		||||
				CentOS: yum-plugin-security, yum-plugin-changelog
 | 
			
		||||
				Amazon: None
 | 
			
		||||
				RHEL:   TODO
 | 
			
		||||
				Ubuntu: None
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*PrepareCmd) Usage() string {
 | 
			
		||||
	return `prepare:
 | 
			
		||||
	prepare
 | 
			
		||||
			[-config=/path/to/config.toml]
 | 
			
		||||
			[-ask-key-password]
 | 
			
		||||
			[-debug]
 | 
			
		||||
 | 
			
		||||
		    [SERVER]...
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *PrepareCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askSudoPassword,
 | 
			
		||||
		"ask-sudo-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASON. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useUnattendedUpgrades,
 | 
			
		||||
		"use-unattended-upgrades",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] For Ubuntu, install unattended-upgrades",
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if p.askSudoPassword {
 | 
			
		||||
		logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Infof("Start Preparing (config: %s)", p.configPath)
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
	for _, arg := range f.Args() {
 | 
			
		||||
		found := false
 | 
			
		||||
		for servername, info := range c.Conf.Servers {
 | 
			
		||||
			if servername == arg {
 | 
			
		||||
				target[servername] = info
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			logrus.Errorf("%s is not in config", arg)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
 | 
			
		||||
 | 
			
		||||
	// Set up custom logger
 | 
			
		||||
	logger := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	logger.Info("Detecting OS... ")
 | 
			
		||||
	scan.InitServers(logger)
 | 
			
		||||
 | 
			
		||||
	logger.Info("Installing...")
 | 
			
		||||
	if errs := scan.Prepare(); 0 < len(errs) {
 | 
			
		||||
		for _, e := range errs {
 | 
			
		||||
			logger.Errorf("Failed: %s", e)
 | 
			
		||||
		}
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Info("Success")
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										451
									
								
								commands/report.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										451
									
								
								commands/report.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,451 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ReportCmd is subcommand for reporting
 | 
			
		||||
type ReportCmd struct {
 | 
			
		||||
	lang       string
 | 
			
		||||
	debug      bool
 | 
			
		||||
	debugSQL   bool
 | 
			
		||||
	configPath string
 | 
			
		||||
	resultsDir string
 | 
			
		||||
	logDir     string
 | 
			
		||||
	refreshCve bool
 | 
			
		||||
 | 
			
		||||
	cvssScoreOver      float64
 | 
			
		||||
	ignoreUnscoredCves bool
 | 
			
		||||
	httpProxy          string
 | 
			
		||||
 | 
			
		||||
	cvedbtype string
 | 
			
		||||
	cvedbpath string
 | 
			
		||||
	cvedbURL  string
 | 
			
		||||
 | 
			
		||||
	toSlack     bool
 | 
			
		||||
	toEMail     bool
 | 
			
		||||
	toLocalFile bool
 | 
			
		||||
	toS3        bool
 | 
			
		||||
	toAzureBlob bool
 | 
			
		||||
 | 
			
		||||
	formatJSON        bool
 | 
			
		||||
	formatXML         bool
 | 
			
		||||
	formatOneEMail    bool
 | 
			
		||||
	formatOneLineText bool
 | 
			
		||||
	formatShortText   bool
 | 
			
		||||
	formatFullText    bool
 | 
			
		||||
 | 
			
		||||
	gzip bool
 | 
			
		||||
 | 
			
		||||
	awsProfile  string
 | 
			
		||||
	awsS3Bucket string
 | 
			
		||||
	awsRegion   string
 | 
			
		||||
 | 
			
		||||
	azureAccount   string
 | 
			
		||||
	azureKey       string
 | 
			
		||||
	azureContainer string
 | 
			
		||||
 | 
			
		||||
	pipe bool
 | 
			
		||||
 | 
			
		||||
	diff bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*ReportCmd) Name() string { return "report" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*ReportCmd) Synopsis() string { return "Reporting" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*ReportCmd) Usage() string {
 | 
			
		||||
	return `report:
 | 
			
		||||
	report
 | 
			
		||||
		[-lang=en|ja]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-refresh-cve]
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql]
 | 
			
		||||
		[-cvedb-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-diff]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-to-email]
 | 
			
		||||
		[-to-slack]
 | 
			
		||||
		[-to-localfile]
 | 
			
		||||
		[-to-s3]
 | 
			
		||||
		[-to-azure-blob]
 | 
			
		||||
		[-format-json]
 | 
			
		||||
		[-format-xml]
 | 
			
		||||
		[-format-one-email]
 | 
			
		||||
		[-format-one-line-text]
 | 
			
		||||
		[-format-short-text]
 | 
			
		||||
		[-format-full-text]
 | 
			
		||||
		[-gzip]
 | 
			
		||||
		[-aws-profile=default]
 | 
			
		||||
		[-aws-region=us-west-2]
 | 
			
		||||
		[-aws-s3-bucket=bucket_name]
 | 
			
		||||
		[-azure-account=accout]
 | 
			
		||||
		[-azure-key=key]
 | 
			
		||||
		[-azure-container=container]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-pipe]
 | 
			
		||||
 | 
			
		||||
		[SERVER]...
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.refreshCve,
 | 
			
		||||
		"refresh-cve",
 | 
			
		||||
		false,
 | 
			
		||||
		"Refresh CVE information in JSON file under results dir")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbtype,
 | 
			
		||||
		"cvedb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching CVE dictionary (sqlite3 or mysql)")
 | 
			
		||||
 | 
			
		||||
	defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbpath,
 | 
			
		||||
		"cvedb-path",
 | 
			
		||||
		defaultCveDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbURL,
 | 
			
		||||
		"cvedb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://cve-dictionary.com:8080 or mysql connection string")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(
 | 
			
		||||
		&p.cvssScoreOver,
 | 
			
		||||
		"cvss-over",
 | 
			
		||||
		0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.diff,
 | 
			
		||||
		"diff",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Difference between previous result and current result "))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.ignoreUnscoredCves,
 | 
			
		||||
		"ignore-unscored-cves",
 | 
			
		||||
		false,
 | 
			
		||||
		"Don't report the unscored CVEs")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.httpProxy,
 | 
			
		||||
		"http-proxy",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://proxy-url:port (default: empty)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatJSON,
 | 
			
		||||
		"format-json",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("JSON format"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatXML,
 | 
			
		||||
		"format-xml",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("XML format"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatOneEMail,
 | 
			
		||||
		"format-one-email",
 | 
			
		||||
		false,
 | 
			
		||||
		"Send all the host report via only one EMail (Specify with -to-email)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatOneLineText,
 | 
			
		||||
		"format-one-line-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("One line summary in plain text"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatShortText,
 | 
			
		||||
		"format-short-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Summary in plain text"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatFullText,
 | 
			
		||||
		"format-full-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Detail report in plain text"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.gzip, "gzip", false, "gzip compression")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toSlack, "to-slack", false, "Send report via Slack")
 | 
			
		||||
	f.BoolVar(&p.toEMail, "to-email", false, "Send report via Email")
 | 
			
		||||
	f.BoolVar(&p.toLocalFile,
 | 
			
		||||
		"to-localfile",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Write report to localfile"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toS3,
 | 
			
		||||
		"to-s3",
 | 
			
		||||
		false,
 | 
			
		||||
		"Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/xml/txt)")
 | 
			
		||||
	f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
 | 
			
		||||
	f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
 | 
			
		||||
	f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toAzureBlob,
 | 
			
		||||
		"to-azure-blob",
 | 
			
		||||
		false,
 | 
			
		||||
		"Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/xml/txt)")
 | 
			
		||||
	f.StringVar(&p.azureAccount,
 | 
			
		||||
		"azure-account",
 | 
			
		||||
		"",
 | 
			
		||||
		"Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureKey,
 | 
			
		||||
		"azure-key",
 | 
			
		||||
		"",
 | 
			
		||||
		"Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureContainer, "azure-container", "", "Azure storage container name")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.pipe,
 | 
			
		||||
		"pipe",
 | 
			
		||||
		false,
 | 
			
		||||
		"Use args passed via PIPE")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.LogDir = p.logDir
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	if err := c.Load(p.configPath, ""); err != nil {
 | 
			
		||||
		util.Log.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Lang = p.lang
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
	c.Conf.CveDBType = p.cvedbtype
 | 
			
		||||
	c.Conf.CveDBPath = p.cvedbpath
 | 
			
		||||
	c.Conf.CveDBURL = p.cvedbURL
 | 
			
		||||
	c.Conf.CvssScoreOver = p.cvssScoreOver
 | 
			
		||||
	c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
 | 
			
		||||
	c.Conf.Pipe = p.pipe
 | 
			
		||||
 | 
			
		||||
	c.Conf.FormatXML = p.formatXML
 | 
			
		||||
	c.Conf.FormatJSON = p.formatJSON
 | 
			
		||||
	c.Conf.FormatOneEMail = p.formatOneEMail
 | 
			
		||||
	c.Conf.FormatOneLineText = p.formatOneLineText
 | 
			
		||||
	c.Conf.FormatShortText = p.formatShortText
 | 
			
		||||
	c.Conf.FormatFullText = p.formatFullText
 | 
			
		||||
 | 
			
		||||
	c.Conf.GZIP = p.gzip
 | 
			
		||||
	c.Conf.Diff = p.diff
 | 
			
		||||
 | 
			
		||||
	var dir string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.diff {
 | 
			
		||||
		dir, err = jsonDir([]string{})
 | 
			
		||||
	} else {
 | 
			
		||||
		dir, err = jsonDir(f.Args())
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to read from JSON: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// report
 | 
			
		||||
	reports := []report.ResultWriter{
 | 
			
		||||
		report.StdoutWriter{},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toSlack {
 | 
			
		||||
		reports = append(reports, report.SlackWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toEMail {
 | 
			
		||||
		reports = append(reports, report.EMailWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toLocalFile {
 | 
			
		||||
		reports = append(reports, report.LocalFileWriter{
 | 
			
		||||
			CurrentDir: dir,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toS3 {
 | 
			
		||||
		c.Conf.AwsRegion = p.awsRegion
 | 
			
		||||
		c.Conf.AwsProfile = p.awsProfile
 | 
			
		||||
		c.Conf.S3Bucket = p.awsS3Bucket
 | 
			
		||||
		if err := report.CheckIfBucketExists(); err != nil {
 | 
			
		||||
			util.Log.Errorf("Check if there is a bucket beforehand: %s, err: %s", c.Conf.S3Bucket, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.S3Writer{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toAzureBlob {
 | 
			
		||||
		c.Conf.AzureAccount = p.azureAccount
 | 
			
		||||
		if len(c.Conf.AzureAccount) == 0 {
 | 
			
		||||
			c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureKey = p.azureKey
 | 
			
		||||
		if len(c.Conf.AzureKey) == 0 {
 | 
			
		||||
			c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureContainer = p.azureContainer
 | 
			
		||||
		if len(c.Conf.AzureContainer) == 0 {
 | 
			
		||||
			util.Log.Error("Azure storage container name is requied with --azure-container option")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		if err := report.CheckIfAzureContainerExists(); err != nil {
 | 
			
		||||
			util.Log.Errorf("Check if there is a container beforehand: %s, err: %s", c.Conf.AzureContainer, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.AzureBlobWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !(p.formatJSON || p.formatOneLineText ||
 | 
			
		||||
		p.formatShortText || p.formatFullText || p.formatXML) {
 | 
			
		||||
		c.Conf.FormatShortText = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnReport() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
	if ok, err := cveapi.CveClient.CheckHealth(); !ok {
 | 
			
		||||
		util.Log.Errorf("CVE HTTP server is not running. err: %s", err)
 | 
			
		||||
		util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with --cvedb-path option")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	if c.Conf.CveDBURL != "" {
 | 
			
		||||
		util.Log.Infof("cve-dictionary: %s", c.Conf.CveDBURL)
 | 
			
		||||
	} else {
 | 
			
		||||
		if c.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
			util.Log.Infof("cve-dictionary: %s", c.Conf.CveDBPath)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var history models.ScanHistory
 | 
			
		||||
	history, err = loadOneScanHistory(dir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("Loaded: %s", jsonDir)
 | 
			
		||||
 | 
			
		||||
	var results []models.ScanResult
 | 
			
		||||
	for _, r := range history.ScanResults {
 | 
			
		||||
		if p.refreshCve || needToRefreshCve(r) {
 | 
			
		||||
			util.Log.Debugf("need to refresh")
 | 
			
		||||
			if c.Conf.CveDBType == "sqlite3" && c.Conf.CveDBURL == "" {
 | 
			
		||||
				if _, err := os.Stat(c.Conf.CveDBPath); os.IsNotExist(err) {
 | 
			
		||||
					util.Log.Errorf("SQLite3 DB(CVE-Dictionary) is not exist: %s",
 | 
			
		||||
						c.Conf.CveDBPath)
 | 
			
		||||
					return subcommands.ExitFailure
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			filled, err := fillCveInfoFromCveDB(r)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				util.Log.Errorf("Failed to fill CVE information: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			filled.Lang = c.Conf.Lang
 | 
			
		||||
			if err := overwriteJSONFile(dir, *filled); err != nil {
 | 
			
		||||
				util.Log.Errorf("Failed to write JSON: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			results = append(results, *filled)
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Debugf("no need to refresh")
 | 
			
		||||
			results = append(results, r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.diff {
 | 
			
		||||
		currentHistory := models.ScanHistory{ScanResults: results}
 | 
			
		||||
		previousHistory, err := loadPreviousScanHistory(currentHistory)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		history, err = diff(currentHistory, previousHistory)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		results = []models.ScanResult{}
 | 
			
		||||
		for _, r := range history.ScanResults {
 | 
			
		||||
			filled, _ := r.FillCveDetail()
 | 
			
		||||
			results = append(results, *filled)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res models.ScanResults
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		res = append(res, r.FilterByCvssOver())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, w := range reports {
 | 
			
		||||
		if err := w.Write(res...); err != nil {
 | 
			
		||||
			util.Log.Errorf("Failed to report: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										366
									
								
								commands/scan.go
									
									
									
									
									
								
							
							
						
						
									
										366
									
								
								commands/scan.go
									
									
									
									
									
								
							@@ -18,6 +18,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
@@ -25,56 +26,26 @@ import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/db"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/scan"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanCmd is Subcommand of host discovery mode
 | 
			
		||||
type ScanCmd struct {
 | 
			
		||||
	lang     string
 | 
			
		||||
	debug    bool
 | 
			
		||||
	debugSQL bool
 | 
			
		||||
 | 
			
		||||
	configPath string
 | 
			
		||||
 | 
			
		||||
	dbpath           string
 | 
			
		||||
	cvedbpath        string
 | 
			
		||||
	cveDictionaryURL string
 | 
			
		||||
 | 
			
		||||
	cvssScoreOver      float64
 | 
			
		||||
	ignoreUnscoredCves bool
 | 
			
		||||
 | 
			
		||||
	httpProxy       string
 | 
			
		||||
	askSudoPassword bool
 | 
			
		||||
	askKeyPassword  bool
 | 
			
		||||
 | 
			
		||||
	// reporting
 | 
			
		||||
	reportSlack     bool
 | 
			
		||||
	reportMail      bool
 | 
			
		||||
	reportJSON      bool
 | 
			
		||||
	reportText      bool
 | 
			
		||||
	reportS3        bool
 | 
			
		||||
	reportAzureBlob bool
 | 
			
		||||
 | 
			
		||||
	awsProfile  string
 | 
			
		||||
	awsS3Bucket string
 | 
			
		||||
	awsRegion   string
 | 
			
		||||
 | 
			
		||||
	azureAccount   string
 | 
			
		||||
	azureKey       string
 | 
			
		||||
	azureContainer string
 | 
			
		||||
 | 
			
		||||
	useYumPluginSecurity  bool
 | 
			
		||||
	useUnattendedUpgrades bool
 | 
			
		||||
 | 
			
		||||
	sshExternal bool
 | 
			
		||||
	debug          bool
 | 
			
		||||
	configPath     string
 | 
			
		||||
	resultsDir     string
 | 
			
		||||
	logDir         string
 | 
			
		||||
	cacheDBPath    string
 | 
			
		||||
	httpProxy      string
 | 
			
		||||
	askKeyPassword bool
 | 
			
		||||
	containersOnly bool
 | 
			
		||||
	skipBroken     bool
 | 
			
		||||
	sshExternal    bool
 | 
			
		||||
	pipe           bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
@@ -87,30 +58,17 @@ func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities" }
 | 
			
		||||
func (*ScanCmd) Usage() string {
 | 
			
		||||
	return `scan:
 | 
			
		||||
	scan
 | 
			
		||||
		[-lang=en|ja]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-dbpath=/path/to/vuls.sqlite3]
 | 
			
		||||
		[-cve-dictionary-dbpath=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cve-dictionary-url=http://127.0.0.1:1323]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-cachedb-path=/path/to/cache.db]
 | 
			
		||||
		[-ssh-external]
 | 
			
		||||
		[-report-azure-blob]
 | 
			
		||||
		[-report-json]
 | 
			
		||||
		[-report-mail]
 | 
			
		||||
		[-report-s3]
 | 
			
		||||
		[-report-slack]
 | 
			
		||||
		[-report-text]
 | 
			
		||||
		[-containers-only]
 | 
			
		||||
		[-skip-broken]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-ask-key-password]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-aws-profile=default]
 | 
			
		||||
		[-aws-region=us-west-2]
 | 
			
		||||
		[-aws-s3-bucket=bucket_name]
 | 
			
		||||
		[-azure-account=accout]
 | 
			
		||||
		[-azure-key=key]
 | 
			
		||||
		[-azure-container=container]
 | 
			
		||||
		[-pipe]
 | 
			
		||||
 | 
			
		||||
		[SERVER]...
 | 
			
		||||
`
 | 
			
		||||
@@ -118,42 +76,25 @@ func (*ScanCmd) Usage() string {
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultDBPath := filepath.Join(wd, "vuls.sqlite3")
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3")
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	defaultCacheDBPath := filepath.Join(wd, "cache.db")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbpath,
 | 
			
		||||
		"cve-dictionary-dbpath",
 | 
			
		||||
		"",
 | 
			
		||||
		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	defaultURL := "http://127.0.0.1:1323"
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDictionaryURL,
 | 
			
		||||
		"cve-dictionary-url",
 | 
			
		||||
		defaultURL,
 | 
			
		||||
		"http://CVE.Dictionary")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(
 | 
			
		||||
		&p.cvssScoreOver,
 | 
			
		||||
		"cvss-over",
 | 
			
		||||
		0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.ignoreUnscoredCves,
 | 
			
		||||
		"ignore-unscored-cves",
 | 
			
		||||
		false,
 | 
			
		||||
		"Don't report the unscored CVEs")
 | 
			
		||||
		&p.cacheDBPath,
 | 
			
		||||
		"cachedb-path",
 | 
			
		||||
		defaultCacheDBPath,
 | 
			
		||||
		"/path/to/cache.db (local cache of changelog for Ubuntu/Debian)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.sshExternal,
 | 
			
		||||
@@ -161,6 +102,18 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		false,
 | 
			
		||||
		"Use external ssh command. Default: Use the Go native implementation")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.containersOnly,
 | 
			
		||||
		"containers-only",
 | 
			
		||||
		false,
 | 
			
		||||
		"Scan containers only. Default: Scan both of hosts and containers")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.skipBroken,
 | 
			
		||||
		"skip-broken",
 | 
			
		||||
		false,
 | 
			
		||||
		"[For CentOS] yum update changelog with --skip-broken option")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.httpProxy,
 | 
			
		||||
		"http-proxy",
 | 
			
		||||
@@ -168,37 +121,6 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		"http://proxy-url:port (default: empty)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.reportSlack, "report-slack", false, "Send report via Slack")
 | 
			
		||||
	f.BoolVar(&p.reportMail, "report-mail", false, "Send report via Email")
 | 
			
		||||
	f.BoolVar(&p.reportJSON,
 | 
			
		||||
		"report-json",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Write report to JSON files (%s/results/current)", wd),
 | 
			
		||||
	)
 | 
			
		||||
	f.BoolVar(&p.reportText,
 | 
			
		||||
		"report-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Write report to text files (%s/results/current)", wd),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.reportS3,
 | 
			
		||||
		"report-s3",
 | 
			
		||||
		false,
 | 
			
		||||
		"Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json)",
 | 
			
		||||
	)
 | 
			
		||||
	f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
 | 
			
		||||
	f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
 | 
			
		||||
	f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.reportAzureBlob,
 | 
			
		||||
		"report-azure-blob",
 | 
			
		||||
		false,
 | 
			
		||||
		"Write report to S3 (container/yyyyMMdd_HHmm/servername.json)",
 | 
			
		||||
	)
 | 
			
		||||
	f.StringVar(&p.azureAccount, "azure-account", "", "Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureKey, "azure-key", "", "Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureContainer, "azure-container", "", "Azure storage container name")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
@@ -207,73 +129,54 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askSudoPassword,
 | 
			
		||||
		"ask-sudo-password",
 | 
			
		||||
		&p.pipe,
 | 
			
		||||
		"pipe",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useYumPluginSecurity,
 | 
			
		||||
		"use-yum-plugin-security",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useUnattendedUpgrades,
 | 
			
		||||
		"use-unattended-upgrades",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
		"Use stdin via PIPE")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
 | 
			
		||||
	// Setup Logger
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.LogDir = p.logDir
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			util.Log.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if p.askSudoPassword {
 | 
			
		||||
		logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		util.Log.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		util.Log.Errorf("If you update Vuls and get this error, there may be incompatible changes in config.toml")
 | 
			
		||||
		util.Log.Errorf("Please check README: https://github.com/future-architect/vuls#configuration")
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Info("Start scanning")
 | 
			
		||||
	logrus.Infof("config: %s", p.configPath)
 | 
			
		||||
	if p.cvedbpath != "" {
 | 
			
		||||
		logrus.Infof("cve-dictionary: %s", p.cvedbpath)
 | 
			
		||||
	} else {
 | 
			
		||||
		logrus.Infof("cve-dictionary: %s", p.cveDictionaryURL)
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Info("Start scanning")
 | 
			
		||||
	util.Log.Infof("config: %s", p.configPath)
 | 
			
		||||
 | 
			
		||||
	c.Conf.Pipe = p.pipe
 | 
			
		||||
	var servernames []string
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		servernames = f.Args()
 | 
			
		||||
	} else {
 | 
			
		||||
		stat, _ := os.Stdin.Stat()
 | 
			
		||||
		if (stat.Mode() & os.ModeCharDevice) == 0 {
 | 
			
		||||
			bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			fields := strings.Fields(string(bytes))
 | 
			
		||||
			if 0 < len(fields) {
 | 
			
		||||
				servernames = fields
 | 
			
		||||
			}
 | 
			
		||||
	} else if c.Conf.Pipe {
 | 
			
		||||
		bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		fields := strings.Fields(string(bytes))
 | 
			
		||||
		if 0 < len(fields) {
 | 
			
		||||
			servernames = fields
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -288,143 +191,44 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			logrus.Errorf("%s is not in config", arg)
 | 
			
		||||
			util.Log.Errorf("%s is not in config", arg)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(servernames) {
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("%s", pp.Sprintf("%v", target))
 | 
			
		||||
 | 
			
		||||
	c.Conf.Lang = p.lang
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
 | 
			
		||||
	// logger
 | 
			
		||||
	Log := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	// report
 | 
			
		||||
	reports := []report.ResultWriter{
 | 
			
		||||
		report.StdoutWriter{},
 | 
			
		||||
		report.LogrusWriter{},
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportSlack {
 | 
			
		||||
		reports = append(reports, report.SlackWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportMail {
 | 
			
		||||
		reports = append(reports, report.MailWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportJSON {
 | 
			
		||||
		reports = append(reports, report.JSONWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportText {
 | 
			
		||||
		reports = append(reports, report.TextFileWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportS3 {
 | 
			
		||||
		c.Conf.AwsRegion = p.awsRegion
 | 
			
		||||
		c.Conf.AwsProfile = p.awsProfile
 | 
			
		||||
		c.Conf.S3Bucket = p.awsS3Bucket
 | 
			
		||||
		if err := report.CheckIfBucketExists(); err != nil {
 | 
			
		||||
			Log.Errorf("Failed to access to the S3 bucket. err: %s", err)
 | 
			
		||||
			Log.Error("Ensure the bucket or check AWS config before scanning")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.S3Writer{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportAzureBlob {
 | 
			
		||||
		c.Conf.AzureAccount = p.azureAccount
 | 
			
		||||
		if c.Conf.AzureAccount == "" {
 | 
			
		||||
			c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureKey = p.azureKey
 | 
			
		||||
		if c.Conf.AzureKey == "" {
 | 
			
		||||
			c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureContainer = p.azureContainer
 | 
			
		||||
		if c.Conf.AzureContainer == "" {
 | 
			
		||||
			Log.Error("Azure storage container name is requied with --azure-container option")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		if err := report.CheckIfAzureContainerExists(); err != nil {
 | 
			
		||||
			Log.Errorf("Failed to access to the Azure Blob container. err: %s", err)
 | 
			
		||||
			Log.Error("Ensure the container or check Azure config before scanning")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.AzureBlobWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.DBPath = p.dbpath
 | 
			
		||||
	c.Conf.CveDBPath = p.cvedbpath
 | 
			
		||||
	c.Conf.CveDictionaryURL = p.cveDictionaryURL
 | 
			
		||||
	c.Conf.CvssScoreOver = p.cvssScoreOver
 | 
			
		||||
	c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
	c.Conf.CacheDBPath = p.cacheDBPath
 | 
			
		||||
	c.Conf.SSHExternal = p.sshExternal
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
	c.Conf.UseYumPluginSecurity = p.useYumPluginSecurity
 | 
			
		||||
	c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
 | 
			
		||||
	c.Conf.ContainersOnly = p.containersOnly
 | 
			
		||||
	c.Conf.SkipBroken = p.skipBroken
 | 
			
		||||
 | 
			
		||||
	Log.Info("Validating Config...")
 | 
			
		||||
	if !c.Conf.Validate() {
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnScan() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ok, err := cveapi.CveClient.CheckHealth(); !ok {
 | 
			
		||||
		Log.Errorf("CVE HTTP server is not running. err: %s", err)
 | 
			
		||||
		Log.Errorf("Run go-cve-dictionary as server mode or specify -cve-dictionary-dbpath option")
 | 
			
		||||
	util.Log.Info("Detecting Server/Container OS... ")
 | 
			
		||||
	if err := scan.InitServers(); err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to init servers: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting Server/Contianer OS... ")
 | 
			
		||||
	scan.InitServers(Log)
 | 
			
		||||
	util.Log.Info("Detecting Platforms... ")
 | 
			
		||||
	scan.DetectPlatforms()
 | 
			
		||||
 | 
			
		||||
	Log.Info("Checking sudo configuration... ")
 | 
			
		||||
	if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers")
 | 
			
		||||
	util.Log.Info("Scanning vulnerabilities... ")
 | 
			
		||||
	if err := scan.Scan(); err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to scan. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting Platforms... ")
 | 
			
		||||
	scan.DetectPlatforms(Log)
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerabilities... ")
 | 
			
		||||
	if errs := scan.Scan(); 0 < len(errs) {
 | 
			
		||||
		for _, e := range errs {
 | 
			
		||||
			Log.Errorf("Failed to scan. err: %s", e)
 | 
			
		||||
		}
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scanResults, err := scan.GetScanResults()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		Log.Fatal(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Insert to DB...")
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.MigrateDB(); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to migrate. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := db.Insert(scanResults); err != nil {
 | 
			
		||||
		Log.Fatalf("Failed to insert. dbpath: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Reporting...")
 | 
			
		||||
	filtered := scanResults.FilterByCvssOver()
 | 
			
		||||
	for _, w := range reports {
 | 
			
		||||
		if err := w.Write(filtered); err != nil {
 | 
			
		||||
			Log.Fatalf("Failed to report, err: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Printf("\n\n\n")
 | 
			
		||||
	fmt.Println("To view the detail, vuls tui is useful.")
 | 
			
		||||
	fmt.Println("To send a report, run vuls report -h.")
 | 
			
		||||
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										140
									
								
								commands/tui.go
									
									
									
									
									
								
							
							
						
						
									
										140
									
								
								commands/tui.go
									
									
									
									
									
								
							@@ -18,26 +18,32 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TuiCmd is Subcommand of host discovery mode
 | 
			
		||||
type TuiCmd struct {
 | 
			
		||||
	lang     string
 | 
			
		||||
	debugSQL bool
 | 
			
		||||
	dbpath   string
 | 
			
		||||
	debug    bool
 | 
			
		||||
	logDir   string
 | 
			
		||||
 | 
			
		||||
	resultsDir       string
 | 
			
		||||
	refreshCve       bool
 | 
			
		||||
	cvedbtype        string
 | 
			
		||||
	cvedbpath        string
 | 
			
		||||
	cveDictionaryURL string
 | 
			
		||||
 | 
			
		||||
	pipe bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
@@ -49,7 +55,16 @@ func (*TuiCmd) Synopsis() string { return "Run Tui view to anayze vulnerabilites
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*TuiCmd) Usage() string {
 | 
			
		||||
	return `tui:
 | 
			
		||||
	tui [-dbpath=/path/to/vuls.sqlite3]
 | 
			
		||||
	tui
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql]
 | 
			
		||||
		[-cvedb-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
 | 
			
		||||
		[-refresh-cve]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-pipe]
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
@@ -58,40 +73,107 @@ func (*TuiCmd) Usage() string {
 | 
			
		||||
func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	//  f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "debug SQL")
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultDBPath := filepath.Join(wd, "vuls.sqlite3")
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath,
 | 
			
		||||
		fmt.Sprintf("/path/to/sqlite3 (default: %s)", defaultDBPath))
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.refreshCve,
 | 
			
		||||
		"refresh-cve",
 | 
			
		||||
		false,
 | 
			
		||||
		"Refresh CVE information in JSON file under results dir")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbtype,
 | 
			
		||||
		"cvedb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching CVE dictionary (sqlite3 or mysql)")
 | 
			
		||||
 | 
			
		||||
	defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbpath,
 | 
			
		||||
		"cvedb-path",
 | 
			
		||||
		defaultCveDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDictionaryURL,
 | 
			
		||||
		"cvedb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://cve-dictionary.com:8080 or mysql connection string")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.pipe,
 | 
			
		||||
		"pipe",
 | 
			
		||||
		false,
 | 
			
		||||
		"Use stdin via PIPE")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	c.Conf.Lang = "en"
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.DBPath = p.dbpath
 | 
			
		||||
 | 
			
		||||
	historyID := ""
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		if _, err := strconv.Atoi(f.Args()[0]); err != nil {
 | 
			
		||||
			log.Errorf("First Argument have to be scan_histores record ID: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		historyID = f.Args()[0]
 | 
			
		||||
	} else {
 | 
			
		||||
		stat, _ := os.Stdin.Stat()
 | 
			
		||||
		if (stat.Mode() & os.ModeCharDevice) == 0 {
 | 
			
		||||
			bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
	// Setup Logger
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.LogDir = p.logDir
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
	log := util.Log
 | 
			
		||||
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
	c.Conf.CveDBType = p.cvedbtype
 | 
			
		||||
	c.Conf.CveDBPath = p.cvedbpath
 | 
			
		||||
	c.Conf.CveDBURL = p.cveDictionaryURL
 | 
			
		||||
 | 
			
		||||
	log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnTui() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Pipe = p.pipe
 | 
			
		||||
	jsonDir, err := jsonDir(f.Args())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Errorf("Failed to read json dir under results: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	history, err := loadOneScanHistory(jsonDir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Errorf("Failed to read from JSON: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var results []models.ScanResult
 | 
			
		||||
	for _, r := range history.ScanResults {
 | 
			
		||||
		if p.refreshCve || needToRefreshCve(r) {
 | 
			
		||||
			if c.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
				if _, err := os.Stat(c.Conf.CveDBPath); os.IsNotExist(err) {
 | 
			
		||||
					log.Errorf("SQLite3 DB(CVE-Dictionary) is not exist: %s",
 | 
			
		||||
						c.Conf.CveDBPath)
 | 
			
		||||
					return subcommands.ExitFailure
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			filled, err := fillCveInfoFromCveDB(r)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
				log.Errorf("Failed to fill CVE information: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			fields := strings.Fields(string(bytes))
 | 
			
		||||
			if 0 < len(fields) {
 | 
			
		||||
				historyID = fields[0]
 | 
			
		||||
 | 
			
		||||
			if err := overwriteJSONFile(jsonDir, *filled); err != nil {
 | 
			
		||||
				log.Errorf("Failed to write JSON: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			results = append(results, *filled)
 | 
			
		||||
		} else {
 | 
			
		||||
			results = append(results, r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return report.RunTui(historyID)
 | 
			
		||||
	history.ScanResults = results
 | 
			
		||||
	return report.RunTui(history)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										333
									
								
								commands/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								commands/util.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,333 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// jsonDirPattern is file name pattern of JSON directory
 | 
			
		||||
// 2016-11-16T10:43:28+09:00
 | 
			
		||||
// 2016-11-16T10:43:28Z
 | 
			
		||||
var jsonDirPattern = regexp.MustCompile(
 | 
			
		||||
	`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$`)
 | 
			
		||||
 | 
			
		||||
// JSONDirs is array of json files path.
 | 
			
		||||
type jsonDirs []string
 | 
			
		||||
 | 
			
		||||
// sort as recent directories are at the head
 | 
			
		||||
func (d jsonDirs) Len() int {
 | 
			
		||||
	return len(d)
 | 
			
		||||
}
 | 
			
		||||
func (d jsonDirs) Swap(i, j int) {
 | 
			
		||||
	d[i], d[j] = d[j], d[i]
 | 
			
		||||
}
 | 
			
		||||
func (d jsonDirs) Less(i, j int) bool {
 | 
			
		||||
	return d[j] < d[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getValidJSONDirs return valid json directory as array
 | 
			
		||||
// Returned array is sorted so that recent directories are at the head
 | 
			
		||||
func lsValidJSONDirs() (dirs jsonDirs, err error) {
 | 
			
		||||
	var dirInfo []os.FileInfo
 | 
			
		||||
	if dirInfo, err = ioutil.ReadDir(c.Conf.ResultsDir); err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to read %s: %s", c.Conf.ResultsDir, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, d := range dirInfo {
 | 
			
		||||
		if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
 | 
			
		||||
			jsonDir := filepath.Join(c.Conf.ResultsDir, d.Name())
 | 
			
		||||
			dirs = append(dirs, jsonDir)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(dirs)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// jsonDir returns
 | 
			
		||||
// If there is an arg, check if it is a valid format and return the corresponding path under results.
 | 
			
		||||
// If arg passed via PIPE (such as history subcommand), return that path.
 | 
			
		||||
// Otherwise, returns the path of the latest directory
 | 
			
		||||
func jsonDir(args []string) (string, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	var dirs jsonDirs
 | 
			
		||||
 | 
			
		||||
	if 0 < len(args) {
 | 
			
		||||
		if dirs, err = lsValidJSONDirs(); err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		path := filepath.Join(c.Conf.ResultsDir, args[0])
 | 
			
		||||
		for _, d := range dirs {
 | 
			
		||||
			ss := strings.Split(d, string(os.PathSeparator))
 | 
			
		||||
			timedir := ss[len(ss)-1]
 | 
			
		||||
			if timedir == args[0] {
 | 
			
		||||
				return path, nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return "", fmt.Errorf("Invalid path: %s", path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// PIPE
 | 
			
		||||
	if c.Conf.Pipe {
 | 
			
		||||
		bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", fmt.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		fields := strings.Fields(string(bytes))
 | 
			
		||||
		if 0 < len(fields) {
 | 
			
		||||
			return filepath.Join(c.Conf.ResultsDir, fields[0]), nil
 | 
			
		||||
		}
 | 
			
		||||
		return "", fmt.Errorf("Stdin is invalid: %s", string(bytes))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// returns latest dir when no args or no PIPE
 | 
			
		||||
	if dirs, err = lsValidJSONDirs(); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if len(dirs) == 0 {
 | 
			
		||||
		return "", fmt.Errorf("No results under %s",
 | 
			
		||||
			c.Conf.ResultsDir)
 | 
			
		||||
	}
 | 
			
		||||
	return dirs[0], nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// loadOneServerScanResult read JSON data of one server
 | 
			
		||||
func loadOneServerScanResult(jsonFile string) (result models.ScanResult, err error) {
 | 
			
		||||
	var data []byte
 | 
			
		||||
	if data, err = ioutil.ReadFile(jsonFile); err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to read %s: %s", jsonFile, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if json.Unmarshal(data, &result) != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to parse %s: %s", jsonFile, err)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// loadOneScanHistory read JSON data
 | 
			
		||||
func loadOneScanHistory(jsonDir string) (scanHistory models.ScanHistory, err error) {
 | 
			
		||||
	var results []models.ScanResult
 | 
			
		||||
	var files []os.FileInfo
 | 
			
		||||
	if files, err = ioutil.ReadDir(jsonDir); err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to read %s: %s", jsonDir, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, f := range files {
 | 
			
		||||
		if filepath.Ext(f.Name()) != ".json" || strings.HasSuffix(f.Name(), "_diff.json") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var r models.ScanResult
 | 
			
		||||
		path := filepath.Join(jsonDir, f.Name())
 | 
			
		||||
		if r, err = loadOneServerScanResult(path); err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		results = append(results, r)
 | 
			
		||||
	}
 | 
			
		||||
	if len(results) == 0 {
 | 
			
		||||
		err = fmt.Errorf("There is no json file under %s", jsonDir)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scanHistory = models.ScanHistory{
 | 
			
		||||
		ScanResults: results,
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fillCveInfoFromCveDB(r models.ScanResult) (*models.ScanResult, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	var vs []models.VulnInfo
 | 
			
		||||
 | 
			
		||||
	sInfo := c.Conf.Servers[r.ServerName]
 | 
			
		||||
	vs, err = scanVulnByCpeNames(sInfo.CpeNames, r.ScannedCves)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	r.ScannedCves = vs
 | 
			
		||||
	return r.FillCveDetail()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadPreviousScanHistory(current models.ScanHistory) (previous models.ScanHistory, err error) {
 | 
			
		||||
	var dirs jsonDirs
 | 
			
		||||
	if dirs, err = lsValidJSONDirs(); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, result := range current.ScanResults {
 | 
			
		||||
		for _, dir := range dirs[1:] {
 | 
			
		||||
			var r models.ScanResult
 | 
			
		||||
			path := filepath.Join(dir, result.ServerName+".json")
 | 
			
		||||
			if r, err = loadOneServerScanResult(path); err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if r.Family == result.Family && r.Release == result.Release {
 | 
			
		||||
				previous.ScanResults = append(previous.ScanResults, r)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return previous, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func diff(currentHistory, previousHistory models.ScanHistory) (diffHistory models.ScanHistory, err error) {
 | 
			
		||||
	for _, currentResult := range currentHistory.ScanResults {
 | 
			
		||||
		found := false
 | 
			
		||||
		var previousResult models.ScanResult
 | 
			
		||||
		for _, previousResult = range previousHistory.ScanResults {
 | 
			
		||||
			if currentResult.ServerName == previousResult.ServerName {
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if found {
 | 
			
		||||
			currentResult.ScannedCves = getNewCves(previousResult, currentResult)
 | 
			
		||||
 | 
			
		||||
			currentResult.KnownCves = []models.CveInfo{}
 | 
			
		||||
			currentResult.UnknownCves = []models.CveInfo{}
 | 
			
		||||
 | 
			
		||||
			currentResult.Packages = models.PackageInfoList{}
 | 
			
		||||
			for _, s := range currentResult.ScannedCves {
 | 
			
		||||
				currentResult.Packages = append(currentResult.Packages, s.Packages...)
 | 
			
		||||
			}
 | 
			
		||||
			currentResult.Packages = currentResult.Packages.UniqByName()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		diffHistory.ScanResults = append(diffHistory.ScanResults, currentResult)
 | 
			
		||||
	}
 | 
			
		||||
	return diffHistory, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getNewCves(previousResult, currentResult models.ScanResult) (newVulninfos []models.VulnInfo) {
 | 
			
		||||
	previousCveIDsSet := map[string]bool{}
 | 
			
		||||
	for _, previousVulnInfo := range previousResult.ScannedCves {
 | 
			
		||||
		previousCveIDsSet[previousVulnInfo.CveID] = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, v := range currentResult.ScannedCves {
 | 
			
		||||
		if previousCveIDsSet[v.CveID] {
 | 
			
		||||
			if isCveInfoUpdated(currentResult, previousResult, v.CveID) {
 | 
			
		||||
				newVulninfos = append(newVulninfos, v)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			newVulninfos = append(newVulninfos, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isCveInfoUpdated(currentResult, previousResult models.ScanResult, CveID string) bool {
 | 
			
		||||
	type lastModified struct {
 | 
			
		||||
		Nvd time.Time
 | 
			
		||||
		Jvn time.Time
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	previousModifies := lastModified{}
 | 
			
		||||
	for _, c := range previousResult.KnownCves {
 | 
			
		||||
		if CveID == c.CveID {
 | 
			
		||||
			previousModifies.Nvd = c.CveDetail.Nvd.LastModifiedDate
 | 
			
		||||
			previousModifies.Jvn = c.CveDetail.Jvn.LastModifiedDate
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	currentModifies := lastModified{}
 | 
			
		||||
	for _, c := range currentResult.KnownCves {
 | 
			
		||||
		if CveID == c.CveDetail.CveID {
 | 
			
		||||
			currentModifies.Nvd = c.CveDetail.Nvd.LastModifiedDate
 | 
			
		||||
			currentModifies.Jvn = c.CveDetail.Jvn.LastModifiedDate
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return !currentModifies.Nvd.Equal(previousModifies.Nvd) ||
 | 
			
		||||
		!currentModifies.Jvn.Equal(previousModifies.Jvn)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func overwriteJSONFile(dir string, r models.ScanResult) error {
 | 
			
		||||
	before := c.Conf.FormatJSON
 | 
			
		||||
	beforeDiff := c.Conf.Diff
 | 
			
		||||
	c.Conf.FormatJSON = true
 | 
			
		||||
	c.Conf.Diff = false
 | 
			
		||||
	w := report.LocalFileWriter{CurrentDir: dir}
 | 
			
		||||
	if err := w.Write(r); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to write summary report: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	c.Conf.FormatJSON = before
 | 
			
		||||
	c.Conf.Diff = beforeDiff
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanVulnByCpeNames(cpeNames []string, scannedVulns []models.VulnInfo) ([]models.VulnInfo, error) {
 | 
			
		||||
	// To remove duplicate
 | 
			
		||||
	set := map[string]models.VulnInfo{}
 | 
			
		||||
	for _, v := range scannedVulns {
 | 
			
		||||
		set[v.CveID] = v
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, name := range cpeNames {
 | 
			
		||||
		details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		for _, detail := range details {
 | 
			
		||||
			if val, ok := set[detail.CveID]; ok {
 | 
			
		||||
				names := val.CpeNames
 | 
			
		||||
				names = util.AppendIfMissing(names, name)
 | 
			
		||||
				val.CpeNames = names
 | 
			
		||||
				val.Confidence = models.CpeNameMatch
 | 
			
		||||
				set[detail.CveID] = val
 | 
			
		||||
			} else {
 | 
			
		||||
				v := models.VulnInfo{
 | 
			
		||||
					CveID:      detail.CveID,
 | 
			
		||||
					CpeNames:   []string{name},
 | 
			
		||||
					Confidence: models.CpeNameMatch,
 | 
			
		||||
				}
 | 
			
		||||
				v.NilSliceToEmpty()
 | 
			
		||||
				set[detail.CveID] = v
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vinfos := []models.VulnInfo{}
 | 
			
		||||
	for key := range set {
 | 
			
		||||
		vinfos = append(vinfos, set[key])
 | 
			
		||||
	}
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func needToRefreshCve(r models.ScanResult) bool {
 | 
			
		||||
	return r.Lang != c.Conf.Lang || len(r.KnownCves) == 0 &&
 | 
			
		||||
		len(r.UnknownCves) == 0 &&
 | 
			
		||||
		len(r.IgnoredCves) == 0
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										294
									
								
								commands/util_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										294
									
								
								commands/util_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,294 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"reflect"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDiff(t *testing.T) {
 | 
			
		||||
	atCurrent, _ := time.Parse("2006-01-02", "2014-12-31")
 | 
			
		||||
	atPrevious, _ := time.Parse("2006-01-02", "2014-11-31")
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		inCurrent  models.ScanHistory
 | 
			
		||||
		inPrevious models.ScanHistory
 | 
			
		||||
		out        models.ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			models.ScanHistory{
 | 
			
		||||
				ScanResults: models.ScanResults{
 | 
			
		||||
					{
 | 
			
		||||
						ScannedAt:  atCurrent,
 | 
			
		||||
						ServerName: "u16",
 | 
			
		||||
						Family:     "ubuntu",
 | 
			
		||||
						Release:    "16.04",
 | 
			
		||||
						ScannedCves: []models.VulnInfo{
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2012-6702",
 | 
			
		||||
								Packages: models.PackageInfoList{
 | 
			
		||||
									{
 | 
			
		||||
										Name:       "libexpat1",
 | 
			
		||||
										Version:    "2.1.0-7",
 | 
			
		||||
										Release:    "",
 | 
			
		||||
										NewVersion: "2.1.0-7ubuntu0.16.04.2",
 | 
			
		||||
										NewRelease: "",
 | 
			
		||||
										Repository: "",
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
								CpeNames:         []string{},
 | 
			
		||||
							},
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2014-9761",
 | 
			
		||||
								Packages: models.PackageInfoList{
 | 
			
		||||
									{
 | 
			
		||||
										Name:       "libc-bin",
 | 
			
		||||
										Version:    "2.21-0ubuntu5",
 | 
			
		||||
										Release:    "",
 | 
			
		||||
										NewVersion: "2.23-0ubuntu5",
 | 
			
		||||
										NewRelease: "",
 | 
			
		||||
										Repository: "",
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
								CpeNames:         []string{},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						KnownCves:   []models.CveInfo{},
 | 
			
		||||
						UnknownCves: []models.CveInfo{},
 | 
			
		||||
						IgnoredCves: []models.CveInfo{},
 | 
			
		||||
 | 
			
		||||
						Packages: models.PackageInfoList{},
 | 
			
		||||
 | 
			
		||||
						Errors:   []string{},
 | 
			
		||||
						Optional: [][]interface{}{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			models.ScanHistory{
 | 
			
		||||
				ScanResults: models.ScanResults{
 | 
			
		||||
					{
 | 
			
		||||
						ScannedAt:  atPrevious,
 | 
			
		||||
						ServerName: "u16",
 | 
			
		||||
						Family:     "ubuntu",
 | 
			
		||||
						Release:    "16.04",
 | 
			
		||||
						ScannedCves: []models.VulnInfo{
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2012-6702",
 | 
			
		||||
								Packages: models.PackageInfoList{
 | 
			
		||||
									{
 | 
			
		||||
										Name:       "libexpat1",
 | 
			
		||||
										Version:    "2.1.0-7",
 | 
			
		||||
										Release:    "",
 | 
			
		||||
										NewVersion: "2.1.0-7ubuntu0.16.04.2",
 | 
			
		||||
										NewRelease: "",
 | 
			
		||||
										Repository: "",
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
								CpeNames:         []string{},
 | 
			
		||||
							},
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2014-9761",
 | 
			
		||||
								Packages: models.PackageInfoList{
 | 
			
		||||
									{
 | 
			
		||||
										Name:       "libc-bin",
 | 
			
		||||
										Version:    "2.21-0ubuntu5",
 | 
			
		||||
										Release:    "",
 | 
			
		||||
										NewVersion: "2.23-0ubuntu5",
 | 
			
		||||
										NewRelease: "",
 | 
			
		||||
										Repository: "",
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
								CpeNames:         []string{},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						KnownCves:   []models.CveInfo{},
 | 
			
		||||
						UnknownCves: []models.CveInfo{},
 | 
			
		||||
						IgnoredCves: []models.CveInfo{},
 | 
			
		||||
 | 
			
		||||
						Packages: models.PackageInfoList{},
 | 
			
		||||
 | 
			
		||||
						Errors:   []string{},
 | 
			
		||||
						Optional: [][]interface{}{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			models.ScanResult{
 | 
			
		||||
				ScannedAt:   atCurrent,
 | 
			
		||||
				ServerName:  "u16",
 | 
			
		||||
				Family:      "ubuntu",
 | 
			
		||||
				Release:     "16.04",
 | 
			
		||||
				KnownCves:   []models.CveInfo{},
 | 
			
		||||
				UnknownCves: []models.CveInfo{},
 | 
			
		||||
				IgnoredCves: []models.CveInfo{},
 | 
			
		||||
 | 
			
		||||
				// Packages: models.PackageInfoList{},
 | 
			
		||||
 | 
			
		||||
				Errors:   []string{},
 | 
			
		||||
				Optional: [][]interface{}{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.ScanHistory{
 | 
			
		||||
				ScanResults: models.ScanResults{
 | 
			
		||||
					{
 | 
			
		||||
						ScannedAt:  atCurrent,
 | 
			
		||||
						ServerName: "u16",
 | 
			
		||||
						Family:     "ubuntu",
 | 
			
		||||
						Release:    "16.04",
 | 
			
		||||
						ScannedCves: []models.VulnInfo{
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2016-6662",
 | 
			
		||||
								Packages: models.PackageInfoList{
 | 
			
		||||
									{
 | 
			
		||||
										Name:       "mysql-libs",
 | 
			
		||||
										Version:    "5.1.73",
 | 
			
		||||
										Release:    "7.el6",
 | 
			
		||||
										NewVersion: "5.1.73",
 | 
			
		||||
										NewRelease: "8.el6_8",
 | 
			
		||||
										Repository: "",
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
								CpeNames:         []string{},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						KnownCves: []models.CveInfo{
 | 
			
		||||
							{
 | 
			
		||||
								CveDetail: cve.CveDetail{
 | 
			
		||||
									CveID: "CVE-2016-6662",
 | 
			
		||||
									Nvd: cve.Nvd{
 | 
			
		||||
										LastModifiedDate: time.Date(2016, 1, 1, 0, 0, 0, 0, time.Local),
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								VulnInfo: models.VulnInfo{
 | 
			
		||||
									CveID: "CVE-2016-6662",
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						UnknownCves: []models.CveInfo{},
 | 
			
		||||
						IgnoredCves: []models.CveInfo{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			models.ScanHistory{
 | 
			
		||||
				ScanResults: models.ScanResults{
 | 
			
		||||
					{
 | 
			
		||||
						ScannedAt:  atPrevious,
 | 
			
		||||
						ServerName: "u16",
 | 
			
		||||
						Family:     "ubuntu",
 | 
			
		||||
						Release:    "16.04",
 | 
			
		||||
						ScannedCves: []models.VulnInfo{
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2016-6662",
 | 
			
		||||
								Packages: models.PackageInfoList{
 | 
			
		||||
									{
 | 
			
		||||
										Name:       "mysql-libs",
 | 
			
		||||
										Version:    "5.1.73",
 | 
			
		||||
										Release:    "7.el6",
 | 
			
		||||
										NewVersion: "5.1.73",
 | 
			
		||||
										NewRelease: "8.el6_8",
 | 
			
		||||
										Repository: "",
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
								CpeNames:         []string{},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						KnownCves: []models.CveInfo{
 | 
			
		||||
							{
 | 
			
		||||
								CveDetail: cve.CveDetail{
 | 
			
		||||
									CveID: "CVE-2016-6662",
 | 
			
		||||
									Nvd: cve.Nvd{
 | 
			
		||||
										LastModifiedDate: time.Date(2017, 3, 15, 13, 40, 57, 0, time.Local),
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								VulnInfo: models.VulnInfo{
 | 
			
		||||
									CveID: "CVE-2016-6662",
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						UnknownCves: []models.CveInfo{},
 | 
			
		||||
						IgnoredCves: []models.CveInfo{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			models.ScanResult{
 | 
			
		||||
				ScannedAt:  atCurrent,
 | 
			
		||||
				ServerName: "u16",
 | 
			
		||||
				Family:     "ubuntu",
 | 
			
		||||
				Release:    "16.04",
 | 
			
		||||
				ScannedCves: []models.VulnInfo{
 | 
			
		||||
					{
 | 
			
		||||
						CveID: "CVE-2016-6662",
 | 
			
		||||
						Packages: models.PackageInfoList{
 | 
			
		||||
							{
 | 
			
		||||
								Name:       "mysql-libs",
 | 
			
		||||
								Version:    "5.1.73",
 | 
			
		||||
								Release:    "7.el6",
 | 
			
		||||
								NewVersion: "5.1.73",
 | 
			
		||||
								NewRelease: "8.el6_8",
 | 
			
		||||
								Repository: "",
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
						CpeNames:         []string{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				KnownCves:   []models.CveInfo{},
 | 
			
		||||
				UnknownCves: []models.CveInfo{},
 | 
			
		||||
				IgnoredCves: []models.CveInfo{},
 | 
			
		||||
				Packages: models.PackageInfoList{
 | 
			
		||||
					models.PackageInfo{
 | 
			
		||||
						Name:       "mysql-libs",
 | 
			
		||||
						Version:    "5.1.73",
 | 
			
		||||
						Release:    "7.el6",
 | 
			
		||||
						NewVersion: "5.1.73",
 | 
			
		||||
						NewRelease: "8.el6_8",
 | 
			
		||||
						Repository: "",
 | 
			
		||||
						Changelog: models.Changelog{
 | 
			
		||||
							Contents: "",
 | 
			
		||||
							Method:   "",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var s models.ScanHistory
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		s, _ = diff(tt.inCurrent, tt.inPrevious)
 | 
			
		||||
		for _, actual := range s.ScanResults {
 | 
			
		||||
			if !reflect.DeepEqual(actual, tt.out) {
 | 
			
		||||
				h := pp.Sprint(actual)
 | 
			
		||||
				x := pp.Sprint(tt.out)
 | 
			
		||||
				t.Errorf("diff result : \n %s \n output result : \n %s", h, x)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										217
									
								
								config/config.go
									
									
									
									
									
								
							
							
						
						
									
										217
									
								
								config/config.go
									
									
									
									
									
								
							@@ -19,6 +19,8 @@ package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
@@ -34,21 +36,35 @@ type Config struct {
 | 
			
		||||
	DebugSQL bool
 | 
			
		||||
	Lang     string
 | 
			
		||||
 | 
			
		||||
	Mail    smtpConf
 | 
			
		||||
	EMail   SMTPConf
 | 
			
		||||
	Slack   SlackConf
 | 
			
		||||
	Default ServerInfo
 | 
			
		||||
	Servers map[string]ServerInfo
 | 
			
		||||
 | 
			
		||||
	CveDictionaryURL string `valid:"url"`
 | 
			
		||||
 | 
			
		||||
	CvssScoreOver      float64
 | 
			
		||||
	IgnoreUnscoredCves bool
 | 
			
		||||
 | 
			
		||||
	SSHExternal bool
 | 
			
		||||
	SSHExternal    bool
 | 
			
		||||
	ContainersOnly bool
 | 
			
		||||
	SkipBroken     bool
 | 
			
		||||
 | 
			
		||||
	HTTPProxy string `valid:"url"`
 | 
			
		||||
	DBPath    string
 | 
			
		||||
	CveDBPath string
 | 
			
		||||
	HTTPProxy  string `valid:"url"`
 | 
			
		||||
	LogDir     string
 | 
			
		||||
	ResultsDir string
 | 
			
		||||
 | 
			
		||||
	CveDBType   string
 | 
			
		||||
	CveDBPath   string
 | 
			
		||||
	CveDBURL    string
 | 
			
		||||
	CacheDBPath string
 | 
			
		||||
 | 
			
		||||
	FormatXML         bool
 | 
			
		||||
	FormatJSON        bool
 | 
			
		||||
	FormatOneEMail    bool
 | 
			
		||||
	FormatOneLineText bool
 | 
			
		||||
	FormatShortText   bool
 | 
			
		||||
	FormatFullText    bool
 | 
			
		||||
 | 
			
		||||
	GZIP bool
 | 
			
		||||
 | 
			
		||||
	AwsProfile string
 | 
			
		||||
	AwsRegion  string
 | 
			
		||||
@@ -58,27 +74,61 @@ type Config struct {
 | 
			
		||||
	AzureKey       string
 | 
			
		||||
	AzureContainer string
 | 
			
		||||
 | 
			
		||||
	//  CpeNames      []string
 | 
			
		||||
	//  SummaryMode          bool
 | 
			
		||||
	UseYumPluginSecurity  bool
 | 
			
		||||
	UseUnattendedUpgrades bool
 | 
			
		||||
	Pipe bool
 | 
			
		||||
	Diff bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate configuration
 | 
			
		||||
func (c Config) Validate() bool {
 | 
			
		||||
// ValidateOnConfigtest validates
 | 
			
		||||
func (c Config) ValidateOnConfigtest() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.DBPath) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.DBPath); !ok {
 | 
			
		||||
	if runtime.GOOS == "windows" && c.SSHExternal {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("-ssh-external cannot be used on windows"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := valid.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnPrepare validates configuration
 | 
			
		||||
func (c Config) ValidateOnPrepare() bool {
 | 
			
		||||
	return c.ValidateOnConfigtest()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnScan validates configuration
 | 
			
		||||
func (c Config) ValidateOnScan() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB path must be a *Absolute* file path. dbpath: %s", c.DBPath))
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.CveDBPath) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
 | 
			
		||||
	if runtime.GOOS == "windows" && c.SSHExternal {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("-ssh-external cannot be used on windows"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB(Cve Dictionary) path must be a *Absolute* file path. dbpath: %s", c.CveDBPath))
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.CacheDBPath) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.CacheDBPath); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"Cache DB path must be a *Absolute* file path. -cache-dbpath: %s", c.CacheDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -87,7 +137,47 @@ func (c Config) Validate() bool {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mailerrs := c.Mail.Validate(); 0 < len(mailerrs) {
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnReport validates configuration
 | 
			
		||||
func (c Config) ValidateOnReport() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch c.CveDBType {
 | 
			
		||||
	case "sqlite3":
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB(CVE-Dictionary) path must be a *Absolute* file path. -cvedb-path: %s",
 | 
			
		||||
				c.CveDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	case "mysql":
 | 
			
		||||
		if c.CveDBURL == "" {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				`MySQL connection string is needed. -cvedb-url="user:pass@tcp(localhost:3306)/dbname"`))
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		errs = append(errs, fmt.Errorf(
 | 
			
		||||
			"CVE DB type must be either 'sqlite3' or 'mysql'.  -cvedb-type: %s", c.CveDBType))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := valid.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mailerrs := c.EMail.Validate(); 0 < len(mailerrs) {
 | 
			
		||||
		errs = append(errs, mailerrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -102,8 +192,38 @@ func (c Config) Validate() bool {
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// smtpConf is smtp config
 | 
			
		||||
type smtpConf struct {
 | 
			
		||||
// ValidateOnTui validates configuration
 | 
			
		||||
func (c Config) ValidateOnTui() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.CveDBType != "sqlite3" && c.CveDBType != "mysql" {
 | 
			
		||||
		errs = append(errs, fmt.Errorf(
 | 
			
		||||
			"CVE DB type must be either 'sqlite3' or 'mysql'.  -cve-dictionary-dbtype: %s", c.CveDBType))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.CveDBType == "sqlite3" {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB(CVE-Dictionary) path must be a *Absolute* file path. -cve-dictionary-dbpath: %s", c.CveDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SMTPConf is smtp config
 | 
			
		||||
type SMTPConf struct {
 | 
			
		||||
	SMTPAddr string
 | 
			
		||||
	SMTPPort string `valid:"port"`
 | 
			
		||||
 | 
			
		||||
@@ -130,7 +250,7 @@ func checkEmails(emails []string) (errs []error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate SMTP configuration
 | 
			
		||||
func (c *smtpConf) Validate() (errs []error) {
 | 
			
		||||
func (c *SMTPConf) Validate() (errs []error) {
 | 
			
		||||
 | 
			
		||||
	if !c.UseThisTime {
 | 
			
		||||
		return
 | 
			
		||||
@@ -181,7 +301,6 @@ type SlackConf struct {
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *SlackConf) Validate() (errs []error) {
 | 
			
		||||
 | 
			
		||||
	if !c.UseThisTime {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -221,18 +340,53 @@ type ServerInfo struct {
 | 
			
		||||
	KeyPath     string
 | 
			
		||||
	KeyPassword string
 | 
			
		||||
 | 
			
		||||
	CpeNames []string
 | 
			
		||||
	CpeNames               []string
 | 
			
		||||
	DependencyCheckXMLPath string
 | 
			
		||||
 | 
			
		||||
	// Container Names or IDs
 | 
			
		||||
	Containers []string
 | 
			
		||||
	Containers Containers
 | 
			
		||||
 | 
			
		||||
	IgnoreCves []string
 | 
			
		||||
 | 
			
		||||
	// Optional key-value set that will be outputted to JSON
 | 
			
		||||
	Optional [][]interface{}
 | 
			
		||||
 | 
			
		||||
	// For CentOS, RHEL, Amazon
 | 
			
		||||
	Enablerepo string
 | 
			
		||||
 | 
			
		||||
	// used internal
 | 
			
		||||
	LogMsgAnsiColor string // DebugLog Color
 | 
			
		||||
	Container       Container
 | 
			
		||||
	Family          string
 | 
			
		||||
	Distro          Distro
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetServerName returns ServerName if this serverInfo is about host.
 | 
			
		||||
// If this serverInfo is abount a container, returns containerID@ServerName
 | 
			
		||||
func (s ServerInfo) GetServerName() string {
 | 
			
		||||
	if len(s.Container.ContainerID) == 0 {
 | 
			
		||||
		return s.ServerName
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s@%s", s.Container.ContainerID, s.ServerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Distro has distribution info
 | 
			
		||||
type Distro struct {
 | 
			
		||||
	Family  string
 | 
			
		||||
	Release string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l Distro) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s %s", l.Family, l.Release)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MajorVersion returns Major version
 | 
			
		||||
func (l Distro) MajorVersion() (ver int, err error) {
 | 
			
		||||
	if 0 < len(l.Release) {
 | 
			
		||||
		ver, err = strconv.Atoi(strings.Split(l.Release, ".")[0])
 | 
			
		||||
	} else {
 | 
			
		||||
		err = fmt.Errorf("Release is empty")
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsContainer returns whether this ServerInfo is about container
 | 
			
		||||
@@ -245,9 +399,16 @@ func (s *ServerInfo) SetContainer(d Container) {
 | 
			
		||||
	s.Container = d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Containers has Containers information.
 | 
			
		||||
type Containers struct {
 | 
			
		||||
	Type     string
 | 
			
		||||
	Includes []string
 | 
			
		||||
	Excludes []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Container has Container information.
 | 
			
		||||
type Container struct {
 | 
			
		||||
	ContainerID string
 | 
			
		||||
	Name        string
 | 
			
		||||
	Type        string
 | 
			
		||||
	Image       string
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,10 +20,11 @@ package config
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/BurntSushi/toml"
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
	"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TOMLLoader loads config
 | 
			
		||||
@@ -31,14 +32,18 @@ type TOMLLoader struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load load the configuraiton TOML file specified by path arg.
 | 
			
		||||
func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
 | 
			
		||||
func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 | 
			
		||||
	if Conf.Debug {
 | 
			
		||||
		log.SetLevel(log.DebugLevel)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var conf Config
 | 
			
		||||
	if _, err := toml.DecodeFile(pathToToml, &conf); err != nil {
 | 
			
		||||
		log.Error("Load config failed", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Conf.Mail = conf.Mail
 | 
			
		||||
	Conf.EMail = conf.EMail
 | 
			
		||||
	Conf.Slack = conf.Slack
 | 
			
		||||
 | 
			
		||||
	d := conf.Default
 | 
			
		||||
@@ -51,24 +56,14 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
 | 
			
		||||
 | 
			
		||||
	i := 0
 | 
			
		||||
	for name, v := range conf.Servers {
 | 
			
		||||
 | 
			
		||||
		if 0 < len(v.KeyPassword) {
 | 
			
		||||
			log.Warn("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE.")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s := ServerInfo{ServerName: name}
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case v.User != "":
 | 
			
		||||
			s.User = v.User
 | 
			
		||||
		case d.User != "":
 | 
			
		||||
			s.User = d.User
 | 
			
		||||
		default:
 | 
			
		||||
			return fmt.Errorf("%s is invalid. User is empty", name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Host = v.Host
 | 
			
		||||
		if s.Host == "" {
 | 
			
		||||
		if len(s.Host) == 0 {
 | 
			
		||||
			return fmt.Errorf("%s is invalid. host is empty", name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -81,8 +76,19 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
 | 
			
		||||
			s.Port = "22"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case v.User != "":
 | 
			
		||||
			s.User = v.User
 | 
			
		||||
		case d.User != "":
 | 
			
		||||
			s.User = d.User
 | 
			
		||||
		default:
 | 
			
		||||
			if s.Port != "local" {
 | 
			
		||||
				return fmt.Errorf("%s is invalid. User is empty", name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.KeyPath = v.KeyPath
 | 
			
		||||
		if s.KeyPath == "" {
 | 
			
		||||
		if len(s.KeyPath) == 0 {
 | 
			
		||||
			s.KeyPath = d.KeyPath
 | 
			
		||||
		}
 | 
			
		||||
		if s.KeyPath != "" {
 | 
			
		||||
@@ -94,7 +100,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
 | 
			
		||||
 | 
			
		||||
		//  s.KeyPassword = keyPass
 | 
			
		||||
		s.KeyPassword = v.KeyPassword
 | 
			
		||||
		if s.KeyPassword == "" {
 | 
			
		||||
		if len(s.KeyPassword) == 0 {
 | 
			
		||||
			s.KeyPassword = d.KeyPassword
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -103,11 +109,42 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
 | 
			
		||||
			s.CpeNames = d.CpeNames
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.DependencyCheckXMLPath = v.DependencyCheckXMLPath
 | 
			
		||||
		if len(s.DependencyCheckXMLPath) == 0 {
 | 
			
		||||
			s.DependencyCheckXMLPath = d.DependencyCheckXMLPath
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Load CPEs from OWASP Dependency Check XML
 | 
			
		||||
		if len(s.DependencyCheckXMLPath) != 0 {
 | 
			
		||||
			cpes, err := parser.Parse(s.DependencyCheckXMLPath)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"Failed to read OWASP Dependency Check XML: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			log.Debugf("Loaded from OWASP Dependency Check XML: %s",
 | 
			
		||||
				s.ServerName)
 | 
			
		||||
			s.CpeNames = append(s.CpeNames, cpes...)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Containers = v.Containers
 | 
			
		||||
		if len(s.Containers) == 0 {
 | 
			
		||||
		if len(s.Containers.Includes) == 0 {
 | 
			
		||||
			s.Containers = d.Containers
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.IgnoreCves = v.IgnoreCves
 | 
			
		||||
		for _, cve := range d.IgnoreCves {
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, c := range s.IgnoreCves {
 | 
			
		||||
				if cve == c {
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !found {
 | 
			
		||||
				s.IgnoreCves = append(s.IgnoreCves, cve)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Optional = v.Optional
 | 
			
		||||
		for _, dkv := range d.Optional {
 | 
			
		||||
			found := false
 | 
			
		||||
@@ -122,13 +159,28 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Enablerepo = v.Enablerepo
 | 
			
		||||
		if len(s.Enablerepo) == 0 {
 | 
			
		||||
			s.Enablerepo = d.Enablerepo
 | 
			
		||||
		}
 | 
			
		||||
		if len(s.Enablerepo) != 0 {
 | 
			
		||||
			for _, repo := range strings.Split(s.Enablerepo, ",") {
 | 
			
		||||
				switch repo {
 | 
			
		||||
				case "base", "updates":
 | 
			
		||||
					// nop
 | 
			
		||||
				default:
 | 
			
		||||
					return fmt.Errorf(
 | 
			
		||||
						"For now, enablerepo have to be base or updates: %s, servername: %s",
 | 
			
		||||
						s.Enablerepo, name)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.LogMsgAnsiColor = Colors[i%len(Colors)]
 | 
			
		||||
		i++
 | 
			
		||||
 | 
			
		||||
		servers[name] = s
 | 
			
		||||
	}
 | 
			
		||||
	log.Debug("Config loaded")
 | 
			
		||||
	log.Debugf("%s", pp.Sprintf("%v", servers))
 | 
			
		||||
	Conf.Servers = servers
 | 
			
		||||
	return
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										64
									
								
								contrib/owasp-dependency-check/parser/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								contrib/owasp-dependency-check/parser/parser.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
package parser
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type analysis struct {
 | 
			
		||||
	Dependencies []dependency `xml:"dependencies>dependency"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dependency struct {
 | 
			
		||||
	Identifiers []identifier `xml:"identifiers>identifier"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type identifier struct {
 | 
			
		||||
	Name string `xml:"name"`
 | 
			
		||||
	Type string `xml:"type,attr"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func appendIfMissing(slice []string, str string) []string {
 | 
			
		||||
	for _, s := range slice {
 | 
			
		||||
		if s == str {
 | 
			
		||||
			return slice
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return append(slice, str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parse parses XML and collect list of cpe
 | 
			
		||||
func Parse(path string) ([]string, error) {
 | 
			
		||||
	file, err := os.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []string{}, fmt.Errorf("Failed to open: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	b, err := ioutil.ReadAll(file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []string{}, fmt.Errorf("Failed to read: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var anal analysis
 | 
			
		||||
	if err := xml.Unmarshal(b, &anal); err != nil {
 | 
			
		||||
		fmt.Errorf("Failed to unmarshal: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cpes := []string{}
 | 
			
		||||
	for _, d := range anal.Dependencies {
 | 
			
		||||
		for _, ident := range d.Identifiers {
 | 
			
		||||
			if ident.Type == "cpe" {
 | 
			
		||||
				name := strings.TrimPrefix(ident.Name, "(")
 | 
			
		||||
				name = strings.TrimSuffix(name, ")")
 | 
			
		||||
				cpes = appendIfMissing(cpes, name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sort.Strings(cpes)
 | 
			
		||||
	return cpes, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -27,7 +27,6 @@ import (
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	cveconfig "github.com/kotakanbe/go-cve-dictionary/config"
 | 
			
		||||
@@ -44,12 +43,12 @@ type cvedictClient struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api *cvedictClient) initialize() {
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
	api.baseURL = config.Conf.CveDBURL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) CheckHealth() (ok bool, err error) {
 | 
			
		||||
	if config.Conf.CveDBPath != "" {
 | 
			
		||||
		log.Debugf("get cve-dictionary from sqlite3")
 | 
			
		||||
	if config.Conf.CveDBURL == "" || config.Conf.CveDBType == "mysql" {
 | 
			
		||||
		util.Log.Debugf("get cve-dictionary from %s", config.Conf.CveDBType)
 | 
			
		||||
		return true, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -59,7 +58,7 @@ func (api cvedictClient) CheckHealth() (ok bool, err error) {
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if len(errs) > 0 || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return false, fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v", url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return true, nil
 | 
			
		||||
@@ -71,11 +70,12 @@ type response struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDetails, err error) {
 | 
			
		||||
	if config.Conf.CveDBPath != "" {
 | 
			
		||||
	switch config.Conf.CveDBType {
 | 
			
		||||
	case "sqlite3", "mysql":
 | 
			
		||||
		return api.FetchCveDetailsFromCveDB(cveIDs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
	api.baseURL = config.Conf.CveDBURL
 | 
			
		||||
	reqChan := make(chan string, len(cveIDs))
 | 
			
		||||
	resChan := make(chan response, len(cveIDs))
 | 
			
		||||
	errChan := make(chan error, len(cveIDs))
 | 
			
		||||
@@ -99,7 +99,7 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDet
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					util.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					api.httpGet(cveID, url, resChan, errChan)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -129,14 +129,19 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDet
 | 
			
		||||
			fmt.Errorf("Failed to fetch CVE. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// order by CVE ID desc
 | 
			
		||||
	sort.Sort(cveDetails)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails cve.CveDetails, err error) {
 | 
			
		||||
	log.Debugf("open cve-dictionary db")
 | 
			
		||||
	cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	util.Log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
 | 
			
		||||
	cveconfig.Conf.DBType = config.Conf.CveDBType
 | 
			
		||||
	if config.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	} else {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBURL
 | 
			
		||||
	}
 | 
			
		||||
	cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
 | 
			
		||||
	if err := cvedb.OpenDB(); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to open DB. err: %s", err)
 | 
			
		||||
@@ -170,7 +175,7 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
		util.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -186,59 +191,25 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  func (api cvedictClient) httpGet(key, url string, query map[string]string, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
 | 
			
		||||
//      var body string
 | 
			
		||||
//      var errs []error
 | 
			
		||||
//      var resp *http.Response
 | 
			
		||||
//      f := func() (err error) {
 | 
			
		||||
//          req := gorequest.New().SetDebug(true).Proxy(api.httpProxy).Get(url)
 | 
			
		||||
//          for key := range query {
 | 
			
		||||
//              req = req.Query(fmt.Sprintf("%s=%s", key, query[key])).Set("Content-Type", "application/x-www-form-urlencoded")
 | 
			
		||||
//          }
 | 
			
		||||
//          pp.Println(req)
 | 
			
		||||
//          resp, body, errs = req.End()
 | 
			
		||||
//          if len(errs) > 0 || resp.StatusCode != 200 {
 | 
			
		||||
//              errChan <- fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
 | 
			
		||||
//          }
 | 
			
		||||
//          return nil
 | 
			
		||||
//      }
 | 
			
		||||
//      notify := func(err error, t time.Duration) {
 | 
			
		||||
//          log.Warnf("Failed to get. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
//      }
 | 
			
		||||
//      err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
//      if err != nil {
 | 
			
		||||
//          errChan <- fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
//      }
 | 
			
		||||
//      //  resChan <- body
 | 
			
		||||
//      cveDetail := cve.CveDetail{}
 | 
			
		||||
//      if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
 | 
			
		||||
//          errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
 | 
			
		||||
//      }
 | 
			
		||||
//      resChan <- response{
 | 
			
		||||
//          key,
 | 
			
		||||
//          cveDetail,
 | 
			
		||||
//      }
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
type responseGetCveDetailByCpeName struct {
 | 
			
		||||
	CpeName    string
 | 
			
		||||
	CveDetails []cve.CveDetail
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeName(cpeName string) ([]cve.CveDetail, error) {
 | 
			
		||||
	if config.Conf.CveDBPath != "" {
 | 
			
		||||
	switch config.Conf.CveDBType {
 | 
			
		||||
	case "sqlite3", "mysql":
 | 
			
		||||
		return api.FetchCveDetailsByCpeNameFromDB(cpeName)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
	api.baseURL = config.Conf.CveDBURL
 | 
			
		||||
	url, err := util.URLPathJoin(api.baseURL, "cpes")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []cve.CveDetail{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	query := map[string]string{"name": cpeName}
 | 
			
		||||
	log.Debugf("HTTP Request to %s, query: %#v", url, query)
 | 
			
		||||
	util.Log.Debugf("HTTP Request to %s, query: %#v", url, query)
 | 
			
		||||
	return api.httpPost(cpeName, url, query)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -252,13 +223,13 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
 | 
			
		||||
			req = req.Send(fmt.Sprintf("%s=%s", key, query[key])).Type("json")
 | 
			
		||||
		}
 | 
			
		||||
		resp, body, errs = req.End()
 | 
			
		||||
		if len(errs) > 0 || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return fmt.Errorf("HTTP POST error: %v, url: %s, resp: %v", errs, url, resp)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
		util.Log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -274,8 +245,15 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeNameFromDB(cpeName string) ([]cve.CveDetail, error) {
 | 
			
		||||
	log.Debugf("open cve-dictionary db")
 | 
			
		||||
	cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	util.Log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
 | 
			
		||||
	cveconfig.Conf.DBType = config.Conf.CveDBType
 | 
			
		||||
	if config.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	} else {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBURL
 | 
			
		||||
	}
 | 
			
		||||
	cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
 | 
			
		||||
 | 
			
		||||
	if err := cvedb.OpenDB(); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to open DB. err: %s", err)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										324
									
								
								db/db.go
									
									
									
									
									
								
							
							
						
						
									
										324
									
								
								db/db.go
									
									
									
									
									
								
							@@ -1,324 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	m "github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/jinzhu/gorm"
 | 
			
		||||
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var db *gorm.DB
 | 
			
		||||
 | 
			
		||||
// OpenDB opens Database
 | 
			
		||||
func OpenDB() (err error) {
 | 
			
		||||
	db, err = gorm.Open("sqlite3", config.Conf.DBPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
 | 
			
		||||
		return
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	db.LogMode(config.Conf.DebugSQL)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MigrateDB migrates Database
 | 
			
		||||
func MigrateDB() error {
 | 
			
		||||
	if err := db.AutoMigrate(
 | 
			
		||||
		&m.ScanHistory{},
 | 
			
		||||
		&m.ScanResult{},
 | 
			
		||||
		//  &m.NWLink{},
 | 
			
		||||
		&m.Container{},
 | 
			
		||||
		&m.CveInfo{},
 | 
			
		||||
		&m.CpeName{},
 | 
			
		||||
		&m.PackageInfo{},
 | 
			
		||||
		&m.DistroAdvisory{},
 | 
			
		||||
		&cve.CveDetail{},
 | 
			
		||||
		&cve.Jvn{},
 | 
			
		||||
		&cve.Nvd{},
 | 
			
		||||
		&cve.Reference{},
 | 
			
		||||
		&cve.Cpe{},
 | 
			
		||||
	).Error; err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to migrate. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errMsg := "Failed to create index. err: %s"
 | 
			
		||||
	//  if err := db.Model(&m.NWLink{}).
 | 
			
		||||
	//      AddIndex("idx_n_w_links_scan_result_id", "scan_result_id").Error; err != nil {
 | 
			
		||||
	//      return fmt.Errorf(errMsg, err)
 | 
			
		||||
	//  }
 | 
			
		||||
	if err := db.Model(&m.Container{}).
 | 
			
		||||
		AddIndex("idx_containers_scan_result_id", "scan_result_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.CveInfo{}).
 | 
			
		||||
		AddIndex("idx_cve_infos_scan_result_id", "scan_result_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.CpeName{}).
 | 
			
		||||
		AddIndex("idx_cpe_names_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.PackageInfo{}).
 | 
			
		||||
		AddIndex("idx_package_infos_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.DistroAdvisory{}).
 | 
			
		||||
		//TODO check table name
 | 
			
		||||
		AddIndex("idx_distro_advisories_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.CveDetail{}).
 | 
			
		||||
		AddIndex("idx_cve_details_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.CveDetail{}).
 | 
			
		||||
		AddIndex("idx_cve_details_cveid", "cve_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Nvd{}).
 | 
			
		||||
		AddIndex("idx_nvds_cve_detail_id", "cve_detail_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Jvn{}).
 | 
			
		||||
		AddIndex("idx_jvns_cve_detail_id", "cve_detail_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Cpe{}).
 | 
			
		||||
		AddIndex("idx_cpes_jvn_id", "jvn_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Reference{}).
 | 
			
		||||
		AddIndex("idx_references_jvn_id", "jvn_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Cpe{}).
 | 
			
		||||
		AddIndex("idx_cpes_nvd_id", "nvd_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Reference{}).
 | 
			
		||||
		AddIndex("idx_references_nvd_id", "nvd_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Insert inserts scan results into DB
 | 
			
		||||
func Insert(results []m.ScanResult) error {
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		r.KnownCves = resetGormIDs(r.KnownCves)
 | 
			
		||||
		r.UnknownCves = resetGormIDs(r.UnknownCves)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	history := m.ScanHistory{
 | 
			
		||||
		ScanResults: results,
 | 
			
		||||
		ScannedAt:   time.Now(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db = db.Set("gorm:save_associations", false)
 | 
			
		||||
	if err := db.Create(&history).Error; err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, scanResult := range history.ScanResults {
 | 
			
		||||
		scanResult.ScanHistoryID = history.ID
 | 
			
		||||
		if err := db.Create(&scanResult).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		scanResult.Container.ScanResultID = scanResult.ID
 | 
			
		||||
		if err := db.Create(&scanResult.Container).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := insertCveInfos(scanResult.ID, scanResult.KnownCves); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := insertCveInfos(scanResult.ID, scanResult.UnknownCves); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func insertCveInfos(scanResultID uint, infos []m.CveInfo) error {
 | 
			
		||||
	for _, cveInfo := range infos {
 | 
			
		||||
		cveInfo.ScanResultID = scanResultID
 | 
			
		||||
		if err := db.Create(&cveInfo).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, pack := range cveInfo.Packages {
 | 
			
		||||
			pack.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&pack).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, distroAdvisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			distroAdvisory.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&distroAdvisory).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, cpeName := range cveInfo.CpeNames {
 | 
			
		||||
			cpeName.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&cpeName).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		db = db.Set("gorm:save_associations", true)
 | 
			
		||||
		cveDetail := cveInfo.CveDetail
 | 
			
		||||
		cveDetail.CveInfoID = cveInfo.ID
 | 
			
		||||
		if err := db.Create(&cveDetail).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		db = db.Set("gorm:save_associations", false)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func resetGormIDs(infos []m.CveInfo) []m.CveInfo {
 | 
			
		||||
	for i := range infos {
 | 
			
		||||
		infos[i].CveDetail.ID = 0
 | 
			
		||||
		// NVD
 | 
			
		||||
		infos[i].CveDetail.Nvd.ID = 0
 | 
			
		||||
		for j := range infos[i].CveDetail.Nvd.Cpes {
 | 
			
		||||
			infos[i].CveDetail.Nvd.Cpes[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
		for j := range infos[i].CveDetail.Nvd.References {
 | 
			
		||||
			infos[i].CveDetail.Nvd.References[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// JVN
 | 
			
		||||
		infos[i].CveDetail.Jvn.ID = 0
 | 
			
		||||
		for j := range infos[i].CveDetail.Jvn.Cpes {
 | 
			
		||||
			infos[i].CveDetail.Jvn.Cpes[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
		for j := range infos[i].CveDetail.Jvn.References {
 | 
			
		||||
			infos[i].CveDetail.Jvn.References[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//Packages
 | 
			
		||||
		for j := range infos[i].Packages {
 | 
			
		||||
			infos[i].Packages[j].ID = 0
 | 
			
		||||
			infos[i].Packages[j].CveInfoID = 0
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return infos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SelectScanHistory select scan history from DB
 | 
			
		||||
func SelectScanHistory(historyID string) (m.ScanHistory, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	scanHistory := m.ScanHistory{}
 | 
			
		||||
	if historyID == "" {
 | 
			
		||||
		// select latest
 | 
			
		||||
		db.Order("scanned_at desc").First(&scanHistory)
 | 
			
		||||
	} else {
 | 
			
		||||
		var id int
 | 
			
		||||
		if id, err = strconv.Atoi(historyID); err != nil {
 | 
			
		||||
			return m.ScanHistory{},
 | 
			
		||||
				fmt.Errorf("historyID have to be numeric number: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		db.First(&scanHistory, id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if scanHistory.ID == 0 {
 | 
			
		||||
		return m.ScanHistory{}, fmt.Errorf("No scanHistory records")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//  results := []m.ScanResult{}
 | 
			
		||||
	results := m.ScanResults{}
 | 
			
		||||
	db.Model(&scanHistory).Related(&results, "ScanResults")
 | 
			
		||||
	scanHistory.ScanResults = results
 | 
			
		||||
 | 
			
		||||
	for i, r := range results {
 | 
			
		||||
		//  nw := []m.NWLink{}
 | 
			
		||||
		//  db.Model(&r).Related(&nw, "NWLinks")
 | 
			
		||||
		//  scanHistory.ScanResults[i].NWLinks = nw
 | 
			
		||||
 | 
			
		||||
		di := m.Container{}
 | 
			
		||||
		db.Model(&r).Related(&di, "Container")
 | 
			
		||||
		scanHistory.ScanResults[i].Container = di
 | 
			
		||||
 | 
			
		||||
		knownCves := selectCveInfos(&r, "KnownCves")
 | 
			
		||||
		sort.Sort(m.CveInfos(knownCves))
 | 
			
		||||
		scanHistory.ScanResults[i].KnownCves = knownCves
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Sort(scanHistory.ScanResults)
 | 
			
		||||
	return scanHistory, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func selectCveInfos(result *m.ScanResult, fieldName string) []m.CveInfo {
 | 
			
		||||
	cveInfos := []m.CveInfo{}
 | 
			
		||||
	db.Model(&result).Related(&cveInfos, fieldName)
 | 
			
		||||
 | 
			
		||||
	for i, cveInfo := range cveInfos {
 | 
			
		||||
		cveDetail := cve.CveDetail{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&cveDetail, "CveDetail")
 | 
			
		||||
		id := cveDetail.CveID
 | 
			
		||||
		filledCveDetail := cvedb.Get(id, db)
 | 
			
		||||
		cveInfos[i].CveDetail = filledCveDetail
 | 
			
		||||
 | 
			
		||||
		packs := []m.PackageInfo{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&packs, "Packages")
 | 
			
		||||
		cveInfos[i].Packages = packs
 | 
			
		||||
 | 
			
		||||
		advisories := []m.DistroAdvisory{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&advisories, "DistroAdvisories")
 | 
			
		||||
		cveInfos[i].DistroAdvisories = advisories
 | 
			
		||||
 | 
			
		||||
		names := []m.CpeName{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&names, "CpeNames")
 | 
			
		||||
		cveInfos[i].CpeNames = names
 | 
			
		||||
	}
 | 
			
		||||
	return cveInfos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SelectScanHistories select latest scan history from DB
 | 
			
		||||
func SelectScanHistories() ([]m.ScanHistory, error) {
 | 
			
		||||
	scanHistories := []m.ScanHistory{}
 | 
			
		||||
	db.Order("scanned_at desc").Find(&scanHistories)
 | 
			
		||||
 | 
			
		||||
	if len(scanHistories) == 0 {
 | 
			
		||||
		return []m.ScanHistory{}, fmt.Errorf("No scanHistory records")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, history := range scanHistories {
 | 
			
		||||
		results := m.ScanResults{}
 | 
			
		||||
		db.Model(&history).Related(&results, "ScanResults")
 | 
			
		||||
		scanHistories[i].ScanResults = results
 | 
			
		||||
 | 
			
		||||
		for j, r := range results {
 | 
			
		||||
			di := m.Container{}
 | 
			
		||||
			db.Model(&r).Related(&di, "Container")
 | 
			
		||||
			scanHistories[i].ScanResults[j].Container = di
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return scanHistories, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										110
									
								
								glide.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										110
									
								
								glide.lock
									
									
									
										generated
									
									
									
								
							@@ -1,117 +1,123 @@
 | 
			
		||||
hash: 9683c87b3cf998e7fac1b12c4a94bf2bd18cb5422e9108539811546e703a439a
 | 
			
		||||
updated: 2016-07-12T16:20:45.462913061+09:00
 | 
			
		||||
hash: c3167d83e68562cd7ef73f138ce60cb9e60b72b50394e8615388d1f3ba9fbef2
 | 
			
		||||
updated: 2017-02-08T11:59:59.893522816+09:00
 | 
			
		||||
imports:
 | 
			
		||||
- name: github.com/asaskevich/govalidator
 | 
			
		||||
  version: df81827fdd59d8b4fb93d8910b286ab7a3919520
 | 
			
		||||
  version: 7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877
 | 
			
		||||
- name: github.com/aws/aws-sdk-go
 | 
			
		||||
  version: 90dec2183a5f5458ee79cbaf4b8e9ab910bc81a6
 | 
			
		||||
  version: 5b341290c488aa6bd76b335d819b4a68516ec3ab
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - aws
 | 
			
		||||
  - aws/credentials
 | 
			
		||||
  - aws/session
 | 
			
		||||
  - service/s3
 | 
			
		||||
  - aws/awserr
 | 
			
		||||
  - aws/client
 | 
			
		||||
  - aws/corehandlers
 | 
			
		||||
  - aws/defaults
 | 
			
		||||
  - aws/request
 | 
			
		||||
  - private/endpoints
 | 
			
		||||
  - aws/awsutil
 | 
			
		||||
  - aws/client
 | 
			
		||||
  - aws/client/metadata
 | 
			
		||||
  - aws/signer/v4
 | 
			
		||||
  - private/protocol
 | 
			
		||||
  - private/protocol/restxml
 | 
			
		||||
  - private/waiter
 | 
			
		||||
  - aws/corehandlers
 | 
			
		||||
  - aws/credentials
 | 
			
		||||
  - aws/credentials/ec2rolecreds
 | 
			
		||||
  - aws/credentials/endpointcreds
 | 
			
		||||
  - aws/credentials/stscreds
 | 
			
		||||
  - aws/defaults
 | 
			
		||||
  - aws/ec2metadata
 | 
			
		||||
  - private/protocol/rest
 | 
			
		||||
  - aws/request
 | 
			
		||||
  - aws/session
 | 
			
		||||
  - aws/signer/v4
 | 
			
		||||
  - private/endpoints
 | 
			
		||||
  - private/protocol
 | 
			
		||||
  - private/protocol/query
 | 
			
		||||
  - private/protocol/xml/xmlutil
 | 
			
		||||
  - private/protocol/query/queryutil
 | 
			
		||||
  - private/protocol/rest
 | 
			
		||||
  - private/protocol/restxml
 | 
			
		||||
  - private/protocol/xml/xmlutil
 | 
			
		||||
  - private/waiter
 | 
			
		||||
  - service/s3
 | 
			
		||||
  - service/sts
 | 
			
		||||
- name: github.com/Azure/azure-sdk-for-go
 | 
			
		||||
  version: 58a13e378daf3b06e65925397185684b16321111
 | 
			
		||||
  version: 27ae5c8b5bc5d90ab0540b4c5d0f2632c8db8b57
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - storage
 | 
			
		||||
- name: github.com/boltdb/bolt
 | 
			
		||||
  version: 315c65d4cf4f5278c73477a35fb1f9b08365d340
 | 
			
		||||
- name: github.com/BurntSushi/toml
 | 
			
		||||
  version: ffaa107fbd880f6d18cd6fec9b511668dcad8639
 | 
			
		||||
  version: 99064174e013895bbd9b025c31100bd1d9b590ca
 | 
			
		||||
- name: github.com/cenkalti/backoff
 | 
			
		||||
  version: cdf48bbc1eb78d1349cbda326a4a037f7ba565c6
 | 
			
		||||
  version: 8edc80b07f38c27352fb186d971c628a6c32552b
 | 
			
		||||
- name: github.com/cheggaaa/pb
 | 
			
		||||
  version: 04b234c80d661c663dbcebd52fc7218fdacc6d0c
 | 
			
		||||
  version: ad4efe000aa550bb54918c06ebbadc0ff17687b9
 | 
			
		||||
- name: github.com/go-ini/ini
 | 
			
		||||
  version: cf53f9204df4fbdd7ec4164b57fa6184ba168292
 | 
			
		||||
  version: 6e4869b434bd001f6983749881c7ead3545887d8
 | 
			
		||||
- name: github.com/go-sql-driver/mysql
 | 
			
		||||
  version: d512f204a577a4ab037a1816604c48c9c13210be
 | 
			
		||||
- name: github.com/google/subcommands
 | 
			
		||||
  version: 1c7173745a6001f67d8d96ab4e178284c77f7759
 | 
			
		||||
  version: a71b91e238406bd68766ee52db63bebedce0e9f6
 | 
			
		||||
- name: github.com/gosuri/uitable
 | 
			
		||||
  version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - util/strutil
 | 
			
		||||
  - util/wordwrap
 | 
			
		||||
- name: github.com/howeyc/gopass
 | 
			
		||||
  version: 66487b23f2880ba32e185121d2cd51a338ea069a
 | 
			
		||||
  version: f5387c492211eb133053880d23dfae62aa14123d
 | 
			
		||||
- name: github.com/jinzhu/gorm
 | 
			
		||||
  version: 613c0655691abb7691b70c5fda80a716d9e20b1b
 | 
			
		||||
  version: 39165d498058a823126af3cbf4d2a3b0e1acf11e
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - dialects/mysql
 | 
			
		||||
  - dialects/sqlite
 | 
			
		||||
- name: github.com/jinzhu/inflection
 | 
			
		||||
  version: 8f4d3a0d04ce0b7c0cf3126fb98524246d00d102
 | 
			
		||||
  version: 74387dc39a75e970e7a3ae6a3386b5bd2e5c5cff
 | 
			
		||||
- name: github.com/jmespath/go-jmespath
 | 
			
		||||
  version: 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74
 | 
			
		||||
  version: bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
 | 
			
		||||
- name: github.com/jroimartin/gocui
 | 
			
		||||
  version: 2dcda558bf18ec07c7065bf1eaf071b5305f7c0c
 | 
			
		||||
  version: ba396278de0a3c63658bbaba13d2d2fa392edb11
 | 
			
		||||
- name: github.com/k0kubun/pp
 | 
			
		||||
  version: f5dce6ed0ccf6c350f1679964ff6b61f3d6d2033
 | 
			
		||||
- name: github.com/kotakanbe/go-cve-dictionary
 | 
			
		||||
  version: 1a336b8ac785badfe89a175ee926d39574901232
 | 
			
		||||
  version: bbfdd41e7785a9b7163b5109b10ac2dea8f36d84
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - config
 | 
			
		||||
  - db
 | 
			
		||||
  - models
 | 
			
		||||
  - log
 | 
			
		||||
  - jvn
 | 
			
		||||
  - log
 | 
			
		||||
  - models
 | 
			
		||||
  - nvd
 | 
			
		||||
  - util
 | 
			
		||||
- name: github.com/kotakanbe/go-pingscanner
 | 
			
		||||
  version: 58e188a3e4f6ab1a6371e33421e4502e26fa1e80
 | 
			
		||||
- name: github.com/kotakanbe/logrus-prefixed-formatter
 | 
			
		||||
  version: f4f7d41649cf1e75e736884da8d05324aa76ea25
 | 
			
		||||
- name: github.com/mattn/go-colorable
 | 
			
		||||
  version: 9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59
 | 
			
		||||
  version: 6c903ff4aa50920ca86087a280590b36b3152b9c
 | 
			
		||||
- name: github.com/mattn/go-isatty
 | 
			
		||||
  version: 56b76bdf51f7708750eac80fa38b952bb9f32639
 | 
			
		||||
  version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
 | 
			
		||||
- name: github.com/mattn/go-runewidth
 | 
			
		||||
  version: d6bea18f789704b5f83375793155289da36a3c7f
 | 
			
		||||
  version: 737072b4e32b7a5018b4a7125da8d12de90e8045
 | 
			
		||||
- name: github.com/mattn/go-sqlite3
 | 
			
		||||
  version: 38ee283dabf11c9cbdb968eebd79b1fa7acbabe6
 | 
			
		||||
  version: 5510da399572b4962c020184bb291120c0a412e2
 | 
			
		||||
- name: github.com/mgutz/ansi
 | 
			
		||||
  version: c286dcecd19ff979eeb73ea444e479b903f2cfcb
 | 
			
		||||
- name: github.com/moul/http2curl
 | 
			
		||||
  version: b1479103caacaa39319f75e7f57fc545287fca0d
 | 
			
		||||
- name: github.com/nsf/termbox-go
 | 
			
		||||
  version: c45773466a30b680355d6494cc8826113c93cd0f
 | 
			
		||||
  version: b6acae516ace002cb8105a89024544a1480655a5
 | 
			
		||||
- name: github.com/parnurzeal/gorequest
 | 
			
		||||
  version: 6e8ad4ebdee4bec2934ed5afaaa1c7b877832a17
 | 
			
		||||
  version: e37b9d1efacf7c94820b29b75dd7d0c2996b3fb1
 | 
			
		||||
- name: github.com/rifflock/lfshook
 | 
			
		||||
  version: 05a24e24fa8d3a2eca8c2baf23aa2d5a2c51490c
 | 
			
		||||
  version: 3f9d976bd7402de39b46357069fb6325a974572e
 | 
			
		||||
- name: github.com/Sirupsen/logrus
 | 
			
		||||
  version: f3cfb454f4c209e6668c95216c4744b8fddb2356
 | 
			
		||||
  version: 3ec0642a7fb6488f65b06f9040adc67e3990296a
 | 
			
		||||
- name: golang.org/x/crypto
 | 
			
		||||
  version: c2f4947f41766b144bb09066e919466da5eddeae
 | 
			
		||||
  version: 9477e0b78b9ac3d0b03822fd95422e2fe07627cd
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - ssh
 | 
			
		||||
  - ssh/agent
 | 
			
		||||
  - ssh/terminal
 | 
			
		||||
  - curve25519
 | 
			
		||||
  - ed25519
 | 
			
		||||
  - ed25519/internal/edwards25519
 | 
			
		||||
  - ssh
 | 
			
		||||
  - ssh/agent
 | 
			
		||||
  - ssh/terminal
 | 
			
		||||
- name: golang.org/x/net
 | 
			
		||||
  version: f841c39de738b1d0df95b5a7187744f0e03d8112
 | 
			
		||||
  version: 1d7a0b2100da090d8b02afcfb42f97e2c77e71a4
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - context
 | 
			
		||||
  - publicsuffix
 | 
			
		||||
- name: golang.org/x/sys
 | 
			
		||||
  version: a408501be4d17ee978c04a618e7a1b22af058c0e
 | 
			
		||||
  version: 9bb9f0998d48b31547d975974935ae9b48c7a03c
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - unix
 | 
			
		||||
- name: gopkg.in/alexcesaro/quotedprintable.v3
 | 
			
		||||
  version: 2caba252f4dc53eaf6b553000885530023f54623
 | 
			
		||||
- name: gopkg.in/gomail.v2
 | 
			
		||||
  version: 81ebce5c23dfd25c6c67194b37d3dd3f338c98b1
 | 
			
		||||
devImports: []
 | 
			
		||||
testImports: []
 | 
			
		||||
 
 | 
			
		||||
@@ -12,17 +12,18 @@ import:
 | 
			
		||||
  - aws/credentials
 | 
			
		||||
  - aws/session
 | 
			
		||||
  - service/s3
 | 
			
		||||
- package: github.com/boltdb/bolt
 | 
			
		||||
- package: github.com/cenkalti/backoff
 | 
			
		||||
- package: github.com/google/subcommands
 | 
			
		||||
- package: github.com/gosuri/uitable
 | 
			
		||||
- package: github.com/howeyc/gopass
 | 
			
		||||
- package: github.com/jinzhu/gorm
 | 
			
		||||
- package: github.com/jroimartin/gocui
 | 
			
		||||
- package: github.com/k0kubun/pp
 | 
			
		||||
- package: github.com/kotakanbe/go-cve-dictionary
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - config
 | 
			
		||||
  - db
 | 
			
		||||
  - log
 | 
			
		||||
  - models
 | 
			
		||||
- package: github.com/kotakanbe/go-pingscanner
 | 
			
		||||
- package: github.com/kotakanbe/logrus-prefixed-formatter
 | 
			
		||||
@@ -33,7 +34,3 @@ import:
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - ssh
 | 
			
		||||
  - ssh/agent
 | 
			
		||||
- package: golang.org/x/net
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - context
 | 
			
		||||
- package: gopkg.in/gomail.v2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1568
									
								
								img/vuls-architecture-localscan.graphml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1568
									
								
								img/vuls-architecture-localscan.graphml
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								img/vuls-architecture-localscan.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								img/vuls-architecture-localscan.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 98 KiB  | 
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 90 KiB  | 
@@ -72,7 +72,7 @@ FreeBSD: pkg<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="10.0" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="260.83984375" x="3.580078125" y="11.8671875">Get upgradable packages
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="260.83984375" x="3.580078125" y="11.8671875">Check upgradable packages
 | 
			
		||||
Debian/Ubuntu: apt-get upgrade --dry-run<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -168,22 +168,6 @@ FreeBSD: pkg audit<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n9">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
          <y:Geometry height="64.1719342604298" width="111.96965865992411" x="687.3850119398792" y="807.0697396491782"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="48.56640625" x="31.701626204962054" y="23.019560880214726">Vuls DB<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="-8.881784197001252E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n10">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
          <y:Geometry height="65.22882427307195" width="136.83944374209864" x="411.5802781289507" y="687.385587863464"/>
 | 
			
		||||
@@ -199,13 +183,13 @@ FreeBSD: pkg audit<y:LabelModel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n11">
 | 
			
		||||
    <node id="n10">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="716.4553275126422"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="126.396484375" x="70.8017578125" y="11.8671875">Insert results into DB
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="152.634765625" x="57.6826171875" y="11.8671875">Write results to JSON files
 | 
			
		||||
Reporting<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -216,13 +200,13 @@ Reporting<y:LabelModel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n12">
 | 
			
		||||
    <node id="n11">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="271.369140625" x="-1.6845703124999432" y="11.8671875">Get all changelogs by using package manager
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="293.06640625" x="-12.533203124999943" y="11.8671875">Get all changelogs of updatable packages at once
 | 
			
		||||
CentOS: yum update --changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -233,7 +217,7 @@ CentOS: yum update --changelog<y:LabelModel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n13">
 | 
			
		||||
    <node id="n12">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="373.8409153761062"/>
 | 
			
		||||
@@ -364,7 +348,7 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e9" source="n7" target="n11">
 | 
			
		||||
    <edge id="e9" source="n7" target="n10">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
@@ -374,7 +358,7 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e10" source="n7" target="n10">
 | 
			
		||||
    <edge id="e10" source="n7" target="n9">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="-134.01566143419018" sy="6.159084623893818" tx="0.0" ty="-29.333162136535975">
 | 
			
		||||
@@ -386,18 +370,7 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e11" source="n11" target="n9">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.86721713021484"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e12" source="n1" target="n12">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
    <edge id="e11" source="n1" target="n11">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="20.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
@@ -415,8 +388,7 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e13" source="n12" target="n13">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
    <edge id="e12" source="n11" target="n12">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
@@ -426,8 +398,7 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e14" source="n13" target="n7">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
    <edge id="e13" source="n12" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="134.00000000000006" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 72 KiB  | 
							
								
								
									
										15
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								main.go
									
									
									
									
									
								
							@@ -22,15 +22,18 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/commands"
 | 
			
		||||
	"github.com/future-architect/vuls/version"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
 | 
			
		||||
	_ "github.com/mattn/go-sqlite3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Version of Vuls
 | 
			
		||||
var version = "0.3.0"
 | 
			
		||||
 | 
			
		||||
// Revision of Git
 | 
			
		||||
var revision string
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	subcommands.Register(subcommands.HelpCommand(), "")
 | 
			
		||||
	subcommands.Register(subcommands.FlagsCommand(), "")
 | 
			
		||||
@@ -38,8 +41,8 @@ func main() {
 | 
			
		||||
	subcommands.Register(&commands.DiscoverCmd{}, "discover")
 | 
			
		||||
	subcommands.Register(&commands.TuiCmd{}, "tui")
 | 
			
		||||
	subcommands.Register(&commands.ScanCmd{}, "scan")
 | 
			
		||||
	subcommands.Register(&commands.PrepareCmd{}, "prepare")
 | 
			
		||||
	subcommands.Register(&commands.HistoryCmd{}, "history")
 | 
			
		||||
	subcommands.Register(&commands.ReportCmd{}, "report")
 | 
			
		||||
	subcommands.Register(&commands.ConfigtestCmd{}, "configtest")
 | 
			
		||||
 | 
			
		||||
	var v = flag.Bool("v", false, "Show version")
 | 
			
		||||
@@ -47,7 +50,7 @@ func main() {
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	if *v {
 | 
			
		||||
		fmt.Printf("%s %s\n", version.Name, version.Version)
 | 
			
		||||
		fmt.Printf("vuls %s %s\n", version, revision)
 | 
			
		||||
		os.Exit(int(subcommands.ExitSuccess))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										424
									
								
								models/models.go
									
									
									
									
									
								
							
							
						
						
									
										424
									
								
								models/models.go
									
									
									
									
									
								
							@@ -23,15 +23,13 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/jinzhu/gorm"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanHistory is the history of Scanning.
 | 
			
		||||
type ScanHistory struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	ScanResults ScanResults
 | 
			
		||||
	ScannedAt   time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResults is slice of ScanResult.
 | 
			
		||||
@@ -55,99 +53,164 @@ func (s ScanResults) Less(i, j int) bool {
 | 
			
		||||
	return s[i].ServerName < s[j].ServerName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver is filter function.
 | 
			
		||||
func (s ScanResults) FilterByCvssOver() (filtered ScanResults) {
 | 
			
		||||
	for _, result := range s {
 | 
			
		||||
		cveInfos := []CveInfo{}
 | 
			
		||||
		for _, cveInfo := range result.KnownCves {
 | 
			
		||||
			if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
 | 
			
		||||
				cveInfos = append(cveInfos, cveInfo)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		result.KnownCves = cveInfos
 | 
			
		||||
		filtered = append(filtered, result)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResult has the result of scanned CVE information.
 | 
			
		||||
type ScanResult struct {
 | 
			
		||||
	gorm.Model    `json:"-"`
 | 
			
		||||
	ScanHistoryID uint `json:"-"`
 | 
			
		||||
	ScannedAt time.Time
 | 
			
		||||
 | 
			
		||||
	Lang       string
 | 
			
		||||
	ServerName string // TOML Section key
 | 
			
		||||
	//  Hostname    string
 | 
			
		||||
	Family  string
 | 
			
		||||
	Release string
 | 
			
		||||
	Family     string
 | 
			
		||||
	Release    string
 | 
			
		||||
	Container  Container
 | 
			
		||||
	Platform   Platform
 | 
			
		||||
 | 
			
		||||
	Container Container
 | 
			
		||||
	// Scanned Vulns via SSH + CPE Vulns
 | 
			
		||||
	ScannedCves []VulnInfo
 | 
			
		||||
 | 
			
		||||
	Platform Platform
 | 
			
		||||
 | 
			
		||||
	//  Fqdn        string
 | 
			
		||||
	//  NWLinks     []NWLink
 | 
			
		||||
	KnownCves   []CveInfo
 | 
			
		||||
	UnknownCves []CveInfo
 | 
			
		||||
	IgnoredCves []CveInfo
 | 
			
		||||
 | 
			
		||||
	Optional [][]interface{} `gorm:"-"`
 | 
			
		||||
	Packages PackageInfoList
 | 
			
		||||
 | 
			
		||||
	Errors   []string
 | 
			
		||||
	Optional [][]interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillCveDetail fetches CVE detailed information from
 | 
			
		||||
// CVE Database, and then set to fields.
 | 
			
		||||
func (r ScanResult) FillCveDetail() (*ScanResult, error) {
 | 
			
		||||
	set := map[string]VulnInfo{}
 | 
			
		||||
	var cveIDs []string
 | 
			
		||||
	for _, v := range r.ScannedCves {
 | 
			
		||||
		set[v.CveID] = v
 | 
			
		||||
		cveIDs = append(cveIDs, v.CveID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ds, err := cveapi.CveClient.FetchCveDetails(cveIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	known, unknown, ignored := CveInfos{}, CveInfos{}, CveInfos{}
 | 
			
		||||
	for _, d := range ds {
 | 
			
		||||
		cinfo := CveInfo{
 | 
			
		||||
			CveDetail: d,
 | 
			
		||||
			VulnInfo:  set[d.CveID],
 | 
			
		||||
		}
 | 
			
		||||
		cinfo.NilSliceToEmpty()
 | 
			
		||||
 | 
			
		||||
		// ignored
 | 
			
		||||
		found := false
 | 
			
		||||
		for _, icve := range config.Conf.Servers[r.ServerName].IgnoreCves {
 | 
			
		||||
			if icve == d.CveID {
 | 
			
		||||
				ignored = append(ignored, cinfo)
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if found {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// unknown
 | 
			
		||||
		if d.CvssScore(config.Conf.Lang) <= 0 {
 | 
			
		||||
			unknown = append(unknown, cinfo)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// known
 | 
			
		||||
		known = append(known, cinfo)
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(known)
 | 
			
		||||
	sort.Sort(unknown)
 | 
			
		||||
	sort.Sort(ignored)
 | 
			
		||||
	r.KnownCves = known
 | 
			
		||||
	r.UnknownCves = unknown
 | 
			
		||||
	r.IgnoredCves = ignored
 | 
			
		||||
	return &r, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver is filter function.
 | 
			
		||||
func (r ScanResult) FilterByCvssOver() ScanResult {
 | 
			
		||||
	cveInfos := []CveInfo{}
 | 
			
		||||
	for _, cveInfo := range r.KnownCves {
 | 
			
		||||
		if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
 | 
			
		||||
			cveInfos = append(cveInfos, cveInfo)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	r.KnownCves = cveInfos
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReportFileName returns the filename on localhost without extention
 | 
			
		||||
func (r ScanResult) ReportFileName() (name string) {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s", r.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReportKeyName returns the name of key on S3, Azure-Blob without extention
 | 
			
		||||
func (r ScanResult) ReportKeyName() (name string) {
 | 
			
		||||
	timestr := r.ScannedAt.Format(time.RFC3339)
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s/%s", timestr, r.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s/%s@%s", timestr, r.Container.Name, r.ServerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfo returns server name one line
 | 
			
		||||
func (r ScanResult) ServerInfo() string {
 | 
			
		||||
	hostinfo := ""
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"%s (%s%s)",
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
		)
 | 
			
		||||
	} else {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"%s / %s (%s%s) on %s",
 | 
			
		||||
			r.Container.Name,
 | 
			
		||||
			r.Container.ContainerID,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
		)
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s (%s%s)",
 | 
			
		||||
			r.ServerName, r.Family, r.Release)
 | 
			
		||||
	}
 | 
			
		||||
	return hostinfo
 | 
			
		||||
	return fmt.Sprintf(
 | 
			
		||||
		"%s / %s (%s%s) on %s",
 | 
			
		||||
		r.Container.Name,
 | 
			
		||||
		r.Container.ContainerID,
 | 
			
		||||
		r.Family,
 | 
			
		||||
		r.Release,
 | 
			
		||||
		r.ServerName,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfoTui returns server infromation for TUI sidebar
 | 
			
		||||
func (r ScanResult) ServerInfoTui() string {
 | 
			
		||||
	hostinfo := ""
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"%s (%s%s)",
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
		)
 | 
			
		||||
	} else {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"|-- %s (%s%s)",
 | 
			
		||||
			r.Container.Name,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
			//  r.Container.ContainerID,
 | 
			
		||||
		)
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s (%s%s)",
 | 
			
		||||
			r.ServerName, r.Family, r.Release)
 | 
			
		||||
	}
 | 
			
		||||
	return hostinfo
 | 
			
		||||
	return fmt.Sprintf(
 | 
			
		||||
		"|-- %s (%s%s)",
 | 
			
		||||
		r.Container.Name,
 | 
			
		||||
		r.Family,
 | 
			
		||||
		r.Release,
 | 
			
		||||
		//  r.Container.ContainerID,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatServerName returns server and container name
 | 
			
		||||
func (r ScanResult) FormatServerName() string {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return r.ServerName
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s@%s",
 | 
			
		||||
		r.Container.Name, r.ServerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveSummary summarize the number of CVEs group by CVSSv2 Severity
 | 
			
		||||
func (r ScanResult) CveSummary() string {
 | 
			
		||||
	var high, middle, low, unknown int
 | 
			
		||||
	var high, medium, low, unknown int
 | 
			
		||||
	cves := append(r.KnownCves, r.UnknownCves...)
 | 
			
		||||
	for _, cveInfo := range cves {
 | 
			
		||||
		score := cveInfo.CveDetail.CvssScore(config.Conf.Lang)
 | 
			
		||||
		switch {
 | 
			
		||||
		case 7.0 < score:
 | 
			
		||||
		case 7.0 <= score:
 | 
			
		||||
			high++
 | 
			
		||||
		case 4.0 < score:
 | 
			
		||||
			middle++
 | 
			
		||||
		case 4.0 <= score:
 | 
			
		||||
			medium++
 | 
			
		||||
		case 0 < score:
 | 
			
		||||
			low++
 | 
			
		||||
		default:
 | 
			
		||||
@@ -156,24 +219,136 @@ func (r ScanResult) CveSummary() string {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d)",
 | 
			
		||||
			high+middle+low, high, middle, low)
 | 
			
		||||
		return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)",
 | 
			
		||||
			high+medium+low, high, medium, low)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d ?:%d)",
 | 
			
		||||
		high+middle+low+unknown, high, middle, low, unknown)
 | 
			
		||||
	return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)",
 | 
			
		||||
		high+medium+low+unknown, high, medium, low, unknown)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AllCves returns Known and Unknown CVEs
 | 
			
		||||
func (r ScanResult) AllCves() []CveInfo {
 | 
			
		||||
	return append(r.KnownCves, r.UnknownCves...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NWLink has network link information.
 | 
			
		||||
type NWLink struct {
 | 
			
		||||
	gorm.Model   `json:"-"`
 | 
			
		||||
	ScanResultID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	IPAddress string
 | 
			
		||||
	Netmask   string
 | 
			
		||||
	DevName   string
 | 
			
		||||
	LinkState string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Confidence is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
// Score: 0 - 100
 | 
			
		||||
type Confidence struct {
 | 
			
		||||
	Score           int
 | 
			
		||||
	DetectionMethod string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Confidence) String() string {
 | 
			
		||||
	return fmt.Sprintf("%d / %s", c.Score, c.DetectionMethod)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// CpeNameMatchStr is a String representation of CpeNameMatch
 | 
			
		||||
	CpeNameMatchStr = "CpeNameMatch"
 | 
			
		||||
 | 
			
		||||
	// YumUpdateSecurityMatchStr is a String representation of YumUpdateSecurityMatch
 | 
			
		||||
	YumUpdateSecurityMatchStr = "YumUpdateSecurityMatch"
 | 
			
		||||
 | 
			
		||||
	// PkgAuditMatchStr is a String representation of PkgAuditMatch
 | 
			
		||||
	PkgAuditMatchStr = "PkgAuditMatch"
 | 
			
		||||
 | 
			
		||||
	// ChangelogExactMatchStr is a String representation of ChangelogExactMatch
 | 
			
		||||
	ChangelogExactMatchStr = "ChangelogExactMatch"
 | 
			
		||||
 | 
			
		||||
	// ChangelogLenientMatchStr is a String representation of ChangelogLenientMatch
 | 
			
		||||
	ChangelogLenientMatchStr = "ChangelogLenientMatch"
 | 
			
		||||
 | 
			
		||||
	// FailedToGetChangelog is a String representation of FailedToGetChangelog
 | 
			
		||||
	FailedToGetChangelog = "FailedToGetChangelog"
 | 
			
		||||
 | 
			
		||||
	// FailedToFindVersionInChangelog is a String representation of FailedToFindVersionInChangelog
 | 
			
		||||
	FailedToFindVersionInChangelog = "FailedToFindVersionInChangelog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CpeNameMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
var CpeNameMatch = Confidence{100, CpeNameMatchStr}
 | 
			
		||||
 | 
			
		||||
// YumUpdateSecurityMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
var YumUpdateSecurityMatch = Confidence{100, YumUpdateSecurityMatchStr}
 | 
			
		||||
 | 
			
		||||
// PkgAuditMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
var PkgAuditMatch = Confidence{100, PkgAuditMatchStr}
 | 
			
		||||
 | 
			
		||||
// ChangelogExactMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
var ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr}
 | 
			
		||||
 | 
			
		||||
// ChangelogLenientMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
var ChangelogLenientMatch = Confidence{50, ChangelogLenientMatchStr}
 | 
			
		||||
 | 
			
		||||
// VulnInfos is VulnInfo list, getter/setter, sortable methods.
 | 
			
		||||
type VulnInfos []VulnInfo
 | 
			
		||||
 | 
			
		||||
// VulnInfo holds a vulnerability information and unsecure packages
 | 
			
		||||
type VulnInfo struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	Confidence       Confidence
 | 
			
		||||
	Packages         PackageInfoList
 | 
			
		||||
	DistroAdvisories []DistroAdvisory // for Aamazon, RHEL, FreeBSD
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NilSliceToEmpty set nil slice fields to empty slice to avoid null in JSON
 | 
			
		||||
func (v *VulnInfo) NilSliceToEmpty() {
 | 
			
		||||
	if v.CpeNames == nil {
 | 
			
		||||
		v.CpeNames = []string{}
 | 
			
		||||
	}
 | 
			
		||||
	if v.DistroAdvisories == nil {
 | 
			
		||||
		v.DistroAdvisories = []DistroAdvisory{}
 | 
			
		||||
	}
 | 
			
		||||
	if v.Packages == nil {
 | 
			
		||||
		v.Packages = PackageInfoList{}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindByCveID find by CVEID
 | 
			
		||||
func (s VulnInfos) FindByCveID(cveID string) (VulnInfo, bool) {
 | 
			
		||||
	for _, p := range s {
 | 
			
		||||
		if cveID == p.CveID {
 | 
			
		||||
			return p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return VulnInfo{CveID: cveID}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// immutable
 | 
			
		||||
func (s VulnInfos) set(cveID string, v VulnInfo) VulnInfos {
 | 
			
		||||
	for i, p := range s {
 | 
			
		||||
		if cveID == p.CveID {
 | 
			
		||||
			s[i] = v
 | 
			
		||||
			return s
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return append(s, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Len implement Sort Interface
 | 
			
		||||
func (s VulnInfos) Len() int {
 | 
			
		||||
	return len(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Swap implement Sort Interface
 | 
			
		||||
func (s VulnInfos) Swap(i, j int) {
 | 
			
		||||
	s[i], s[j] = s[j], s[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Less implement Sort Interface
 | 
			
		||||
func (s VulnInfos) Less(i, j int) bool {
 | 
			
		||||
	return s[i].CveID < s[j].CveID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveInfos is for sorting
 | 
			
		||||
type CveInfos []CveInfo
 | 
			
		||||
 | 
			
		||||
@@ -190,26 +365,29 @@ func (c CveInfos) Less(i, j int) bool {
 | 
			
		||||
	if c[i].CveDetail.CvssScore(lang) == c[j].CveDetail.CvssScore(lang) {
 | 
			
		||||
		return c[i].CveDetail.CveID < c[j].CveDetail.CveID
 | 
			
		||||
	}
 | 
			
		||||
	return c[i].CveDetail.CvssScore(lang) > c[j].CveDetail.CvssScore(lang)
 | 
			
		||||
	return c[j].CveDetail.CvssScore(lang) < c[i].CveDetail.CvssScore(lang)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveInfo has Cve Information.
 | 
			
		||||
type CveInfo struct {
 | 
			
		||||
	gorm.Model   `json:"-"`
 | 
			
		||||
	ScanResultID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	CveDetail        cve.CveDetail
 | 
			
		||||
	Packages         []PackageInfo
 | 
			
		||||
	DistroAdvisories []DistroAdvisory
 | 
			
		||||
	CpeNames         []CpeName
 | 
			
		||||
	CveDetail cve.CveDetail
 | 
			
		||||
	VulnInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CpeName has CPE name
 | 
			
		||||
type CpeName struct {
 | 
			
		||||
	gorm.Model `json:"-"`
 | 
			
		||||
	CveInfoID  uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	Name string
 | 
			
		||||
// NilSliceToEmpty set nil slice fields to empty slice to avoid null in JSON
 | 
			
		||||
func (c *CveInfo) NilSliceToEmpty() {
 | 
			
		||||
	if c.CveDetail.Nvd.Cpes == nil {
 | 
			
		||||
		c.CveDetail.Nvd.Cpes = []cve.Cpe{}
 | 
			
		||||
	}
 | 
			
		||||
	if c.CveDetail.Jvn.Cpes == nil {
 | 
			
		||||
		c.CveDetail.Jvn.Cpes = []cve.Cpe{}
 | 
			
		||||
	}
 | 
			
		||||
	if c.CveDetail.Nvd.References == nil {
 | 
			
		||||
		c.CveDetail.Nvd.References = []cve.Reference{}
 | 
			
		||||
	}
 | 
			
		||||
	if c.CveDetail.Jvn.References == nil {
 | 
			
		||||
		c.CveDetail.Jvn.References = []cve.Reference{}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageInfoList is slice of PackageInfo
 | 
			
		||||
@@ -253,6 +431,36 @@ func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found boo
 | 
			
		||||
	return PackageInfo{}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MergeNewVersion merges candidate version information to the receiver struct
 | 
			
		||||
func (ps PackageInfoList) MergeNewVersion(as PackageInfoList) {
 | 
			
		||||
	for _, a := range as {
 | 
			
		||||
		for i, p := range ps {
 | 
			
		||||
			if p.Name == a.Name {
 | 
			
		||||
				ps[i].NewVersion = a.NewVersion
 | 
			
		||||
				ps[i].NewRelease = a.NewRelease
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ps PackageInfoList) countUpdatablePacks() int {
 | 
			
		||||
	count := 0
 | 
			
		||||
	set := make(map[string]bool)
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if len(p.NewVersion) != 0 && !set[p.Name] {
 | 
			
		||||
			count++
 | 
			
		||||
			set[p.Name] = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return count
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatUpdatablePacksSummary returns a summary of updatable packages
 | 
			
		||||
func (ps PackageInfoList) FormatUpdatablePacksSummary() string {
 | 
			
		||||
	return fmt.Sprintf("%d updatable packages",
 | 
			
		||||
		ps.countUpdatablePacks())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Find search PackageInfo by name-version-release
 | 
			
		||||
//  func (ps PackageInfoList) find(nameVersionRelease string) (PackageInfo, bool) {
 | 
			
		||||
//      for _, p := range ps {
 | 
			
		||||
@@ -270,17 +478,30 @@ func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found boo
 | 
			
		||||
//      return PackageInfo{}, false
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// PackageInfosByName implements sort.Interface for []PackageInfo based on
 | 
			
		||||
// the Name field.
 | 
			
		||||
type PackageInfosByName []PackageInfo
 | 
			
		||||
 | 
			
		||||
func (a PackageInfosByName) Len() int           { return len(a) }
 | 
			
		||||
func (a PackageInfosByName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 | 
			
		||||
func (a PackageInfosByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
 | 
			
		||||
 | 
			
		||||
// PackageInfo has installed packages.
 | 
			
		||||
type PackageInfo struct {
 | 
			
		||||
	gorm.Model `json:"-"`
 | 
			
		||||
	CveInfoID  uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	Name    string
 | 
			
		||||
	Version string
 | 
			
		||||
	Release string
 | 
			
		||||
 | 
			
		||||
	Name       string
 | 
			
		||||
	Version    string
 | 
			
		||||
	Release    string
 | 
			
		||||
	NewVersion string
 | 
			
		||||
	NewRelease string
 | 
			
		||||
	Repository string
 | 
			
		||||
	Changelog  Changelog
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Changelog has contents of changelog and how to get it.
 | 
			
		||||
// Method: modesl.detectionMethodStr
 | 
			
		||||
type Changelog struct {
 | 
			
		||||
	Contents string
 | 
			
		||||
	Method   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToStringCurrentVersion returns package name-version-release
 | 
			
		||||
@@ -309,9 +530,6 @@ func (p PackageInfo) ToStringNewVersion() string {
 | 
			
		||||
 | 
			
		||||
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
 | 
			
		||||
type DistroAdvisory struct {
 | 
			
		||||
	gorm.Model `json:"-"`
 | 
			
		||||
	CveInfoID  uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	AdvisoryID string
 | 
			
		||||
	Severity   string
 | 
			
		||||
	Issued     time.Time
 | 
			
		||||
@@ -320,18 +538,14 @@ type DistroAdvisory struct {
 | 
			
		||||
 | 
			
		||||
// Container has Container information
 | 
			
		||||
type Container struct {
 | 
			
		||||
	gorm.Model   `json:"-"`
 | 
			
		||||
	ScanResultID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	ContainerID string
 | 
			
		||||
	Name        string
 | 
			
		||||
	Image       string
 | 
			
		||||
	Type        string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Platform has platform information
 | 
			
		||||
type Platform struct {
 | 
			
		||||
	gorm.Model   `json:"-"`
 | 
			
		||||
	ScanResultID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	Name       string // aws or azure or gcp or other...
 | 
			
		||||
	InstanceID string
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,9 +17,14 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
func TestPackageInfosUniqByName(t *testing.T) {
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestPackageInfoListUniqByName(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in  PackageInfoList
 | 
			
		||||
		out PackageInfoList
 | 
			
		||||
@@ -52,3 +57,81 @@ func TestPackageInfosUniqByName(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMergeNewVersion(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		a        PackageInfoList
 | 
			
		||||
		b        PackageInfoList
 | 
			
		||||
		expected PackageInfoList
 | 
			
		||||
	}{
 | 
			
		||||
		PackageInfoList{
 | 
			
		||||
			{
 | 
			
		||||
				Name: "hoge",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		PackageInfoList{
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "hoge",
 | 
			
		||||
				NewVersion: "1.0.0",
 | 
			
		||||
				NewRelease: "release1",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		PackageInfoList{
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "hoge",
 | 
			
		||||
				NewVersion: "1.0.0",
 | 
			
		||||
				NewRelease: "release1",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	test.a.MergeNewVersion(test.b)
 | 
			
		||||
	if !reflect.DeepEqual(test.a, test.expected) {
 | 
			
		||||
		e := pp.Sprintf("%v", test.a)
 | 
			
		||||
		a := pp.Sprintf("%v", test.expected)
 | 
			
		||||
		t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
func TestVulnInfosSetGet(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in  []string
 | 
			
		||||
		out []string
 | 
			
		||||
	}{
 | 
			
		||||
		[]string{
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
		},
 | 
			
		||||
		[]string{
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//  var ps packageCveInfos
 | 
			
		||||
	var ps VulnInfos
 | 
			
		||||
	for _, cid := range test.in {
 | 
			
		||||
		ps = ps.set(cid, VulnInfo{CveID: cid})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(test.out) != len(ps) {
 | 
			
		||||
		t.Errorf("length: expected %d, actual %d", len(test.out), len(ps))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, expectedCid := range test.out {
 | 
			
		||||
		if expectedCid != ps[i].CveID {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", expectedCid, ps[i].CveID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cid := range test.in {
 | 
			
		||||
		p, _ := ps.FindByCveID(cid)
 | 
			
		||||
		if p.CveID != cid {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", cid, p.CveID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ package report
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
@@ -27,12 +28,76 @@ import (
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AzureBlobWriter writes results to AzureBlob
 | 
			
		||||
type AzureBlobWriter struct{}
 | 
			
		||||
 | 
			
		||||
// Write results to Azure Blob storage
 | 
			
		||||
func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	if len(rs) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cli, err := getBlobClient()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.FormatOneLineText {
 | 
			
		||||
		timestr := rs[0].ScannedAt.Format(time.RFC3339)
 | 
			
		||||
		k := fmt.Sprintf(timestr + "/summary.txt")
 | 
			
		||||
		text := formatOneLineSummary(rs...)
 | 
			
		||||
		b := []byte(text)
 | 
			
		||||
		if err := createBlockBlob(cli, k, b); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		key := r.ReportKeyName()
 | 
			
		||||
		if c.Conf.FormatJSON {
 | 
			
		||||
			k := key + ".json"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = json.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			if err := createBlockBlob(cli, k, b); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatShortText {
 | 
			
		||||
			k := key + "_short.txt"
 | 
			
		||||
			b := []byte(formatShortPlainText(r))
 | 
			
		||||
			if err := createBlockBlob(cli, k, b); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatFullText {
 | 
			
		||||
			k := key + "_full.txt"
 | 
			
		||||
			b := []byte(formatFullPlainText(r))
 | 
			
		||||
			if err := createBlockBlob(cli, k, b); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatXML {
 | 
			
		||||
			k := key + ".xml"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = xml.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to XML: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
 | 
			
		||||
			if err := createBlockBlob(cli, k, allBytes); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfAzureContainerExists check the existence of Azure storage container
 | 
			
		||||
func CheckIfAzureContainerExists() error {
 | 
			
		||||
	cli, err := getBlobClient()
 | 
			
		||||
@@ -57,84 +122,24 @@ func getBlobClient() (storage.BlobStorageClient, error) {
 | 
			
		||||
	return api.GetBlobService(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write results to Azure Blob storage
 | 
			
		||||
func (w AzureBlobWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
	reqChan := make(chan models.ScanResult, len(scanResults))
 | 
			
		||||
	resChan := make(chan bool)
 | 
			
		||||
	errChan := make(chan error, len(scanResults))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(10 * 60 * time.Second)
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, r := range scanResults {
 | 
			
		||||
			reqChan <- r
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	for range scanResults {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case sresult := <-reqChan:
 | 
			
		||||
				func(r models.ScanResult) {
 | 
			
		||||
					err := w.upload(r)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						errChan <- err
 | 
			
		||||
					}
 | 
			
		||||
					resChan <- true
 | 
			
		||||
				}(sresult)
 | 
			
		||||
			}
 | 
			
		||||
func createBlockBlob(cli storage.BlobStorageClient, k string, b []byte) error {
 | 
			
		||||
	var err error
 | 
			
		||||
	if c.Conf.GZIP {
 | 
			
		||||
		if b, err = gz(b); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		k = k + ".gz"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
	for i := 0; i < len(scanResults); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-resChan:
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			errs = append(errs, fmt.Errorf("Timeout while uploading to azure Blob"))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		return fmt.Errorf("Failed to upload json to Azure Blob: %v", errs)
 | 
			
		||||
	if err := cli.CreateBlockBlobFromReader(
 | 
			
		||||
		c.Conf.AzureContainer,
 | 
			
		||||
		k,
 | 
			
		||||
		uint64(len(b)),
 | 
			
		||||
		bytes.NewReader(b),
 | 
			
		||||
		map[string]string{},
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to upload data to %s/%s, %s",
 | 
			
		||||
			c.Conf.AzureContainer, k, err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w AzureBlobWriter) upload(res models.ScanResult) (err error) {
 | 
			
		||||
	cli, err := getBlobClient()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	timestr := time.Now().Format("20060102_1504")
 | 
			
		||||
	name := ""
 | 
			
		||||
	if res.Container.ContainerID == "" {
 | 
			
		||||
		name = fmt.Sprintf("%s/%s.json", timestr, res.ServerName)
 | 
			
		||||
	} else {
 | 
			
		||||
		name = fmt.Sprintf("%s/%s_%s.json", timestr, res.ServerName, res.Container.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	jsonBytes, err := json.Marshal(res)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = cli.CreateBlockBlobFromReader(
 | 
			
		||||
		c.Conf.AzureContainer,
 | 
			
		||||
		name,
 | 
			
		||||
		uint64(len(jsonBytes)),
 | 
			
		||||
		bytes.NewReader(jsonBytes),
 | 
			
		||||
		map[string]string{},
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return fmt.Errorf("%s/%s, %s",
 | 
			
		||||
			c.Conf.AzureContainer, name, err)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										137
									
								
								report/email.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								report/email.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/mail"
 | 
			
		||||
	"net/smtp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// EMailWriter send mail
 | 
			
		||||
type EMailWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf
 | 
			
		||||
	var message string
 | 
			
		||||
	var totalResult models.ScanResult
 | 
			
		||||
	sender := NewEMailSender()
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		if conf.FormatOneEMail {
 | 
			
		||||
			message += formatFullPlainText(r) + "\r\n\r\n"
 | 
			
		||||
			totalResult.KnownCves = append(totalResult.KnownCves, r.KnownCves...)
 | 
			
		||||
			totalResult.UnknownCves = append(totalResult.UnknownCves, r.UnknownCves...)
 | 
			
		||||
		} else {
 | 
			
		||||
			var subject string
 | 
			
		||||
			if len(r.Errors) != 0 {
 | 
			
		||||
				subject = fmt.Sprintf("%s%s An error occurred while scanning",
 | 
			
		||||
					conf.EMail.SubjectPrefix, r.ServerInfo())
 | 
			
		||||
			} else {
 | 
			
		||||
				subject = fmt.Sprintf("%s%s %s",
 | 
			
		||||
					conf.EMail.SubjectPrefix, r.ServerInfo(), r.CveSummary())
 | 
			
		||||
			}
 | 
			
		||||
			message = formatFullPlainText(r)
 | 
			
		||||
			if err := sender.Send(subject, message); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if conf.FormatOneEMail {
 | 
			
		||||
		message = fmt.Sprintf(
 | 
			
		||||
			`
 | 
			
		||||
One Line Summary
 | 
			
		||||
================
 | 
			
		||||
%s
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
%s`,
 | 
			
		||||
			formatOneLineSummary(rs...), message)
 | 
			
		||||
 | 
			
		||||
		subject := fmt.Sprintf("%s %s",
 | 
			
		||||
			conf.EMail.SubjectPrefix,
 | 
			
		||||
			totalResult.CveSummary(),
 | 
			
		||||
		)
 | 
			
		||||
		return sender.Send(subject, message)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EMailSender is interface of sending e-mail
 | 
			
		||||
type EMailSender interface {
 | 
			
		||||
	Send(subject, body string) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type emailSender struct {
 | 
			
		||||
	conf config.SMTPConf
 | 
			
		||||
	send func(string, smtp.Auth, string, []string, []byte) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *emailSender) Send(subject, body string) (err error) {
 | 
			
		||||
	emailConf := e.conf
 | 
			
		||||
	to := strings.Join(emailConf.To[:], ", ")
 | 
			
		||||
	cc := strings.Join(emailConf.Cc[:], ", ")
 | 
			
		||||
	mailAddresses := append(emailConf.To, emailConf.Cc...)
 | 
			
		||||
	if _, err := mail.ParseAddressList(strings.Join(mailAddresses[:], ", ")); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to parse email addresses: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	headers := make(map[string]string)
 | 
			
		||||
	headers["From"] = emailConf.From
 | 
			
		||||
	headers["To"] = to
 | 
			
		||||
	headers["Cc"] = cc
 | 
			
		||||
	headers["Subject"] = subject
 | 
			
		||||
	headers["Date"] = time.Now().Format(time.RFC1123Z)
 | 
			
		||||
	headers["Content-Type"] = "text/plain; charset=utf-8"
 | 
			
		||||
 | 
			
		||||
	var header string
 | 
			
		||||
	for k, v := range headers {
 | 
			
		||||
		header += fmt.Sprintf("%s: %s\r\n", k, v)
 | 
			
		||||
	}
 | 
			
		||||
	message := fmt.Sprintf("%s\r\n%s", header, body)
 | 
			
		||||
 | 
			
		||||
	smtpServer := net.JoinHostPort(emailConf.SMTPAddr, emailConf.SMTPPort)
 | 
			
		||||
	err = e.send(
 | 
			
		||||
		smtpServer,
 | 
			
		||||
		smtp.PlainAuth(
 | 
			
		||||
			"",
 | 
			
		||||
			emailConf.User,
 | 
			
		||||
			emailConf.Password,
 | 
			
		||||
			emailConf.SMTPAddr,
 | 
			
		||||
		),
 | 
			
		||||
		emailConf.From,
 | 
			
		||||
		mailAddresses,
 | 
			
		||||
		[]byte(message),
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to send emails: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewEMailSender creates emailSender
 | 
			
		||||
func NewEMailSender() EMailSender {
 | 
			
		||||
	return &emailSender{config.Conf.EMail, smtp.SendMail}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										132
									
								
								report/email_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								report/email_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/smtp"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type emailRecorder struct {
 | 
			
		||||
	addr string
 | 
			
		||||
	auth smtp.Auth
 | 
			
		||||
	from string
 | 
			
		||||
	to   []string
 | 
			
		||||
	body string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type mailTest struct {
 | 
			
		||||
	in  config.SMTPConf
 | 
			
		||||
	out emailRecorder
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var mailTests = []mailTest{
 | 
			
		||||
	{
 | 
			
		||||
		config.SMTPConf{
 | 
			
		||||
			SMTPAddr: "127.0.0.1",
 | 
			
		||||
			SMTPPort: "25",
 | 
			
		||||
 | 
			
		||||
			From: "from@address.com",
 | 
			
		||||
			To:   []string{"to@address.com"},
 | 
			
		||||
			Cc:   []string{"cc@address.com"},
 | 
			
		||||
		},
 | 
			
		||||
		emailRecorder{
 | 
			
		||||
			addr: "127.0.0.1:25",
 | 
			
		||||
			auth: smtp.PlainAuth("", "", "", "127.0.0.1"),
 | 
			
		||||
			from: "from@address.com",
 | 
			
		||||
			to:   []string{"to@address.com", "cc@address.com"},
 | 
			
		||||
			body: "body",
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		config.SMTPConf{
 | 
			
		||||
			SMTPAddr: "127.0.0.1",
 | 
			
		||||
			SMTPPort: "25",
 | 
			
		||||
 | 
			
		||||
			User:     "vuls",
 | 
			
		||||
			Password: "password",
 | 
			
		||||
 | 
			
		||||
			From: "from@address.com",
 | 
			
		||||
			To:   []string{"to1@address.com", "to2@address.com"},
 | 
			
		||||
			Cc:   []string{"cc1@address.com", "cc2@address.com"},
 | 
			
		||||
		},
 | 
			
		||||
		emailRecorder{
 | 
			
		||||
			addr: "127.0.0.1:25",
 | 
			
		||||
			auth: smtp.PlainAuth(
 | 
			
		||||
				"",
 | 
			
		||||
				"vuls",
 | 
			
		||||
				"password",
 | 
			
		||||
				"127.0.0.1",
 | 
			
		||||
			),
 | 
			
		||||
			from: "from@address.com",
 | 
			
		||||
			to: []string{"to1@address.com", "to2@address.com",
 | 
			
		||||
				"cc1@address.com", "cc2@address.com"},
 | 
			
		||||
			body: "body",
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSend(t *testing.T) {
 | 
			
		||||
	for i, test := range mailTests {
 | 
			
		||||
		f, r := mockSend(nil)
 | 
			
		||||
		sender := &emailSender{conf: test.in, send: f}
 | 
			
		||||
 | 
			
		||||
		subject := "subject"
 | 
			
		||||
		body := "body"
 | 
			
		||||
		if err := sender.Send(subject, body); err != nil {
 | 
			
		||||
			t.Errorf("unexpected error: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if r.addr != test.out.addr {
 | 
			
		||||
			t.Errorf("#%d: wrong 'addr' field.\r\nexpected: %s\n got: %s", i, test.out.addr, r.addr)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !reflect.DeepEqual(r.auth, test.out.auth) {
 | 
			
		||||
			t.Errorf("#%d: wrong 'auth' field.\r\nexpected: %v\n got: %v", i, test.out.auth, r.auth)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if r.from != test.out.from {
 | 
			
		||||
			t.Errorf("#%d: wrong 'from' field.\r\nexpected: %v\n got: %v", i, test.out.from, r.from)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !reflect.DeepEqual(r.to, test.out.to) {
 | 
			
		||||
			t.Errorf("#%d: wrong 'to' field.\r\nexpected: %v\n got: %v", i, test.out.to, r.to)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if r.body != test.out.body {
 | 
			
		||||
			t.Errorf("#%d: wrong 'body' field.\r\nexpected: %v\n got: %v", i, test.out.body, r.body)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mockSend(errToReturn error) (func(string, smtp.Auth, string, []string, []byte) error, *emailRecorder) {
 | 
			
		||||
	r := new(emailRecorder)
 | 
			
		||||
	return func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
 | 
			
		||||
		// Split into header and body
 | 
			
		||||
		messages := strings.Split(string(msg), "\r\n\r\n")
 | 
			
		||||
		body := messages[1]
 | 
			
		||||
		*r = emailRecorder{addr, a, from, to, body}
 | 
			
		||||
		return errToReturn
 | 
			
		||||
	}, r
 | 
			
		||||
}
 | 
			
		||||
@@ -1,62 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// JSONWriter writes results to file.
 | 
			
		||||
type JSONWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
 | 
			
		||||
	path, err := ensureResultDir()
 | 
			
		||||
 | 
			
		||||
	var jsonBytes []byte
 | 
			
		||||
	if jsonBytes, err = json.Marshal(scanResults); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	all := filepath.Join(path, "all.json")
 | 
			
		||||
	if err := ioutil.WriteFile(all, jsonBytes, 0644); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to write JSON. path: %s, err: %s", all, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range scanResults {
 | 
			
		||||
		jsonPath := ""
 | 
			
		||||
		if r.Container.ContainerID == "" {
 | 
			
		||||
			jsonPath = filepath.Join(path, fmt.Sprintf("%s.json", r.ServerName))
 | 
			
		||||
		} else {
 | 
			
		||||
			jsonPath = filepath.Join(path,
 | 
			
		||||
				fmt.Sprintf("%s_%s.json", r.ServerName, r.Container.Name))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if jsonBytes, err = json.Marshal(r); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		if err := ioutil.WriteFile(jsonPath, jsonBytes, 0644); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to write JSON. path: %s, err: %s", jsonPath, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										135
									
								
								report/localfile.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								report/localfile.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LocalFileWriter writes results to a local file.
 | 
			
		||||
type LocalFileWriter struct {
 | 
			
		||||
	CurrentDir string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	if c.Conf.FormatOneLineText {
 | 
			
		||||
		path := filepath.Join(w.CurrentDir, "summary.txt")
 | 
			
		||||
		text := formatOneLineSummary(rs...)
 | 
			
		||||
		if err := writeFile(path, []byte(text), 0600); err != nil {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"Failed to write to file. path: %s, err: %s",
 | 
			
		||||
				path, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		path := filepath.Join(w.CurrentDir, r.ReportFileName())
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatJSON {
 | 
			
		||||
			var p string
 | 
			
		||||
			if c.Conf.Diff {
 | 
			
		||||
				p = path + "_diff.json"
 | 
			
		||||
			} else {
 | 
			
		||||
				p = path + ".json"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = json.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			if err := writeFile(p, b, 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to write JSON. path: %s, err: %s", p, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatShortText {
 | 
			
		||||
			var p string
 | 
			
		||||
			if c.Conf.Diff {
 | 
			
		||||
				p = path + "_short_diff.txt"
 | 
			
		||||
			} else {
 | 
			
		||||
				p = path + "_short.txt"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := writeFile(
 | 
			
		||||
				p, []byte(formatShortPlainText(r)), 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"Failed to write text files. path: %s, err: %s", p, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatFullText {
 | 
			
		||||
			var p string
 | 
			
		||||
			if c.Conf.Diff {
 | 
			
		||||
				p = path + "_full_diff.txt"
 | 
			
		||||
			} else {
 | 
			
		||||
				p = path + "_full.txt"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := writeFile(
 | 
			
		||||
				p, []byte(formatFullPlainText(r)), 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"Failed to write text files. path: %s, err: %s", p, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatXML {
 | 
			
		||||
			var p string
 | 
			
		||||
			if c.Conf.Diff {
 | 
			
		||||
				p = path + "_diff.xml"
 | 
			
		||||
			} else {
 | 
			
		||||
				p = path + ".xml"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = xml.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to XML: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
 | 
			
		||||
			if err := writeFile(p, allBytes, 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to write XML. path: %s, err: %s", p, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func writeFile(path string, data []byte, perm os.FileMode) error {
 | 
			
		||||
	var err error
 | 
			
		||||
	if c.Conf.GZIP {
 | 
			
		||||
		if data, err = gz(data); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		path = path + ".gz"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ioutil.WriteFile(
 | 
			
		||||
		path, []byte(data), perm); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	formatter "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LogrusWriter write to logfile
 | 
			
		||||
type LogrusWriter struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w LogrusWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
	path := "/var/log/vuls/report.log"
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		path = filepath.Join(os.Getenv("APPDATA"), "vuls", "report.log")
 | 
			
		||||
	}
 | 
			
		||||
	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	log := logrus.New()
 | 
			
		||||
	log.Formatter = &formatter.TextFormatter{}
 | 
			
		||||
	log.Out = f
 | 
			
		||||
	log.Level = logrus.InfoLevel
 | 
			
		||||
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		text, err := toPlainText(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		log.Infof(text)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,70 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"gopkg.in/gomail.v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MailWriter send mail
 | 
			
		||||
type MailWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w MailWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		m := gomail.NewMessage()
 | 
			
		||||
		m.SetHeader("From", conf.Mail.From)
 | 
			
		||||
		m.SetHeader("To", conf.Mail.To...)
 | 
			
		||||
		m.SetHeader("Cc", conf.Mail.Cc...)
 | 
			
		||||
 | 
			
		||||
		subject := fmt.Sprintf("%s%s %s",
 | 
			
		||||
			conf.Mail.SubjectPrefix,
 | 
			
		||||
			s.ServerInfo(),
 | 
			
		||||
			s.CveSummary(),
 | 
			
		||||
		)
 | 
			
		||||
		m.SetHeader("Subject", subject)
 | 
			
		||||
 | 
			
		||||
		var body string
 | 
			
		||||
		if body, err = toPlainText(s); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		m.SetBody("text/plain", body)
 | 
			
		||||
		port, _ := strconv.Atoi(conf.Mail.SMTPPort)
 | 
			
		||||
		d := gomail.NewPlainDialer(
 | 
			
		||||
			conf.Mail.SMTPAddr,
 | 
			
		||||
			port,
 | 
			
		||||
			conf.Mail.User,
 | 
			
		||||
			conf.Mail.Password,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		d.TLSConfig = &tls.Config{
 | 
			
		||||
			InsecureSkipVerify: true,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := d.DialAndSend(m); err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										140
									
								
								report/s3.go
									
									
									
									
									
								
							
							
						
						
									
										140
									
								
								report/s3.go
									
									
									
									
									
								
							@@ -20,11 +20,14 @@ package report
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/credentials"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/session"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/service/s3"
 | 
			
		||||
 | 
			
		||||
@@ -32,6 +35,83 @@ import (
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// S3Writer writes results to S3
 | 
			
		||||
type S3Writer struct{}
 | 
			
		||||
 | 
			
		||||
func getS3() *s3.S3 {
 | 
			
		||||
	Config := &aws.Config{
 | 
			
		||||
		Region: aws.String(c.Conf.AwsRegion),
 | 
			
		||||
		Credentials: credentials.NewChainCredentials([]credentials.Provider{
 | 
			
		||||
			&credentials.EnvProvider{},
 | 
			
		||||
			&credentials.SharedCredentialsProvider{Filename: "", Profile: c.Conf.AwsProfile},
 | 
			
		||||
			&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(session.New())},
 | 
			
		||||
		}),
 | 
			
		||||
	}
 | 
			
		||||
	return s3.New(session.New(Config))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write results to S3
 | 
			
		||||
// http://docs.aws.amazon.com/sdk-for-go/latest/v1/developerguide/common-examples.title.html
 | 
			
		||||
func (w S3Writer) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	if len(rs) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	svc := getS3()
 | 
			
		||||
 | 
			
		||||
	if c.Conf.FormatOneLineText {
 | 
			
		||||
		timestr := rs[0].ScannedAt.Format(time.RFC3339)
 | 
			
		||||
		k := fmt.Sprintf(timestr + "/summary.txt")
 | 
			
		||||
		text := formatOneLineSummary(rs...)
 | 
			
		||||
		if err := putObject(svc, k, []byte(text)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		key := r.ReportKeyName()
 | 
			
		||||
		if c.Conf.FormatJSON {
 | 
			
		||||
			k := key + ".json"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = json.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			if err := putObject(svc, k, b); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatShortText {
 | 
			
		||||
			k := key + "_short.txt"
 | 
			
		||||
			text := formatShortPlainText(r)
 | 
			
		||||
			if err := putObject(svc, k, []byte(text)); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatFullText {
 | 
			
		||||
			k := key + "_full.txt"
 | 
			
		||||
			text := formatFullPlainText(r)
 | 
			
		||||
			if err := putObject(svc, k, []byte(text)); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatXML {
 | 
			
		||||
			k := key + ".xml"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = xml.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to XML: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
 | 
			
		||||
			if err := putObject(svc, k, allBytes); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfBucketExists check the existence of S3 bucket
 | 
			
		||||
func CheckIfBucketExists() error {
 | 
			
		||||
	svc := getS3()
 | 
			
		||||
@@ -57,56 +137,22 @@ func CheckIfBucketExists() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// S3Writer writes results to S3
 | 
			
		||||
type S3Writer struct{}
 | 
			
		||||
 | 
			
		||||
func getS3() *s3.S3 {
 | 
			
		||||
	return s3.New(session.New(&aws.Config{
 | 
			
		||||
		Region:      aws.String(c.Conf.AwsRegion),
 | 
			
		||||
		Credentials: credentials.NewSharedCredentials("", c.Conf.AwsProfile),
 | 
			
		||||
	}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write results to S3
 | 
			
		||||
func (w S3Writer) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
 | 
			
		||||
	var jsonBytes []byte
 | 
			
		||||
	if jsonBytes, err = json.Marshal(scanResults); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
func putObject(svc *s3.S3, k string, b []byte) error {
 | 
			
		||||
	var err error
 | 
			
		||||
	if c.Conf.GZIP {
 | 
			
		||||
		if b, err = gz(b); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		k = k + ".gz"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// http://docs.aws.amazon.com/sdk-for-go/latest/v1/developerguide/common-examples.title.html
 | 
			
		||||
	svc := getS3()
 | 
			
		||||
	timestr := time.Now().Format("20060102_1504")
 | 
			
		||||
	key := fmt.Sprintf("%s/%s", timestr, "all.json")
 | 
			
		||||
	_, err = svc.PutObject(&s3.PutObjectInput{
 | 
			
		||||
	if _, err := svc.PutObject(&s3.PutObjectInput{
 | 
			
		||||
		Bucket: &c.Conf.S3Bucket,
 | 
			
		||||
		Key:    &key,
 | 
			
		||||
		Body:   bytes.NewReader(jsonBytes),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to upload data to %s/%s, %s", c.Conf.S3Bucket, key, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range scanResults {
 | 
			
		||||
		key := ""
 | 
			
		||||
		if r.Container.ContainerID == "" {
 | 
			
		||||
			key = fmt.Sprintf("%s/%s.json", timestr, r.ServerName)
 | 
			
		||||
		} else {
 | 
			
		||||
			key = fmt.Sprintf("%s/%s_%s.json", timestr, r.ServerName, r.Container.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if jsonBytes, err = json.Marshal(r); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		_, err = svc.PutObject(&s3.PutObjectInput{
 | 
			
		||||
			Bucket: &c.Conf.S3Bucket,
 | 
			
		||||
			Key:    &key,
 | 
			
		||||
			Body:   bytes.NewReader(jsonBytes),
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to upload data to %s/%s, %s", c.Conf.S3Bucket, key, err)
 | 
			
		||||
		}
 | 
			
		||||
		Key:    &k,
 | 
			
		||||
		Body:   bytes.NewReader(b),
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to upload data to %s/%s, %s",
 | 
			
		||||
			c.Conf.S3Bucket, k, err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										144
									
								
								report/slack.go
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								report/slack.go
									
									
									
									
									
								
							@@ -20,6 +20,7 @@ package report
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
@@ -56,59 +57,109 @@ type message struct {
 | 
			
		||||
// SlackWriter send report to slack
 | 
			
		||||
type SlackWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w SlackWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
func (w SlackWriter) Write(rs ...models.ScanResult) error {
 | 
			
		||||
	conf := config.Conf.Slack
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
	channel := conf.Channel
 | 
			
		||||
 | 
			
		||||
		channel := conf.Channel
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		if channel == "${servername}" {
 | 
			
		||||
			channel = fmt.Sprintf("#%s", s.ServerName)
 | 
			
		||||
			channel = fmt.Sprintf("#%s", r.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		msg := message{
 | 
			
		||||
			Text:        msgText(s),
 | 
			
		||||
			Username:    conf.AuthUser,
 | 
			
		||||
			IconEmoji:   conf.IconEmoji,
 | 
			
		||||
			Channel:     channel,
 | 
			
		||||
			Attachments: toSlackAttachments(s),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bytes, _ := json.Marshal(msg)
 | 
			
		||||
		jsonBody := string(bytes)
 | 
			
		||||
		f := func() (err error) {
 | 
			
		||||
			resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).
 | 
			
		||||
				Send(string(jsonBody)).End()
 | 
			
		||||
			if resp.StatusCode != 200 {
 | 
			
		||||
				log.Errorf("Resonse body: %s", body)
 | 
			
		||||
				if len(errs) > 0 {
 | 
			
		||||
					return errs[0]
 | 
			
		||||
				}
 | 
			
		||||
		if 0 < len(r.Errors) {
 | 
			
		||||
			serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
 | 
			
		||||
			notifyUsers := getNotifyUsers(config.Conf.Slack.NotifyUsers)
 | 
			
		||||
			txt := fmt.Sprintf("%s\n%s\nError: %s", notifyUsers, serverInfo, r.Errors)
 | 
			
		||||
			msg := message{
 | 
			
		||||
				Text:      txt,
 | 
			
		||||
				Username:  conf.AuthUser,
 | 
			
		||||
				IconEmoji: conf.IconEmoji,
 | 
			
		||||
				Channel:   channel,
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
			if err := send(msg); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		notify := func(err error, t time.Duration) {
 | 
			
		||||
			log.Warn("Retrying in ", t)
 | 
			
		||||
 | 
			
		||||
		// A maximum of 100 attachments are allowed on a message.
 | 
			
		||||
		// Split into chunks with 100 elements
 | 
			
		||||
		// https://api.slack.com/methods/chat.postMessage
 | 
			
		||||
		maxAttachments := 100
 | 
			
		||||
		m := map[int][]*attachment{}
 | 
			
		||||
		for i, a := range toSlackAttachments(r) {
 | 
			
		||||
			m[i/maxAttachments] = append(m[i/maxAttachments], a)
 | 
			
		||||
		}
 | 
			
		||||
		if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
 | 
			
		||||
			return fmt.Errorf("HTTP Error: %s", err)
 | 
			
		||||
		chunkKeys := []int{}
 | 
			
		||||
		for k := range m {
 | 
			
		||||
			chunkKeys = append(chunkKeys, k)
 | 
			
		||||
		}
 | 
			
		||||
		sort.Ints(chunkKeys)
 | 
			
		||||
 | 
			
		||||
		for i, k := range chunkKeys {
 | 
			
		||||
			txt := ""
 | 
			
		||||
			if i == 0 {
 | 
			
		||||
				txt = msgText(r)
 | 
			
		||||
			}
 | 
			
		||||
			msg := message{
 | 
			
		||||
				Text:        txt,
 | 
			
		||||
				Username:    conf.AuthUser,
 | 
			
		||||
				IconEmoji:   conf.IconEmoji,
 | 
			
		||||
				Channel:     channel,
 | 
			
		||||
				Attachments: m[k],
 | 
			
		||||
			}
 | 
			
		||||
			if err := send(msg); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func msgText(r models.ScanResult) string {
 | 
			
		||||
func send(msg message) error {
 | 
			
		||||
	conf := config.Conf.Slack
 | 
			
		||||
	count, retryMax := 0, 10
 | 
			
		||||
 | 
			
		||||
	bytes, _ := json.Marshal(msg)
 | 
			
		||||
	jsonBody := string(bytes)
 | 
			
		||||
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).Send(string(jsonBody)).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			count++
 | 
			
		||||
			if count == retryMax {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"HTTP POST error: %v, url: %s, resp: %v, body: %s",
 | 
			
		||||
				errs, conf.HookURL, resp, body)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		log.Warnf("Error %s", err)
 | 
			
		||||
		log.Warn("Retrying in ", t)
 | 
			
		||||
	}
 | 
			
		||||
	boff := backoff.NewExponentialBackOff()
 | 
			
		||||
	if err := backoff.RetryNotify(f, boff, notify); err != nil {
 | 
			
		||||
		return fmt.Errorf("HTTP error: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if count == retryMax {
 | 
			
		||||
		return fmt.Errorf("Retry count exceeded")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func msgText(r models.ScanResult) string {
 | 
			
		||||
	notifyUsers := ""
 | 
			
		||||
	if 0 < len(r.KnownCves) || 0 < len(r.UnknownCves) {
 | 
			
		||||
		notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, serverInfo, r.CveSummary())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
 | 
			
		||||
 | 
			
		||||
	cves := scanResult.KnownCves
 | 
			
		||||
	if !config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		cves = append(cves, scanResult.UnknownCves...)
 | 
			
		||||
@@ -121,8 +172,8 @@ func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
 | 
			
		||||
		for _, p := range cveInfo.Packages {
 | 
			
		||||
			curentPackages = append(curentPackages, p.ToStringCurrentVersion())
 | 
			
		||||
		}
 | 
			
		||||
		for _, cpename := range cveInfo.CpeNames {
 | 
			
		||||
			curentPackages = append(curentPackages, cpename.Name)
 | 
			
		||||
		for _, n := range cveInfo.CpeNames {
 | 
			
		||||
			curentPackages = append(curentPackages, n)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		newPackages := []string{}
 | 
			
		||||
@@ -170,41 +221,54 @@ func color(cvssScore float64) string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func attachmentText(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
 | 
			
		||||
	linkText := links(cveInfo, osFamily)
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case config.Conf.Lang == "ja" &&
 | 
			
		||||
		0 < cveInfo.CveDetail.Jvn.CvssScore():
 | 
			
		||||
 | 
			
		||||
		jvn := cveInfo.CveDetail.Jvn
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s\n*Confidence:* %v",
 | 
			
		||||
			cveInfo.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
			jvn.CvssSeverity(),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, jvn.CvssVector()),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate,
 | 
			
		||||
				cveInfo.CveDetail.CveID, jvn.CvssVector()),
 | 
			
		||||
			jvn.CvssVector(),
 | 
			
		||||
			jvn.CveTitle(),
 | 
			
		||||
			linkText,
 | 
			
		||||
			cveInfo.VulnInfo.Confidence,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
	case 0 < cveInfo.CveDetail.CvssScore("en"):
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s\n*Confidence:* %v",
 | 
			
		||||
			cveInfo.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
			nvd.CvssSeverity(),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, nvd.CvssVector()),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate,
 | 
			
		||||
				cveInfo.CveDetail.CveID, nvd.CvssVector()),
 | 
			
		||||
			nvd.CvssVector(),
 | 
			
		||||
			nvd.CveSummary(),
 | 
			
		||||
			linkText,
 | 
			
		||||
			cveInfo.VulnInfo.Confidence,
 | 
			
		||||
		)
 | 
			
		||||
	default:
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		return fmt.Sprintf("?\n%s\n%s", nvd.CveSummary(), linkText)
 | 
			
		||||
		return fmt.Sprintf("?\n%s\n%s\n*Confidence:* %v",
 | 
			
		||||
			nvd.CveSummary(), linkText, cveInfo.VulnInfo.Confidence)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func links(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	links := []string{}
 | 
			
		||||
 | 
			
		||||
	cweID := cveInfo.CveDetail.CweID()
 | 
			
		||||
	if 0 < len(cweID) {
 | 
			
		||||
		links = append(links, fmt.Sprintf("<%s|%s>",
 | 
			
		||||
			cweURL(cweID), cweID))
 | 
			
		||||
		if config.Conf.Lang == "ja" {
 | 
			
		||||
			links = append(links, fmt.Sprintf("<%s|%s(JVN)>",
 | 
			
		||||
				cweJvnURL(cweID), cweID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
	if config.Conf.Lang == "ja" && 0 < len(cveInfo.CveDetail.Jvn.Link()) {
 | 
			
		||||
		jvn := fmt.Sprintf("<%s|JVN>", cveInfo.CveDetail.Jvn.Link())
 | 
			
		||||
 
 | 
			
		||||
@@ -20,19 +20,40 @@ package report
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// StdoutWriter write to stdout
 | 
			
		||||
type StdoutWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w StdoutWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		text, err := toPlainText(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
// WriteScanSummary prints Scan summary at the end of scan
 | 
			
		||||
func (w StdoutWriter) WriteScanSummary(rs ...models.ScanResult) {
 | 
			
		||||
	fmt.Printf("\n\n")
 | 
			
		||||
	fmt.Println("One Line Summary")
 | 
			
		||||
	fmt.Println("================")
 | 
			
		||||
	fmt.Printf("%s\n", formatScanSummary(rs...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w StdoutWriter) Write(rs ...models.ScanResult) error {
 | 
			
		||||
	if c.Conf.FormatOneLineText {
 | 
			
		||||
		fmt.Print("\n\n")
 | 
			
		||||
		fmt.Println("One Line Summary")
 | 
			
		||||
		fmt.Println("================")
 | 
			
		||||
		fmt.Println(formatOneLineSummary(rs...))
 | 
			
		||||
		fmt.Print("\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.FormatShortText {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			fmt.Println(formatShortPlainText(r))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.FormatFullText {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			fmt.Println(formatFullPlainText(r))
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println(text)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,63 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TextFileWriter writes results to file.
 | 
			
		||||
type TextFileWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w TextFileWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
 | 
			
		||||
	path, err := ensureResultDir()
 | 
			
		||||
 | 
			
		||||
	all := []string{}
 | 
			
		||||
	for _, r := range scanResults {
 | 
			
		||||
		textFilePath := ""
 | 
			
		||||
		if r.Container.ContainerID == "" {
 | 
			
		||||
			textFilePath = filepath.Join(path, fmt.Sprintf("%s.txt", r.ServerName))
 | 
			
		||||
		} else {
 | 
			
		||||
			textFilePath = filepath.Join(path,
 | 
			
		||||
				fmt.Sprintf("%s_%s.txt", r.ServerName, r.Container.Name))
 | 
			
		||||
		}
 | 
			
		||||
		text, err := toPlainText(r)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		all = append(all, text)
 | 
			
		||||
		b := []byte(text)
 | 
			
		||||
		if err := ioutil.WriteFile(textFilePath, b, 0644); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to write text files. path: %s, err: %s", textFilePath, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	text := strings.Join(all, "\n\n")
 | 
			
		||||
	b := []byte(text)
 | 
			
		||||
	allPath := filepath.Join(path, "all.txt")
 | 
			
		||||
	if err := ioutil.WriteFile(allPath, b, 0644); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to write text files. path: %s, err: %s", allPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										253
									
								
								report/tui.go
									
									
									
									
									
								
							
							
						
						
									
										253
									
								
								report/tui.go
									
									
									
									
									
								
							@@ -20,13 +20,13 @@ package report
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"text/template"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/db"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"github.com/gosuri/uitable"
 | 
			
		||||
@@ -40,45 +40,33 @@ var currentCveInfo int
 | 
			
		||||
var currentDetailLimitY int
 | 
			
		||||
 | 
			
		||||
// RunTui execute main logic
 | 
			
		||||
func RunTui(historyID string) subcommands.ExitStatus {
 | 
			
		||||
	var err error
 | 
			
		||||
	scanHistory, err = selectScanHistory(historyID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
func RunTui(history models.ScanHistory) subcommands.ExitStatus {
 | 
			
		||||
	scanHistory = history
 | 
			
		||||
 | 
			
		||||
	g := gocui.NewGui()
 | 
			
		||||
	if err := g.Init(); err != nil {
 | 
			
		||||
		log.Panicln(err)
 | 
			
		||||
	g, err := gocui.NewGui(gocui.OutputNormal)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Errorf("%s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	defer g.Close()
 | 
			
		||||
 | 
			
		||||
	g.SetLayout(layout)
 | 
			
		||||
	g.SetManagerFunc(layout)
 | 
			
		||||
	if err := keybindings(g); err != nil {
 | 
			
		||||
		log.Panicln(err)
 | 
			
		||||
		log.Errorf("%s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	g.SelBgColor = gocui.ColorGreen
 | 
			
		||||
	g.SelFgColor = gocui.ColorBlack
 | 
			
		||||
	g.Cursor = true
 | 
			
		||||
 | 
			
		||||
	if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
 | 
			
		||||
		log.Panicln(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	if err := g.MainLoop(); err != nil {
 | 
			
		||||
		g.Close()
 | 
			
		||||
		log.Errorf("%s", err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func selectScanHistory(historyID string) (latest models.ScanHistory, err error) {
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		return latest, fmt.Errorf(
 | 
			
		||||
			"Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	latest, err = db.SelectScanHistory(historyID)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func keybindings(g *gocui.Gui) (err error) {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
@@ -145,6 +133,27 @@ func keybindings(g *gocui.Gui) (err error) {
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, nextView))
 | 
			
		||||
 | 
			
		||||
	// changelog
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyTab, gocui.ModNone, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlQ, gocui.ModNone, previousView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlH, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlL, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowUp, gocui.ModAlt, previousView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowLeft, gocui.ModAlt, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeySpace, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyEnter, gocui.ModNone, nextView))
 | 
			
		||||
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, showMsg))
 | 
			
		||||
 | 
			
		||||
@@ -163,35 +172,45 @@ func keybindings(g *gocui.Gui) (err error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func nextView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		return g.SetCurrentView("summary")
 | 
			
		||||
		_, err = g.SetCurrentView("summary")
 | 
			
		||||
	case "summary":
 | 
			
		||||
		return g.SetCurrentView("detail")
 | 
			
		||||
		_, err = g.SetCurrentView("detail")
 | 
			
		||||
	case "detail":
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
		_, err = g.SetCurrentView("changelog")
 | 
			
		||||
	case "changelog":
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
	default:
 | 
			
		||||
		return g.SetCurrentView("summary")
 | 
			
		||||
		_, err = g.SetCurrentView("summary")
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func previousView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
	case "summary":
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
	case "detail":
 | 
			
		||||
		return g.SetCurrentView("summary")
 | 
			
		||||
		_, err = g.SetCurrentView("summary")
 | 
			
		||||
	case "changelog":
 | 
			
		||||
		_, err = g.SetCurrentView("detail")
 | 
			
		||||
	default:
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
 | 
			
		||||
@@ -203,7 +222,7 @@ func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
 | 
			
		||||
		}
 | 
			
		||||
		return true, yLimit
 | 
			
		||||
	case "summary":
 | 
			
		||||
		yLimit = len(currentScanResult.KnownCves) - 1
 | 
			
		||||
		yLimit = len(currentScanResult.AllCves()) - 1
 | 
			
		||||
		if yLimit < nextY {
 | 
			
		||||
			return false, yLimit
 | 
			
		||||
		}
 | 
			
		||||
@@ -213,6 +232,11 @@ func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
 | 
			
		||||
			return false, currentDetailLimitY
 | 
			
		||||
		}
 | 
			
		||||
		return true, currentDetailLimitY
 | 
			
		||||
	case "changelog":
 | 
			
		||||
		if currentDetailLimitY < nextY {
 | 
			
		||||
			return false, currentDetailLimitY
 | 
			
		||||
		}
 | 
			
		||||
		return true, currentDetailLimitY
 | 
			
		||||
	default:
 | 
			
		||||
		return true, 0
 | 
			
		||||
	}
 | 
			
		||||
@@ -223,7 +247,7 @@ func pageUpDownJumpCount(v *gocui.View) int {
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side", "summary":
 | 
			
		||||
		jump = 8
 | 
			
		||||
	case "detail":
 | 
			
		||||
	case "detail", "changelog":
 | 
			
		||||
		jump = 30
 | 
			
		||||
	default:
 | 
			
		||||
		jump = 8
 | 
			
		||||
@@ -238,6 +262,9 @@ func onMovingCursorRedrawView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
		if err := redrawDetail(g); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := redrawChangelog(g); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case "side":
 | 
			
		||||
		if err := changeHost(g, v); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
@@ -332,7 +359,7 @@ func cursorUp(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		ox, oy := v.Origin()
 | 
			
		||||
		cx, cy := v.Cursor()
 | 
			
		||||
		if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
 | 
			
		||||
		if err := v.SetCursor(cx, cy-1); err != nil && 0 < oy {
 | 
			
		||||
			if err := v.SetOrigin(ox, oy-1); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
@@ -360,7 +387,7 @@ func cursorPageUp(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
func previousSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		// cursor to summary
 | 
			
		||||
		if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// move next line
 | 
			
		||||
@@ -368,7 +395,7 @@ func previousSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// cursor to detail
 | 
			
		||||
		if err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -378,7 +405,7 @@ func previousSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
func nextSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		// cursor to summary
 | 
			
		||||
		if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// move next line
 | 
			
		||||
@@ -386,7 +413,7 @@ func nextSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// cursor to detail
 | 
			
		||||
		if err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -401,6 +428,9 @@ func changeHost(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if err := g.DeleteView("detail"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := g.DeleteView("changelog"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, cy := v.Cursor()
 | 
			
		||||
	l, err := v.Line(cy)
 | 
			
		||||
@@ -422,6 +452,9 @@ func changeHost(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if err := setDetailLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := setChangelogLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -436,6 +469,17 @@ func redrawDetail(g *gocui.Gui) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func redrawChangelog(g *gocui.Gui) error {
 | 
			
		||||
	if err := g.DeleteView("changelog"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := setChangelogLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getLine(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	var l string
 | 
			
		||||
	var err error
 | 
			
		||||
@@ -451,7 +495,7 @@ func getLine(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintln(v, l)
 | 
			
		||||
		if err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -473,7 +517,7 @@ func showMsg(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintln(v, l)
 | 
			
		||||
		if err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -484,7 +528,7 @@ func delMsg(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if err := g.DeleteView("msg"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
	if _, err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -504,12 +548,15 @@ func layout(g *gocui.Gui) error {
 | 
			
		||||
	if err := setDetailLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := setChangelogLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setSideLayout(g *gocui.Gui) error {
 | 
			
		||||
	_, maxY := g.Size()
 | 
			
		||||
	if v, err := g.SetView("side", -1, -1, 40, maxY); err != nil {
 | 
			
		||||
	if v, err := g.SetView("side", -1, -1, 40, int(float64(maxY)*0.2)); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
@@ -518,8 +565,11 @@ func setSideLayout(g *gocui.Gui) error {
 | 
			
		||||
		for _, result := range scanHistory.ScanResults {
 | 
			
		||||
			fmt.Fprintln(v, result.ServerInfoTui())
 | 
			
		||||
		}
 | 
			
		||||
		if len(scanHistory.ScanResults) == 0 {
 | 
			
		||||
			return fmt.Errorf("No scan results")
 | 
			
		||||
		}
 | 
			
		||||
		currentScanResult = scanHistory.ScanResults[0]
 | 
			
		||||
		if err := g.SetCurrentView("side"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("side"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -533,7 +583,7 @@ func setSummaryLayout(g *gocui.Gui) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lines := summaryLines(currentScanResult)
 | 
			
		||||
		lines := summaryLines()
 | 
			
		||||
		fmt.Fprintf(v, lines)
 | 
			
		||||
 | 
			
		||||
		v.Highlight = true
 | 
			
		||||
@@ -543,21 +593,25 @@ func setSummaryLayout(g *gocui.Gui) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func summaryLines(data models.ScanResult) string {
 | 
			
		||||
func summaryLines() string {
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = 1000
 | 
			
		||||
	stable.Wrap = false
 | 
			
		||||
 | 
			
		||||
	if len(currentScanResult.Errors) != 0 {
 | 
			
		||||
		return "Error: Scan with --debug to view the details"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	indexFormat := ""
 | 
			
		||||
	if len(data.KnownCves) < 10 {
 | 
			
		||||
	if len(currentScanResult.AllCves()) < 10 {
 | 
			
		||||
		indexFormat = "[%1d]"
 | 
			
		||||
	} else if len(data.KnownCves) < 100 {
 | 
			
		||||
	} else if len(currentScanResult.AllCves()) < 100 {
 | 
			
		||||
		indexFormat = "[%2d]"
 | 
			
		||||
	} else {
 | 
			
		||||
		indexFormat = "[%3d]"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, d := range data.KnownCves {
 | 
			
		||||
	for i, d := range currentScanResult.AllCves() {
 | 
			
		||||
		var cols []string
 | 
			
		||||
		//  packs := []string{}
 | 
			
		||||
		//  for _, pack := range d.Packages {
 | 
			
		||||
@@ -568,11 +622,9 @@ func summaryLines(data models.ScanResult) string {
 | 
			
		||||
			cols = []string{
 | 
			
		||||
				fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("|  %-4.1f(%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Jvn.CvssSeverity(),
 | 
			
		||||
				),
 | 
			
		||||
				//  strings.Join(packs, ","),
 | 
			
		||||
				fmt.Sprintf("| %4.1f",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang)),
 | 
			
		||||
				fmt.Sprintf("| %3d |", d.VulnInfo.Confidence.Score),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
@@ -580,18 +632,17 @@ func summaryLines(data models.ScanResult) string {
 | 
			
		||||
 | 
			
		||||
			var cvssScore string
 | 
			
		||||
			if d.CveDetail.CvssScore("en") <= 0 {
 | 
			
		||||
				cvssScore = "| ?"
 | 
			
		||||
				cvssScore = "|   ?"
 | 
			
		||||
			} else {
 | 
			
		||||
				cvssScore = fmt.Sprintf("| %-4.1f(%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Nvd.CvssSeverity(),
 | 
			
		||||
				)
 | 
			
		||||
				cvssScore = fmt.Sprintf("| %4.1f",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			cols = []string{
 | 
			
		||||
				fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				cvssScore,
 | 
			
		||||
				fmt.Sprintf("| %3d |", d.VulnInfo.Confidence.Score),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -616,14 +667,10 @@ func setDetailLayout(g *gocui.Gui) error {
 | 
			
		||||
	_, oy := summaryView.Origin()
 | 
			
		||||
	currentCveInfo = cy + oy
 | 
			
		||||
 | 
			
		||||
	if v, err := g.SetView("detail", 40, int(float64(maxY)*0.2), maxX, maxY); err != nil {
 | 
			
		||||
	if v, err := g.SetView("detail", -1, int(float64(maxY)*0.2), int(float64(maxX)*0.5), maxY); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		//  text := report.ToPlainTextDetailsLangEn(
 | 
			
		||||
		//      currentScanResult.KnownCves[currentCveInfo],
 | 
			
		||||
		//      currentScanResult.Family)
 | 
			
		||||
 | 
			
		||||
		text, err := detailLines()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
@@ -637,26 +684,70 @@ func setDetailLayout(g *gocui.Gui) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setChangelogLayout(g *gocui.Gui) error {
 | 
			
		||||
	maxX, maxY := g.Size()
 | 
			
		||||
 | 
			
		||||
	summaryView, err := g.View("summary")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	_, cy := summaryView.Cursor()
 | 
			
		||||
	_, oy := summaryView.Origin()
 | 
			
		||||
	currentCveInfo = cy + oy
 | 
			
		||||
 | 
			
		||||
	if v, err := g.SetView("changelog", int(float64(maxX)*0.5), int(float64(maxY)*0.2), maxX, maxY); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if len(currentScanResult.Errors) != 0 || len(currentScanResult.AllCves()) == 0 {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lines := []string{}
 | 
			
		||||
		cveInfo := currentScanResult.AllCves()[currentCveInfo]
 | 
			
		||||
		for _, pack := range cveInfo.Packages {
 | 
			
		||||
			for _, p := range currentScanResult.Packages {
 | 
			
		||||
				if pack.Name == p.Name {
 | 
			
		||||
					lines = append(lines, formatOneChangelog(p), "\n")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		text := strings.Join(lines, "\n")
 | 
			
		||||
		fmt.Fprint(v, text)
 | 
			
		||||
		v.Editable = false
 | 
			
		||||
		v.Wrap = true
 | 
			
		||||
 | 
			
		||||
		currentDetailLimitY = len(strings.Split(text, "\n")) - 1
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dataForTmpl struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	CvssScore        string
 | 
			
		||||
	CvssVector       string
 | 
			
		||||
	CvssSeverity     string
 | 
			
		||||
	Summary          string
 | 
			
		||||
	Confidence       models.Confidence
 | 
			
		||||
	CweURL           string
 | 
			
		||||
	VulnSiteLinks    []string
 | 
			
		||||
	References       []cve.Reference
 | 
			
		||||
	Packages         []string
 | 
			
		||||
	CpeNames         []models.CpeName
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
	PublishedDate    time.Time
 | 
			
		||||
	LastModifiedDate time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detailLines() (string, error) {
 | 
			
		||||
	if len(currentScanResult.KnownCves) == 0 {
 | 
			
		||||
	if len(currentScanResult.Errors) != 0 {
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(currentScanResult.AllCves()) == 0 {
 | 
			
		||||
		return "No vulnerable packages", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveInfo := currentScanResult.KnownCves[currentCveInfo]
 | 
			
		||||
	cveInfo := currentScanResult.AllCves()[currentCveInfo]
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
 | 
			
		||||
	tmpl, err := template.New("detail").Parse(detailTemplate())
 | 
			
		||||
@@ -682,6 +773,8 @@ func detailLines() (string, error) {
 | 
			
		||||
		refs = nvd.VulnSiteReferences()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cweURL := cweURL(cveInfo.CveDetail.CweID())
 | 
			
		||||
 | 
			
		||||
	links := []string{
 | 
			
		||||
		fmt.Sprintf("[NVD]( %s )", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID)),
 | 
			
		||||
		fmt.Sprintf("[MITRE]( %s )", fmt.Sprintf("%s%s", mitreBaseURL, cveID)),
 | 
			
		||||
@@ -715,6 +808,8 @@ func detailLines() (string, error) {
 | 
			
		||||
		CvssSeverity:  cvssSeverity,
 | 
			
		||||
		CvssVector:    cvssVector,
 | 
			
		||||
		Summary:       summary,
 | 
			
		||||
		Confidence:    cveInfo.VulnInfo.Confidence,
 | 
			
		||||
		CweURL:        cweURL,
 | 
			
		||||
		VulnSiteLinks: links,
 | 
			
		||||
		References:    refs,
 | 
			
		||||
		Packages:      packages,
 | 
			
		||||
@@ -729,8 +824,6 @@ func detailLines() (string, error) {
 | 
			
		||||
	return string(buf.Bytes()), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  * {{.Name}}-{{.Version}}-{{.Release}}
 | 
			
		||||
 | 
			
		||||
func detailTemplate() string {
 | 
			
		||||
	return `
 | 
			
		||||
{{.CveID}}
 | 
			
		||||
@@ -746,14 +839,24 @@ Summary
 | 
			
		||||
 | 
			
		||||
 {{.Summary }}
 | 
			
		||||
 | 
			
		||||
Confidence
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
 {{.Confidence }}
 | 
			
		||||
 | 
			
		||||
CWE
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
 {{.CweURL }}
 | 
			
		||||
 | 
			
		||||
Package/CPE
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range $pack := .Packages -}}
 | 
			
		||||
* {{$pack}}
 | 
			
		||||
{{end -}}
 | 
			
		||||
{{range .CpeNames -}}
 | 
			
		||||
* {{.Name}}
 | 
			
		||||
{{range $name := .CpeNames -}}
 | 
			
		||||
* {{$name}}
 | 
			
		||||
{{end}}
 | 
			
		||||
Links
 | 
			
		||||
--------------
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										338
									
								
								report/util.go
									
									
									
									
									
								
							
							
						
						
									
										338
									
								
								report/util.go
									
									
									
									
									
								
							@@ -20,88 +20,68 @@ package report
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/gosuri/uitable"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ensureResultDir() (path string, err error) {
 | 
			
		||||
	if resultDirPath != "" {
 | 
			
		||||
		return resultDirPath, nil
 | 
			
		||||
	}
 | 
			
		||||
const maxColWidth = 80
 | 
			
		||||
 | 
			
		||||
	const timeLayout = "20060102_1504"
 | 
			
		||||
	timedir := time.Now().Format(timeLayout)
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	dir := filepath.Join(wd, "results", timedir)
 | 
			
		||||
	if err := os.MkdirAll(dir, 0755); err != nil {
 | 
			
		||||
		return "", fmt.Errorf("Failed to create dir: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	symlinkPath := filepath.Join(wd, "results", "current")
 | 
			
		||||
	if _, err := os.Stat(symlinkPath); err == nil {
 | 
			
		||||
		if err := os.Remove(symlinkPath); err != nil {
 | 
			
		||||
			return "", fmt.Errorf(
 | 
			
		||||
				"Failed to remove symlink. path: %s, err: %s", symlinkPath, err)
 | 
			
		||||
func formatScanSummary(rs ...models.ScanResult) string {
 | 
			
		||||
	table := uitable.New()
 | 
			
		||||
	table.MaxColWidth = maxColWidth
 | 
			
		||||
	table.Wrap = true
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		var cols []interface{}
 | 
			
		||||
		if len(r.Errors) == 0 {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
				r.FormatServerName(),
 | 
			
		||||
				fmt.Sprintf("%s%s", r.Family, r.Release),
 | 
			
		||||
				fmt.Sprintf("%d CVEs", len(r.ScannedCves)),
 | 
			
		||||
				r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
				r.FormatServerName(),
 | 
			
		||||
				"Error",
 | 
			
		||||
				"",
 | 
			
		||||
				"Run with --debug to view the details",
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow(cols...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := os.Symlink(dir, symlinkPath); err != nil {
 | 
			
		||||
		return "", fmt.Errorf(
 | 
			
		||||
			"Failed to create symlink: path: %s, err: %s", symlinkPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	return dir, nil
 | 
			
		||||
	return fmt.Sprintf("%s\n", table)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toPlainText(scanResult models.ScanResult) (string, error) {
 | 
			
		||||
	serverInfo := scanResult.ServerInfo()
 | 
			
		||||
 | 
			
		||||
	var buffer bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(serverInfo); i++ {
 | 
			
		||||
		buffer.WriteString("=")
 | 
			
		||||
func formatOneLineSummary(rs ...models.ScanResult) string {
 | 
			
		||||
	table := uitable.New()
 | 
			
		||||
	table.MaxColWidth = maxColWidth
 | 
			
		||||
	table.Wrap = true
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		var cols []interface{}
 | 
			
		||||
		if len(r.Errors) == 0 {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
				r.FormatServerName(),
 | 
			
		||||
				r.CveSummary(),
 | 
			
		||||
				r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
				r.FormatServerName(),
 | 
			
		||||
				"Error: Scan with --debug to view the details",
 | 
			
		||||
				"",
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow(cols...)
 | 
			
		||||
	}
 | 
			
		||||
	header := fmt.Sprintf("%s\n%s", serverInfo, buffer.String())
 | 
			
		||||
 | 
			
		||||
	if len(scanResult.KnownCves) == 0 && len(scanResult.UnknownCves) == 0 {
 | 
			
		||||
		return fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
No unsecure packages.
 | 
			
		||||
`, header), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	summary := ToPlainTextSummary(scanResult)
 | 
			
		||||
	scoredReport, unscoredReport := []string{}, []string{}
 | 
			
		||||
	scoredReport, unscoredReport = toPlainTextDetails(scanResult, scanResult.Family)
 | 
			
		||||
 | 
			
		||||
	scored := strings.Join(scoredReport, "\n\n")
 | 
			
		||||
 | 
			
		||||
	unscored := ""
 | 
			
		||||
	if !config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		unscored = strings.Join(unscoredReport, "\n\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	detail := fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
 | 
			
		||||
%s
 | 
			
		||||
`,
 | 
			
		||||
		scored,
 | 
			
		||||
		unscored,
 | 
			
		||||
	)
 | 
			
		||||
	text := fmt.Sprintf("%s\n%s\n%s\n", header, summary, detail)
 | 
			
		||||
 | 
			
		||||
	return text, nil
 | 
			
		||||
	return fmt.Sprintf("%s\n", table)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToPlainTextSummary format summary for plain text.
 | 
			
		||||
func ToPlainTextSummary(r models.ScanResult) string {
 | 
			
		||||
func formatShortPlainText(r models.ScanResult) string {
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = 84
 | 
			
		||||
	stable.MaxColWidth = maxColWidth
 | 
			
		||||
	stable.Wrap = true
 | 
			
		||||
 | 
			
		||||
	cves := r.KnownCves
 | 
			
		||||
@@ -109,14 +89,52 @@ func ToPlainTextSummary(r models.ScanResult) string {
 | 
			
		||||
		cves = append(cves, r.UnknownCves...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, d := range cves {
 | 
			
		||||
		var scols []string
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(r.ServerInfo()); i++ {
 | 
			
		||||
		buf.WriteString("=")
 | 
			
		||||
	}
 | 
			
		||||
	header := fmt.Sprintf("%s\n%s\n%s\t%s\n\n",
 | 
			
		||||
		r.ServerInfo(),
 | 
			
		||||
		buf.String(),
 | 
			
		||||
		r.CveSummary(),
 | 
			
		||||
		r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if len(r.Errors) != 0 {
 | 
			
		||||
		return fmt.Sprintf(
 | 
			
		||||
			"%s\nError: Scan with --debug to view the details\n%s\n\n",
 | 
			
		||||
			header, r.Errors)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(cves) == 0 {
 | 
			
		||||
		return fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
No CVE-IDs are found in updatable packages.
 | 
			
		||||
%s
 | 
			
		||||
`, header, r.Packages.FormatUpdatablePacksSummary())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, d := range cves {
 | 
			
		||||
		var packsVer string
 | 
			
		||||
		for _, p := range d.Packages {
 | 
			
		||||
			packsVer += fmt.Sprintf(
 | 
			
		||||
				"%s -> %s\n", p.ToStringCurrentVersion(), p.ToStringNewVersion())
 | 
			
		||||
		}
 | 
			
		||||
		for _, n := range d.CpeNames {
 | 
			
		||||
			packsVer += n
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var scols []string
 | 
			
		||||
		switch {
 | 
			
		||||
		case config.Conf.Lang == "ja" &&
 | 
			
		||||
			0 < d.CveDetail.Jvn.CvssScore():
 | 
			
		||||
 | 
			
		||||
			summary := d.CveDetail.Jvn.CveTitle()
 | 
			
		||||
			summary := fmt.Sprintf("%s\n%s\n%s\n%sConfidence: %v",
 | 
			
		||||
				d.CveDetail.Jvn.CveTitle(),
 | 
			
		||||
				d.CveDetail.Jvn.Link(),
 | 
			
		||||
				distroLinks(d, r.Family)[0].url,
 | 
			
		||||
				packsVer,
 | 
			
		||||
				d.VulnInfo.Confidence,
 | 
			
		||||
			)
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("%-4.1f (%s)",
 | 
			
		||||
@@ -125,8 +143,16 @@ func ToPlainTextSummary(r models.ScanResult) string {
 | 
			
		||||
				),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case 0 < d.CveDetail.CvssScore("en"):
 | 
			
		||||
			summary := d.CveDetail.Nvd.CveSummary()
 | 
			
		||||
			summary := fmt.Sprintf("%s\n%s/%s\n%s\n%sConfidence: %v",
 | 
			
		||||
				d.CveDetail.Nvd.CveSummary(),
 | 
			
		||||
				cveDetailsBaseURL,
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				distroLinks(d, r.Family)[0].url,
 | 
			
		||||
				packsVer,
 | 
			
		||||
				d.VulnInfo.Confidence,
 | 
			
		||||
			)
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("%-4.1f (%s)",
 | 
			
		||||
@@ -136,10 +162,12 @@ func ToPlainTextSummary(r models.ScanResult) string {
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			summary := fmt.Sprintf("%s\n%sConfidence: %v",
 | 
			
		||||
				distroLinks(d, r.Family)[0].url, packsVer, d.VulnInfo.Confidence)
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				"?",
 | 
			
		||||
				d.CveDetail.Nvd.CveSummary(),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -148,45 +176,94 @@ func ToPlainTextSummary(r models.ScanResult) string {
 | 
			
		||||
			cols[i] = scols[i]
 | 
			
		||||
		}
 | 
			
		||||
		stable.AddRow(cols...)
 | 
			
		||||
		stable.AddRow("")
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s", stable)
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n", header, stable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
 | 
			
		||||
	for _, cve := range data.KnownCves {
 | 
			
		||||
func formatFullPlainText(r models.ScanResult) string {
 | 
			
		||||
	serverInfo := r.ServerInfo()
 | 
			
		||||
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(serverInfo); i++ {
 | 
			
		||||
		buf.WriteString("=")
 | 
			
		||||
	}
 | 
			
		||||
	header := fmt.Sprintf("%s\n%s\n%s\t%s\n",
 | 
			
		||||
		r.ServerInfo(),
 | 
			
		||||
		buf.String(),
 | 
			
		||||
		r.CveSummary(),
 | 
			
		||||
		r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if len(r.Errors) != 0 {
 | 
			
		||||
		return fmt.Sprintf(
 | 
			
		||||
			"%s\nError: Scan with --debug to view the details\n%s\n\n",
 | 
			
		||||
			header, r.Errors)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(r.KnownCves) == 0 && len(r.UnknownCves) == 0 {
 | 
			
		||||
		return fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
No CVE-IDs are found in updatable packages.
 | 
			
		||||
%s
 | 
			
		||||
`, header, r.Packages.FormatUpdatablePacksSummary())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scoredReport, unscoredReport := []string{}, []string{}
 | 
			
		||||
	scoredReport, unscoredReport = formatPlainTextDetails(r, r.Family)
 | 
			
		||||
 | 
			
		||||
	unscored := ""
 | 
			
		||||
	if !config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		unscored = strings.Join(unscoredReport, "\n\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scored := strings.Join(scoredReport, "\n\n")
 | 
			
		||||
	detail := fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
 | 
			
		||||
%s
 | 
			
		||||
`,
 | 
			
		||||
		scored,
 | 
			
		||||
		unscored,
 | 
			
		||||
	)
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n%s", header, detail, formatChangelogs(r))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatPlainTextDetails(r models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
 | 
			
		||||
	for _, cve := range r.KnownCves {
 | 
			
		||||
		switch config.Conf.Lang {
 | 
			
		||||
		case "en":
 | 
			
		||||
			if 0 < cve.CveDetail.Nvd.CvssScore() {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
					scoredReport, formatPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
			} else {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
					scoredReport, formatPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
			}
 | 
			
		||||
		case "ja":
 | 
			
		||||
			if 0 < cve.CveDetail.Jvn.CvssScore() {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangJa(cve, osFamily))
 | 
			
		||||
					scoredReport, formatPlainTextDetailsLangJa(cve, osFamily))
 | 
			
		||||
			} else if 0 < cve.CveDetail.Nvd.CvssScore() {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
					scoredReport, formatPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
			} else {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
					scoredReport, formatPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cve := range data.UnknownCves {
 | 
			
		||||
	for _, cve := range r.UnknownCves {
 | 
			
		||||
		unscoredReport = append(
 | 
			
		||||
			unscoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
			unscoredReport, formatPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
func formatPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
	dtable := uitable.New()
 | 
			
		||||
	dtable.MaxColWidth = 100
 | 
			
		||||
	dtable.MaxColWidth = maxColWidth
 | 
			
		||||
	dtable.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
@@ -200,19 +277,20 @@ func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	for _, link := range dlinks {
 | 
			
		||||
		dtable.AddRow(link.title, link.url)
 | 
			
		||||
	}
 | 
			
		||||
	dtable = addPackageInfos(dtable, cveInfo.Packages)
 | 
			
		||||
	dtable = addCpeNames(dtable, cveInfo.CpeNames)
 | 
			
		||||
	dtable.AddRow("Confidence", cveInfo.VulnInfo.Confidence)
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s", dtable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
 | 
			
		||||
func formatPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	cveDetail := cveInfo.CveDetail
 | 
			
		||||
	cveID := cveDetail.CveID
 | 
			
		||||
	jvn := cveDetail.Jvn
 | 
			
		||||
 | 
			
		||||
	dtable := uitable.New()
 | 
			
		||||
	//TODO resize
 | 
			
		||||
	dtable.MaxColWidth = 100
 | 
			
		||||
	dtable.MaxColWidth = maxColWidth
 | 
			
		||||
	dtable.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
@@ -228,6 +306,8 @@ func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	dtable.AddRow("Vector", jvn.CvssVector())
 | 
			
		||||
	dtable.AddRow("Title", jvn.CveTitle())
 | 
			
		||||
	dtable.AddRow("Description", jvn.CveSummary())
 | 
			
		||||
	dtable.AddRow(cveDetail.CweID(), cweURL(cveDetail.CweID()))
 | 
			
		||||
	dtable.AddRow(cveDetail.CweID()+"(JVN)", cweJvnURL(cveDetail.CweID()))
 | 
			
		||||
 | 
			
		||||
	dtable.AddRow("JVN", jvn.Link())
 | 
			
		||||
	dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
 | 
			
		||||
@@ -242,18 +322,18 @@ func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
 | 
			
		||||
	dtable = addPackageInfos(dtable, cveInfo.Packages)
 | 
			
		||||
	dtable = addCpeNames(dtable, cveInfo.CpeNames)
 | 
			
		||||
	dtable.AddRow("Confidence", cveInfo.VulnInfo.Confidence)
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s", dtable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
 | 
			
		||||
func formatPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
 | 
			
		||||
	cveDetail := d.CveDetail
 | 
			
		||||
	cveID := cveDetail.CveID
 | 
			
		||||
	nvd := cveDetail.Nvd
 | 
			
		||||
 | 
			
		||||
	dtable := uitable.New()
 | 
			
		||||
	//TODO resize
 | 
			
		||||
	dtable.MaxColWidth = 100
 | 
			
		||||
	dtable.MaxColWidth = maxColWidth
 | 
			
		||||
	dtable.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
@@ -270,6 +350,8 @@ func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
 | 
			
		||||
 | 
			
		||||
	dtable.AddRow("Vector", nvd.CvssVector())
 | 
			
		||||
	dtable.AddRow("Summary", nvd.CveSummary())
 | 
			
		||||
	dtable.AddRow("CWE", cweURL(cveDetail.CweID()))
 | 
			
		||||
 | 
			
		||||
	dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("MITRE", fmt.Sprintf("%s%s", mitreBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVE Details", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
 | 
			
		||||
@@ -281,6 +363,7 @@ func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
 | 
			
		||||
	}
 | 
			
		||||
	dtable = addPackageInfos(dtable, d.Packages)
 | 
			
		||||
	dtable = addCpeNames(dtable, d.CpeNames)
 | 
			
		||||
	dtable.AddRow("Confidence", d.VulnInfo.Confidence)
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s\n", dtable)
 | 
			
		||||
}
 | 
			
		||||
@@ -310,6 +393,21 @@ func distroLinks(cveInfo models.CveInfo, osFamily string) []distroLink {
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case "oraclelinux":
 | 
			
		||||
		links := []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"Oracle-CVE",
 | 
			
		||||
				fmt.Sprintf(oracleSecurityBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		for _, advisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			links = append(links, distroLink{
 | 
			
		||||
				// "Oracle-ELSA"
 | 
			
		||||
				advisory.AdvisoryID,
 | 
			
		||||
				fmt.Sprintf(oracleELSABaseBaseURL, advisory.AdvisoryID),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case "amazon":
 | 
			
		||||
		links := []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
@@ -355,13 +453,12 @@ func distroLinks(cveInfo models.CveInfo, osFamily string) []distroLink {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO
 | 
			
		||||
// addPackageInfos add package information related the CVE to table
 | 
			
		||||
func addPackageInfos(table *uitable.Table, packs []models.PackageInfo) *uitable.Table {
 | 
			
		||||
	for i, p := range packs {
 | 
			
		||||
		var title string
 | 
			
		||||
		if i == 0 {
 | 
			
		||||
			title = "Package/CPE"
 | 
			
		||||
			title = "Package"
 | 
			
		||||
		}
 | 
			
		||||
		ver := fmt.Sprintf(
 | 
			
		||||
			"%s -> %s", p.ToStringCurrentVersion(), p.ToStringNewVersion())
 | 
			
		||||
@@ -370,9 +467,58 @@ func addPackageInfos(table *uitable.Table, packs []models.PackageInfo) *uitable.
 | 
			
		||||
	return table
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func addCpeNames(table *uitable.Table, names []models.CpeName) *uitable.Table {
 | 
			
		||||
	for _, p := range names {
 | 
			
		||||
		table.AddRow("CPE", fmt.Sprintf("%s", p.Name))
 | 
			
		||||
func addCpeNames(table *uitable.Table, names []string) *uitable.Table {
 | 
			
		||||
	for _, n := range names {
 | 
			
		||||
		table.AddRow("CPE", fmt.Sprintf("%s", n))
 | 
			
		||||
	}
 | 
			
		||||
	return table
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cweURL(cweID string) string {
 | 
			
		||||
	return fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html",
 | 
			
		||||
		strings.TrimPrefix(cweID, "CWE-"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cweJvnURL(cweID string) string {
 | 
			
		||||
	return fmt.Sprintf("http://jvndb.jvn.jp/ja/cwe/%s.html", cweID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatChangelogs(r models.ScanResult) string {
 | 
			
		||||
	buf := []string{}
 | 
			
		||||
	for _, p := range r.Packages {
 | 
			
		||||
		if p.NewVersion == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		clog := formatOneChangelog(p)
 | 
			
		||||
		buf = append(buf, clog, "\n\n")
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(buf, "\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatOneChangelog(p models.PackageInfo) string {
 | 
			
		||||
	buf := []string{}
 | 
			
		||||
	if p.NewVersion == "" {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packVer := fmt.Sprintf("%s -> %s",
 | 
			
		||||
		p.ToStringCurrentVersion(), p.ToStringNewVersion())
 | 
			
		||||
	var delim bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(packVer); i++ {
 | 
			
		||||
		delim.WriteString("-")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clog := p.Changelog.Contents
 | 
			
		||||
	if lines := strings.Split(clog, "\n"); len(lines) != 0 {
 | 
			
		||||
		clog = strings.Join(lines[0:len(lines)-1], "\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch p.Changelog.Method {
 | 
			
		||||
	case models.FailedToGetChangelog:
 | 
			
		||||
		clog = "No changelogs"
 | 
			
		||||
	case models.FailedToFindVersionInChangelog:
 | 
			
		||||
		clog = "Failed to parse changelogs. For detials, check yourself"
 | 
			
		||||
	}
 | 
			
		||||
	buf = append(buf, packVer, delim.String(), clog)
 | 
			
		||||
	return strings.Join(buf, "\n")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,12 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import "github.com/future-architect/vuls/models"
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"compress/gzip"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	nvdBaseURL            = "https://web.nvd.nist.gov/view/vuln/detail"
 | 
			
		||||
@@ -28,16 +33,34 @@ const (
 | 
			
		||||
	redhatSecurityBaseURL = "https://access.redhat.com/security/cve"
 | 
			
		||||
	redhatRHSABaseBaseURL = "https://rhn.redhat.com/errata/%s.html"
 | 
			
		||||
	amazonSecurityBaseURL = "https://alas.aws.amazon.com/%s.html"
 | 
			
		||||
	oracleSecurityBaseURL = "https://linux.oracle.com/cve/%s.html"
 | 
			
		||||
	oracleELSABaseBaseURL = "https://linux.oracle.com/errata/%s.html"
 | 
			
		||||
 | 
			
		||||
	ubuntuSecurityBaseURL = "http://people.ubuntu.com/~ubuntu-security/cve"
 | 
			
		||||
	debianTrackerBaseURL  = "https://security-tracker.debian.org/tracker"
 | 
			
		||||
 | 
			
		||||
	freeBSDVuXMLBaseURL = "https://vuxml.freebsd.org/freebsd/%s.html"
 | 
			
		||||
 | 
			
		||||
	vulsOpenTag  = "<vulsreport>"
 | 
			
		||||
	vulsCloseTag = "</vulsreport>"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ResultWriter Interface
 | 
			
		||||
type ResultWriter interface {
 | 
			
		||||
	Write([]models.ScanResult) error
 | 
			
		||||
	Write(...models.ScanResult) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var resultDirPath string
 | 
			
		||||
func gz(data []byte) ([]byte, error) {
 | 
			
		||||
	var b bytes.Buffer
 | 
			
		||||
	gz := gzip.NewWriter(&b)
 | 
			
		||||
	if _, err := gz.Write(data); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := gz.Flush(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := gz.Close(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return b.Bytes(), nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										226
									
								
								scan/base.go
									
									
									
									
									
								
							
							
						
						
									
										226
									
								
								scan/base.go
									
									
									
									
									
								
							@@ -19,29 +19,29 @@ package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type base struct {
 | 
			
		||||
	ServerInfo config.ServerInfo
 | 
			
		||||
	Distro     config.Distro
 | 
			
		||||
	Platform   models.Platform
 | 
			
		||||
 | 
			
		||||
	Family   string
 | 
			
		||||
	Release  string
 | 
			
		||||
	Platform models.Platform
 | 
			
		||||
	osPackages
 | 
			
		||||
 | 
			
		||||
	log  *logrus.Entry
 | 
			
		||||
	errs []error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) ssh(cmd string, sudo bool) sshResult {
 | 
			
		||||
	return sshExec(l.ServerInfo, cmd, sudo, l.log)
 | 
			
		||||
func (l *base) exec(cmd string, sudo bool) execResult {
 | 
			
		||||
	return exec(l.ServerInfo, cmd, sudo, l.log)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setServerInfo(c config.ServerInfo) {
 | 
			
		||||
@@ -52,13 +52,20 @@ func (l base) getServerInfo() config.ServerInfo {
 | 
			
		||||
	return l.ServerInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setDistributionInfo(fam, rel string) {
 | 
			
		||||
	l.Family = fam
 | 
			
		||||
	l.Release = rel
 | 
			
		||||
func (l *base) setDistro(fam, rel string) {
 | 
			
		||||
	d := config.Distro{
 | 
			
		||||
		Family:  fam,
 | 
			
		||||
		Release: rel,
 | 
			
		||||
	}
 | 
			
		||||
	l.Distro = d
 | 
			
		||||
 | 
			
		||||
	s := l.getServerInfo()
 | 
			
		||||
	s.Distro = d
 | 
			
		||||
	l.setServerInfo(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getDistributionInfo() string {
 | 
			
		||||
	return fmt.Sprintf("%s %s", l.Family, l.Release)
 | 
			
		||||
func (l base) getDistro() config.Distro {
 | 
			
		||||
	return l.Distro
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setPlatform(p models.Platform) {
 | 
			
		||||
@@ -70,56 +77,83 @@ func (l base) getPlatform() models.Platform {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) allContainers() (containers []config.Container, err error) {
 | 
			
		||||
	switch l.ServerInfo.Container.Type {
 | 
			
		||||
	switch l.ServerInfo.Containers.Type {
 | 
			
		||||
	case "", "docker":
 | 
			
		||||
		stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}}'")
 | 
			
		||||
		stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}} {{.Image}}'")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseDockerPs(stdout)
 | 
			
		||||
	case "lxd":
 | 
			
		||||
		stdout, err := l.lxdPs("-c n")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseLxdPs(stdout)
 | 
			
		||||
	default:
 | 
			
		||||
		return containers, fmt.Errorf(
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Container.Type)
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Containers.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) runningContainers() (containers []config.Container, err error) {
 | 
			
		||||
	switch l.ServerInfo.Container.Type {
 | 
			
		||||
	switch l.ServerInfo.Containers.Type {
 | 
			
		||||
	case "", "docker":
 | 
			
		||||
		stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}}'")
 | 
			
		||||
		stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}} {{.Image}}'")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseDockerPs(stdout)
 | 
			
		||||
	case "lxd":
 | 
			
		||||
		stdout, err := l.lxdPs("volatile.last_state.power=RUNNING -c n")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseLxdPs(stdout)
 | 
			
		||||
	default:
 | 
			
		||||
		return containers, fmt.Errorf(
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Container.Type)
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Containers.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) exitedContainers() (containers []config.Container, err error) {
 | 
			
		||||
	switch l.ServerInfo.Container.Type {
 | 
			
		||||
	switch l.ServerInfo.Containers.Type {
 | 
			
		||||
	case "", "docker":
 | 
			
		||||
		stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}}'")
 | 
			
		||||
		stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}} {{.Image}}'")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseDockerPs(stdout)
 | 
			
		||||
	case "lxd":
 | 
			
		||||
		stdout, err := l.lxdPs("volatile.last_state.power=STOPPED -c n")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseLxdPs(stdout)
 | 
			
		||||
	default:
 | 
			
		||||
		return containers, fmt.Errorf(
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Container.Type)
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Containers.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) dockerPs(option string) (string, error) {
 | 
			
		||||
	cmd := fmt.Sprintf("docker ps %s", option)
 | 
			
		||||
	r := l.ssh(cmd, noSudo)
 | 
			
		||||
	r := l.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return "", fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return r.Stdout, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) lxdPs(option string) (string, error) {
 | 
			
		||||
	cmd := fmt.Sprintf("lxc list %s", option)
 | 
			
		||||
	r := l.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return "", fmt.Errorf("failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return r.Stdout, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) parseDockerPs(stdout string) (containers []config.Container, err error) {
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
@@ -127,46 +161,65 @@ func (l *base) parseDockerPs(stdout string) (containers []config.Container, err
 | 
			
		||||
		if len(fields) == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if len(fields) != 2 {
 | 
			
		||||
		if len(fields) != 3 {
 | 
			
		||||
			return containers, fmt.Errorf("Unknown format: %s", line)
 | 
			
		||||
		}
 | 
			
		||||
		containers = append(containers, config.Container{
 | 
			
		||||
			ContainerID: fields[0],
 | 
			
		||||
			Name:        fields[1],
 | 
			
		||||
			Image:       fields[2],
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) detectPlatform() error {
 | 
			
		||||
func (l *base) parseLxdPs(stdout string) (containers []config.Container, err error) {
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for i, line := range lines[3:] {
 | 
			
		||||
		if i%2 == 1 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		fields := strings.Fields(strings.Replace(line, "|", " ", -1))
 | 
			
		||||
		if len(fields) == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if len(fields) != 1 {
 | 
			
		||||
			return containers, fmt.Errorf("Unknown format: %s", line)
 | 
			
		||||
		}
 | 
			
		||||
		containers = append(containers, config.Container{
 | 
			
		||||
			ContainerID: fields[0],
 | 
			
		||||
			Name:        fields[0],
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) detectPlatform() {
 | 
			
		||||
	ok, instanceID, err := l.detectRunningOnAws()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		l.setPlatform(models.Platform{Name: "other"})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if ok {
 | 
			
		||||
		l.setPlatform(models.Platform{
 | 
			
		||||
			Name:       "aws",
 | 
			
		||||
			InstanceID: instanceID,
 | 
			
		||||
		})
 | 
			
		||||
		return nil
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//TODO Azure, GCP...
 | 
			
		||||
	l.setPlatform(models.Platform{
 | 
			
		||||
		Name: "other",
 | 
			
		||||
	})
 | 
			
		||||
	return nil
 | 
			
		||||
	l.setPlatform(models.Platform{Name: "other"})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) detectRunningOnAws() (ok bool, instanceID string, err error) {
 | 
			
		||||
	if r := l.ssh("type curl", noSudo); r.isSuccess() {
 | 
			
		||||
	if r := l.exec("type curl", noSudo); r.isSuccess() {
 | 
			
		||||
		cmd := "curl --max-time 1 --retry 3 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id"
 | 
			
		||||
		r := l.ssh(cmd, noSudo)
 | 
			
		||||
		r := l.exec(cmd, noSudo)
 | 
			
		||||
		if r.isSuccess() {
 | 
			
		||||
			id := strings.TrimSpace(r.Stdout)
 | 
			
		||||
 | 
			
		||||
			if id == "not found" {
 | 
			
		||||
				// status: 0, stdout: "not found" on degitalocean or Azure
 | 
			
		||||
			if !l.isAwsInstanceID(id) {
 | 
			
		||||
				return false, "", nil
 | 
			
		||||
			}
 | 
			
		||||
			return true, id, nil
 | 
			
		||||
@@ -181,11 +234,14 @@ func (l base) detectRunningOnAws() (ok bool, instanceID string, err error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := l.ssh("type wget", noSudo); r.isSuccess() {
 | 
			
		||||
	if r := l.exec("type wget", noSudo); r.isSuccess() {
 | 
			
		||||
		cmd := "wget --tries=3 --timeout=1 --no-proxy -q -O - http://169.254.169.254/latest/meta-data/instance-id"
 | 
			
		||||
		r := l.ssh(cmd, noSudo)
 | 
			
		||||
		r := l.exec(cmd, noSudo)
 | 
			
		||||
		if r.isSuccess() {
 | 
			
		||||
			id := strings.TrimSpace(r.Stdout)
 | 
			
		||||
			if !l.isAwsInstanceID(id) {
 | 
			
		||||
				return false, "", nil
 | 
			
		||||
			}
 | 
			
		||||
			return true, id, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -202,90 +258,52 @@ func (l base) detectRunningOnAws() (ok bool, instanceID string, err error) {
 | 
			
		||||
		l.ServerInfo.ServerName, l.ServerInfo.Container.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) convertToModel() (models.ScanResult, error) {
 | 
			
		||||
	var scoredCves, unscoredCves models.CveInfos
 | 
			
		||||
	for _, p := range l.UnsecurePackages {
 | 
			
		||||
		if p.CveDetail.CvssScore(config.Conf.Lang) <= 0 {
 | 
			
		||||
			unscoredCves = append(unscoredCves, models.CveInfo{
 | 
			
		||||
				CveDetail:        p.CveDetail,
 | 
			
		||||
				Packages:         p.Packs,
 | 
			
		||||
				DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
 | 
			
		||||
			})
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/resource-ids.html
 | 
			
		||||
var awsInstanceIDPattern = regexp.MustCompile(`^i-[0-9a-f]+$`)
 | 
			
		||||
 | 
			
		||||
		cpenames := []models.CpeName{}
 | 
			
		||||
		for _, cpename := range p.CpeNames {
 | 
			
		||||
			cpenames = append(cpenames,
 | 
			
		||||
				models.CpeName{Name: cpename})
 | 
			
		||||
		}
 | 
			
		||||
func (l base) isAwsInstanceID(str string) bool {
 | 
			
		||||
	return awsInstanceIDPattern.MatchString(str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		cve := models.CveInfo{
 | 
			
		||||
			CveDetail:        p.CveDetail,
 | 
			
		||||
			Packages:         p.Packs,
 | 
			
		||||
			DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
 | 
			
		||||
			CpeNames:         cpenames,
 | 
			
		||||
		}
 | 
			
		||||
		scoredCves = append(scoredCves, cve)
 | 
			
		||||
func (l *base) convertToModel() models.ScanResult {
 | 
			
		||||
	for _, p := range l.VulnInfos {
 | 
			
		||||
		sort.Sort(models.PackageInfosByName(p.Packages))
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(l.VulnInfos)
 | 
			
		||||
 | 
			
		||||
	ctype := l.ServerInfo.Containers.Type
 | 
			
		||||
	if l.ServerInfo.Container.ContainerID != "" && ctype == "" {
 | 
			
		||||
		ctype = "docker"
 | 
			
		||||
	}
 | 
			
		||||
	container := models.Container{
 | 
			
		||||
		ContainerID: l.ServerInfo.Container.ContainerID,
 | 
			
		||||
		Name:        l.ServerInfo.Container.Name,
 | 
			
		||||
		Image:       l.ServerInfo.Container.Image,
 | 
			
		||||
		Type:        ctype,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Sort(scoredCves)
 | 
			
		||||
	sort.Sort(unscoredCves)
 | 
			
		||||
	errs := []string{}
 | 
			
		||||
	for _, e := range l.errs {
 | 
			
		||||
		errs = append(errs, fmt.Sprintf("%s", e))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Avoid null slice being null in JSON
 | 
			
		||||
	for i := range l.VulnInfos {
 | 
			
		||||
		l.VulnInfos[i].NilSliceToEmpty()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return models.ScanResult{
 | 
			
		||||
		ServerName:  l.ServerInfo.ServerName,
 | 
			
		||||
		Family:      l.Family,
 | 
			
		||||
		Release:     l.Release,
 | 
			
		||||
		ScannedAt:   time.Now(),
 | 
			
		||||
		Family:      l.Distro.Family,
 | 
			
		||||
		Release:     l.Distro.Release,
 | 
			
		||||
		Container:   container,
 | 
			
		||||
		Platform:    l.Platform,
 | 
			
		||||
		KnownCves:   scoredCves,
 | 
			
		||||
		UnknownCves: unscoredCves,
 | 
			
		||||
		ScannedCves: l.VulnInfos,
 | 
			
		||||
		Packages:    l.Packages,
 | 
			
		||||
		Optional:    l.ServerInfo.Optional,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
 | 
			
		||||
func (l *base) scanVulnByCpeName() error {
 | 
			
		||||
	unsecurePacks := CvePacksList{}
 | 
			
		||||
 | 
			
		||||
	serverInfo := l.getServerInfo()
 | 
			
		||||
	cpeNames := serverInfo.CpeNames
 | 
			
		||||
 | 
			
		||||
	// remove duplicate
 | 
			
		||||
	set := map[string]CvePacksInfo{}
 | 
			
		||||
 | 
			
		||||
	for _, name := range cpeNames {
 | 
			
		||||
		details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for _, detail := range details {
 | 
			
		||||
			if val, ok := set[detail.CveID]; ok {
 | 
			
		||||
				names := val.CpeNames
 | 
			
		||||
				names = append(names, name)
 | 
			
		||||
				val.CpeNames = names
 | 
			
		||||
				set[detail.CveID] = val
 | 
			
		||||
			} else {
 | 
			
		||||
				set[detail.CveID] = CvePacksInfo{
 | 
			
		||||
					CveID:     detail.CveID,
 | 
			
		||||
					CveDetail: detail,
 | 
			
		||||
					CpeNames:  []string{name},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		Errors:      errs,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for key := range set {
 | 
			
		||||
		unsecurePacks = append(unsecurePacks, set[key])
 | 
			
		||||
	}
 | 
			
		||||
	unsecurePacks = append(unsecurePacks, l.UnsecurePackages...)
 | 
			
		||||
	l.setUnsecurePackages(unsecurePacks)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setErrs(errs []error) {
 | 
			
		||||
 
 | 
			
		||||
@@ -30,16 +30,18 @@ func TestParseDockerPs(t *testing.T) {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []config.Container
 | 
			
		||||
	}{
 | 
			
		||||
		`c7ca0992415a romantic_goldberg
 | 
			
		||||
f570ae647edc agitated_lovelace`,
 | 
			
		||||
		`c7ca0992415a romantic_goldberg ubuntu:14.04.5
 | 
			
		||||
f570ae647edc agitated_lovelace centos:latest`,
 | 
			
		||||
		[]config.Container{
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "c7ca0992415a",
 | 
			
		||||
				Name:        "romantic_goldberg",
 | 
			
		||||
				Image:       "ubuntu:14.04.5",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "f570ae647edc",
 | 
			
		||||
				Name:        "agitated_lovelace",
 | 
			
		||||
				Image:       "centos:latest",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@@ -56,3 +58,63 @@ f570ae647edc agitated_lovelace`,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseLxdPs(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []config.Container
 | 
			
		||||
	}{
 | 
			
		||||
		`+-------+
 | 
			
		||||
| NAME  |
 | 
			
		||||
+-------+
 | 
			
		||||
| test1 |
 | 
			
		||||
+-------+
 | 
			
		||||
| test2 |
 | 
			
		||||
+-------+`,
 | 
			
		||||
		[]config.Container{
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "test1",
 | 
			
		||||
				Name:        "test1",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "test2",
 | 
			
		||||
				Name:        "test2",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	actual, err := r.parseLxdPs(test.in)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Error occurred. in: %s, err: %s", test.in, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for i, e := range test.expected {
 | 
			
		||||
		if !reflect.DeepEqual(e, actual[i]) {
 | 
			
		||||
			t.Errorf("expected %v, actual %v", e, actual[i])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsAwsInstanceID(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected bool
 | 
			
		||||
	}{
 | 
			
		||||
		{"i-1234567a", true},
 | 
			
		||||
		{"i-1234567890abcdef0", true},
 | 
			
		||||
		{"i-1234567890abcdef0000000", true},
 | 
			
		||||
		{"e-1234567890abcdef0", false},
 | 
			
		||||
		{"i-1234567890abcdef0 foo bar", false},
 | 
			
		||||
		{"no data", false},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := r.isAwsInstanceID(tt.in)
 | 
			
		||||
		if tt.expected != actual {
 | 
			
		||||
			t.Errorf("expected %t, actual %t, str: %s", tt.expected, actual, tt.in)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										643
									
								
								scan/debian.go
									
									
									
									
									
								
							
							
						
						
									
										643
									
								
								scan/debian.go
									
									
									
									
									
								
							@@ -24,8 +24,8 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/cache"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
@@ -39,75 +39,87 @@ type debian struct {
 | 
			
		||||
func newDebian(c config.ServerInfo) *debian {
 | 
			
		||||
	d := &debian{}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ubuntu, Debian
 | 
			
		||||
// Ubuntu, Debian, Raspbian
 | 
			
		||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/debian.rb
 | 
			
		||||
func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err error) {
 | 
			
		||||
	deb = newDebian(c)
 | 
			
		||||
	deb.setServerInfo(c)
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
 | 
			
		||||
	if r := exec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
 | 
			
		||||
		if r.Error != nil {
 | 
			
		||||
			return false, deb, r.Error
 | 
			
		||||
			return false, deb, nil
 | 
			
		||||
		}
 | 
			
		||||
		if r.ExitStatus == 255 {
 | 
			
		||||
			return false, deb, fmt.Errorf(
 | 
			
		||||
				"Unable to connect via SSH. Check SSH settings. %s", r)
 | 
			
		||||
		}
 | 
			
		||||
		Log.Debugf("Not Debian like Linux. %s", r)
 | 
			
		||||
		util.Log.Debugf("Not Debian like Linux. %s", r)
 | 
			
		||||
		return false, deb, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "lsb_release -ir", noSudo); r.isSuccess() {
 | 
			
		||||
	// Raspbian
 | 
			
		||||
	// lsb_release in Raspbian Jessie returns 'Distributor ID: Raspbian'.
 | 
			
		||||
	// However, lsb_release in Raspbian Wheezy returns 'Distributor ID: Debian'.
 | 
			
		||||
	if r := exec(c, "cat /etc/issue", noSudo); r.isSuccess() {
 | 
			
		||||
		//  e.g.
 | 
			
		||||
		//  Raspbian GNU/Linux 7 \n \l
 | 
			
		||||
		result := strings.Fields(r.Stdout)
 | 
			
		||||
		if len(result) > 2 && result[0] == "Raspbian" {
 | 
			
		||||
			distro := strings.ToLower(trim(result[0]))
 | 
			
		||||
			deb.setDistro(distro, trim(result[2]))
 | 
			
		||||
			return true, deb, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "lsb_release -ir", noSudo); r.isSuccess() {
 | 
			
		||||
		//  e.g.
 | 
			
		||||
		//  root@fa3ec524be43:/# lsb_release -ir
 | 
			
		||||
		//  Distributor ID:	Ubuntu
 | 
			
		||||
		//  Release:	14.04
 | 
			
		||||
		re, _ := regexp.Compile(
 | 
			
		||||
			`(?s)^Distributor ID:\s*(.+?)\n*Release:\s*(.+?)$`)
 | 
			
		||||
		re := regexp.MustCompile(`(?s)^Distributor ID:\s*(.+?)\n*Release:\s*(.+?)$`)
 | 
			
		||||
		result := re.FindStringSubmatch(trim(r.Stdout))
 | 
			
		||||
 | 
			
		||||
		if len(result) == 0 {
 | 
			
		||||
			deb.setDistributionInfo("debian/ubuntu", "unknown")
 | 
			
		||||
			Log.Warnf(
 | 
			
		||||
			deb.setDistro("debian/ubuntu", "unknown")
 | 
			
		||||
			util.Log.Warnf(
 | 
			
		||||
				"Unknown Debian/Ubuntu version. lsb_release -ir: %s", r)
 | 
			
		||||
		} else {
 | 
			
		||||
			distro := strings.ToLower(trim(result[1]))
 | 
			
		||||
			deb.setDistributionInfo(distro, trim(result[2]))
 | 
			
		||||
			deb.setDistro(distro, trim(result[2]))
 | 
			
		||||
		}
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
 | 
			
		||||
	if r := exec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
 | 
			
		||||
		//  e.g.
 | 
			
		||||
		//  DISTRIB_ID=Ubuntu
 | 
			
		||||
		//  DISTRIB_RELEASE=14.04
 | 
			
		||||
		//  DISTRIB_CODENAME=trusty
 | 
			
		||||
		//  DISTRIB_DESCRIPTION="Ubuntu 14.04.2 LTS"
 | 
			
		||||
		re, _ := regexp.Compile(
 | 
			
		||||
			`(?s)^DISTRIB_ID=(.+?)\n*DISTRIB_RELEASE=(.+?)\n.*$`)
 | 
			
		||||
		re := regexp.MustCompile(`(?s)^DISTRIB_ID=(.+?)\n*DISTRIB_RELEASE=(.+?)\n.*$`)
 | 
			
		||||
		result := re.FindStringSubmatch(trim(r.Stdout))
 | 
			
		||||
		if len(result) == 0 {
 | 
			
		||||
			Log.Warnf(
 | 
			
		||||
			util.Log.Warnf(
 | 
			
		||||
				"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s", r)
 | 
			
		||||
			deb.setDistributionInfo("debian/ubuntu", "unknown")
 | 
			
		||||
			deb.setDistro("debian/ubuntu", "unknown")
 | 
			
		||||
		} else {
 | 
			
		||||
			distro := strings.ToLower(trim(result[1]))
 | 
			
		||||
			deb.setDistributionInfo(distro, trim(result[2]))
 | 
			
		||||
			deb.setDistro(distro, trim(result[2]))
 | 
			
		||||
		}
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Debian
 | 
			
		||||
	cmd := "cat /etc/debian_version"
 | 
			
		||||
	if r := sshExec(c, cmd, noSudo); r.isSuccess() {
 | 
			
		||||
		deb.setDistributionInfo("debian", trim(r.Stdout))
 | 
			
		||||
	if r := exec(c, cmd, noSudo); r.isSuccess() {
 | 
			
		||||
		deb.setDistro("debian", trim(r.Stdout))
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Debugf("Not Debian like Linux: %s", c.ServerName)
 | 
			
		||||
	util.Log.Debugf("Not Debian like Linux: %s", c.ServerName)
 | 
			
		||||
	return false, deb, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -116,58 +128,36 @@ func trim(str string) string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) checkIfSudoNoPasswd() error {
 | 
			
		||||
	r := o.ssh("apt-get -v", sudo)
 | 
			
		||||
	cmd := util.PrependProxyEnv("apt-get update")
 | 
			
		||||
	o.log.Infof("Checking... sudo %s", cmd)
 | 
			
		||||
	r := o.exec(cmd, sudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		o.log.Errorf("sudo error on %s", r)
 | 
			
		||||
		return fmt.Errorf("Failed to sudo: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Infof("sudo ... OK")
 | 
			
		||||
	o.log.Infof("Sudo... Pass")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) install() error {
 | 
			
		||||
func (o *debian) checkDependencies() error {
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "ubuntu", "raspbian":
 | 
			
		||||
		return nil
 | 
			
		||||
 | 
			
		||||
	// apt-get update
 | 
			
		||||
	o.log.Infof("apt-get update...")
 | 
			
		||||
	cmd := util.PrependProxyEnv("apt-get update")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		msg := fmt.Sprintf("Failed to SSH: %s", r)
 | 
			
		||||
		o.log.Errorf(msg)
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.Family == "debian" {
 | 
			
		||||
		// install aptitude
 | 
			
		||||
		cmd = util.PrependProxyEnv("apt-get install --force-yes -y aptitude")
 | 
			
		||||
		if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
			msg := fmt.Sprintf("Failed to SSH: %s", r)
 | 
			
		||||
	case "debian":
 | 
			
		||||
		// Debian needs aptitude to get changelogs.
 | 
			
		||||
		// Because unable to get changelogs via apt-get changelog on Debian.
 | 
			
		||||
		if r := o.exec("test -f /usr/bin/aptitude", noSudo); !r.isSuccess() {
 | 
			
		||||
			msg := fmt.Sprintf("aptitude is not installed: %s", r)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
		o.log.Infof("Installed: aptitude")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// install unattended-upgrades
 | 
			
		||||
	if !config.Conf.UseUnattendedUpgrades {
 | 
			
		||||
		o.log.Infof("Dependencies... Pass")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := o.ssh("type unattended-upgrade", noSudo); r.isSuccess() {
 | 
			
		||||
		o.log.Infof(
 | 
			
		||||
			"Ignored: unattended-upgrade already installed")
 | 
			
		||||
		return nil
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("Not implemented yet: %s", o.Distro)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd = util.PrependProxyEnv(
 | 
			
		||||
		"apt-get install --force-yes -y unattended-upgrades")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		msg := fmt.Sprintf("Failed to SSH: %s", r)
 | 
			
		||||
		o.log.Errorf(msg)
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Infof("Installed: unattended-upgrades")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackages() error {
 | 
			
		||||
@@ -179,17 +169,17 @@ func (o *debian) scanPackages() error {
 | 
			
		||||
	}
 | 
			
		||||
	o.setPackages(packs)
 | 
			
		||||
 | 
			
		||||
	var unsecurePacks []CvePacksInfo
 | 
			
		||||
	var unsecurePacks []models.VulnInfo
 | 
			
		||||
	if unsecurePacks, err = o.scanUnsecurePackages(packs); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan vulnerable packages")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setUnsecurePackages(unsecurePacks)
 | 
			
		||||
	o.setVulnInfos(unsecurePacks)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error) {
 | 
			
		||||
	r := o.ssh("dpkg-query -W", noSudo)
 | 
			
		||||
	r := o.exec("dpkg-query -W", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return packs, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
@@ -214,12 +204,16 @@ func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var packageLinePattern = regexp.MustCompile(`^([^\t']+)\t(.+)$`)
 | 
			
		||||
 | 
			
		||||
func (o *debian) parseScannedPackagesLine(line string) (name, version string, err error) {
 | 
			
		||||
	re, _ := regexp.Compile(`^([^\t']+)\t(.+)$`)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
	result := packageLinePattern.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) == 3 {
 | 
			
		||||
		// remove :amd64, i386...
 | 
			
		||||
		name = regexp.MustCompile(":.+").ReplaceAllString(result[1], "")
 | 
			
		||||
		name = result[1]
 | 
			
		||||
		if i := strings.IndexRune(name, ':'); i >= 0 {
 | 
			
		||||
			name = name[:i]
 | 
			
		||||
		}
 | 
			
		||||
		version = result[2]
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -227,174 +221,122 @@ func (o *debian) parseScannedPackagesLine(line string) (name, version string, er
 | 
			
		||||
	return "", "", fmt.Errorf("Unknown format: %s", line)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  unattended-upgrade command need to check security upgrades).
 | 
			
		||||
func (o *debian) checkRequiredPackagesInstalled() error {
 | 
			
		||||
 | 
			
		||||
	if o.Family == "debian" {
 | 
			
		||||
		if r := o.ssh("test -f /usr/bin/aptitude", noSudo); !r.isSuccess() {
 | 
			
		||||
			msg := fmt.Sprintf("aptitude is not installed: %s", r)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !config.Conf.UseUnattendedUpgrades {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := o.ssh("type unattended-upgrade", noSudo); !r.isSuccess() {
 | 
			
		||||
		msg := fmt.Sprintf("unattended-upgrade is not installed: %s", r)
 | 
			
		||||
		o.log.Errorf(msg)
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO return whether already expired.
 | 
			
		||||
func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInfo, error) {
 | 
			
		||||
	//  cmd := prependProxyEnv(conf.HTTPProxy, "apt-get update | cat; echo 1")
 | 
			
		||||
func (o *debian) scanUnsecurePackages(installed []models.PackageInfo) ([]models.VulnInfo, error) {
 | 
			
		||||
	o.log.Infof("apt-get update...")
 | 
			
		||||
	cmd := util.PrependProxyEnv("apt-get update")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
	if r := o.exec(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var upgradablePackNames []string
 | 
			
		||||
	var err error
 | 
			
		||||
	if config.Conf.UseUnattendedUpgrades {
 | 
			
		||||
		upgradablePackNames, err = o.GetUnsecurePackNamesUsingUnattendedUpgrades()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return []CvePacksInfo{}, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		upgradablePackNames, err = o.GetUpgradablePackNames()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return []CvePacksInfo{}, err
 | 
			
		||||
		}
 | 
			
		||||
	// Convert the name of upgradable packages to PackageInfo struct
 | 
			
		||||
	upgradableNames, err := o.GetUpgradablePackNames()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Convert package name to PackageInfo struct
 | 
			
		||||
	var unsecurePacks []models.PackageInfo
 | 
			
		||||
	for _, name := range upgradablePackNames {
 | 
			
		||||
		for _, pack := range packs {
 | 
			
		||||
	var upgradablePacks []models.PackageInfo
 | 
			
		||||
	for _, name := range upgradableNames {
 | 
			
		||||
		for _, pack := range installed {
 | 
			
		||||
			if pack.Name == name {
 | 
			
		||||
				unsecurePacks = append(unsecurePacks, pack)
 | 
			
		||||
				upgradablePacks = append(upgradablePacks, pack)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	unsecurePacks, err = o.fillCandidateVersion(unsecurePacks)
 | 
			
		||||
	// Fill the candidate versions of upgradable packages
 | 
			
		||||
	upgradablePacks, err = o.fillCandidateVersion(upgradablePacks)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.Packages.MergeNewVersion(upgradablePacks)
 | 
			
		||||
 | 
			
		||||
	// Setup changelog cache
 | 
			
		||||
	current := cache.Meta{
 | 
			
		||||
		Name:   o.getServerInfo().GetServerName(),
 | 
			
		||||
		Distro: o.getServerInfo().Distro,
 | 
			
		||||
		Packs:  upgradablePacks,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Debugf("Ensure changelog cache: %s", current.Name)
 | 
			
		||||
	var meta *cache.Meta
 | 
			
		||||
	if meta, err = o.ensureChangelogCache(current); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Collect CVE information of upgradable packages
 | 
			
		||||
	cvePacksInfos, err := o.scanPackageCveInfos(unsecurePacks)
 | 
			
		||||
	vulnInfos, err := o.scanVulnInfos(upgradablePacks, meta)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cvePacksInfos, nil
 | 
			
		||||
	return vulnInfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) fillCandidateVersion(packs []models.PackageInfo) ([]models.PackageInfo, error) {
 | 
			
		||||
	reqChan := make(chan models.PackageInfo, len(packs))
 | 
			
		||||
	resChan := make(chan models.PackageInfo, len(packs))
 | 
			
		||||
	errChan := make(chan error, len(packs))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, pack := range packs {
 | 
			
		||||
			reqChan <- pack
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(5 * 60 * time.Second)
 | 
			
		||||
	concurrency := 5
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for range packs {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case pack := <-reqChan:
 | 
			
		||||
				func(p models.PackageInfo) {
 | 
			
		||||
					cmd := fmt.Sprintf("LANG=en_US.UTF-8 apt-cache policy %s", p.Name)
 | 
			
		||||
					r := o.ssh(cmd, sudo)
 | 
			
		||||
					if !r.isSuccess() {
 | 
			
		||||
						errChan <- fmt.Errorf("Failed to SSH: %s.", r)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					ver, err := o.parseAptCachePolicy(r.Stdout, p.Name)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						errChan <- fmt.Errorf("Failed to parse %s", err)
 | 
			
		||||
					}
 | 
			
		||||
					p.NewVersion = ver.Candidate
 | 
			
		||||
					resChan <- p
 | 
			
		||||
				}(pack)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
	result := []models.PackageInfo{}
 | 
			
		||||
	for i := 0; i < len(packs); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case pack := <-resChan:
 | 
			
		||||
			result = append(result, pack)
 | 
			
		||||
			o.log.Infof("(%d/%d) Upgradable: %s-%s -> %s",
 | 
			
		||||
				i+1, len(packs), pack.Name, pack.Version, pack.NewVersion)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return nil, fmt.Errorf("Timeout fillCandidateVersion")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		return nil, fmt.Errorf("%v", errs)
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) GetUnsecurePackNamesUsingUnattendedUpgrades() (packNames []string, err error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("unattended-upgrades --dry-run -d 2>&1 ")
 | 
			
		||||
	release, err := strconv.ParseFloat(o.Release, 64)
 | 
			
		||||
func (o *debian) ensureChangelogCache(current cache.Meta) (*cache.Meta, error) {
 | 
			
		||||
	// Search from cache
 | 
			
		||||
	cached, found, err := cache.DB.GetMeta(current.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return packNames, fmt.Errorf(
 | 
			
		||||
			"OS Release Version is invalid, %s, %s", o.Family, o.Release)
 | 
			
		||||
	}
 | 
			
		||||
	switch {
 | 
			
		||||
	case release < 12:
 | 
			
		||||
		return packNames, fmt.Errorf(
 | 
			
		||||
			"Support expired. %s, %s", o.Family, o.Release)
 | 
			
		||||
 | 
			
		||||
	case 12 < release && release < 14:
 | 
			
		||||
		cmd += `| grep 'pkgs that look like they should be upgraded:' |
 | 
			
		||||
			sed -e 's/pkgs that look like they should be upgraded://g'`
 | 
			
		||||
 | 
			
		||||
	case 14 < release:
 | 
			
		||||
		cmd += `| grep 'Packages that will be upgraded:' |
 | 
			
		||||
			sed -e 's/Packages that will be upgraded://g'`
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		return packNames, fmt.Errorf(
 | 
			
		||||
			"Not supported yet. %s, %s", o.Family, o.Release)
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Failed to get meta. Please remove cache.db and then try again. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := o.ssh(cmd, sudo)
 | 
			
		||||
	if r.isSuccess(0, 1) {
 | 
			
		||||
		packNames = strings.Split(strings.TrimSpace(r.Stdout), " ")
 | 
			
		||||
		return packNames, nil
 | 
			
		||||
	if !found {
 | 
			
		||||
		o.log.Debugf("Not found in meta: %s", current.Name)
 | 
			
		||||
		err = cache.DB.EnsureBuckets(current)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("Failed to ensure buckets. err: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		return ¤t, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return packNames, fmt.Errorf(
 | 
			
		||||
		"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
		cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
	if current.Distro.Family != cached.Distro.Family ||
 | 
			
		||||
		current.Distro.Release != cached.Distro.Release {
 | 
			
		||||
		o.log.Debugf("Need to refesh meta: %s", current.Name)
 | 
			
		||||
		err = cache.DB.EnsureBuckets(current)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("Failed to ensure buckets. err: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		return ¤t, nil
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Debugf("Reuse meta: %s", current.Name)
 | 
			
		||||
	if config.Conf.Debug {
 | 
			
		||||
		cache.DB.PrettyPrint(current)
 | 
			
		||||
	}
 | 
			
		||||
	return &cached, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) fillCandidateVersion(before models.PackageInfoList) (filled []models.PackageInfo, err error) {
 | 
			
		||||
	names := []string{}
 | 
			
		||||
	for _, p := range before {
 | 
			
		||||
		names = append(names, p.Name)
 | 
			
		||||
	}
 | 
			
		||||
	cmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 apt-cache policy %s", strings.Join(names, " "))
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	packChangelog := o.splitAptCachePolicy(r.Stdout)
 | 
			
		||||
	for k, v := range packChangelog {
 | 
			
		||||
		ver, err := o.parseAptCachePolicy(v, k)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("Failed to parse %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		p, found := before.FindByName(k)
 | 
			
		||||
		if !found {
 | 
			
		||||
			return nil, fmt.Errorf("Not found: %s", k)
 | 
			
		||||
		}
 | 
			
		||||
		p.NewVersion = ver.Candidate
 | 
			
		||||
		filled = append(filled, p)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("LANG=en_US.UTF-8 apt-get upgrade --dry-run")
 | 
			
		||||
	r := o.ssh(cmd, sudo)
 | 
			
		||||
	cmd := util.PrependProxyEnv("LANGUAGE=en_US.UTF-8 apt-get upgrade --dry-run")
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if r.isSuccess(0, 1) {
 | 
			
		||||
		return o.parseAptGetUpgrade(r.Stdout)
 | 
			
		||||
	}
 | 
			
		||||
@@ -404,8 +346,8 @@ func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, err error) {
 | 
			
		||||
	startRe, _ := regexp.Compile(`The following packages will be upgraded:`)
 | 
			
		||||
	stopRe, _ := regexp.Compile(`^(\d+) upgraded.*`)
 | 
			
		||||
	startRe := regexp.MustCompile(`The following packages will be upgraded:`)
 | 
			
		||||
	stopRe := regexp.MustCompile(`^(\d+) upgraded.*`)
 | 
			
		||||
	startLineFound, stopLineFound := false, false
 | 
			
		||||
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
@@ -445,24 +387,19 @@ func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, er
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePacksList CvePacksList, err error) {
 | 
			
		||||
 | 
			
		||||
	// { CVE ID: [packageInfo] }
 | 
			
		||||
	cvePackages := make(map[string][]models.PackageInfo)
 | 
			
		||||
 | 
			
		||||
	type strarray []string
 | 
			
		||||
func (o *debian) scanVulnInfos(upgradablePacks []models.PackageInfo, meta *cache.Meta) (models.VulnInfos, error) {
 | 
			
		||||
	resChan := make(chan struct {
 | 
			
		||||
		models.PackageInfo
 | 
			
		||||
		strarray
 | 
			
		||||
	}, len(unsecurePacks))
 | 
			
		||||
	errChan := make(chan error, len(unsecurePacks))
 | 
			
		||||
	reqChan := make(chan models.PackageInfo, len(unsecurePacks))
 | 
			
		||||
		DetectedCveIDs
 | 
			
		||||
	}, len(upgradablePacks))
 | 
			
		||||
	errChan := make(chan error, len(upgradablePacks))
 | 
			
		||||
	reqChan := make(chan models.PackageInfo, len(upgradablePacks))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, pack := range unsecurePacks {
 | 
			
		||||
		for _, pack := range upgradablePacks {
 | 
			
		||||
			reqChan <- pack
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
@@ -470,17 +407,30 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
 | 
			
		||||
	timeout := time.After(30 * 60 * time.Second)
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for range unsecurePacks {
 | 
			
		||||
	for range upgradablePacks {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case pack := <-reqChan:
 | 
			
		||||
				func(p models.PackageInfo) {
 | 
			
		||||
					changelog := o.getChangelogCache(meta, p)
 | 
			
		||||
					if 0 < len(changelog) {
 | 
			
		||||
						cveIDs, _ := o.getCveIDsFromChangelog(changelog, p.Name, p.Version)
 | 
			
		||||
						resChan <- struct {
 | 
			
		||||
							models.PackageInfo
 | 
			
		||||
							DetectedCveIDs
 | 
			
		||||
						}{p, cveIDs}
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// if the changelog is not in cache or failed to get from local cache,
 | 
			
		||||
					// get the changelog of the package via internet.
 | 
			
		||||
					// After that, store it in the cache.
 | 
			
		||||
					if cveIDs, err := o.scanPackageCveIDs(p); err != nil {
 | 
			
		||||
						errChan <- err
 | 
			
		||||
					} else {
 | 
			
		||||
						resChan <- struct {
 | 
			
		||||
							models.PackageInfo
 | 
			
		||||
							strarray
 | 
			
		||||
							DetectedCveIDs
 | 
			
		||||
						}{p, cveIDs}
 | 
			
		||||
					}
 | 
			
		||||
				}(pack)
 | 
			
		||||
@@ -488,130 +438,261 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// { DetectedCveID{} : [packageInfo] }
 | 
			
		||||
	cvePackages := make(map[DetectedCveID][]models.PackageInfo)
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
	for i := 0; i < len(unsecurePacks); i++ {
 | 
			
		||||
	for i := 0; i < len(upgradablePacks); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case pair := <-resChan:
 | 
			
		||||
			pack := pair.PackageInfo
 | 
			
		||||
			cveIDs := pair.strarray
 | 
			
		||||
			cveIDs := pair.DetectedCveIDs
 | 
			
		||||
			for _, cveID := range cveIDs {
 | 
			
		||||
				cvePackages[cveID] = appendPackIfMissing(cvePackages[cveID], pack)
 | 
			
		||||
			}
 | 
			
		||||
			o.log.Infof("(%d/%d) Scanned %s-%s : %s",
 | 
			
		||||
				i+1, len(unsecurePacks), pair.Name, pair.PackageInfo.Version, cveIDs)
 | 
			
		||||
				i+1, len(upgradablePacks), pair.Name, pair.PackageInfo.Version, cveIDs)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			//TODO append to errs
 | 
			
		||||
			return nil, fmt.Errorf("Timeout scanPackageCveIDs")
 | 
			
		||||
			errs = append(errs, fmt.Errorf("Timeout scanPackageCveIDs"))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		return nil, fmt.Errorf("%v", errs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cveIDs []string
 | 
			
		||||
	var cveIDs []DetectedCveID
 | 
			
		||||
	for k := range cvePackages {
 | 
			
		||||
		cveIDs = append(cveIDs, k)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Debugf("%d Cves are found. cves: %v", len(cveIDs), cveIDs)
 | 
			
		||||
 | 
			
		||||
	o.log.Info("Fetching CVE details...")
 | 
			
		||||
	cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Info("Done")
 | 
			
		||||
 | 
			
		||||
	for _, detail := range cveDetails {
 | 
			
		||||
		cvePacksList = append(cvePacksList, CvePacksInfo{
 | 
			
		||||
			CveID:     detail.CveID,
 | 
			
		||||
			CveDetail: detail,
 | 
			
		||||
			Packs:     cvePackages[detail.CveID],
 | 
			
		||||
			//  CvssScore: cinfo.CvssScore(conf.Lang),
 | 
			
		||||
	var vinfos models.VulnInfos
 | 
			
		||||
	for k, v := range cvePackages {
 | 
			
		||||
		vinfos = append(vinfos, models.VulnInfo{
 | 
			
		||||
			CveID:      k.CveID,
 | 
			
		||||
			Confidence: k.Confidence,
 | 
			
		||||
			Packages:   v,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
 | 
			
		||||
	// Update meta package information of changelog cache to the latest one.
 | 
			
		||||
	meta.Packs = upgradablePacks
 | 
			
		||||
	if err := cache.DB.RefreshMeta(*meta); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackageCveIDs(pack models.PackageInfo) ([]string, error) {
 | 
			
		||||
func (o *debian) getChangelogCache(meta *cache.Meta, pack models.PackageInfo) string {
 | 
			
		||||
	cachedPack, found := meta.FindPack(pack.Name)
 | 
			
		||||
	if !found {
 | 
			
		||||
		o.log.Debugf("Not found: %s", pack.Name)
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cachedPack.NewVersion != pack.NewVersion {
 | 
			
		||||
		o.log.Debugf("Expired: %s, cache: %s, new: %s",
 | 
			
		||||
			pack.Name, cachedPack.NewVersion, pack.NewVersion)
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	changelog, err := cache.DB.GetChangelog(meta.Name, pack.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Warnf("Failed to get changelog. bucket: %s, key:%s, err: %s",
 | 
			
		||||
			meta.Name, pack.Name, err)
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	if len(changelog) == 0 {
 | 
			
		||||
		o.log.Debugf("Empty string: %s", pack.Name)
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Debugf("Hit: %s, %s, cache: %s, new: %s len: %d, %s...",
 | 
			
		||||
		meta.Name, pack.Name, cachedPack.NewVersion, pack.NewVersion, len(changelog), util.Truncate(changelog, 30))
 | 
			
		||||
	return changelog
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackageCveIDs(pack models.PackageInfo) ([]DetectedCveID, error) {
 | 
			
		||||
	cmd := ""
 | 
			
		||||
	switch o.Family {
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		cmd = fmt.Sprintf(`apt-get changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "ubuntu", "raspbian":
 | 
			
		||||
		cmd = fmt.Sprintf(`PAGER=cat apt-get -q=2 changelog %s`, pack.Name)
 | 
			
		||||
	case "debian":
 | 
			
		||||
		cmd = fmt.Sprintf(`aptitude changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
 | 
			
		||||
		cmd = fmt.Sprintf(`PAGER=cat aptitude -q=2 changelog %s`, pack.Name)
 | 
			
		||||
	}
 | 
			
		||||
	cmd = util.PrependProxyEnv(cmd)
 | 
			
		||||
 | 
			
		||||
	r := o.ssh(cmd, noSudo)
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		o.log.Warnf("Failed to SSH: %s", r)
 | 
			
		||||
		// Ignore this Error.
 | 
			
		||||
		return nil, nil
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	return o.getCveIDParsingChangelog(r.Stdout, pack.Name, pack.Version)
 | 
			
		||||
 | 
			
		||||
	stdout := strings.Replace(r.Stdout, "\r", "", -1)
 | 
			
		||||
	cveIDs, clog := o.getCveIDsFromChangelog(
 | 
			
		||||
		stdout, pack.Name, pack.Version)
 | 
			
		||||
 | 
			
		||||
	if clog.Method != models.FailedToGetChangelog {
 | 
			
		||||
		err := cache.DB.PutChangelog(o.getServerInfo().GetServerName(), pack.Name, clog.Contents)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("Failed to put changelog into cache")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// No error will be returned. Only logging.
 | 
			
		||||
	return cveIDs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) getCveIDParsingChangelog(changelog string,
 | 
			
		||||
	packName string, versionOrLater string) (cveIDs []string, err error) {
 | 
			
		||||
// Debian Version Numbers
 | 
			
		||||
// https://readme.phys.ethz.ch/documentation/debian_version_numbers/
 | 
			
		||||
func (o *debian) getCveIDsFromChangelog(
 | 
			
		||||
	changelog, name, ver string) ([]DetectedCveID, models.Changelog) {
 | 
			
		||||
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, versionOrLater)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	if cveIDs, relevant, err := o.parseChangelog(
 | 
			
		||||
		changelog, name, ver, models.ChangelogExactMatch); err == nil {
 | 
			
		||||
		return cveIDs, relevant
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ver := strings.Split(versionOrLater, "ubuntu")[0]
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, ver)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var verAfterColon string
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	splittedByColon := strings.Split(versionOrLater, ":")
 | 
			
		||||
	splittedByColon := strings.Split(ver, ":")
 | 
			
		||||
	if 1 < len(splittedByColon) {
 | 
			
		||||
		ver = splittedByColon[1]
 | 
			
		||||
	}
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, ver)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
		verAfterColon = splittedByColon[1]
 | 
			
		||||
		if cveIDs, relevant, err := o.parseChangelog(
 | 
			
		||||
			changelog, name, verAfterColon, models.ChangelogLenientMatch); err == nil {
 | 
			
		||||
			return cveIDs, relevant
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//TODO report as unable to parse changelog.
 | 
			
		||||
	delim := []string{"+", "~", "build"}
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		delim = append(delim, "ubuntu")
 | 
			
		||||
	case "debian":
 | 
			
		||||
	case "Raspbian":
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, d := range delim {
 | 
			
		||||
		ss := strings.Split(ver, d)
 | 
			
		||||
		if 1 < len(ss) {
 | 
			
		||||
			if cveIDs, relevant, err := o.parseChangelog(
 | 
			
		||||
				changelog, name, ss[0], models.ChangelogLenientMatch); err == nil {
 | 
			
		||||
				return cveIDs, relevant
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ss = strings.Split(verAfterColon, d)
 | 
			
		||||
		if 1 < len(ss) {
 | 
			
		||||
			if cveIDs, relevant, err := o.parseChangelog(
 | 
			
		||||
				changelog, name, ss[0], models.ChangelogLenientMatch); err == nil {
 | 
			
		||||
				return cveIDs, relevant
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Only logging the error.
 | 
			
		||||
	o.log.Error(err)
 | 
			
		||||
	return []string{}, nil
 | 
			
		||||
 | 
			
		||||
	for i, p := range o.Packages {
 | 
			
		||||
		if p.Name == name {
 | 
			
		||||
			o.Packages[i].Changelog = models.Changelog{
 | 
			
		||||
				Contents: "",
 | 
			
		||||
				Method:   models.FailedToFindVersionInChangelog,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If the version is not in changelog, return entire changelog to put into cache
 | 
			
		||||
	return []DetectedCveID{}, models.Changelog{
 | 
			
		||||
		Contents: changelog,
 | 
			
		||||
		Method:   models.FailedToFindVersionInChangelog,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectedCveID has CveID, Confidence and DetectionMethod fields
 | 
			
		||||
// LenientMatching will be true if this vulnerability is not detected by accurate version matching.
 | 
			
		||||
// see https://github.com/future-architect/vuls/pull/328
 | 
			
		||||
type DetectedCveID struct {
 | 
			
		||||
	CveID      string
 | 
			
		||||
	Confidence models.Confidence
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectedCveIDs is a slice of DetectedCveID
 | 
			
		||||
type DetectedCveIDs []DetectedCveID
 | 
			
		||||
 | 
			
		||||
var cveRe = regexp.MustCompile(`(CVE-\d{4}-\d{4,})`)
 | 
			
		||||
 | 
			
		||||
// Collect CVE-IDs included in the changelog.
 | 
			
		||||
// The version which specified in argument(versionOrLater) is excluded.
 | 
			
		||||
func (o *debian) parseChangelog(changelog string,
 | 
			
		||||
	packName string, versionOrLater string) (cveIDs []string, err error) {
 | 
			
		||||
 | 
			
		||||
	cveRe, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`)
 | 
			
		||||
	stopRe, _ := regexp.Compile(fmt.Sprintf(`\(%s\)`, regexp.QuoteMeta(versionOrLater)))
 | 
			
		||||
func (o *debian) parseChangelog(changelog, name, ver string, confidence models.Confidence) ([]DetectedCveID, models.Changelog, error) {
 | 
			
		||||
	buf, cveIDs := []string{}, []string{}
 | 
			
		||||
	stopRe := regexp.MustCompile(fmt.Sprintf(`\(%s\)`, regexp.QuoteMeta(ver)))
 | 
			
		||||
	stopLineFound := false
 | 
			
		||||
	lines := strings.Split(changelog, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		if matche := stopRe.MatchString(line); matche {
 | 
			
		||||
			o.log.Debugf("Found the stop line. line: %s", line)
 | 
			
		||||
		buf = append(buf, line)
 | 
			
		||||
		if match := stopRe.MatchString(line); match {
 | 
			
		||||
			//  o.log.Debugf("Found the stop line: %s", line)
 | 
			
		||||
			stopLineFound = true
 | 
			
		||||
			break
 | 
			
		||||
		} else if matches := cveRe.FindAllString(line, -1); len(matches) > 0 {
 | 
			
		||||
		} else if matches := cveRe.FindAllString(line, -1); 0 < len(matches) {
 | 
			
		||||
			for _, m := range matches {
 | 
			
		||||
				cveIDs = util.AppendIfMissing(cveIDs, m)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !stopLineFound {
 | 
			
		||||
		return []string{}, fmt.Errorf(
 | 
			
		||||
			"Failed to scan CVE IDs. The version is not in changelog. name: %s, version: %s",
 | 
			
		||||
			packName,
 | 
			
		||||
			versionOrLater,
 | 
			
		||||
		)
 | 
			
		||||
		return nil, models.Changelog{
 | 
			
		||||
				Contents: "",
 | 
			
		||||
				Method:   models.FailedToFindVersionInChangelog,
 | 
			
		||||
			}, fmt.Errorf(
 | 
			
		||||
				"Failed to scan CVE IDs. The version is not in changelog. name: %s, version: %s",
 | 
			
		||||
				name,
 | 
			
		||||
				ver,
 | 
			
		||||
			)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
 | 
			
		||||
	clog := models.Changelog{
 | 
			
		||||
		Contents: strings.Join(buf, "\n"),
 | 
			
		||||
		Method:   string(confidence.DetectionMethod),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, p := range o.Packages {
 | 
			
		||||
		if p.Name == name {
 | 
			
		||||
			o.Packages[i].Changelog = clog
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cves := []DetectedCveID{}
 | 
			
		||||
	for _, id := range cveIDs {
 | 
			
		||||
		cves = append(cves, DetectedCveID{id, confidence})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cves, clog, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) splitAptCachePolicy(stdout string) map[string]string {
 | 
			
		||||
	re := regexp.MustCompile(`(?m:^[^ \t]+:\r?\n)`)
 | 
			
		||||
	ii := re.FindAllStringIndex(stdout, -1)
 | 
			
		||||
	ri := []int{}
 | 
			
		||||
	for i := len(ii) - 1; 0 <= i; i-- {
 | 
			
		||||
		ri = append(ri, ii[i][0])
 | 
			
		||||
	}
 | 
			
		||||
	splitted := []string{}
 | 
			
		||||
	lasti := len(stdout)
 | 
			
		||||
	for _, i := range ri {
 | 
			
		||||
		splitted = append(splitted, stdout[i:lasti])
 | 
			
		||||
		lasti = i
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packChangelog := map[string]string{}
 | 
			
		||||
	for _, r := range splitted {
 | 
			
		||||
		packName := r[:strings.Index(r, ":")]
 | 
			
		||||
		packChangelog[packName] = r
 | 
			
		||||
	}
 | 
			
		||||
	return packChangelog
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type packCandidateVer struct {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,10 +18,14 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/cache"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -54,11 +58,12 @@ func TestParseScannedPackagesLineDebian(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestgetCveIDParsingChangelog(t *testing.T) {
 | 
			
		||||
func TestGetCveIDsFromChangelog(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       []string
 | 
			
		||||
		expected []string
 | 
			
		||||
		in        []string
 | 
			
		||||
		cveIDs    []DetectedCveID
 | 
			
		||||
		changelog models.Changelog
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			// verubuntu1
 | 
			
		||||
@@ -73,73 +78,99 @@ CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
systemd (228-5) unstable; urgency=medium
 | 
			
		||||
systemd (228-4) unstable; urgency=medium
 | 
			
		||||
systemd (228-3) unstable; urgency=medium
 | 
			
		||||
systemd (228-2) unstable; urgency=medium
 | 
			
		||||
systemd (228-1) unstable; urgency=medium
 | 
			
		||||
systemd (227-3) unstable; urgency=medium
 | 
			
		||||
systemd (227-2) unstable; urgency=medium
 | 
			
		||||
systemd (227-1) unstable; urgency=medium`,
 | 
			
		||||
systemd (228-3) unstable; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `systemd (229-2) unstable; urgency=medium
 | 
			
		||||
systemd (229-1) unstable; urgency=medium
 | 
			
		||||
systemd (228-6) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
systemd (228-5) unstable; urgency=medium
 | 
			
		||||
systemd (228-4) unstable; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogLenientMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// ver
 | 
			
		||||
			[]string{
 | 
			
		||||
				"libpcre3",
 | 
			
		||||
				"2:8.38-1ubuntu1",
 | 
			
		||||
				"2:8.35-7.1ubuntu1",
 | 
			
		||||
				`pcre3 (2:8.38-2) unstable; urgency=low
 | 
			
		||||
pcre3 (2:8.38-1) unstable; urgency=low
 | 
			
		||||
pcre3 (2:8.35-8) unstable; urgency=low
 | 
			
		||||
pcre3 (2:8.35-7.4) unstable; urgency=medium
 | 
			
		||||
pcre3 (2:8.35-7.3) unstable; urgency=medium
 | 
			
		||||
pcre3 (2:8.35-7.2) unstable; urgency=low
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
pcre3 (2:8.35-7.1) unstable; urgency=medium
 | 
			
		||||
pcre3 (2:8.35-7) unstable; urgency=medium`,
 | 
			
		||||
		 pcre3 (2:8.38-1) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.35-8) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.35-7.4) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7.3) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7.2) unstable; urgency=low
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 pcre3 (2:8.35-7.1) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7) unstable; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `pcre3 (2:8.38-2) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.38-1) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.35-8) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.35-7.4) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7.3) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7.2) unstable; urgency=low
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 pcre3 (2:8.35-7.1) unstable; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogLenientMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
				"sysvinit",
 | 
			
		||||
				"2.88dsf-59.2ubuntu3",
 | 
			
		||||
				`sysvinit (2.88dsf-59.3ubuntu1) xenial; urgency=low
 | 
			
		||||
sysvinit (2.88dsf-59.3) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.2ubuntu2) wily; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.2ubuntu1) wily; urgency=medium
 | 
			
		||||
CVE-2015-2321: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
sysvinit (2.88dsf-59.2) unstable; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.1ubuntu3) wily; urgency=medium
 | 
			
		||||
CVE-2015-2322: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
sysvinit (2.88dsf-59.1ubuntu2) wily; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.1ubuntu1) wily; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.1) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
sysvinit (2.88dsf-59) unstable; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-58) unstable; urgency=low
 | 
			
		||||
sysvinit (2.88dsf-57) unstable; urgency=low`,
 | 
			
		||||
		 sysvinit (2.88dsf-59.3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.2ubuntu2) wily; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.2ubuntu1) wily; urgency=medium
 | 
			
		||||
		 CVE-2015-2321: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 sysvinit (2.88dsf-59.2) unstable; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.1ubuntu3) wily; urgency=medium
 | 
			
		||||
		 CVE-2015-2322: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 sysvinit (2.88dsf-59.1ubuntu2) wily; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.1ubuntu1) wily; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.1) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 sysvinit (2.88dsf-59) unstable; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-58) unstable; urgency=low
 | 
			
		||||
		 sysvinit (2.88dsf-57) unstable; urgency=low`,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogExactMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `sysvinit (2.88dsf-59.3ubuntu1) xenial; urgency=low
 | 
			
		||||
		 sysvinit (2.88dsf-59.3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
@@ -147,59 +178,119 @@ sysvinit (2.88dsf-57) unstable; urgency=low`,
 | 
			
		||||
			[]string{
 | 
			
		||||
				"bsdutils",
 | 
			
		||||
				"1:2.27.1-1ubuntu3",
 | 
			
		||||
				` util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27-3) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27-2) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27-1) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27~rc2-2) experimental; urgency=medium
 | 
			
		||||
util-linux (2.27~rc2-1) experimental; urgency=medium
 | 
			
		||||
util-linux (2.27~rc1-1) experimental; urgency=medium
 | 
			
		||||
util-linux (2.26.2-9) unstable; urgency=medium
 | 
			
		||||
util-linux (2.26.2-8) experimental; urgency=medium
 | 
			
		||||
util-linux (2.26.2-7) experimental; urgency=medium
 | 
			
		||||
util-linux (2.26.2-6ubuntu3) wily; urgency=medium
 | 
			
		||||
CVE-2015-2329: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
util-linux (2.26.2-6ubuntu2) wily; urgency=medium
 | 
			
		||||
util-linux (2.26.2-6ubuntu1) wily; urgency=medium
 | 
			
		||||
util-linux (2.26.2-6) unstable; urgency=medium`,
 | 
			
		||||
				`util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27-3ubuntu1) xenial; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2016-1000000", models.ChangelogLenientMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu3) xenial; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogLenientMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// 1:ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
				"bsdutils",
 | 
			
		||||
				"1:2.27-3ubuntu3",
 | 
			
		||||
				`util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27-3) xenial; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2016-1000000", models.ChangelogLenientMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27-3) xenial; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogLenientMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// https://github.com/future-architect/vuls/pull/350
 | 
			
		||||
			[]string{
 | 
			
		||||
				"tar",
 | 
			
		||||
				"1.27.1-2+b1",
 | 
			
		||||
				`tar (1.27.1-2+deb8u1) jessie-security; urgency=high
 | 
			
		||||
		   * CVE-2016-6321: Bypassing the extract path name.
 | 
			
		||||
		 tar (1.27.1-2) unstable; urgency=low`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2016-6321", models.ChangelogLenientMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `tar (1.27.1-2+deb8u1) jessie-security; urgency=high
 | 
			
		||||
		   * CVE-2016-6321: Bypassing the extract path name.
 | 
			
		||||
		 tar (1.27.1-2) unstable; urgency=low`,
 | 
			
		||||
				Method: models.ChangelogLenientMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, _ := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], tt.in[1])
 | 
			
		||||
		if len(actual) != len(tt.expected) {
 | 
			
		||||
			t.Errorf("Len of return array are'nt same. expected %#v, actual %#v", tt.expected, actual)
 | 
			
		||||
	d.Distro.Family = "ubuntu"
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		aCveIDs, aClog := d.getCveIDsFromChangelog(tt.in[2], tt.in[0], tt.in[1])
 | 
			
		||||
		if len(aCveIDs) != len(tt.cveIDs) {
 | 
			
		||||
			t.Errorf("[%d] Len of return array are'nt same. expected %#v, actual %#v", i, tt.cveIDs, aCveIDs)
 | 
			
		||||
			t.Errorf(pp.Sprintf("%s", tt.in))
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		for i := range tt.expected {
 | 
			
		||||
			if actual[i] != tt.expected[i] {
 | 
			
		||||
				t.Errorf("expected %s, actual %s", tt.expected[i], actual[i])
 | 
			
		||||
		for j := range tt.cveIDs {
 | 
			
		||||
			if !reflect.DeepEqual(tt.cveIDs[j], aCveIDs[j]) {
 | 
			
		||||
				t.Errorf("[%d] expected %v, actual %v", i, tt.cveIDs[j], aCveIDs[j])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		_, err := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], "version number do'nt match case")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Returning error is unexpected")
 | 
			
		||||
		if aClog.Contents != tt.changelog.Contents {
 | 
			
		||||
			t.Errorf(pp.Sprintf("expected: %s, actual: %s", tt.changelog.Contents, aClog.Contents))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if aClog.Method != tt.changelog.Method {
 | 
			
		||||
			t.Errorf(pp.Sprintf("expected: %s, actual: %s", tt.changelog.Method, aClog.Method))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -520,6 +611,95 @@ Calculating upgrade... Done
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetChangelogCache(t *testing.T) {
 | 
			
		||||
	const servername = "server1"
 | 
			
		||||
	pack := models.PackageInfo{
 | 
			
		||||
		Name:       "apt",
 | 
			
		||||
		Version:    "1.0.0",
 | 
			
		||||
		NewVersion: "1.0.1",
 | 
			
		||||
	}
 | 
			
		||||
	var meta = cache.Meta{
 | 
			
		||||
		Name: servername,
 | 
			
		||||
		Distro: config.Distro{
 | 
			
		||||
			Family:  "ubuntu",
 | 
			
		||||
			Release: "16.04",
 | 
			
		||||
		},
 | 
			
		||||
		Packs: []models.PackageInfo{pack},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const path = "/tmp/vuls-test-cache-11111111.db"
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	if err := cache.SetupBolt(path, log); err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(path)
 | 
			
		||||
 | 
			
		||||
	if err := cache.DB.EnsureBuckets(meta); err != nil {
 | 
			
		||||
		t.Errorf("Failed to ensure buckets: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	actual := d.getChangelogCache(&meta, pack)
 | 
			
		||||
	if actual != "" {
 | 
			
		||||
		t.Errorf("Failed to get empty stirng from cache:")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clog := "changelog-text"
 | 
			
		||||
	if err := cache.DB.PutChangelog(servername, "apt", clog); err != nil {
 | 
			
		||||
		t.Errorf("Failed to put changelog: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	actual = d.getChangelogCache(&meta, pack)
 | 
			
		||||
	if actual != clog {
 | 
			
		||||
		t.Errorf("Failed to get changelog from cache: %s", actual)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// increment a version of the pack
 | 
			
		||||
	pack.NewVersion = "1.0.2"
 | 
			
		||||
	actual = d.getChangelogCache(&meta, pack)
 | 
			
		||||
	if actual != "" {
 | 
			
		||||
		t.Errorf("The changelog is not invalidated: %s", actual)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// change a name of the pack
 | 
			
		||||
	pack.Name = "bash"
 | 
			
		||||
	actual = d.getChangelogCache(&meta, pack)
 | 
			
		||||
	if actual != "" {
 | 
			
		||||
		t.Errorf("The changelog is not invalidated: %s", actual)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSplitAptCachePolicy(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		stdout   string
 | 
			
		||||
		expected map[string]string
 | 
			
		||||
	}{
 | 
			
		||||
		// This function parse apt-cache policy by using Regexp multi-line mode.
 | 
			
		||||
		// So, test data includes "\r\n"
 | 
			
		||||
		{
 | 
			
		||||
			"apt:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\napt-utils:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\nbase-files:\r\n  Installed: 9.4ubuntu3\r\n  Candidate: 9.4ubuntu4.2\r\n  Version table:\r\n     9.4ubuntu4.2 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     9.4ubuntu4 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 9.4ubuntu3 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
 | 
			
		||||
			map[string]string{
 | 
			
		||||
				"apt": "apt:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
 | 
			
		||||
				"apt-utils": "apt-utils:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
 | 
			
		||||
				"base-files": "base-files:\r\n  Installed: 9.4ubuntu3\r\n  Candidate: 9.4ubuntu4.2\r\n  Version table:\r\n     9.4ubuntu4.2 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     9.4ubuntu4 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 9.4ubuntu3 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := d.splitAptCachePolicy(tt.stdout)
 | 
			
		||||
		if !reflect.DeepEqual(tt.expected, actual) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.expected)
 | 
			
		||||
			a := pp.Sprintf("%v", actual)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseAptCachePolicy(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,7 @@ import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	ex "os/exec"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -40,7 +39,7 @@ import (
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type sshResult struct {
 | 
			
		||||
type execResult struct {
 | 
			
		||||
	Servername string
 | 
			
		||||
	Host       string
 | 
			
		||||
	Port       string
 | 
			
		||||
@@ -51,16 +50,13 @@ type sshResult struct {
 | 
			
		||||
	Error      error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s sshResult) String() string {
 | 
			
		||||
func (s execResult) String() string {
 | 
			
		||||
	return fmt.Sprintf(
 | 
			
		||||
		"SSHResult: servername: %s, cmd: %s, exitstatus: %d, stdout: %s, stderr: %s, err: %s",
 | 
			
		||||
		"execResult: servername: %s\n  cmd: %s\n  exitstatus: %d\n  stdout: %s\n  stderr: %s\n  err: %s",
 | 
			
		||||
		s.Servername, s.Cmd, s.ExitStatus, s.Stdout, s.Stderr, s.Error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s sshResult) isSuccess(expectedStatusCodes ...int) bool {
 | 
			
		||||
	if s.Error != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
func (s execResult) isSuccess(expectedStatusCodes ...int) bool {
 | 
			
		||||
	if len(expectedStatusCodes) == 0 {
 | 
			
		||||
		return s.ExitStatus == 0
 | 
			
		||||
	}
 | 
			
		||||
@@ -69,38 +65,36 @@ func (s sshResult) isSuccess(expectedStatusCodes ...int) bool {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if s.Error != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sudo is Const value for sudo mode
 | 
			
		||||
// sudo is Const value for sudo mode
 | 
			
		||||
const sudo = true
 | 
			
		||||
 | 
			
		||||
// NoSudo is Const value for normal user mode
 | 
			
		||||
// noSudo is Const value for normal user mode
 | 
			
		||||
const noSudo = false
 | 
			
		||||
 | 
			
		||||
func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []error) {
 | 
			
		||||
	resChan := make(chan string, len(servers))
 | 
			
		||||
	errChan := make(chan error, len(servers))
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
//  Issue commands to the target servers in parallel via SSH or local execution.  If execution fails, the server will be excluded from the target server list(servers) and added to the error server list(errServers).
 | 
			
		||||
func parallelExec(fn func(osTypeInterface) error, timeoutSec ...int) {
 | 
			
		||||
	resChan := make(chan osTypeInterface, len(servers))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		go func(s osTypeInterface) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if p := recover(); p != nil {
 | 
			
		||||
					logrus.Debugf("Panic: %s on %s",
 | 
			
		||||
						p, s.getServerInfo().ServerName)
 | 
			
		||||
					util.Log.Debugf("Panic: %s on %s",
 | 
			
		||||
						p, s.getServerInfo().GetServerName())
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			if err := fn(s); err != nil {
 | 
			
		||||
				errChan <- fmt.Errorf("%s@%s:%s: %s",
 | 
			
		||||
					s.getServerInfo().User,
 | 
			
		||||
					s.getServerInfo().Host,
 | 
			
		||||
					s.getServerInfo().Port,
 | 
			
		||||
					err,
 | 
			
		||||
				)
 | 
			
		||||
				s.setErrs([]error{err})
 | 
			
		||||
				resChan <- s
 | 
			
		||||
			} else {
 | 
			
		||||
				resChan <- s.getServerInfo().ServerName
 | 
			
		||||
				resChan <- s
 | 
			
		||||
			}
 | 
			
		||||
		}(s)
 | 
			
		||||
	}
 | 
			
		||||
@@ -112,48 +106,55 @@ func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []
 | 
			
		||||
		timeout = timeoutSec[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var snames []string
 | 
			
		||||
	var successes []osTypeInterface
 | 
			
		||||
	isTimedout := false
 | 
			
		||||
	for i := 0; i < len(servers); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case s := <-resChan:
 | 
			
		||||
			snames = append(snames, s)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
			if len(s.getErrs()) == 0 {
 | 
			
		||||
				successes = append(successes, s)
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Errorf("Error: %s, err: %s",
 | 
			
		||||
					s.getServerInfo().GetServerName(), s.getErrs())
 | 
			
		||||
				errServers = append(errServers, s)
 | 
			
		||||
			}
 | 
			
		||||
		case <-time.After(time.Duration(timeout) * time.Second):
 | 
			
		||||
			isTimedout = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// collect timed out servernames
 | 
			
		||||
	var timedoutSnames []string
 | 
			
		||||
	if isTimedout {
 | 
			
		||||
		// set timed out error and append to errServers
 | 
			
		||||
		for _, s := range servers {
 | 
			
		||||
			name := s.getServerInfo().ServerName
 | 
			
		||||
			name := s.getServerInfo().GetServerName()
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, t := range snames {
 | 
			
		||||
				if name == t {
 | 
			
		||||
			for _, ss := range successes {
 | 
			
		||||
				if name == ss.getServerInfo().GetServerName() {
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !found {
 | 
			
		||||
				timedoutSnames = append(timedoutSnames, name)
 | 
			
		||||
				msg := fmt.Sprintf("Timed out: %s",
 | 
			
		||||
					s.getServerInfo().GetServerName())
 | 
			
		||||
				util.Log.Errorf(msg)
 | 
			
		||||
				s.setErrs([]error{fmt.Errorf(msg)})
 | 
			
		||||
				errServers = append(errServers, s)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if isTimedout {
 | 
			
		||||
		errs = append(errs, fmt.Errorf(
 | 
			
		||||
			"Timed out: %s", timedoutSnames))
 | 
			
		||||
	}
 | 
			
		||||
	servers = successes
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result sshResult) {
 | 
			
		||||
	if isSSHExecNative() {
 | 
			
		||||
		result = sshExecNative(c, cmd, sudo)
 | 
			
		||||
	} else {
 | 
			
		||||
func exec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result execResult) {
 | 
			
		||||
	if c.Port == "local" &&
 | 
			
		||||
		(c.Host == "127.0.0.1" || c.Host == "localhost") {
 | 
			
		||||
		result = localExec(c, cmd, sudo)
 | 
			
		||||
	} else if conf.Conf.SSHExternal {
 | 
			
		||||
		result = sshExecExternal(c, cmd, sudo)
 | 
			
		||||
	} else {
 | 
			
		||||
		result = sshExecNative(c, cmd, sudo)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger := getSSHLogger(log...)
 | 
			
		||||
@@ -161,11 +162,37 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isSSHExecNative() bool {
 | 
			
		||||
	return runtime.GOOS == "windows" || !conf.Conf.SSHExternal
 | 
			
		||||
func localExec(c conf.ServerInfo, cmdstr string, sudo bool) (result execResult) {
 | 
			
		||||
	cmdstr = decorateCmd(c, cmdstr, sudo)
 | 
			
		||||
	var cmd *ex.Cmd
 | 
			
		||||
	if c.Distro.Family == "FreeBSD" {
 | 
			
		||||
		cmd = ex.Command("/bin/sh", "-c", cmdstr)
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd = ex.Command("/bin/bash", "-c", cmdstr)
 | 
			
		||||
	}
 | 
			
		||||
	var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
	cmd.Stdout = &stdoutBuf
 | 
			
		||||
	cmd.Stderr = &stderrBuf
 | 
			
		||||
 | 
			
		||||
	if err := cmd.Run(); err != nil {
 | 
			
		||||
		result.Error = err
 | 
			
		||||
		if exitError, ok := err.(*ex.ExitError); ok {
 | 
			
		||||
			waitStatus := exitError.Sys().(syscall.WaitStatus)
 | 
			
		||||
			result.ExitStatus = waitStatus.ExitStatus()
 | 
			
		||||
		} else {
 | 
			
		||||
			result.ExitStatus = 999
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		result.ExitStatus = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result.Stdout = stdoutBuf.String()
 | 
			
		||||
	result.Stderr = stderrBuf.String()
 | 
			
		||||
	result.Cmd = strings.Replace(cmdstr, "\n", "", -1)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult) {
 | 
			
		||||
func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result execResult) {
 | 
			
		||||
	result.Servername = c.ServerName
 | 
			
		||||
	result.Host = c.Host
 | 
			
		||||
	result.Port = c.Port
 | 
			
		||||
@@ -195,7 +222,7 @@ func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult)
 | 
			
		||||
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
 | 
			
		||||
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
 | 
			
		||||
	}
 | 
			
		||||
	if err = session.RequestPty("xterm", 400, 256, modes); err != nil {
 | 
			
		||||
	if err = session.RequestPty("xterm", 400, 1000, modes); err != nil {
 | 
			
		||||
		result.Error = fmt.Errorf(
 | 
			
		||||
			"Failed to request for pseudo terminal. servername: %s, err: %s",
 | 
			
		||||
			c.ServerName, err)
 | 
			
		||||
@@ -207,7 +234,7 @@ func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult)
 | 
			
		||||
	session.Stdout = &stdoutBuf
 | 
			
		||||
	session.Stderr = &stderrBuf
 | 
			
		||||
 | 
			
		||||
	cmd = decolateCmd(c, cmd, sudo)
 | 
			
		||||
	cmd = decorateCmd(c, cmd, sudo)
 | 
			
		||||
	if err := session.Run(cmd); err != nil {
 | 
			
		||||
		if exitErr, ok := err.(*ssh.ExitError); ok {
 | 
			
		||||
			result.ExitStatus = exitErr.ExitStatus()
 | 
			
		||||
@@ -224,14 +251,14 @@ func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result sshResult) {
 | 
			
		||||
	sshBinaryPath, err := exec.LookPath("ssh")
 | 
			
		||||
func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result execResult) {
 | 
			
		||||
	sshBinaryPath, err := ex.LookPath("ssh")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return sshExecNative(c, cmd, sudo)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defaultSSHArgs := []string{
 | 
			
		||||
		"-t",
 | 
			
		||||
		"-tt",
 | 
			
		||||
		"-o", "StrictHostKeyChecking=no",
 | 
			
		||||
		"-o", "UserKnownHostsFile=/dev/null",
 | 
			
		||||
		"-o", "LogLevel=quiet",
 | 
			
		||||
@@ -257,17 +284,17 @@ func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result sshResult
 | 
			
		||||
		args = append(args, "-o", "PasswordAuthentication=no")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd = decolateCmd(c, cmd, sudo)
 | 
			
		||||
	//  cmd = fmt.Sprintf("stty cols 256; set -o pipefail; %s", cmd)
 | 
			
		||||
	cmd = decorateCmd(c, cmd, sudo)
 | 
			
		||||
	cmd = fmt.Sprintf("stty cols 1000; %s", cmd)
 | 
			
		||||
 | 
			
		||||
	args = append(args, cmd)
 | 
			
		||||
	execCmd := exec.Command(sshBinaryPath, args...)
 | 
			
		||||
	execCmd := ex.Command(sshBinaryPath, args...)
 | 
			
		||||
 | 
			
		||||
	var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
	execCmd.Stdout = &stdoutBuf
 | 
			
		||||
	execCmd.Stderr = &stderrBuf
 | 
			
		||||
	if err := execCmd.Run(); err != nil {
 | 
			
		||||
		if e, ok := err.(*exec.ExitError); ok {
 | 
			
		||||
		if e, ok := err.(*ex.ExitError); ok {
 | 
			
		||||
			if s, ok := e.Sys().(syscall.WaitStatus); ok {
 | 
			
		||||
				result.ExitStatus = s.ExitStatus()
 | 
			
		||||
			} else {
 | 
			
		||||
@@ -296,21 +323,27 @@ func getSSHLogger(log ...*logrus.Entry) *logrus.Entry {
 | 
			
		||||
	return log[0]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func decolateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
 | 
			
		||||
func decorateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
 | 
			
		||||
	if sudo && c.User != "root" && !c.IsContainer() {
 | 
			
		||||
		cmd = fmt.Sprintf("sudo -S %s", cmd)
 | 
			
		||||
		cmd = strings.Replace(cmd, "|", "| sudo ", -1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Family != "FreeBSD" {
 | 
			
		||||
		// set pipefail option. Bash only
 | 
			
		||||
		// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
 | 
			
		||||
		cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
 | 
			
		||||
	}
 | 
			
		||||
	// If you are using pipe and you want to detect preprocessing errors, remove comment out
 | 
			
		||||
	//  switch c.Distro.Family {
 | 
			
		||||
	//  case "FreeBSD", "ubuntu", "debian", "raspbian":
 | 
			
		||||
	//  default:
 | 
			
		||||
	//      // set pipefail option. Bash only
 | 
			
		||||
	//      // http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
 | 
			
		||||
	//      cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
 | 
			
		||||
	//  }
 | 
			
		||||
 | 
			
		||||
	if c.IsContainer() {
 | 
			
		||||
		switch c.Container.Type {
 | 
			
		||||
		switch c.Containers.Type {
 | 
			
		||||
		case "", "docker":
 | 
			
		||||
			cmd = fmt.Sprintf(`docker exec %s /bin/bash -c "%s"`, c.Container.ContainerID, cmd)
 | 
			
		||||
		case "lxd":
 | 
			
		||||
			cmd = fmt.Sprintf(`lxc exec %s -- /bin/bash -c "%s"`, c.Container.Name, cmd)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	//  cmd = fmt.Sprintf("set -x; %s", cmd)
 | 
			
		||||
@@ -318,7 +351,7 @@ func decolateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAgentAuth() (auth ssh.AuthMethod, ok bool) {
 | 
			
		||||
	if sock := os.Getenv("SSH_AUTH_SOCK"); len(sock) > 0 {
 | 
			
		||||
	if sock := os.Getenv("SSH_AUTH_SOCK"); 0 < len(sock) {
 | 
			
		||||
		if agconn, err := net.Dial("unix", sock); err == nil {
 | 
			
		||||
			ag := agent.NewClient(agconn)
 | 
			
		||||
			auth = ssh.PublicKeysCallback(ag.Signers)
 | 
			
		||||
@@ -1,3 +1,20 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@@ -5,7 +22,6 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
@@ -19,37 +35,37 @@ type bsd struct {
 | 
			
		||||
func newBsd(c config.ServerInfo) *bsd {
 | 
			
		||||
	d := &bsd{}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/freebsd.rb
 | 
			
		||||
func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) {
 | 
			
		||||
	bsd = newBsd(c)
 | 
			
		||||
	c.Family = "FreeBSD"
 | 
			
		||||
	if r := sshExec(c, "uname", noSudo); r.isSuccess() {
 | 
			
		||||
 | 
			
		||||
	// Prevent from adding `set -o pipefail` option
 | 
			
		||||
	c.Distro = config.Distro{Family: "FreeBSD"}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "uname", noSudo); r.isSuccess() {
 | 
			
		||||
		if strings.Contains(r.Stdout, "FreeBSD") == true {
 | 
			
		||||
			if b := sshExec(c, "uname -r", noSudo); b.isSuccess() {
 | 
			
		||||
				bsd.setDistributionInfo("FreeBSD", strings.TrimSpace(b.Stdout))
 | 
			
		||||
				bsd.setServerInfo(c)
 | 
			
		||||
			if b := exec(c, "freebsd-version", noSudo); b.isSuccess() {
 | 
			
		||||
				rel := strings.TrimSpace(b.Stdout)
 | 
			
		||||
				bsd.setDistro("FreeBSD", rel)
 | 
			
		||||
				return true, bsd
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	Log.Debugf("Not FreeBSD. servernam: %s", c.ServerName)
 | 
			
		||||
	util.Log.Debugf("Not FreeBSD. servernam: %s", c.ServerName)
 | 
			
		||||
	return false, bsd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) checkIfSudoNoPasswd() error {
 | 
			
		||||
	// FreeBSD doesn't need root privilege
 | 
			
		||||
	o.log.Infof("sudo ... OK")
 | 
			
		||||
	o.log.Infof("sudo ... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) install() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) checkRequiredPackagesInstalled() error {
 | 
			
		||||
func (o *bsd) checkDependencies() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -62,40 +78,40 @@ func (o *bsd) scanPackages() error {
 | 
			
		||||
	}
 | 
			
		||||
	o.setPackages(packs)
 | 
			
		||||
 | 
			
		||||
	var unsecurePacks []CvePacksInfo
 | 
			
		||||
	if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
 | 
			
		||||
	var vinfos []models.VulnInfo
 | 
			
		||||
	if vinfos, err = o.scanUnsecurePackages(); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan vulnerable packages")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setUnsecurePackages(unsecurePacks)
 | 
			
		||||
	o.setVulnInfos(vinfos)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanInstalledPackages() ([]models.PackageInfo, error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("pkg version -v")
 | 
			
		||||
	r := o.ssh(cmd, noSudo)
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.parsePkgVersion(r.Stdout), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
 | 
			
		||||
func (o *bsd) scanUnsecurePackages() (vulnInfos []models.VulnInfo, err error) {
 | 
			
		||||
	const vulndbPath = "/tmp/vuln.db"
 | 
			
		||||
	cmd := "rm -f " + vulndbPath
 | 
			
		||||
	r := o.ssh(cmd, noSudo)
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess(0) {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd = util.PrependProxyEnv("pkg audit -F -r -f " + vulndbPath)
 | 
			
		||||
	r = o.ssh(cmd, noSudo)
 | 
			
		||||
	r = o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess(0, 1) {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	if r.ExitStatus == 0 {
 | 
			
		||||
		// no vulnerabilities
 | 
			
		||||
		return []CvePacksInfo{}, nil
 | 
			
		||||
		return []models.VulnInfo{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var packAdtRslt []pkgAuditResult
 | 
			
		||||
@@ -126,35 +142,24 @@ func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveIDs := []string{}
 | 
			
		||||
	for k := range cveIDAdtMap {
 | 
			
		||||
		cveIDs = append(cveIDs, k)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Info("Done")
 | 
			
		||||
 | 
			
		||||
	for _, d := range cveDetails {
 | 
			
		||||
		packs := []models.PackageInfo{}
 | 
			
		||||
		for _, r := range cveIDAdtMap[d.CveID] {
 | 
			
		||||
		for _, r := range cveIDAdtMap[k] {
 | 
			
		||||
			packs = append(packs, r.pack)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		disAdvs := []models.DistroAdvisory{}
 | 
			
		||||
		for _, r := range cveIDAdtMap[d.CveID] {
 | 
			
		||||
		for _, r := range cveIDAdtMap[k] {
 | 
			
		||||
			disAdvs = append(disAdvs, models.DistroAdvisory{
 | 
			
		||||
				AdvisoryID: r.vulnIDCveIDs.vulnID,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cvePacksList = append(cvePacksList, CvePacksInfo{
 | 
			
		||||
			CveID:            d.CveID,
 | 
			
		||||
			CveDetail:        d,
 | 
			
		||||
			Packs:            packs,
 | 
			
		||||
		vulnInfos = append(vulnInfos, models.VulnInfo{
 | 
			
		||||
			CveID:            k,
 | 
			
		||||
			Packages:         packs,
 | 
			
		||||
			DistroAdvisories: disAdvs,
 | 
			
		||||
			Confidence:       models.PkgAuditMatch,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
 
 | 
			
		||||
@@ -139,17 +139,17 @@ WWW: https://vuxml.FreeBSD.org/freebsd/ab3e98d9-8175-11e4-907d-d050992ecde8.html
 | 
			
		||||
 | 
			
		||||
	d := newBsd(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		aName, aCveIDs, aVunlnID := d.parseBlock(tt.in)
 | 
			
		||||
		aName, aCveIDs, aVulnID := d.parseBlock(tt.in)
 | 
			
		||||
		if tt.name != aName {
 | 
			
		||||
			t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVunlnID)
 | 
			
		||||
			t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVulnID)
 | 
			
		||||
		}
 | 
			
		||||
		for i := range tt.cveIDs {
 | 
			
		||||
			if tt.cveIDs[i] != aCveIDs[i] {
 | 
			
		||||
				t.Errorf("expected cveID: %s, actual %s", tt.cveIDs[i], aCveIDs[i])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if tt.vulnID != aVunlnID {
 | 
			
		||||
			t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVunlnID)
 | 
			
		||||
		if tt.vulnID != aVulnID {
 | 
			
		||||
			t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVulnID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										557
									
								
								scan/redhat.go
									
									
									
									
									
								
							
							
						
						
									
										557
									
								
								scan/redhat.go
									
									
									
									
									
								
							@@ -21,12 +21,10 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
 | 
			
		||||
@@ -42,179 +40,188 @@ type redhat struct {
 | 
			
		||||
func newRedhat(c config.ServerInfo) *redhat {
 | 
			
		||||
	r := &redhat{}
 | 
			
		||||
	r.log = util.NewCustomLogger(c)
 | 
			
		||||
	r.setServerInfo(c)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/redhat.rb
 | 
			
		||||
func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
 | 
			
		||||
	red = newRedhat(c)
 | 
			
		||||
	red.setServerInfo(c)
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
 | 
			
		||||
		red.setDistributionInfo("fedora", "unknown")
 | 
			
		||||
		Log.Warn("Fedora not tested yet: %s", r)
 | 
			
		||||
	if r := exec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
 | 
			
		||||
		red.setDistro("fedora", "unknown")
 | 
			
		||||
		util.Log.Warn("Fedora not tested yet: %s", r)
 | 
			
		||||
		return true, red
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/redhat-release", noSudo); r.isSuccess() {
 | 
			
		||||
	if r := exec(c, "ls /etc/oracle-release", noSudo); r.isSuccess() {
 | 
			
		||||
		// Need to discover Oracle Linux first, because it provides an
 | 
			
		||||
		// /etc/redhat-release that matches the upstream distribution
 | 
			
		||||
		if r := exec(c, "cat /etc/oracle-release", noSudo); r.isSuccess() {
 | 
			
		||||
			re := regexp.MustCompile(`(.*) release (\d[\d.]*)`)
 | 
			
		||||
			result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
			
		||||
			if len(result) != 3 {
 | 
			
		||||
				util.Log.Warn("Failed to parse Oracle Linux version: %s", r)
 | 
			
		||||
				return true, red
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			release := result[2]
 | 
			
		||||
			red.setDistro("oraclelinux", release)
 | 
			
		||||
			return true, red
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "ls /etc/redhat-release", noSudo); r.isSuccess() {
 | 
			
		||||
		// https://www.rackaid.com/blog/how-to-determine-centos-or-red-hat-version/
 | 
			
		||||
		// e.g.
 | 
			
		||||
		// $ cat /etc/redhat-release
 | 
			
		||||
		// CentOS release 6.5 (Final)
 | 
			
		||||
		if r := sshExec(c, "cat /etc/redhat-release", noSudo); r.isSuccess() {
 | 
			
		||||
			re, _ := regexp.Compile(`(.*) release (\d[\d.]*)`)
 | 
			
		||||
		if r := exec(c, "cat /etc/redhat-release", noSudo); r.isSuccess() {
 | 
			
		||||
			re := regexp.MustCompile(`(.*) release (\d[\d.]*)`)
 | 
			
		||||
			result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
			
		||||
			if len(result) != 3 {
 | 
			
		||||
				Log.Warn("Failed to parse RedHat/CentOS version: %s", r)
 | 
			
		||||
				util.Log.Warn("Failed to parse RedHat/CentOS version: %s", r)
 | 
			
		||||
				return true, red
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			release := result[2]
 | 
			
		||||
			switch strings.ToLower(result[1]) {
 | 
			
		||||
			case "centos", "centos linux":
 | 
			
		||||
				red.setDistributionInfo("centos", release)
 | 
			
		||||
				red.setDistro("centos", release)
 | 
			
		||||
			default:
 | 
			
		||||
				red.setDistributionInfo("rhel", release)
 | 
			
		||||
				red.setDistro("rhel", release)
 | 
			
		||||
			}
 | 
			
		||||
			return true, red
 | 
			
		||||
		}
 | 
			
		||||
		return true, red
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/system-release", noSudo); r.isSuccess() {
 | 
			
		||||
	if r := exec(c, "ls /etc/system-release", noSudo); r.isSuccess() {
 | 
			
		||||
		family := "amazon"
 | 
			
		||||
		release := "unknown"
 | 
			
		||||
		if r := sshExec(c, "cat /etc/system-release", noSudo); r.isSuccess() {
 | 
			
		||||
		if r := exec(c, "cat /etc/system-release", noSudo); r.isSuccess() {
 | 
			
		||||
			fields := strings.Fields(r.Stdout)
 | 
			
		||||
			if len(fields) == 5 {
 | 
			
		||||
				release = fields[4]
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		red.setDistributionInfo(family, release)
 | 
			
		||||
		red.setDistro(family, release)
 | 
			
		||||
		return true, red
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Debugf("Not RedHat like Linux. servername: %s", c.ServerName)
 | 
			
		||||
	util.Log.Debugf("Not RedHat like Linux. servername: %s", c.ServerName)
 | 
			
		||||
	return false, red
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) checkIfSudoNoPasswd() error {
 | 
			
		||||
	r := o.ssh("yum --version", sudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		o.log.Errorf("sudo error on %s", r)
 | 
			
		||||
		return fmt.Errorf("Failed to sudo: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Infof("sudo ... OK")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CentOS 5 ... yum-plugin-security, yum-changelog
 | 
			
		||||
// CentOS 6 ... yum-plugin-security, yum-plugin-changelog
 | 
			
		||||
// CentOS 7 ... yum-plugin-security, yum-plugin-changelog
 | 
			
		||||
// RHEL, Amazon ... no additinal packages needed
 | 
			
		||||
func (o *redhat) install() error {
 | 
			
		||||
 | 
			
		||||
	switch o.Family {
 | 
			
		||||
	case "rhel", "amazon":
 | 
			
		||||
		o.log.Infof("Nothing to do")
 | 
			
		||||
	if !o.sudo() {
 | 
			
		||||
		o.log.Infof("sudo ... No need")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := o.installYumPluginSecurity(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	type cmd struct {
 | 
			
		||||
		cmd                 string
 | 
			
		||||
		expectedStatusCodes []int
 | 
			
		||||
	}
 | 
			
		||||
	return o.installYumChangelog()
 | 
			
		||||
}
 | 
			
		||||
	var cmds []cmd
 | 
			
		||||
	var zero = []int{0}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) installYumPluginSecurity() error {
 | 
			
		||||
 | 
			
		||||
	if r := o.ssh("rpm -q yum-plugin-security", noSudo); r.isSuccess() {
 | 
			
		||||
		o.log.Infof("Ignored: yum-plugin-security already installed")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Info("Installing yum-plugin-security...")
 | 
			
		||||
	cmd := util.PrependProxyEnv("yum install -y yum-plugin-security")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		return fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) installYumChangelog() error {
 | 
			
		||||
 | 
			
		||||
	if o.Family == "centos" {
 | 
			
		||||
		var majorVersion int
 | 
			
		||||
		if 0 < len(o.Release) {
 | 
			
		||||
			majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
 | 
			
		||||
		} else {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"Not implemented yet. family: %s, release: %s",
 | 
			
		||||
				o.Family, o.Release)
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "centos":
 | 
			
		||||
		cmds = []cmd{
 | 
			
		||||
			{"yum --changelog --assumeno update yum", []int{0, 1}},
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case "rhel", "oraclelinux":
 | 
			
		||||
		majorVersion, err := o.Distro.MajorVersion()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Not implemented yet: %s, err: %s", o.Distro, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var packName = ""
 | 
			
		||||
		if majorVersion < 6 {
 | 
			
		||||
			packName = "yum-changelog"
 | 
			
		||||
			cmds = []cmd{
 | 
			
		||||
				{"yum --color=never repolist", zero},
 | 
			
		||||
				{"yum --color=never check-update", []int{0, 100}},
 | 
			
		||||
				{"yum --color=never list-security --security", zero},
 | 
			
		||||
				{"yum --color=never info-security", zero},
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			packName = "yum-plugin-changelog"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cmd := "rpm -q " + packName
 | 
			
		||||
		if r := o.ssh(cmd, noSudo); r.isSuccess() {
 | 
			
		||||
			o.log.Infof("Ignored: %s already installed", packName)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		o.log.Infof("Installing %s...", packName)
 | 
			
		||||
		cmd = util.PrependProxyEnv("yum install -y " + packName)
 | 
			
		||||
		if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
			return fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
		}
 | 
			
		||||
		o.log.Infof("Installed: %s", packName)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) checkRequiredPackagesInstalled() error {
 | 
			
		||||
	if config.Conf.UseYumPluginSecurity {
 | 
			
		||||
		// check if yum-plugin-security is installed.
 | 
			
		||||
		// Amazon Linux, REHL can execute 'yum updateinfo --security updates' without yum-plugin-security
 | 
			
		||||
		if o.Family == "centos" {
 | 
			
		||||
			cmd := "rpm -q yum-plugin-security"
 | 
			
		||||
			if r := o.ssh(cmd, noSudo); !r.isSuccess() {
 | 
			
		||||
				msg := "yum-plugin-security is not installed"
 | 
			
		||||
				o.log.Errorf(msg)
 | 
			
		||||
				return fmt.Errorf(msg)
 | 
			
		||||
			cmds = []cmd{
 | 
			
		||||
				{"yum --color=never repolist", zero},
 | 
			
		||||
				{"yum --color=never check-update", []int{0, 100}},
 | 
			
		||||
				{"yum --color=never --security updateinfo list updates", zero},
 | 
			
		||||
				{"yum --color=never --security updateinfo updates", zero},
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, c := range cmds {
 | 
			
		||||
		cmd := util.PrependProxyEnv(c.cmd)
 | 
			
		||||
		o.log.Infof("Checking... sudo %s", cmd)
 | 
			
		||||
		r := o.exec(util.PrependProxyEnv(cmd), o.sudo())
 | 
			
		||||
		if !r.isSuccess(c.expectedStatusCodes...) {
 | 
			
		||||
			o.log.Errorf("Check sudo or proxy settings: %s", r)
 | 
			
		||||
			return fmt.Errorf("Failed to sudo: %s", r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Infof("Sudo... Pass")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CentOS 6, 7 	... yum-plugin-changelog
 | 
			
		||||
// RHEL 5     	... yum-security
 | 
			
		||||
// RHEL 6, 7    ... -
 | 
			
		||||
// Amazon 		... -
 | 
			
		||||
func (o *redhat) checkDependencies() error {
 | 
			
		||||
	var packName string
 | 
			
		||||
	if o.Distro.Family == "amazon" {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.Family == "centos" {
 | 
			
		||||
		var majorVersion int
 | 
			
		||||
		if 0 < len(o.Release) {
 | 
			
		||||
			majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
 | 
			
		||||
		} else {
 | 
			
		||||
			msg := fmt.Sprintf("Not implemented yet. family: %s, release: %s", o.Family, o.Release)
 | 
			
		||||
	majorVersion, err := o.Distro.MajorVersion()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		msg := fmt.Sprintf("Not implemented yet: %s, err: %s", o.Distro, err)
 | 
			
		||||
		o.log.Errorf(msg)
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.Distro.Family == "centos" {
 | 
			
		||||
		if majorVersion < 6 {
 | 
			
		||||
			msg := fmt.Sprintf("CentOS %s is not supported", o.Distro.Release)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var packName = ""
 | 
			
		||||
		if majorVersion < 6 {
 | 
			
		||||
			packName = "yum-changelog"
 | 
			
		||||
		} else {
 | 
			
		||||
			packName = "yum-plugin-changelog"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cmd := "rpm -q " + packName
 | 
			
		||||
		if r := o.ssh(cmd, noSudo); !r.isSuccess() {
 | 
			
		||||
			msg := fmt.Sprintf("%s is not installed", packName)
 | 
			
		||||
		// --assumeno option of yum is needed.
 | 
			
		||||
		cmd := "yum -h | grep assumeno"
 | 
			
		||||
		if r := o.exec(cmd, noSudo); !r.isSuccess() {
 | 
			
		||||
			msg := fmt.Sprintf("Installed yum is old. Please update yum and then retry")
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "centos":
 | 
			
		||||
		packName = "yum-plugin-changelog"
 | 
			
		||||
	case "rhel", "oraclelinux":
 | 
			
		||||
		if majorVersion < 6 {
 | 
			
		||||
			packName = "yum-security"
 | 
			
		||||
		} else {
 | 
			
		||||
			// yum-plugin-security is installed by default on RHEL6, 7
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("Not implemented yet: %s", o.Distro)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := "rpm -q " + packName
 | 
			
		||||
	if r := o.exec(cmd, noSudo); !r.isSuccess() {
 | 
			
		||||
		msg := fmt.Sprintf("%s is not installed", packName)
 | 
			
		||||
		o.log.Errorf(msg)
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Infof("Dependencies... Pass")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -227,18 +234,18 @@ func (o *redhat) scanPackages() error {
 | 
			
		||||
	}
 | 
			
		||||
	o.setPackages(packs)
 | 
			
		||||
 | 
			
		||||
	var unsecurePacks []CvePacksInfo
 | 
			
		||||
	if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
 | 
			
		||||
	var vinfos []models.VulnInfo
 | 
			
		||||
	if vinfos, err = o.scanVulnInfos(); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan vulnerable packages")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setUnsecurePackages(unsecurePacks)
 | 
			
		||||
	o.setVulnInfos(vinfos)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) scanInstalledPackages() (installedPackages models.PackageInfoList, err error) {
 | 
			
		||||
	cmd := "rpm -qa --queryformat '%{NAME}\t%{VERSION}\t%{RELEASE}\n'"
 | 
			
		||||
	r := o.ssh(cmd, noSudo)
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if r.isSuccess() {
 | 
			
		||||
		//  e.g.
 | 
			
		||||
		// openssl	1.0.1e	30.el6.11
 | 
			
		||||
@@ -273,9 +280,9 @@ func (o *redhat) parseScannedPackagesLine(line string) (models.PackageInfo, erro
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
 | 
			
		||||
	if o.Family != "centos" || config.Conf.UseYumPluginSecurity {
 | 
			
		||||
		// Amazon, RHEL has yum updateinfo as default
 | 
			
		||||
func (o *redhat) scanVulnInfos() ([]models.VulnInfo, error) {
 | 
			
		||||
	if o.Distro.Family != "centos" {
 | 
			
		||||
		// Amazon, RHEL, Oracle Linux has yum updateinfo as default
 | 
			
		||||
		// yum updateinfo can collenct vendor advisory information.
 | 
			
		||||
		return o.scanUnsecurePackagesUsingYumPluginSecurity()
 | 
			
		||||
	}
 | 
			
		||||
@@ -284,11 +291,16 @@ func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
 | 
			
		||||
	return o.scanUnsecurePackagesUsingYumCheckUpdate()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO return whether already expired.
 | 
			
		||||
func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error) {
 | 
			
		||||
// For CentOS
 | 
			
		||||
func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (models.VulnInfos, error) {
 | 
			
		||||
	cmd := "LANGUAGE=en_US.UTF-8 yum --color=never %s check-update"
 | 
			
		||||
	if o.getServerInfo().Enablerepo != "" {
 | 
			
		||||
		cmd = fmt.Sprintf(cmd, "--enablerepo="+o.getServerInfo().Enablerepo)
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd = fmt.Sprintf(cmd, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := "LANG=en_US.UTF-8 yum --color=never check-update"
 | 
			
		||||
	r := o.ssh(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	r := o.exec(util.PrependProxyEnv(cmd), noSudo)
 | 
			
		||||
	if !r.isSuccess(0, 100) {
 | 
			
		||||
		//returns an exit code of 100 if there are available updates.
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
@@ -301,24 +313,42 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error)
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Debugf("%s", pp.Sprintf("%v", packInfoList))
 | 
			
		||||
 | 
			
		||||
	// set candidate version info
 | 
			
		||||
	o.Packages.MergeNewVersion(packInfoList)
 | 
			
		||||
 | 
			
		||||
	// Collect CVE-IDs in changelog
 | 
			
		||||
	type PackInfoCveIDs struct {
 | 
			
		||||
		PackInfo models.PackageInfo
 | 
			
		||||
		CveIDs   []string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// { packageName: changelog-lines }
 | 
			
		||||
	var rpm2changelog map[string]*string
 | 
			
		||||
	allChangelog, err := o.getAllChangelog(packInfoList)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to getAllchangelog. err: %s", err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	rpm2changelog, err = o.parseAllChangelog(allChangelog)
 | 
			
		||||
 | 
			
		||||
	// { packageName: changelog-lines }
 | 
			
		||||
	var rpm2changelog map[string]*string
 | 
			
		||||
	rpm2changelog, err = o.divideChangelogByPackage(allChangelog)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to parseAllChangelog. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for name, clog := range rpm2changelog {
 | 
			
		||||
		for i, p := range o.Packages {
 | 
			
		||||
			n := fmt.Sprintf("%s-%s-%s",
 | 
			
		||||
				p.Name, p.NewVersion, p.NewRelease)
 | 
			
		||||
			if name == n {
 | 
			
		||||
				o.Packages[i].Changelog = models.Changelog{
 | 
			
		||||
					Contents: *clog,
 | 
			
		||||
					Method:   models.ChangelogExactMatchStr,
 | 
			
		||||
				}
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var results []PackInfoCveIDs
 | 
			
		||||
	for i, packInfo := range packInfoList {
 | 
			
		||||
		changelog := o.getChangelogCVELines(rpm2changelog, packInfo)
 | 
			
		||||
@@ -370,39 +400,21 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error)
 | 
			
		||||
	cveIDPackInfoMap := make(map[string][]models.PackageInfo)
 | 
			
		||||
	for _, res := range results {
 | 
			
		||||
		for _, cveID := range res.CveIDs {
 | 
			
		||||
			//  packInfo, found := o.Packages.FindByName(res.Packname)
 | 
			
		||||
			//  if !found {
 | 
			
		||||
			//      return CvePacksList{}, fmt.Errorf(
 | 
			
		||||
			//          "Faild to transform data structure: %v", res.Packname)
 | 
			
		||||
			//  }
 | 
			
		||||
			cveIDPackInfoMap[cveID] = append(cveIDPackInfoMap[cveID], res.PackInfo)
 | 
			
		||||
			cveIDPackInfoMap[cveID] = append(
 | 
			
		||||
				cveIDPackInfoMap[cveID], res.PackInfo)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var uniqueCveIDs []string
 | 
			
		||||
	for cveID := range cveIDPackInfoMap {
 | 
			
		||||
		uniqueCveIDs = append(uniqueCveIDs, cveID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// cveIDs => []cve.CveInfo
 | 
			
		||||
	o.log.Info("Fetching CVE details...")
 | 
			
		||||
	cveDetails, err := cveapi.CveClient.FetchCveDetails(uniqueCveIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Info("Done")
 | 
			
		||||
 | 
			
		||||
	cvePacksList := []CvePacksInfo{}
 | 
			
		||||
	for _, detail := range cveDetails {
 | 
			
		||||
	vinfos := []models.VulnInfo{}
 | 
			
		||||
	for k, v := range cveIDPackInfoMap {
 | 
			
		||||
		// Amazon, RHEL do not use this method, so VendorAdvisory do not set.
 | 
			
		||||
		cvePacksList = append(cvePacksList, CvePacksInfo{
 | 
			
		||||
			CveID:     detail.CveID,
 | 
			
		||||
			CveDetail: detail,
 | 
			
		||||
			Packs:     cveIDPackInfoMap[detail.CveID],
 | 
			
		||||
			//  CvssScore: cinfo.CvssScore(conf.Lang),
 | 
			
		||||
		vinfos = append(vinfos, models.VulnInfo{
 | 
			
		||||
			CveID:      k,
 | 
			
		||||
			Packages:   v,
 | 
			
		||||
			Confidence: models.ChangelogExactMatch,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return cvePacksList, nil
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseYumCheckUpdateLines parse yum check-update to get package name, candidate version
 | 
			
		||||
@@ -416,7 +428,9 @@ func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.Package
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if needToParse {
 | 
			
		||||
			if strings.HasPrefix(line, "Obsoleting") {
 | 
			
		||||
			if strings.HasPrefix(line, "Obsoleting") ||
 | 
			
		||||
				strings.HasPrefix(line, "Security:") {
 | 
			
		||||
				// see https://github.com/future-architect/vuls/issues/165
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			candidate, err := o.parseYumCheckUpdateLine(line)
 | 
			
		||||
@@ -433,6 +447,7 @@ func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.Package
 | 
			
		||||
			}
 | 
			
		||||
			installed.NewVersion = candidate.NewVersion
 | 
			
		||||
			installed.NewRelease = candidate.NewRelease
 | 
			
		||||
			installed.Repository = candidate.Repository
 | 
			
		||||
			results = append(results, installed)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -441,7 +456,7 @@ func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.Package
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumCheckUpdateLine(line string) (models.PackageInfo, error) {
 | 
			
		||||
	fields := strings.Fields(line)
 | 
			
		||||
	if len(fields) != 3 {
 | 
			
		||||
	if len(fields) < 3 {
 | 
			
		||||
		return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line)
 | 
			
		||||
	}
 | 
			
		||||
	splitted := strings.Split(fields[0], ".")
 | 
			
		||||
@@ -452,16 +467,19 @@ func (o *redhat) parseYumCheckUpdateLine(line string) (models.PackageInfo, error
 | 
			
		||||
		packName = strings.Join(strings.Split(fields[0], ".")[0:(len(splitted)-1)], ".")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fields = strings.Split(fields[1], "-")
 | 
			
		||||
	if len(fields) != 2 {
 | 
			
		||||
	verfields := strings.Split(fields[1], "-")
 | 
			
		||||
	if len(verfields) != 2 {
 | 
			
		||||
		return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line)
 | 
			
		||||
	}
 | 
			
		||||
	version := o.regexpReplace(fields[0], `^[0-9]+:`, "")
 | 
			
		||||
	release := fields[1]
 | 
			
		||||
	version := o.regexpReplace(verfields[0], `^[0-9]+:`, "")
 | 
			
		||||
	release := verfields[1]
 | 
			
		||||
	repos := strings.Join(fields[2:len(fields)], " ")
 | 
			
		||||
 | 
			
		||||
	return models.PackageInfo{
 | 
			
		||||
		Name:       packName,
 | 
			
		||||
		NewVersion: version,
 | 
			
		||||
		NewRelease: release,
 | 
			
		||||
		Repository: repos,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -471,18 +489,19 @@ func (o *redhat) mkPstring() *string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) regexpReplace(src string, pat string, rep string) string {
 | 
			
		||||
	r := regexp.MustCompile(pat)
 | 
			
		||||
	return r.ReplaceAllString(src, rep)
 | 
			
		||||
	re := regexp.MustCompile(pat)
 | 
			
		||||
	return re.ReplaceAllString(src, rep)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var changeLogCVEPattern = regexp.MustCompile(`CVE-[0-9]+-[0-9]+`)
 | 
			
		||||
 | 
			
		||||
func (o *redhat) getChangelogCVELines(rpm2changelog map[string]*string, packInfo models.PackageInfo) string {
 | 
			
		||||
	rpm := fmt.Sprintf("%s-%s-%s", packInfo.Name, packInfo.NewVersion, packInfo.NewRelease)
 | 
			
		||||
	retLine := ""
 | 
			
		||||
	if rpm2changelog[rpm] != nil {
 | 
			
		||||
		lines := strings.Split(*rpm2changelog[rpm], "\n")
 | 
			
		||||
		for _, line := range lines {
 | 
			
		||||
			match, _ := regexp.MatchString("CVE-[0-9]+-[0-9]+", line)
 | 
			
		||||
			if match {
 | 
			
		||||
			if changeLogCVEPattern.MatchString(line) {
 | 
			
		||||
				retLine += fmt.Sprintf("%s\n", line)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -490,27 +509,26 @@ func (o *redhat) getChangelogCVELines(rpm2changelog map[string]*string, packInfo
 | 
			
		||||
	return retLine
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseAllChangelog(allChangelog string) (map[string]*string, error) {
 | 
			
		||||
func (o *redhat) divideChangelogByPackage(allChangelog string) (map[string]*string, error) {
 | 
			
		||||
	var majorVersion int
 | 
			
		||||
	if 0 < len(o.Release) && o.Family == "centos" {
 | 
			
		||||
		majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Not implemented yet. family: %s, release: %s",
 | 
			
		||||
			o.Family, o.Release)
 | 
			
		||||
	var err error
 | 
			
		||||
	if o.Distro.Family == "centos" {
 | 
			
		||||
		majorVersion, err = o.Distro.MajorVersion()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("Not implemented yet: %s, err: %s", o.Distro, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	orglines := strings.Split(allChangelog, "\n")
 | 
			
		||||
	tmpline := ""
 | 
			
		||||
	var lines []string
 | 
			
		||||
	var prev, now bool
 | 
			
		||||
	var err error
 | 
			
		||||
	for i := range orglines {
 | 
			
		||||
		if majorVersion == 5 {
 | 
			
		||||
			/* for CentOS5 (yum-util < 1.1.20) */
 | 
			
		||||
			prev = false
 | 
			
		||||
			now = false
 | 
			
		||||
			if i > 0 {
 | 
			
		||||
			if 0 < i {
 | 
			
		||||
				prev, err = o.isRpmPackageNameLine(orglines[i-1])
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
@@ -560,8 +578,7 @@ func (o *redhat) parseAllChangelog(allChangelog string) (map[string]*string, err
 | 
			
		||||
				rpm2changelog[rpm] = pNewString
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			stop, _ := regexp.MatchString("^Dependencies Resolved", line)
 | 
			
		||||
			if stop {
 | 
			
		||||
			if strings.HasPrefix(line, "Dependencies Resolved") {
 | 
			
		||||
				return rpm2changelog, nil
 | 
			
		||||
			}
 | 
			
		||||
			*writePointer += fmt.Sprintf("%s\n", line)
 | 
			
		||||
@@ -570,6 +587,7 @@ func (o *redhat) parseAllChangelog(allChangelog string) (map[string]*string, err
 | 
			
		||||
	return rpm2changelog, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CentOS
 | 
			
		||||
func (o *redhat) getAllChangelog(packInfoList models.PackageInfoList) (stdout string, err error) {
 | 
			
		||||
	packageNames := ""
 | 
			
		||||
	for _, packInfo := range packInfoList {
 | 
			
		||||
@@ -577,23 +595,28 @@ func (o *redhat) getAllChangelog(packInfoList models.PackageInfoList) (stdout st
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	command := ""
 | 
			
		||||
	if o.ServerInfo.User == "root" {
 | 
			
		||||
		command = "echo N | "
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(config.Conf.HTTPProxy) {
 | 
			
		||||
		command += util.ProxyEnv()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// yum update --changelog doesn't have --color option.
 | 
			
		||||
	command += fmt.Sprintf(" LANG=en_US.UTF-8 yum update --changelog %s", packageNames)
 | 
			
		||||
	yumopts := ""
 | 
			
		||||
	if o.getServerInfo().Enablerepo != "" {
 | 
			
		||||
		yumopts = " --enablerepo=" + o.getServerInfo().Enablerepo
 | 
			
		||||
	}
 | 
			
		||||
	if config.Conf.SkipBroken {
 | 
			
		||||
		yumopts += " --skip-broken"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := o.ssh(command, sudo)
 | 
			
		||||
	// yum update --changelog doesn't have --color option.
 | 
			
		||||
	command += fmt.Sprintf(" LANGUAGE=en_US.UTF-8 yum --changelog --assumeno update %s ", yumopts) + packageNames
 | 
			
		||||
 | 
			
		||||
	r := o.exec(command, sudo)
 | 
			
		||||
	if !r.isSuccess(0, 1) {
 | 
			
		||||
		return "", fmt.Errorf(
 | 
			
		||||
			"Failed to get changelog. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
	}
 | 
			
		||||
	return r.Stdout, nil
 | 
			
		||||
	return strings.Replace(r.Stdout, "\r", "", -1), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type distroAdvisoryCveIDs struct {
 | 
			
		||||
@@ -602,33 +625,41 @@ type distroAdvisoryCveIDs struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scaning unsecure packages using yum-plugin-security.
 | 
			
		||||
//TODO return whether already expired.
 | 
			
		||||
func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, error) {
 | 
			
		||||
	if o.Family == "centos" {
 | 
			
		||||
// Amazon, RHEL, Oracle Linux
 | 
			
		||||
func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (models.VulnInfos, error) {
 | 
			
		||||
	if o.Distro.Family == "centos" {
 | 
			
		||||
		// CentOS has no security channel.
 | 
			
		||||
		// So use yum check-update && parse changelog
 | 
			
		||||
		return CvePacksList{}, fmt.Errorf(
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"yum updateinfo is not suppported on CentOS")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := "yum --color=never repolist"
 | 
			
		||||
	r := o.ssh(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	r := o.exec(util.PrependProxyEnv(cmd), o.sudo())
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get advisoryID(RHSA, ALAS) - package name,version
 | 
			
		||||
	cmd = "yum --color=never updateinfo list available --security"
 | 
			
		||||
	r = o.ssh(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	// get advisoryID(RHSA, ALAS, ELSA) - package name,version
 | 
			
		||||
	major, err := (o.Distro.MajorVersion())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Not implemented yet: %s, err: %s", o.Distro, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (o.Distro.Family == "rhel" || o.Distro.Family == "oraclelinux") && major == 5 {
 | 
			
		||||
		cmd = "yum --color=never list-security --security"
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd = "yum --color=never --security updateinfo list updates"
 | 
			
		||||
	}
 | 
			
		||||
	r = o.exec(util.PrependProxyEnv(cmd), o.sudo())
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	advIDPackNamesList, err := o.parseYumUpdateinfoListAvailable(r.Stdout)
 | 
			
		||||
 | 
			
		||||
	// get package name, version, rel to be upgrade.
 | 
			
		||||
	//  cmd = "yum check-update --security"
 | 
			
		||||
	cmd = "LANG=en_US.UTF-8 yum --color=never check-update"
 | 
			
		||||
	r = o.ssh(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	cmd = "LANGUAGE=en_US.UTF-8 yum --color=never check-update"
 | 
			
		||||
	r = o.exec(util.PrependProxyEnv(cmd), o.sudo())
 | 
			
		||||
	if !r.isSuccess(0, 100) {
 | 
			
		||||
		//returns an exit code of 100 if there are available updates.
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
@@ -639,6 +670,9 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Debugf("%s", pp.Sprintf("%v", updatable))
 | 
			
		||||
 | 
			
		||||
	// set candidate version info
 | 
			
		||||
	o.Packages.MergeNewVersion(updatable)
 | 
			
		||||
 | 
			
		||||
	dict := map[string][]models.PackageInfo{}
 | 
			
		||||
	for _, advIDPackNames := range advIDPackNamesList {
 | 
			
		||||
		packInfoList := models.PackageInfoList{}
 | 
			
		||||
@@ -654,58 +688,57 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
 | 
			
		||||
		dict[advIDPackNames.AdvisoryID] = packInfoList
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get advisoryID(RHSA, ALAS) - CVE IDs
 | 
			
		||||
	cmd = "yum --color=never updateinfo --security update"
 | 
			
		||||
	r = o.ssh(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	// get advisoryID(RHSA, ALAS, ELSA) - CVE IDs
 | 
			
		||||
	if (o.Distro.Family == "rhel" || o.Distro.Family == "oraclelinux") && major == 5 {
 | 
			
		||||
		cmd = "yum --color=never info-security"
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd = "yum --color=never --security updateinfo updates"
 | 
			
		||||
	}
 | 
			
		||||
	r = o.exec(util.PrependProxyEnv(cmd), o.sudo())
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	advisoryCveIDsList, err := o.parseYumUpdateinfo(r.Stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return CvePacksList{}, err
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	//  pp.Println(advisoryCveIDsList)
 | 
			
		||||
 | 
			
		||||
	// All information collected.
 | 
			
		||||
	// Convert to CvePacksList.
 | 
			
		||||
	o.log.Info("Fetching CVE details...")
 | 
			
		||||
	result := CvePacksList{}
 | 
			
		||||
	// Convert to VulnInfos.
 | 
			
		||||
	vinfos := models.VulnInfos{}
 | 
			
		||||
	for _, advIDCveIDs := range advisoryCveIDsList {
 | 
			
		||||
		cveDetails, err :=
 | 
			
		||||
			cveapi.CveClient.FetchCveDetails(advIDCveIDs.CveIDs)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, cveDetail := range cveDetails {
 | 
			
		||||
		for _, cveID := range advIDCveIDs.CveIDs {
 | 
			
		||||
			found := false
 | 
			
		||||
			for i, p := range result {
 | 
			
		||||
				if cveDetail.CveID == p.CveID {
 | 
			
		||||
			for i, p := range vinfos {
 | 
			
		||||
				if cveID == p.CveID {
 | 
			
		||||
					advAppended := append(p.DistroAdvisories, advIDCveIDs.DistroAdvisory)
 | 
			
		||||
					result[i].DistroAdvisories = advAppended
 | 
			
		||||
					vinfos[i].DistroAdvisories = advAppended
 | 
			
		||||
 | 
			
		||||
					packs := dict[advIDCveIDs.DistroAdvisory.AdvisoryID]
 | 
			
		||||
					result[i].Packs = append(result[i].Packs, packs...)
 | 
			
		||||
					vinfos[i].Packages = append(vinfos[i].Packages, packs...)
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if !found {
 | 
			
		||||
				cpinfo := CvePacksInfo{
 | 
			
		||||
					CveID:            cveDetail.CveID,
 | 
			
		||||
					CveDetail:        cveDetail,
 | 
			
		||||
				cpinfo := models.VulnInfo{
 | 
			
		||||
					CveID:            cveID,
 | 
			
		||||
					DistroAdvisories: []models.DistroAdvisory{advIDCveIDs.DistroAdvisory},
 | 
			
		||||
					Packs:            dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
 | 
			
		||||
					Packages:         dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
 | 
			
		||||
					Confidence:       models.YumUpdateSecurityMatch,
 | 
			
		||||
				}
 | 
			
		||||
				result = append(result, cpinfo)
 | 
			
		||||
				vinfos = append(vinfos, cpinfo)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Info("Done")
 | 
			
		||||
	return result, nil
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var horizontalRulePattern = regexp.MustCompile(`^=+$`)
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveIDs, err error) {
 | 
			
		||||
	sectionState := Outside
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
@@ -723,7 +756,7 @@ func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveID
 | 
			
		||||
		line = strings.TrimSpace(line)
 | 
			
		||||
 | 
			
		||||
		// find the new section pattern
 | 
			
		||||
		if match, _ := o.isHorizontalRule(line); match {
 | 
			
		||||
		if horizontalRulePattern.MatchString(line) {
 | 
			
		||||
 | 
			
		||||
			// set previous section's result to return-variable
 | 
			
		||||
			if sectionState == Content {
 | 
			
		||||
@@ -750,13 +783,13 @@ func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveID
 | 
			
		||||
 | 
			
		||||
		switch sectionState {
 | 
			
		||||
		case Header:
 | 
			
		||||
			switch o.Family {
 | 
			
		||||
			switch o.Distro.Family {
 | 
			
		||||
			case "centos":
 | 
			
		||||
				// CentOS has no security channel.
 | 
			
		||||
				// So use yum check-update && parse changelog
 | 
			
		||||
				return result, fmt.Errorf(
 | 
			
		||||
					"yum updateinfo is not suppported on  CentOS")
 | 
			
		||||
			case "rhel", "amazon":
 | 
			
		||||
			case "rhel", "amazon", "oraclelinux":
 | 
			
		||||
				// nop
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@@ -817,9 +850,8 @@ func (o *redhat) changeSectionState(state int) (newState int) {
 | 
			
		||||
	return newState
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) isHorizontalRule(line string) (bool, error) {
 | 
			
		||||
	return regexp.MatchString("^=+$", line)
 | 
			
		||||
}
 | 
			
		||||
var rpmPackageArchPattern = regexp.MustCompile(
 | 
			
		||||
	`^[^ ]+\.(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)$`)
 | 
			
		||||
 | 
			
		||||
func (o *redhat) isRpmPackageNameLine(line string) (bool, error) {
 | 
			
		||||
	s := strings.TrimPrefix(line, "ChangeLog for: ")
 | 
			
		||||
@@ -829,71 +861,43 @@ func (o *redhat) isRpmPackageNameLine(line string) (bool, error) {
 | 
			
		||||
	}
 | 
			
		||||
	for _, s := range ss {
 | 
			
		||||
		s = strings.TrimRight(s, " \r\n")
 | 
			
		||||
		ok, err := regexp.MatchString(
 | 
			
		||||
			`^[^ ]+\.(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)$`, s)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return false, err
 | 
			
		||||
		if !rpmPackageArchPattern.MatchString(s) {
 | 
			
		||||
			return false, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// see test case
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoHeaderCentOS(line string) (packs []models.PackageInfo, err error) {
 | 
			
		||||
	pkgs := strings.Split(strings.TrimSpace(line), ",")
 | 
			
		||||
	for _, pkg := range pkgs {
 | 
			
		||||
		packs = append(packs, models.PackageInfo{})
 | 
			
		||||
		s := strings.Split(pkg, "-")
 | 
			
		||||
		if len(s) == 3 {
 | 
			
		||||
			packs[len(packs)-1].Name = s[0]
 | 
			
		||||
			packs[len(packs)-1].Version = s[1]
 | 
			
		||||
			packs[len(packs)-1].Release = s[2]
 | 
			
		||||
		} else {
 | 
			
		||||
			return packs, fmt.Errorf("CentOS: Unknown Header format: %s", line)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoHeaderAmazon(line string) (a models.DistroAdvisory, names []string, err error) {
 | 
			
		||||
	re, _ := regexp.Compile(`(ALAS-.+): (.+) priority package update for (.+)$`)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) == 4 {
 | 
			
		||||
		a.AdvisoryID = result[1]
 | 
			
		||||
		a.Severity = result[2]
 | 
			
		||||
		spaceSeparatedPacknames := result[3]
 | 
			
		||||
		names = strings.Fields(spaceSeparatedPacknames)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err = fmt.Errorf("Amazon Linux: Unknown Header Format. %s", line)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
var yumCveIDPattern = regexp.MustCompile(`(CVE-\d{4}-\d{4,})`)
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoLineToGetCveIDs(line string) []string {
 | 
			
		||||
	re, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`)
 | 
			
		||||
	return re.FindAllString(line, -1)
 | 
			
		||||
	return yumCveIDPattern.FindAllString(line, -1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var yumAdvisoryIDPattern = regexp.MustCompile(`^ *Update ID : (.*)$`)
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoToGetAdvisoryID(line string) (advisoryID string, found bool) {
 | 
			
		||||
	re, _ := regexp.Compile(`^ *Update ID : (.*)$`)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
	result := yumAdvisoryIDPattern.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) != 2 {
 | 
			
		||||
		return "", false
 | 
			
		||||
	}
 | 
			
		||||
	return strings.TrimSpace(result[1]), true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var yumIssuedPattern = regexp.MustCompile(`^\s*Issued : (\d{4}-\d{2}-\d{2})`)
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoLineToGetIssued(line string) (date time.Time, found bool) {
 | 
			
		||||
	return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Issued : (\d{4}-\d{2}-\d{2})`)
 | 
			
		||||
	return o.parseYumUpdateinfoLineToGetDate(line, yumIssuedPattern)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var yumUpdatedPattern = regexp.MustCompile(`^\s*Updated : (\d{4}-\d{2}-\d{2})`)
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoLineToGetUpdated(line string) (date time.Time, found bool) {
 | 
			
		||||
	return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Updated : (\d{4}-\d{2}-\d{2})`)
 | 
			
		||||
	return o.parseYumUpdateinfoLineToGetDate(line, yumUpdatedPattern)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoLineToGetDate(line, regexpFormat string) (date time.Time, found bool) {
 | 
			
		||||
	re, _ := regexp.Compile(regexpFormat)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoLineToGetDate(line string, regexpPattern *regexp.Regexp) (date time.Time, found bool) {
 | 
			
		||||
	result := regexpPattern.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) != 2 {
 | 
			
		||||
		return date, false
 | 
			
		||||
	}
 | 
			
		||||
@@ -904,14 +908,16 @@ func (o *redhat) parseYumUpdateinfoLineToGetDate(line, regexpFormat string) (dat
 | 
			
		||||
	return t, true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var yumDescriptionPattern = regexp.MustCompile(`^\s*Description : `)
 | 
			
		||||
 | 
			
		||||
func (o *redhat) isDescriptionLine(line string) bool {
 | 
			
		||||
	re, _ := regexp.Compile(`^\s*Description : `)
 | 
			
		||||
	return re.MatchString(line)
 | 
			
		||||
	return yumDescriptionPattern.MatchString(line)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var yumSeverityPattern = regexp.MustCompile(`^ *Severity : (.*)$`)
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoToGetSeverity(line string) (severity string, found bool) {
 | 
			
		||||
	re, _ := regexp.Compile(`^ *Severity : (.*)$`)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
	result := yumSeverityPattern.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) != 2 {
 | 
			
		||||
		return "", false
 | 
			
		||||
	}
 | 
			
		||||
@@ -944,15 +950,15 @@ func (o *redhat) extractPackNameVerRel(nameVerRel string) (name, ver, rel string
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS), packages
 | 
			
		||||
// parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS, ELSA), packages
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacksList, error) {
 | 
			
		||||
 | 
			
		||||
	result := []advisoryIDPacks{}
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
 | 
			
		||||
		if !(strings.HasPrefix(line, "RHSA") ||
 | 
			
		||||
			strings.HasPrefix(line, "ALAS")) {
 | 
			
		||||
			strings.HasPrefix(line, "ALAS") ||
 | 
			
		||||
			strings.HasPrefix(line, "ELSA")) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -990,3 +996,12 @@ func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacks
 | 
			
		||||
func (o *redhat) clone() osTypeInterface {
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) sudo() bool {
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "amazon":
 | 
			
		||||
		return false
 | 
			
		||||
	default:
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -91,47 +91,6 @@ func TestChangeSectionState(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoHeader(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out []models.PackageInfo
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"     nodejs-0.10.36-3.el6,libuv-0.10.34-1.el6,v8-3.14.5.10-17.el6    ",
 | 
			
		||||
			[]models.PackageInfo{
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "nodejs",
 | 
			
		||||
					Version: "0.10.36",
 | 
			
		||||
					Release: "3.el6",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "libuv",
 | 
			
		||||
					Version: "0.10.34",
 | 
			
		||||
					Release: "1.el6",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "v8",
 | 
			
		||||
					Version: "3.14.5.10",
 | 
			
		||||
					Release: "17.el6",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		if a, err := r.parseYumUpdateinfoHeaderCentOS(tt.in); err != nil {
 | 
			
		||||
			t.Errorf("err: %s", err)
 | 
			
		||||
		} else {
 | 
			
		||||
			if !reflect.DeepEqual(a, tt.out) {
 | 
			
		||||
				e := pp.Sprintf("%#v", tt.out)
 | 
			
		||||
				a := pp.Sprintf("%#v", a)
 | 
			
		||||
				t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoLineToGetCveIDs(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
@@ -143,11 +102,11 @@ func TestParseYumUpdateinfoLineToGetCveIDs(t *testing.T) {
 | 
			
		||||
			[]string{"CVE-2015-0278"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			": 1195457 - nodejs-0.10.35 causes undefined symbolsCVE-2015-0278, CVE-2015-0278, CVE-2015-0277",
 | 
			
		||||
			": 1195457 - nodejs-0.10.35 causes undefined symbolsCVE-2015-0278, CVE-2015-0278, CVE-2015-02770000000 ",
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-0278",
 | 
			
		||||
				"CVE-2015-0278",
 | 
			
		||||
				"CVE-2015-0277",
 | 
			
		||||
				"CVE-2015-02770000000",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@@ -385,6 +344,111 @@ func TestParseYumUpdateinfoToGetSeverity(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoOL(t *testing.T) {
 | 
			
		||||
	stdout := `===============================================================================
 | 
			
		||||
   bind security update
 | 
			
		||||
===============================================================================
 | 
			
		||||
  Update ID : ELSA-2017-0276
 | 
			
		||||
    Release : Oracle Linux 7
 | 
			
		||||
       Type : security
 | 
			
		||||
     Status : final
 | 
			
		||||
     Issued : 2017-02-15
 | 
			
		||||
       CVEs : CVE-2017-3135
 | 
			
		||||
Description : [32:9.9.4-38.2]
 | 
			
		||||
            : - Fix CVE-2017-3135 (ISC change 4557)
 | 
			
		||||
            : - Fix and test caching CNAME before DNAME (ISC
 | 
			
		||||
            :   change 4558)
 | 
			
		||||
   Severity : Moderate
 | 
			
		||||
   
 | 
			
		||||
===============================================================================
 | 
			
		||||
   openssl security update
 | 
			
		||||
===============================================================================
 | 
			
		||||
  Update ID : ELSA-2017-0286
 | 
			
		||||
    Release : Oracle Linux 7
 | 
			
		||||
       Type : security
 | 
			
		||||
     Status : final
 | 
			
		||||
     Issued : 2017-02-15
 | 
			
		||||
       CVEs : CVE-2016-8610
 | 
			
		||||
	    : CVE-2017-3731
 | 
			
		||||
Description : [1.0.1e-48.4]
 | 
			
		||||
            : - fix CVE-2017-3731 - DoS via truncated packets
 | 
			
		||||
            :   with RC4-MD5 cipher
 | 
			
		||||
            : - fix CVE-2016-8610 - DoS of single-threaded
 | 
			
		||||
            :   servers via excessive alerts
 | 
			
		||||
   Severity : Moderate
 | 
			
		||||
   
 | 
			
		||||
===============================================================================
 | 
			
		||||
   Unbreakable Enterprise kernel security update
 | 
			
		||||
===============================================================================
 | 
			
		||||
  Update ID : ELSA-2017-3520
 | 
			
		||||
    Release : Oracle Linux 7
 | 
			
		||||
       Type : security
 | 
			
		||||
     Status : final
 | 
			
		||||
     Issued : 2017-02-15
 | 
			
		||||
       CVEs : CVE-2017-6074
 | 
			
		||||
Description : kernel-uek
 | 
			
		||||
            : [4.1.12-61.1.28]
 | 
			
		||||
            : - dccp: fix freeing skb too early for
 | 
			
		||||
            :   IPV6_RECVPKTINFO (Andrey Konovalov)  [Orabug:
 | 
			
		||||
            :   25598257]  {CVE-2017-6074}
 | 
			
		||||
   Severity : Important
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
	issued, _ := time.Parse("2006-01-02", "2017-02-15")
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: "oraclelinux"}
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out []distroAdvisoryCveIDs
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			stdout,
 | 
			
		||||
			[]distroAdvisoryCveIDs{
 | 
			
		||||
				{
 | 
			
		||||
					DistroAdvisory: models.DistroAdvisory{
 | 
			
		||||
						AdvisoryID: "ELSA-2017-0276",
 | 
			
		||||
						Severity:   "Moderate",
 | 
			
		||||
						Issued:     issued,
 | 
			
		||||
					},
 | 
			
		||||
					CveIDs: []string{"CVE-2017-3135"},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					DistroAdvisory: models.DistroAdvisory{
 | 
			
		||||
						AdvisoryID: "ELSA-2017-0286",
 | 
			
		||||
						Severity:   "Moderate",
 | 
			
		||||
						Issued:     issued,
 | 
			
		||||
					},
 | 
			
		||||
					CveIDs: []string{
 | 
			
		||||
						"CVE-2016-8610",
 | 
			
		||||
						"CVE-2017-3731",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					DistroAdvisory: models.DistroAdvisory{
 | 
			
		||||
						AdvisoryID: "ELSA-2017-3520",
 | 
			
		||||
						Severity:   "Important",
 | 
			
		||||
						Issued:     issued,
 | 
			
		||||
					},
 | 
			
		||||
					CveIDs: []string{"CVE-2017-6074"},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, _ := r.parseYumUpdateinfo(tt.in)
 | 
			
		||||
		for i, advisoryCveIDs := range actual {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out[i], advisoryCveIDs) {
 | 
			
		||||
				e := pp.Sprintf("%v", tt.out[i])
 | 
			
		||||
				a := pp.Sprintf("%v", advisoryCveIDs)
 | 
			
		||||
				t.Errorf("[%d] Alas is not same. \nexpected: %s\nactual: %s",
 | 
			
		||||
					i, e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoRHEL(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	stdout := `===============================================================================
 | 
			
		||||
@@ -451,7 +515,7 @@ Description : The Berkeley Internet Name Domain (BIND) is an implementation of
 | 
			
		||||
	updated, _ := time.Parse("2006-01-02", "2015-09-04")
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "redhat"
 | 
			
		||||
	r.Distro = config.Distro{Family: "redhat"}
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
@@ -511,7 +575,7 @@ Description : The Berkeley Internet Name Domain (BIND) is an implementation of
 | 
			
		||||
func TestParseYumUpdateinfoAmazon(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "amazon"
 | 
			
		||||
	r.Distro = config.Distro{Family: "redhat"}
 | 
			
		||||
 | 
			
		||||
	issued, _ := time.Parse("2006-01-02", "2015-12-15")
 | 
			
		||||
	updated, _ := time.Parse("2006-01-02", "2015-12-16")
 | 
			
		||||
@@ -601,7 +665,7 @@ Description : Package updates are available for Amazon Linux AMI that fix the
 | 
			
		||||
 | 
			
		||||
func TestParseYumCheckUpdateLines(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "centos"
 | 
			
		||||
	r.Distro = config.Distro{Family: "centos"}
 | 
			
		||||
	stdout := `Loaded plugins: changelog, fastestmirror, keys, protect-packages, protectbase, security
 | 
			
		||||
Loading mirror speeds from cached hostfile
 | 
			
		||||
 * base: mirror.fairway.ne.jp
 | 
			
		||||
@@ -616,6 +680,7 @@ Obsoleting Packages
 | 
			
		||||
python-libs.i686    2.6.6-64.el6   rhui-REGION-rhel-server-releases
 | 
			
		||||
    python-ordereddict.noarch     1.1-3.el6ev    installed
 | 
			
		||||
bind-utils.x86_64                       30:9.3.6-25.P1.el5_11.8          updates
 | 
			
		||||
pytalloc.x86_64                 2.0.7-2.el6                      @CentOS 6.5/6.5
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
	r.Packages = []models.PackageInfo{
 | 
			
		||||
@@ -644,6 +709,11 @@ bind-utils.x86_64                       30:9.3.6-25.P1.el5_11.8          updates
 | 
			
		||||
			Version: "1.0",
 | 
			
		||||
			Release: "1",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "pytalloc",
 | 
			
		||||
			Version: "2.0.1",
 | 
			
		||||
			Release: "0",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
@@ -658,6 +728,7 @@ bind-utils.x86_64                       30:9.3.6-25.P1.el5_11.8          updates
 | 
			
		||||
					Release:    "4.el6",
 | 
			
		||||
					NewVersion: "2.3.7",
 | 
			
		||||
					NewRelease: "5.el6",
 | 
			
		||||
					Repository: "base",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "bash",
 | 
			
		||||
@@ -665,6 +736,7 @@ bind-utils.x86_64                       30:9.3.6-25.P1.el5_11.8          updates
 | 
			
		||||
					Release:    "33",
 | 
			
		||||
					NewVersion: "4.1.2",
 | 
			
		||||
					NewRelease: "33.el6_7.1",
 | 
			
		||||
					Repository: "updates",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "python-libs",
 | 
			
		||||
@@ -672,6 +744,7 @@ bind-utils.x86_64                       30:9.3.6-25.P1.el5_11.8          updates
 | 
			
		||||
					Release:    "1.1-0",
 | 
			
		||||
					NewVersion: "2.6.6",
 | 
			
		||||
					NewRelease: "64.el6",
 | 
			
		||||
					Repository: "rhui-REGION-rhel-server-releases",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "python-ordereddict",
 | 
			
		||||
@@ -679,6 +752,7 @@ bind-utils.x86_64                       30:9.3.6-25.P1.el5_11.8          updates
 | 
			
		||||
					Release:    "1",
 | 
			
		||||
					NewVersion: "1.1",
 | 
			
		||||
					NewRelease: "3.el6ev",
 | 
			
		||||
					Repository: "installed",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "bind-utils",
 | 
			
		||||
@@ -686,6 +760,15 @@ bind-utils.x86_64                       30:9.3.6-25.P1.el5_11.8          updates
 | 
			
		||||
					Release:    "1",
 | 
			
		||||
					NewVersion: "9.3.6",
 | 
			
		||||
					NewRelease: "25.P1.el5_11.8",
 | 
			
		||||
					Repository: "updates",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "pytalloc",
 | 
			
		||||
					Version:    "2.0.1",
 | 
			
		||||
					Release:    "0",
 | 
			
		||||
					NewVersion: "2.0.7",
 | 
			
		||||
					NewRelease: "2.el6",
 | 
			
		||||
					Repository: "@CentOS 6.5/6.5",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
@@ -709,7 +792,7 @@ bind-utils.x86_64                       30:9.3.6-25.P1.el5_11.8          updates
 | 
			
		||||
 | 
			
		||||
func TestParseYumCheckUpdateLinesAmazon(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "amazon"
 | 
			
		||||
	r.Distro = config.Distro{Family: "amazon"}
 | 
			
		||||
	stdout := `Loaded plugins: priorities, update-motd, upgrade-helper
 | 
			
		||||
34 package(s) needed for security, out of 71 available
 | 
			
		||||
 | 
			
		||||
@@ -747,6 +830,7 @@ if-not-architecture        100-200                         amzn-main
 | 
			
		||||
					Release:    "0.33.rc1.45.amzn1",
 | 
			
		||||
					NewVersion: "9.8.2",
 | 
			
		||||
					NewRelease: "0.37.rc1.45.amzn1",
 | 
			
		||||
					Repository: "amzn-main",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "java-1.7.0-openjdk",
 | 
			
		||||
@@ -754,6 +838,7 @@ if-not-architecture        100-200                         amzn-main
 | 
			
		||||
					Release:    "2.6.4.0.0.amzn1",
 | 
			
		||||
					NewVersion: "1.7.0.95",
 | 
			
		||||
					NewRelease: "2.6.4.0.65.amzn1",
 | 
			
		||||
					Repository: "amzn-main",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "if-not-architecture",
 | 
			
		||||
@@ -761,6 +846,7 @@ if-not-architecture        100-200                         amzn-main
 | 
			
		||||
					Release:    "20",
 | 
			
		||||
					NewVersion: "100",
 | 
			
		||||
					NewRelease: "200",
 | 
			
		||||
					Repository: "amzn-main",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
@@ -782,31 +868,6 @@ if-not-architecture        100-200                         amzn-main
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoAmazonLinuxHeader(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out models.DistroAdvisory
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"Amazon Linux AMI 2014.03 - ALAS-2015-598: low priority package update for grep",
 | 
			
		||||
			models.DistroAdvisory{
 | 
			
		||||
				AdvisoryID: "ALAS-2015-598",
 | 
			
		||||
				Severity:   "low",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		a, _, _ := r.parseYumUpdateinfoHeaderAmazon(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(a, tt.out) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.out)
 | 
			
		||||
			a := pp.Sprintf("%v", a)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoListAvailable(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	rhelStdout := `RHSA-2015:2315 Moderate/Sec.  NetworkManager-1:1.0.6-27.el7.x86_64
 | 
			
		||||
@@ -1110,10 +1171,12 @@ func TestGetChangelogCVELines(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "centos"
 | 
			
		||||
	r.Release = "6.7"
 | 
			
		||||
	r.Distro = config.Distro{
 | 
			
		||||
		Family:  "centos",
 | 
			
		||||
		Release: "6.7",
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range testsCentos6 {
 | 
			
		||||
		rpm2changelog, err := r.parseAllChangelog(stdoutCentos6)
 | 
			
		||||
		rpm2changelog, err := r.divideChangelogByPackage(stdoutCentos6)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("err: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
@@ -1194,9 +1257,12 @@ func TestGetChangelogCVELines(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.Release = "5.6"
 | 
			
		||||
	r.Distro = config.Distro{
 | 
			
		||||
		Family:  "centos",
 | 
			
		||||
		Release: "5.6",
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range testsCentos5 {
 | 
			
		||||
		rpm2changelog, err := r.parseAllChangelog(stdoutCentos5)
 | 
			
		||||
		rpm2changelog, err := r.divideChangelogByPackage(stdoutCentos5)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("err: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,37 +1,52 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/cache"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Log for localhsot
 | 
			
		||||
var Log *logrus.Entry
 | 
			
		||||
 | 
			
		||||
var servers []osTypeInterface
 | 
			
		||||
var servers, errServers []osTypeInterface
 | 
			
		||||
 | 
			
		||||
// Base Interface of redhat, debian, freebsd
 | 
			
		||||
type osTypeInterface interface {
 | 
			
		||||
	setServerInfo(config.ServerInfo)
 | 
			
		||||
	getServerInfo() config.ServerInfo
 | 
			
		||||
 | 
			
		||||
	setDistributionInfo(string, string)
 | 
			
		||||
	getDistributionInfo() string
 | 
			
		||||
 | 
			
		||||
	checkIfSudoNoPasswd() error
 | 
			
		||||
	detectPlatform() error
 | 
			
		||||
	setDistro(string, string)
 | 
			
		||||
	getDistro() config.Distro
 | 
			
		||||
	detectPlatform()
 | 
			
		||||
	getPlatform() models.Platform
 | 
			
		||||
 | 
			
		||||
	checkRequiredPackagesInstalled() error
 | 
			
		||||
	// checkDependencies checks if dependencies are installed on the target server.
 | 
			
		||||
	checkDependencies() error
 | 
			
		||||
	checkIfSudoNoPasswd() error
 | 
			
		||||
 | 
			
		||||
	scanPackages() error
 | 
			
		||||
	scanVulnByCpeName() error
 | 
			
		||||
	install() error
 | 
			
		||||
	convertToModel() (models.ScanResult, error)
 | 
			
		||||
	convertToModel() models.ScanResult
 | 
			
		||||
 | 
			
		||||
	runningContainers() ([]config.Container, error)
 | 
			
		||||
	exitedContainers() ([]config.Container, error)
 | 
			
		||||
@@ -41,70 +56,21 @@ type osTypeInterface interface {
 | 
			
		||||
	setErrs([]error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// osPackages included by linux struct
 | 
			
		||||
// osPackages is included by base struct
 | 
			
		||||
type osPackages struct {
 | 
			
		||||
	// installed packages
 | 
			
		||||
	Packages models.PackageInfoList
 | 
			
		||||
 | 
			
		||||
	// unsecure packages
 | 
			
		||||
	UnsecurePackages CvePacksList
 | 
			
		||||
	VulnInfos models.VulnInfos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *osPackages) setPackages(pi models.PackageInfoList) {
 | 
			
		||||
	p.Packages = pi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *osPackages) setUnsecurePackages(pi []CvePacksInfo) {
 | 
			
		||||
	p.UnsecurePackages = pi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CvePacksList have CvePacksInfo list, getter/setter, sortable methods.
 | 
			
		||||
type CvePacksList []CvePacksInfo
 | 
			
		||||
 | 
			
		||||
// CvePacksInfo hold the CVE information.
 | 
			
		||||
type CvePacksInfo struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	CveDetail        cve.CveDetail
 | 
			
		||||
	Packs            models.PackageInfoList
 | 
			
		||||
	DistroAdvisories []models.DistroAdvisory // for Aamazon, RHEL, FreeBSD
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindByCveID find by CVEID
 | 
			
		||||
func (s CvePacksList) FindByCveID(cveID string) (pi CvePacksInfo, found bool) {
 | 
			
		||||
	for _, p := range s {
 | 
			
		||||
		if cveID == p.CveID {
 | 
			
		||||
			return p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return CvePacksInfo{CveID: cveID}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// immutable
 | 
			
		||||
func (s CvePacksList) set(cveID string, cvePacksInfo CvePacksInfo) CvePacksList {
 | 
			
		||||
	for i, p := range s {
 | 
			
		||||
		if cveID == p.CveID {
 | 
			
		||||
			s[i] = cvePacksInfo
 | 
			
		||||
			return s
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return append(s, cvePacksInfo)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Len implement Sort Interface
 | 
			
		||||
func (s CvePacksList) Len() int {
 | 
			
		||||
	return len(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Swap implement Sort Interface
 | 
			
		||||
func (s CvePacksList) Swap(i, j int) {
 | 
			
		||||
	s[i], s[j] = s[j], s[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Less implement Sort Interface
 | 
			
		||||
func (s CvePacksList) Less(i, j int) bool {
 | 
			
		||||
	return s[i].CveDetail.CvssScore(config.Conf.Lang) >
 | 
			
		||||
		s[j].CveDetail.CvssScore(config.Conf.Lang)
 | 
			
		||||
func (p *osPackages) setVulnInfos(vi []models.VulnInfo) {
 | 
			
		||||
	p.VulnInfos = vi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectOS(c config.ServerInfo) (osType osTypeInterface) {
 | 
			
		||||
@@ -113,30 +79,34 @@ func detectOS(c config.ServerInfo) (osType osTypeInterface) {
 | 
			
		||||
 | 
			
		||||
	itsMe, osType, fatalErr = detectDebian(c)
 | 
			
		||||
	if fatalErr != nil {
 | 
			
		||||
		osType.setServerInfo(c)
 | 
			
		||||
		osType.setErrs([]error{fatalErr})
 | 
			
		||||
		osType.setErrs([]error{
 | 
			
		||||
			fmt.Errorf("Failed to detect OS: %s", fatalErr)})
 | 
			
		||||
		return
 | 
			
		||||
	} else if itsMe {
 | 
			
		||||
		Log.Debugf("Debian like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe {
 | 
			
		||||
		util.Log.Debugf("Debian like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType = detectRedhat(c); itsMe {
 | 
			
		||||
		Log.Debugf("Redhat like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		util.Log.Debugf("Redhat like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType = detectFreebsd(c); itsMe {
 | 
			
		||||
		Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		util.Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	osType.setServerInfo(c)
 | 
			
		||||
 | 
			
		||||
	//TODO darwin https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/darwin.rb
 | 
			
		||||
	osType.setErrs([]error{fmt.Errorf("Unknown OS Type")})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrintSSHableServerNames print SSH-able servernames
 | 
			
		||||
func PrintSSHableServerNames() {
 | 
			
		||||
	Log.Info("SSH-able servers are below...")
 | 
			
		||||
	util.Log.Info("Scannable servers are below...")
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		if s.getServerInfo().IsContainer() {
 | 
			
		||||
			fmt.Printf("%s@%s ",
 | 
			
		||||
@@ -151,58 +121,74 @@ func PrintSSHableServerNames() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InitServers detect the kind of OS distribution of target servers
 | 
			
		||||
func InitServers(localLogger *logrus.Entry) {
 | 
			
		||||
	Log = localLogger
 | 
			
		||||
	servers = detectServerOSes()
 | 
			
		||||
	containers := detectContainerOSes()
 | 
			
		||||
	servers = append(servers, containers...)
 | 
			
		||||
func InitServers() error {
 | 
			
		||||
	servers, errServers = detectServerOSes()
 | 
			
		||||
	if len(servers) == 0 {
 | 
			
		||||
		return fmt.Errorf("No scannable servers")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	actives, inactives := detectContainerOSes()
 | 
			
		||||
	if config.Conf.ContainersOnly {
 | 
			
		||||
		servers = actives
 | 
			
		||||
		errServers = inactives
 | 
			
		||||
	} else {
 | 
			
		||||
		servers = append(servers, actives...)
 | 
			
		||||
		errServers = append(errServers, inactives...)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectServerOSes() (sshAbleOses []osTypeInterface) {
 | 
			
		||||
	Log.Info("Detecting OS of servers... ")
 | 
			
		||||
func detectServerOSes() (servers, errServers []osTypeInterface) {
 | 
			
		||||
	util.Log.Info("Detecting OS of servers... ")
 | 
			
		||||
	osTypeChan := make(chan osTypeInterface, len(config.Conf.Servers))
 | 
			
		||||
	defer close(osTypeChan)
 | 
			
		||||
	for _, s := range config.Conf.Servers {
 | 
			
		||||
		go func(s config.ServerInfo) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if p := recover(); p != nil {
 | 
			
		||||
					Log.Debugf("Panic: %s on %s", p, s.ServerName)
 | 
			
		||||
					util.Log.Debugf("Panic: %s on %s", p, s.ServerName)
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			osTypeChan <- detectOS(s)
 | 
			
		||||
		}(s)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var oses []osTypeInterface
 | 
			
		||||
	timeout := time.After(30 * time.Second)
 | 
			
		||||
	for i := 0; i < len(config.Conf.Servers); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-osTypeChan:
 | 
			
		||||
			oses = append(oses, res)
 | 
			
		||||
			if 0 < len(res.getErrs()) {
 | 
			
		||||
				Log.Errorf("(%d/%d) Failed: %s, err: %s",
 | 
			
		||||
				errServers = append(errServers, res)
 | 
			
		||||
				util.Log.Errorf("(%d/%d) Failed: %s, err: %s",
 | 
			
		||||
					i+1, len(config.Conf.Servers),
 | 
			
		||||
					res.getServerInfo().ServerName,
 | 
			
		||||
					res.getErrs())
 | 
			
		||||
			} else {
 | 
			
		||||
				Log.Infof("(%d/%d) Detected: %s: %s",
 | 
			
		||||
				servers = append(servers, res)
 | 
			
		||||
				util.Log.Infof("(%d/%d) Detected: %s: %s",
 | 
			
		||||
					i+1, len(config.Conf.Servers),
 | 
			
		||||
					res.getServerInfo().ServerName,
 | 
			
		||||
					res.getDistributionInfo())
 | 
			
		||||
					res.getDistro())
 | 
			
		||||
			}
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			msg := "Timed out while detecting servers"
 | 
			
		||||
			Log.Error(msg)
 | 
			
		||||
			for servername := range config.Conf.Servers {
 | 
			
		||||
			util.Log.Error(msg)
 | 
			
		||||
			for servername, sInfo := range config.Conf.Servers {
 | 
			
		||||
				found := false
 | 
			
		||||
				for _, o := range oses {
 | 
			
		||||
				for _, o := range append(servers, errServers...) {
 | 
			
		||||
					if servername == o.getServerInfo().ServerName {
 | 
			
		||||
						found = true
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if !found {
 | 
			
		||||
					Log.Errorf("(%d/%d) Timed out: %s",
 | 
			
		||||
					u := &unknown{}
 | 
			
		||||
					u.setServerInfo(sInfo)
 | 
			
		||||
					u.setErrs([]error{
 | 
			
		||||
						fmt.Errorf("Timed out"),
 | 
			
		||||
					})
 | 
			
		||||
					errServers = append(errServers, u)
 | 
			
		||||
					util.Log.Errorf("(%d/%d) Timed out: %s",
 | 
			
		||||
						i+1, len(config.Conf.Servers),
 | 
			
		||||
						servername)
 | 
			
		||||
					i++
 | 
			
		||||
@@ -210,32 +196,25 @@ func detectServerOSes() (sshAbleOses []osTypeInterface) {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, o := range oses {
 | 
			
		||||
		if len(o.getErrs()) == 0 {
 | 
			
		||||
			sshAbleOses = append(sshAbleOses, o)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectContainerOSes() (actives []osTypeInterface) {
 | 
			
		||||
	Log.Info("Detecting OS of containers... ")
 | 
			
		||||
func detectContainerOSes() (actives, inactives []osTypeInterface) {
 | 
			
		||||
	util.Log.Info("Detecting OS of containers... ")
 | 
			
		||||
	osTypesChan := make(chan []osTypeInterface, len(servers))
 | 
			
		||||
	defer close(osTypesChan)
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		go func(s osTypeInterface) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if p := recover(); p != nil {
 | 
			
		||||
					Log.Debugf("Panic: %s on %s",
 | 
			
		||||
						p, s.getServerInfo().ServerName)
 | 
			
		||||
					util.Log.Debugf("Panic: %s on %s",
 | 
			
		||||
						p, s.getServerInfo().GetServerName())
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			osTypesChan <- detectContainerOSesOnServer(s)
 | 
			
		||||
		}(s)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var oses []osTypeInterface
 | 
			
		||||
	timeout := time.After(30 * time.Second)
 | 
			
		||||
	for i := 0; i < len(servers); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
@@ -243,42 +222,44 @@ func detectContainerOSes() (actives []osTypeInterface) {
 | 
			
		||||
			for _, osi := range res {
 | 
			
		||||
				sinfo := osi.getServerInfo()
 | 
			
		||||
				if 0 < len(osi.getErrs()) {
 | 
			
		||||
					Log.Errorf("Failed: %s err: %s", sinfo.ServerName, osi.getErrs())
 | 
			
		||||
					inactives = append(inactives, osi)
 | 
			
		||||
					util.Log.Errorf("Failed: %s err: %s", sinfo.ServerName, osi.getErrs())
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				oses = append(oses, res...)
 | 
			
		||||
				Log.Infof("Detected: %s@%s: %s",
 | 
			
		||||
					sinfo.Container.Name, sinfo.ServerName, osi.getDistributionInfo())
 | 
			
		||||
				actives = append(actives, osi)
 | 
			
		||||
				util.Log.Infof("Detected: %s@%s: %s",
 | 
			
		||||
					sinfo.Container.Name, sinfo.ServerName, osi.getDistro())
 | 
			
		||||
			}
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			msg := "Timed out while detecting containers"
 | 
			
		||||
			Log.Error(msg)
 | 
			
		||||
			for servername := range config.Conf.Servers {
 | 
			
		||||
			util.Log.Error(msg)
 | 
			
		||||
			for servername, sInfo := range config.Conf.Servers {
 | 
			
		||||
				found := false
 | 
			
		||||
				for _, o := range oses {
 | 
			
		||||
				for _, o := range append(actives, inactives...) {
 | 
			
		||||
					if servername == o.getServerInfo().ServerName {
 | 
			
		||||
						found = true
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if !found {
 | 
			
		||||
					Log.Errorf("Timed out: %s", servername)
 | 
			
		||||
					u := &unknown{}
 | 
			
		||||
					u.setServerInfo(sInfo)
 | 
			
		||||
					u.setErrs([]error{
 | 
			
		||||
						fmt.Errorf("Timed out"),
 | 
			
		||||
					})
 | 
			
		||||
					inactives = append(inactives)
 | 
			
		||||
					util.Log.Errorf("Timed out: %s", servername)
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, o := range oses {
 | 
			
		||||
		if len(o.getErrs()) == 0 {
 | 
			
		||||
			actives = append(actives, o)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeInterface) {
 | 
			
		||||
	containerHostInfo := containerHost.getServerInfo()
 | 
			
		||||
	if len(containerHostInfo.Containers) == 0 {
 | 
			
		||||
	if len(containerHostInfo.Containers.Includes) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -290,12 +271,24 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn
 | 
			
		||||
		return append(oses, containerHost)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if containerHostInfo.Containers[0] == "${running}" {
 | 
			
		||||
	if containerHostInfo.Containers.Includes[0] == "${running}" {
 | 
			
		||||
		for _, containerInfo := range running {
 | 
			
		||||
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, ex := range containerHost.getServerInfo().Containers.Excludes {
 | 
			
		||||
				if containerInfo.Name == ex || containerInfo.ContainerID == ex {
 | 
			
		||||
					found = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if found {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			copied := containerHostInfo
 | 
			
		||||
			copied.SetContainer(config.Container{
 | 
			
		||||
				ContainerID: containerInfo.ContainerID,
 | 
			
		||||
				Name:        containerInfo.Name,
 | 
			
		||||
				Image:       containerInfo.Image,
 | 
			
		||||
			})
 | 
			
		||||
			os := detectOS(copied)
 | 
			
		||||
			oses = append(oses, os)
 | 
			
		||||
@@ -312,7 +305,7 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var exited, unknown []string
 | 
			
		||||
	for _, container := range containerHostInfo.Containers {
 | 
			
		||||
	for _, container := range containerHostInfo.Containers.Includes {
 | 
			
		||||
		found := false
 | 
			
		||||
		for _, c := range running {
 | 
			
		||||
			if c.ContainerID == container || c.Name == container {
 | 
			
		||||
@@ -348,29 +341,28 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn
 | 
			
		||||
	return oses
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckDependencies checks dependencies are installed on target servers.
 | 
			
		||||
func CheckDependencies(timeoutSec int) {
 | 
			
		||||
	parallelExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.checkDependencies()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfSudoNoPasswd checks whether vuls can sudo with nopassword via SSH
 | 
			
		||||
func CheckIfSudoNoPasswd(localLogger *logrus.Entry) error {
 | 
			
		||||
	timeoutSec := 1 * 15
 | 
			
		||||
	errs := parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
func CheckIfSudoNoPasswd(timeoutSec int) {
 | 
			
		||||
	parallelExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.checkIfSudoNoPasswd()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		return fmt.Errorf(fmt.Sprintf("%s", errs))
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectPlatforms detects the platform of each servers.
 | 
			
		||||
func DetectPlatforms(localLogger *logrus.Entry) {
 | 
			
		||||
	errs := detectPlatforms()
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		// Only logging
 | 
			
		||||
		Log.Warnf("Failed to detect platforms. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
func DetectPlatforms() {
 | 
			
		||||
	detectPlatforms()
 | 
			
		||||
	for i, s := range servers {
 | 
			
		||||
		if s.getServerInfo().IsContainer() {
 | 
			
		||||
			Log.Infof("(%d/%d) %s on %s is running on %s",
 | 
			
		||||
			util.Log.Infof("(%d/%d) %s on %s is running on %s",
 | 
			
		||||
				i+1, len(servers),
 | 
			
		||||
				s.getServerInfo().Container.Name,
 | 
			
		||||
				s.getServerInfo().ServerName,
 | 
			
		||||
@@ -378,7 +370,7 @@ func DetectPlatforms(localLogger *logrus.Entry) {
 | 
			
		||||
			)
 | 
			
		||||
 | 
			
		||||
		} else {
 | 
			
		||||
			Log.Infof("(%d/%d) %s is running on %s",
 | 
			
		||||
			util.Log.Infof("(%d/%d) %s is running on %s",
 | 
			
		||||
				i+1, len(servers),
 | 
			
		||||
				s.getServerInfo().ServerName,
 | 
			
		||||
				s.getPlatform().Name,
 | 
			
		||||
@@ -388,79 +380,112 @@ func DetectPlatforms(localLogger *logrus.Entry) {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectPlatforms() []error {
 | 
			
		||||
func detectPlatforms() {
 | 
			
		||||
	timeoutSec := 1 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.detectPlatform()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Prepare installs requred packages to scan vulnerabilities.
 | 
			
		||||
func Prepare() []error {
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		if err := o.install(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	parallelExec(func(o osTypeInterface) error {
 | 
			
		||||
		o.detectPlatform()
 | 
			
		||||
		// Logging only if platform can not be specified
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scan scan
 | 
			
		||||
func Scan() []error {
 | 
			
		||||
func Scan() error {
 | 
			
		||||
	if len(servers) == 0 {
 | 
			
		||||
		return []error{fmt.Errorf("No server defined. Check the configuration")}
 | 
			
		||||
		return fmt.Errorf("No server defined. Check the configuration")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Check required packages for scanning...")
 | 
			
		||||
	if errs := checkRequiredPackagesInstalled(); errs != nil {
 | 
			
		||||
		Log.Error("Please execute with [prepare] subcommand to install required packages before scanning")
 | 
			
		||||
		return errs
 | 
			
		||||
	if err := setupChangelogCache(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if cache.DB != nil {
 | 
			
		||||
			cache.DB.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Scanning vulnerable OS packages...")
 | 
			
		||||
	scannedAt := time.Now()
 | 
			
		||||
	dir, err := ensureResultDir(scannedAt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := scanVulns(dir, scannedAt); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerable OS packages...")
 | 
			
		||||
	if errs := scanPackages(); errs != nil {
 | 
			
		||||
		return errs
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerable software specified in the CPE...")
 | 
			
		||||
	if errs := scanVulnByCpeName(); errs != nil {
 | 
			
		||||
		return errs
 | 
			
		||||
func setupChangelogCache() error {
 | 
			
		||||
	needToSetupCache := false
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		switch s.getDistro().Family {
 | 
			
		||||
		case "ubuntu", "debian", "raspbian":
 | 
			
		||||
			needToSetupCache = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if needToSetupCache {
 | 
			
		||||
		if err := cache.SetupBolt(config.Conf.CacheDBPath, util.Log); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkRequiredPackagesInstalled() []error {
 | 
			
		||||
	timeoutSec := 30 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.checkRequiredPackagesInstalled()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanPackages() []error {
 | 
			
		||||
func scanVulns(jsonDir string, scannedAt time.Time) error {
 | 
			
		||||
	var results models.ScanResults
 | 
			
		||||
	timeoutSec := 120 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
	parallelExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.scanPackages()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
 | 
			
		||||
func scanVulnByCpeName() []error {
 | 
			
		||||
	timeoutSec := 30 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.scanVulnByCpeName()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetScanResults returns Scan Resutls
 | 
			
		||||
func GetScanResults() (results models.ScanResults, err error) {
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		r, err := s.convertToModel()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return results, fmt.Errorf("Failed converting to model: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	for _, s := range append(servers, errServers...) {
 | 
			
		||||
		r := s.convertToModel()
 | 
			
		||||
		r.ScannedAt = scannedAt
 | 
			
		||||
		results = append(results, r)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
 | 
			
		||||
	config.Conf.FormatJSON = true
 | 
			
		||||
	ws := []report.ResultWriter{
 | 
			
		||||
		report.LocalFileWriter{CurrentDir: jsonDir},
 | 
			
		||||
	}
 | 
			
		||||
	for _, w := range ws {
 | 
			
		||||
		if err := w.Write(results...); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to write summary report: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	report.StdoutWriter{}.WriteScanSummary(results...)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ensureResultDir(scannedAt time.Time) (currentDir string, err error) {
 | 
			
		||||
	jsonDirName := scannedAt.Format(time.RFC3339)
 | 
			
		||||
 | 
			
		||||
	resultsDir := config.Conf.ResultsDir
 | 
			
		||||
	if len(resultsDir) == 0 {
 | 
			
		||||
		wd, _ := os.Getwd()
 | 
			
		||||
		resultsDir = filepath.Join(wd, "results")
 | 
			
		||||
	}
 | 
			
		||||
	jsonDir := filepath.Join(resultsDir, jsonDirName)
 | 
			
		||||
	if err := os.MkdirAll(jsonDir, 0700); err != nil {
 | 
			
		||||
		return "", fmt.Errorf("Failed to create dir: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	symlinkPath := filepath.Join(resultsDir, "current")
 | 
			
		||||
	if _, err := os.Lstat(symlinkPath); err == nil {
 | 
			
		||||
		if err := os.Remove(symlinkPath); err != nil {
 | 
			
		||||
			return "", fmt.Errorf(
 | 
			
		||||
				"Failed to remove symlink. path: %s, err: %s", symlinkPath, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := os.Symlink(jsonDir, symlinkPath); err != nil {
 | 
			
		||||
		return "", fmt.Errorf(
 | 
			
		||||
			"Failed to create symlink: path: %s, err: %s", symlinkPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	return jsonDir, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,47 +1 @@
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestPackageCveInfosSetGet(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in  []string
 | 
			
		||||
		out []string
 | 
			
		||||
	}{
 | 
			
		||||
		[]string{
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
		},
 | 
			
		||||
		[]string{
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//  var ps packageCveInfos
 | 
			
		||||
	var ps CvePacksList
 | 
			
		||||
	for _, cid := range test.in {
 | 
			
		||||
		ps = ps.set(cid, CvePacksInfo{CveID: cid})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(test.out) != len(ps) {
 | 
			
		||||
		t.Errorf("length: expected %d, actual %d", len(test.out), len(ps))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, expectedCid := range test.out {
 | 
			
		||||
		if expectedCid != ps[i].CveID {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", expectedCid, ps[i].CveID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cid := range test.in {
 | 
			
		||||
		p, _ := ps.FindByCveID(cid)
 | 
			
		||||
		if p.CveID != cid {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", cid, p.CveID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,10 +15,21 @@ You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package version
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
// Name is Vuls
 | 
			
		||||
const Name string = "vuls"
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type unknown struct {
 | 
			
		||||
	base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Version of Vuls
 | 
			
		||||
const Version string = "0.1.5"
 | 
			
		||||
func (o *unknown) checkIfSudoNoPasswd() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o unknown) checkDependencies() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *unknown) scanPackages() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,101 +0,0 @@
 | 
			
		||||
# Vuls on Docker
 | 
			
		||||
 | 
			
		||||
## What's Vuls-On-Docker
 | 
			
		||||
 | 
			
		||||
- 数個のコマンドを実行するだけでVulsとvulsrepoのセットアップが出来るスクリプト
 | 
			
		||||
- Dockerコンテナ上にVulsと[vulsrepo](https://github.com/usiusi360/vulsrepo)をセットアップ可能
 | 
			
		||||
- スキャン結果をvulsrepoでブラウザで分析可能
 | 
			
		||||
- 脆弱性データベースの更新が可能
 | 
			
		||||
- モジュールのアップデートが可能
 | 
			
		||||
 | 
			
		||||
## Setting up your machine
 | 
			
		||||
	
 | 
			
		||||
1. [Install Docker](https://docs.docker.com/engine/installation/)
 | 
			
		||||
2. [Install Docker-Compose](https://docs.docker.com/compose/install/)
 | 
			
		||||
3. 実行前に以下のコマンドが実行可能なことを確認する
 | 
			
		||||
 | 
			
		||||
	```
 | 
			
		||||
	$ docker version
 | 
			
		||||
	$ docker-compose version
 | 
			
		||||
	```
 | 
			
		||||
 | 
			
		||||
4. Vulsをgit clone
 | 
			
		||||
	```
 | 
			
		||||
	mkdir work
 | 
			
		||||
	cd work
 | 
			
		||||
	git clone https://github.com/future-architect/vuls.git
 | 
			
		||||
	cd vuls/setup/docker
 | 
			
		||||
	```
 | 
			
		||||
 | 
			
		||||
## Start A Vuls Container
 | 
			
		||||
 | 
			
		||||
- 以下のコマンドを実行してコンテナをビルドする
 | 
			
		||||
 | 
			
		||||
	```
 | 
			
		||||
	$ docker-compose up -d
 | 
			
		||||
	```
 | 
			
		||||
 | 
			
		||||
## Setting up Vuls
 | 
			
		||||
 | 
			
		||||
1. スキャン対象サーバのSSH秘密鍵を保存(vuls/setup/docker/conf/)する
 | 
			
		||||
2. config.toml(vuls/docker/conf/config.toml) を環境に合わせて作成する
 | 
			
		||||
	
 | 
			
		||||
	```
 | 
			
		||||
	[servers]
 | 
			
		||||
 | 
			
		||||
  	[servers.172-31-4-82]
 | 
			
		||||
  	host        = "172.31.4.82"
 | 
			
		||||
  	user        = "ec2-user"
 | 
			
		||||
  	keyPath     = "conf/id_rsa"
 | 
			
		||||
	```
 | 
			
		||||
 | 
			
		||||
## Fetch Vulnerability database
 | 
			
		||||
 | 
			
		||||
- NVDから脆弱性データベースを取得する
 | 
			
		||||
	```
 | 
			
		||||
	$ docker exec -t vuls scripts/fetch_nvd_all.sh
 | 
			
		||||
	```
 | 
			
		||||
 | 
			
		||||
- レポートを日本語化する場合は、JVNから脆弱性データを取得する
 | 
			
		||||
	```
 | 
			
		||||
	$ docker exec -t vuls scripts/fetch_jvn_all.sh
 | 
			
		||||
	```
 | 
			
		||||
 | 
			
		||||
## Scan servers with Vuls-On-Docker
 | 
			
		||||
 | 
			
		||||
- スキャンを実行する
 | 
			
		||||
 | 
			
		||||
	```
 | 
			
		||||
	$ docker exec -t vuls vuls prepare -config=conf/config.toml
 | 
			
		||||
	$ docker exec -t vuls scripts/scan_for_vulsrepo.sh
 | 
			
		||||
	```
 | 
			
		||||
 | 
			
		||||
## See the results in a browser 
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
http://${Vuls_Host}/vulsrepo/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# Update modules
 | 
			
		||||
 | 
			
		||||
- vuls, go-cve-dictionary, vulsrepoのモジュールをアップデートする
 | 
			
		||||
	```
 | 
			
		||||
	$ docker exec -t vuls scripts/update_modules.sh
 | 
			
		||||
	```
 | 
			
		||||
 | 
			
		||||
# Update Vulnerability database
 | 
			
		||||
 | 
			
		||||
- NVDの過去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
 | 
			
		||||
	```
 | 
			
		||||
@@ -1,87 +1,191 @@
 | 
			
		||||
# Vuls on Docker
 | 
			
		||||
# Vuls Docker components
 | 
			
		||||
 | 
			
		||||
## What's Vuls-On-Docker
 | 
			
		||||
This is the Git repo of the official Docker image for vuls.
 | 
			
		||||
 | 
			
		||||
- This is a dockernized-Vuls with vulsrepo UI in it.
 | 
			
		||||
- It's designed to reduce the cost of installation and the dependencies that vuls requires.
 | 
			
		||||
- You can run install and run Vuls on your machine with only a few commands.
 | 
			
		||||
- The result can be viewed with a browser
 | 
			
		||||
# Supported tags and respective `Dockerfile` links
 | 
			
		||||
 | 
			
		||||
## Setting up your machine
 | 
			
		||||
	
 | 
			
		||||
1. [Install Docker](https://docs.docker.com/engine/installation/)
 | 
			
		||||
2. [Install Docker-Compose](https://docs.docker.com/compose/install/)
 | 
			
		||||
3. Make sure that you can run the following commands before you move on.
 | 
			
		||||
- go-cve-dictionary
 | 
			
		||||
  - [`latest` (*go-cve-dictionary:latest Dockerfile*)]()
 | 
			
		||||
- vuls
 | 
			
		||||
  - [`latest` (*vuls:latest Dockerfile*)]()
 | 
			
		||||
- vulsrepo
 | 
			
		||||
  - [`latest` (*vulsrepo:latest Dockerfile*)]()
 | 
			
		||||
 | 
			
		||||
	```
 | 
			
		||||
	$ docker version
 | 
			
		||||
	$ docker-compose version
 | 
			
		||||
	```
 | 
			
		||||
	
 | 
			
		||||
4. git clone vuls
 | 
			
		||||
	```
 | 
			
		||||
	mkdir work
 | 
			
		||||
	cd work
 | 
			
		||||
	git clone https://github.com/future-architect/vuls.git
 | 
			
		||||
	cd vuls/setup/docker
 | 
			
		||||
	```
 | 
			
		||||
This image version is same as the github repository version.
 | 
			
		||||
 | 
			
		||||
# Caution
 | 
			
		||||
 | 
			
		||||
This image is built per commit.
 | 
			
		||||
If you want to use the latest docker image, you should remove the existing image, and pull it once again.
 | 
			
		||||
 | 
			
		||||
## Start A Vuls Container
 | 
			
		||||
1. Confirm your vuls version
 | 
			
		||||
 | 
			
		||||
- Execute the following command to build and run a Vuls Container
 | 
			
		||||
- go-cve-dictionary
 | 
			
		||||
 | 
			
		||||
	```
 | 
			
		||||
	$ docker-compose up -d
 | 
			
		||||
	```
 | 
			
		||||
```console
 | 
			
		||||
$ docker run  --rm  vuls/go-cve-dictionary -v
 | 
			
		||||
 | 
			
		||||
## Setting up Vuls
 | 
			
		||||
 | 
			
		||||
1. Locate ssh-keys of targer servers in (vuls/docker/conf/)
 | 
			
		||||
2. Create and ajust config.toml(vuls/docker/conf/config.toml) to your environment
 | 
			
		||||
	
 | 
			
		||||
	```
 | 
			
		||||
	[servers]
 | 
			
		||||
 | 
			
		||||
  	[servers.172-31-4-82]
 | 
			
		||||
  	host        = "172.31.4.82"
 | 
			
		||||
  	user        = "ec2-user"
 | 
			
		||||
  	keyPath     = "conf/id_rsa"
 | 
			
		||||
	```
 | 
			
		||||
 | 
			
		||||
## Fetch Vulnerability database
 | 
			
		||||
 | 
			
		||||
- Fetch Vulnerability database from NVD
 | 
			
		||||
	```
 | 
			
		||||
	$ docker exec -t vuls scripts/fetch_nvd_all.sh
 | 
			
		||||
	```
 | 
			
		||||
 | 
			
		||||
## Scan servers with Vuls-On-Docker
 | 
			
		||||
 | 
			
		||||
- Use the embedded script to scan servers for vulsrepo(or run whatever with docker exec)
 | 
			
		||||
 | 
			
		||||
	```
 | 
			
		||||
	$ docker exec -t vuls vuls prepare -config=conf/config.toml
 | 
			
		||||
	$ docker exec -t vuls scripts/scan_for_vulsrepo.sh
 | 
			
		||||
	```
 | 
			
		||||
 | 
			
		||||
## See the results in a browser 
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
http://${Vuls_Host}/vulsrepo/
 | 
			
		||||
go-cve-dictionary v0.0.xxx xxxx
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# Update modules
 | 
			
		||||
- vuls
 | 
			
		||||
 | 
			
		||||
- update vuls, go-cve-dictionary, vulsrepo
 | 
			
		||||
	```
 | 
			
		||||
	$ docker exec -t vuls scripts/update_modules.sh
 | 
			
		||||
	```
 | 
			
		||||
```console
 | 
			
		||||
$ docker run  --rm  vuls/vuls -v
 | 
			
		||||
 | 
			
		||||
# Update Vulnerability database
 | 
			
		||||
vuls v0.0.xxx xxxx
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- Fetch Vulnerability database from NVD
 | 
			
		||||
	```
 | 
			
		||||
	$ docker exec -t vuls scripts/fetch_nvd_last2y.sh
 | 
			
		||||
	```
 | 
			
		||||
2. Remove your old docker images
 | 
			
		||||
 | 
			
		||||
- go-cve-dictionary
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker rmi vuls/go-cve-dictionary
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- vuls
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker rmi vuls/vuls
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
3. Pull new vuls docker images
 | 
			
		||||
 | 
			
		||||
- go-cve-dictionary
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker pull vuls/go-cve-dictionary
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- vuls
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker pull vuls/vuls
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
4. Confirm your vuls version
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run  --rm  vuls/go-cve-dictionary -v
 | 
			
		||||
 | 
			
		||||
go-cve-dictionary v0.1.xxx xxxx
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- vuls
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run  --rm  vuls/vuls -v
 | 
			
		||||
 | 
			
		||||
vuls v0.1.xxx xxxx
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# How to use this image
 | 
			
		||||
 | 
			
		||||
1. fetch nvd (vuls/go-cve-dictionary)
 | 
			
		||||
1. configuration (vuls/vuls)
 | 
			
		||||
1. configtest (vuls/vuls)
 | 
			
		||||
1. scan (vuls/vuls)
 | 
			
		||||
1. vulsrepo (vuls/vulsrepo)
 | 
			
		||||
 | 
			
		||||
## Step1. Fetch NVD
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ for i in `seq 2002 $(date +"%Y")`; do \
 | 
			
		||||
    docker run --rm -it \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/go-cve-dictionary-log:/var/log/vuls \
 | 
			
		||||
    vuls/go-cve-dictionary fetchnvd -years $i; \
 | 
			
		||||
  done
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step2. Configuration
 | 
			
		||||
 | 
			
		||||
Create config.toml referring to [this](https://github.com/future-architect/vuls#configuration).
 | 
			
		||||
 | 
			
		||||
```toml
 | 
			
		||||
[servers]
 | 
			
		||||
 | 
			
		||||
[servers.amazon]
 | 
			
		||||
host         = "54.249.93.16"
 | 
			
		||||
port        = "22"
 | 
			
		||||
user        = "vuls-user"
 | 
			
		||||
keyPath     = "/root/.ssh/id_rsa" # path to ssh private key in docker
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run --rm \
 | 
			
		||||
    -v ~/.ssh:/root/.ssh:ro \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/vuls-log:/var/log/vuls \
 | 
			
		||||
    vuls/vuls configtest \
 | 
			
		||||
    -config=./config.toml # path to config.toml in docker
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step3. Configtest
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run --rm -it\
 | 
			
		||||
    -v ~/.ssh:/root/.ssh:ro \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/vuls-log:/var/log/vuls \
 | 
			
		||||
    vuls/vuls configtest \
 | 
			
		||||
    -config=./config.toml # path to config.toml in docker
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step4. Scan
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run --rm -it \
 | 
			
		||||
    -v ~/.ssh:/root/.ssh:ro \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/vuls-log:/var/log/vuls \
 | 
			
		||||
    -v /etc/localtime:/etc/localtime:ro \
 | 
			
		||||
    -e "TZ=Asia/Tokyo" \
 | 
			
		||||
    vuls/vuls scan \
 | 
			
		||||
    -config=./config.toml # path to config.toml in docker
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step5. Report
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run --rm -it \
 | 
			
		||||
    -v ~/.ssh:/root/.ssh:ro \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/vuls-log:/var/log/vuls \
 | 
			
		||||
    -v /etc/localtime:/etc/localtime:ro \
 | 
			
		||||
    vuls/vuls report \
 | 
			
		||||
    -cvedb-path=/vuls/cve.sqlite3 \
 | 
			
		||||
    -format-short-text \
 | 
			
		||||
    -config=./config.toml # path to config.toml in docker
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step6. vulsrepo
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$docker run -dt \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -p 80:80 \
 | 
			
		||||
    vuls/vulsrepo
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# User Feedback
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
Documentation for this image is stored in the [`docker/` directory]() of the [`future-architect/vuls` GitHub repo](https://github.com/future-architect/vuls). 
 | 
			
		||||
 | 
			
		||||
## Issues
 | 
			
		||||
 | 
			
		||||
If you have any problems with or questions about this image, please contact us through a [GitHub issue](https://github.com/future-architect/vuls/issues). 
 | 
			
		||||
 | 
			
		||||
## Contributing
 | 
			
		||||
 | 
			
		||||
1. fork a repository: github.com/future-architect/vuls to github.com/you/repo
 | 
			
		||||
1. get original code: go get github.com/future-architect/vuls
 | 
			
		||||
1. work on original code
 | 
			
		||||
1. add remote to your repo: git remote add myfork https://github.com/you/repo.git
 | 
			
		||||
1. push your changes: git push myfork
 | 
			
		||||
1. create a new Pull Request
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
version: '2'
 | 
			
		||||
services:
 | 
			
		||||
  vuls:
 | 
			
		||||
    container_name: vuls
 | 
			
		||||
    build: ./dockerfile
 | 
			
		||||
    image: vuls-docker:0.1
 | 
			
		||||
    volumes:
 | 
			
		||||
    - ./conf:/opt/vuls/conf
 | 
			
		||||
    ports:
 | 
			
		||||
      - "80:80"
 | 
			
		||||
 | 
			
		||||
@@ -1,89 +0,0 @@
 | 
			
		||||
FROM buildpack-deps:jessie-scm
 | 
			
		||||
 | 
			
		||||
# golang Install
 | 
			
		||||
RUN apt-get update && apt-get install -y --no-install-recommends \
 | 
			
		||||
        g++ \
 | 
			
		||||
        gcc \
 | 
			
		||||
        libc6-dev \
 | 
			
		||||
        make \
 | 
			
		||||
        curl \
 | 
			
		||||
    && rm -rf /var/lib/apt/lists/*
 | 
			
		||||
 | 
			
		||||
ENV GOLANG_VERSION 1.6.2
 | 
			
		||||
ENV GOLANG_DOWNLOAD_URL https://golang.org/dl/go$GOLANG_VERSION.linux-amd64.tar.gz
 | 
			
		||||
ENV GOLANG_DOWNLOAD_SHA256 e40c36ae71756198478624ed1bb4ce17597b3c19d243f3f0899bb5740d56212a
 | 
			
		||||
 | 
			
		||||
RUN curl -fsSL "$GOLANG_DOWNLOAD_URL" -o golang.tar.gz \
 | 
			
		||||
    && echo "$GOLANG_DOWNLOAD_SHA256  golang.tar.gz" | sha256sum -c - \
 | 
			
		||||
    && tar -C /usr/local -xzf golang.tar.gz \
 | 
			
		||||
    && rm golang.tar.gz
 | 
			
		||||
 | 
			
		||||
ENV GOPATH /go
 | 
			
		||||
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
 | 
			
		||||
 | 
			
		||||
RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"
 | 
			
		||||
 | 
			
		||||
# glide install
 | 
			
		||||
ENV GLIDE_VERSION 0.10.2
 | 
			
		||||
ENV GLIDE_DOWNLOAD_URL https://github.com/Masterminds/glide/releases/download/$GLIDE_VERSION/glide-$GLIDE_VERSION-linux-amd64.tar.gz
 | 
			
		||||
RUN curl -fsSL "$GLIDE_DOWNLOAD_URL" -o glide.tar.gz \
 | 
			
		||||
    && mkdir /usr/local/glide \
 | 
			
		||||
    && tar -C /usr/local/glide -xzf glide.tar.gz \
 | 
			
		||||
    && ln -s /usr/local/glide/linux-amd64/glide /usr/local/bin/ \
 | 
			
		||||
    && rm glide.tar.gz
 | 
			
		||||
 | 
			
		||||
# nginx Install
 | 
			
		||||
RUN apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62 \
 | 
			
		||||
    && echo "deb http://nginx.org/packages/mainline/debian/ jessie nginx" >> /etc/apt/sources.list \
 | 
			
		||||
    && apt-get update \
 | 
			
		||||
    && apt-get install --no-install-recommends --no-install-suggests -y \
 | 
			
		||||
                        ca-certificates \
 | 
			
		||||
                        nginx \
 | 
			
		||||
                        nginx-module-xslt \
 | 
			
		||||
                        nginx-module-geoip \
 | 
			
		||||
                        nginx-module-image-filter \
 | 
			
		||||
                        nginx-module-perl \
 | 
			
		||||
                        nginx-module-njs \
 | 
			
		||||
                        gettext-base \
 | 
			
		||||
                        wget \
 | 
			
		||||
                        unzip \
 | 
			
		||||
    && rm -rf /var/lib/apt/lists/*
 | 
			
		||||
 | 
			
		||||
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
 | 
			
		||||
        && ln -sf /dev/stderr /var/log/nginx/error.log
 | 
			
		||||
 | 
			
		||||
COPY nginx.conf /etc/nginx/nginx.conf
 | 
			
		||||
 | 
			
		||||
#Vuls Install
 | 
			
		||||
ENV VULS_ROOT /opt/vuls
 | 
			
		||||
RUN mkdir -p /var/log/vuls ${VULS_ROOT}/conf /root/.ssh/
 | 
			
		||||
RUN chmod 700 -R /var/log/vuls $VULS_ROOT
 | 
			
		||||
#  RUN go get github.com/kotakanbe/go-cve-dictionary
 | 
			
		||||
#  RUN go get github.com/future-architect/vuls
 | 
			
		||||
 | 
			
		||||
RUN go get -v -d github.com/kotakanbe/go-cve-dictionary \
 | 
			
		||||
    && cd $GOPATH/src/github.com/kotakanbe/go-cve-dictionary \
 | 
			
		||||
    && glide install \
 | 
			
		||||
    && go install
 | 
			
		||||
 | 
			
		||||
RUN go get -v -d github.com/future-architect/vuls \
 | 
			
		||||
    && cd $GOPATH/src/github.com/future-architect/vuls \
 | 
			
		||||
    && glide install \
 | 
			
		||||
    && go install
 | 
			
		||||
 | 
			
		||||
# Copy custom Scripts
 | 
			
		||||
COPY ./scripts/ ${VULS_ROOT}/scripts
 | 
			
		||||
RUN chmod 755 ${VULS_ROOT}/scripts/*
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#Vulrepo Install
 | 
			
		||||
RUN git clone https://github.com/usiusi360/vulsrepo /tmp/vulsrepo
 | 
			
		||||
RUN mkdir /usr/share/nginx/html/vulsrepo/
 | 
			
		||||
RUN cp -rp /tmp/vulsrepo/src/* /usr/share/nginx/html/vulsrepo
 | 
			
		||||
RUN rm -rf /tmp/vulsrepo
 | 
			
		||||
 | 
			
		||||
#Home
 | 
			
		||||
WORKDIR /opt/vuls
 | 
			
		||||
EXPOSE 80 443
 | 
			
		||||
CMD ["nginx", "-g", "daemon off;"]
 | 
			
		||||
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
 | 
			
		||||
user  root;
 | 
			
		||||
worker_processes  1;
 | 
			
		||||
 | 
			
		||||
error_log  /var/log/nginx/error.log warn;
 | 
			
		||||
pid        /var/run/nginx.pid;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
events {
 | 
			
		||||
    worker_connections  1024;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
http {
 | 
			
		||||
    include       /etc/nginx/mime.types;
 | 
			
		||||
    default_type  application/octet-stream;
 | 
			
		||||
 | 
			
		||||
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
 | 
			
		||||
                      '$status $body_bytes_sent "$http_referer" '
 | 
			
		||||
                      '"$http_user_agent" "$http_x_forwarded_for"';
 | 
			
		||||
 | 
			
		||||
    access_log  /var/log/nginx/access.log  main;
 | 
			
		||||
 | 
			
		||||
    sendfile        on;
 | 
			
		||||
    #tcp_nopush     on;
 | 
			
		||||
 | 
			
		||||
    keepalive_timeout  65;
 | 
			
		||||
 | 
			
		||||
    #gzip  on;
 | 
			
		||||
 | 
			
		||||
    include /etc/nginx/conf.d/*.conf;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
VULS_ROOT=/opt/vuls
 | 
			
		||||
#VULS_CONF=${VULS_ROOT}/conf
 | 
			
		||||
cd $VULS_ROOT
 | 
			
		||||
go-cve-dictionary fetchjvn -entire
 | 
			
		||||
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
VULS_ROOT=/opt/vuls
 | 
			
		||||
#VULS_CONF=${VULS_ROOT}/conf
 | 
			
		||||
cd $VULS_ROOT
 | 
			
		||||
go-cve-dictionary fetchjvn -month
 | 
			
		||||
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
VULS_ROOT=/opt/vuls
 | 
			
		||||
#VULS_CONF=${VULS_ROOT}/conf
 | 
			
		||||
cd $VULS_ROOT
 | 
			
		||||
go-cve-dictionary fetchjvn -week
 | 
			
		||||
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
VULS_ROOT=/opt/vuls
 | 
			
		||||
#VULS_CONF=${VULS_ROOT}/conf
 | 
			
		||||
cd $VULS_ROOT
 | 
			
		||||
for i in {2002..2016}; do go-cve-dictionary fetchnvd -years $i; done
 | 
			
		||||
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
VULS_ROOT=/opt/vuls
 | 
			
		||||
#VULS_CONF=${VULS_ROOT}/conf
 | 
			
		||||
cd $VULS_ROOT
 | 
			
		||||
go-cve-dictionary fetchnvd -last2y
 | 
			
		||||
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
VULS_ROOT=/opt/vuls
 | 
			
		||||
VULS_CONF=${VULS_ROOT}/conf
 | 
			
		||||
NGINX_VULSREPO_ROOT=/usr/share/nginx/html/vulsrepo
 | 
			
		||||
cd $VULS_ROOT
 | 
			
		||||
vuls scan -report-json --cve-dictionary-dbpath=${VULS_ROOT}/cve.sqlite3 -config=${VULS_CONF}/config.toml 
 | 
			
		||||
ln -sf ${VULS_ROOT}/results/current ${NGINX_VULSREPO_ROOT}/current
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
cd $GOPATH/src/github.com/future-architect/vuls
 | 
			
		||||
git pull origin master
 | 
			
		||||
glide install
 | 
			
		||||
go install
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cd $GOPATH/src/github.com/kotakanbe/go-cve-dictionary
 | 
			
		||||
git pull origin master
 | 
			
		||||
glide install
 | 
			
		||||
go install
 | 
			
		||||
 | 
			
		||||
git clone https://github.com/usiusi360/vulsrepo /tmp/vulsrepo
 | 
			
		||||
cp -rp /tmp/vulsrepo/src/* /usr/share/nginx/html/vulsrepo
 | 
			
		||||
rm -rf /tmp/vulsrepo
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								setup/docker/go-cve-dictionary/latest/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								setup/docker/go-cve-dictionary/latest/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
FROM golang:latest
 | 
			
		||||
 | 
			
		||||
MAINTAINER hikachan sadayuki-matsuno
 | 
			
		||||
 | 
			
		||||
ENV REPOSITORY github.com/kotakanbe/go-cve-dictionary
 | 
			
		||||
ENV LOGDIR /var/log/vuls
 | 
			
		||||
ENV WORKDIR /vuls
 | 
			
		||||
# go-cve-dictionary install
 | 
			
		||||
RUN git clone https://$REPOSITORY.git $GOPATH/src/$REPOSITORY \
 | 
			
		||||
    && cd $GOPATH/src/$REPOSITORY \
 | 
			
		||||
    && make install \
 | 
			
		||||
    && mkdir -p $LOGDIR
 | 
			
		||||
 | 
			
		||||
VOLUME [$WORKDIR, $LOGDIR]
 | 
			
		||||
WORKDIR $WORKDIR
 | 
			
		||||
ENV PWD $WORKDIR
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["go-cve-dictionary"]
 | 
			
		||||
CMD ["--help"]
 | 
			
		||||
							
								
								
									
										89
									
								
								setup/docker/go-cve-dictionary/latest/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								setup/docker/go-cve-dictionary/latest/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
# go-cve-dictionary-Docker
 | 
			
		||||
 | 
			
		||||
This is the Git repo of the official Docker image for go-cve-dictionary.
 | 
			
		||||
See the [Hub page](https://hub.docker.com/r/vuls/go-cve-dictionary/) for the full readme on how to use the Docker image and for information regarding contributing and issues.
 | 
			
		||||
 | 
			
		||||
# Supported tags and respective `Dockerfile` links
 | 
			
		||||
 | 
			
		||||
- [`latest` (*go-cve-dictionary:latest Dockerfile*)](https://github.com/future-architect/vuls/blob/master/setup/docker/go-cve-dictionary/latest/Dockerfile)
 | 
			
		||||
 | 
			
		||||
# Caution
 | 
			
		||||
 | 
			
		||||
This image is built per commit.
 | 
			
		||||
If you want to use the latest docker image, you should remove the existing image, and pull it once again.
 | 
			
		||||
 | 
			
		||||
- Remove old docker image
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker rmi vuls/go-cve-dictionary
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- Pull new docker image
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker pull vuls/go-cve-dictionary
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# What is go-cve-dictionary?
 | 
			
		||||
 | 
			
		||||
This is tool to build a local copy of the NVD (National Vulnerabilities Database) [1] and the Japanese JVN [2], which contain security vulnerabilities according to their CVE identifiers [3] including exhaustive information and a risk score. The local copy is generated in sqlite format, and the tool has a server mode for easy querying.
 | 
			
		||||
 | 
			
		||||
[1] https://en.wikipedia.org/wiki/National_Vulnerability_Database  
 | 
			
		||||
[2] https://en.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures  
 | 
			
		||||
[3] http://jvndb.jvn.jp/apis/termsofuse.html  
 | 
			
		||||
 | 
			
		||||
# How to use this image
 | 
			
		||||
 | 
			
		||||
## check vuls version
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker run --rm vuls/go-cve-dictionary -v
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## fetchnvd
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ for i in `seq 2002 $(date +"%Y")`; do \
 | 
			
		||||
    docker run --rm -it \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/go-cve-dictionary-log:/var/log/vuls \
 | 
			
		||||
    vuls/go-cve-dictionary fetchnvd -years $i; \
 | 
			
		||||
  done
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## server
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run -dt \
 | 
			
		||||
    --name go-cve-dictionary \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/go-cve-dictionary-log:/var/log/vuls \
 | 
			
		||||
    --expose 1323 \
 | 
			
		||||
    -p 1323:1323 \
 | 
			
		||||
    vuls/go-cve-dictionary server --bind=0.0.0.0
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Prease refer to [this](https://hub.docker.com/r/vuls/go-cve-dictionary).
 | 
			
		||||
 | 
			
		||||
## vuls
 | 
			
		||||
 | 
			
		||||
Please refer to [this](https://hub.docker.com/r/vuls/vuls/).
 | 
			
		||||
 | 
			
		||||
# User Feedback
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
Documentation for this image is stored in the [`docker/` directory](https://github.com/future-architect/vuls/tree/master/setup/docker) of the [`future-architect/vuls` GitHub repo](https://github.com/future-architect/vuls). 
 | 
			
		||||
 | 
			
		||||
## Issues
 | 
			
		||||
 | 
			
		||||
If you have any problems with or questions about this image, please contact us through a [GitHub issue](https://github.com/future-architect/vuls/issues). 
 | 
			
		||||
 | 
			
		||||
## Contributing
 | 
			
		||||
 | 
			
		||||
1. fork a repository: github.com/future-architect/vuls to github.com/you/repo
 | 
			
		||||
1. get original code: go get github.com/future-architect/vuls
 | 
			
		||||
1. work on original code
 | 
			
		||||
1. add remote to your repo: git remote add myfork https://github.com/you/repo.git
 | 
			
		||||
1. push your changes: git push myfork
 | 
			
		||||
1. create a new Pull Request
 | 
			
		||||
							
								
								
									
										19
									
								
								setup/docker/vuls/latest/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								setup/docker/vuls/latest/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
FROM golang:latest
 | 
			
		||||
 | 
			
		||||
MAINTAINER hikachan sadayuki-matsuno
 | 
			
		||||
 | 
			
		||||
ENV REPOSITORY github.com/future-architect/vuls
 | 
			
		||||
ENV LOGDIR /var/log/vuls
 | 
			
		||||
ENV WORKDIR /vuls
 | 
			
		||||
# go-cve-dictionary install
 | 
			
		||||
RUN git clone https://$REPOSITORY.git $GOPATH/src/$REPOSITORY \
 | 
			
		||||
    && cd $GOPATH/src/$REPOSITORY \
 | 
			
		||||
    && make install \
 | 
			
		||||
    && mkdir -p $LOGDIR
 | 
			
		||||
 | 
			
		||||
VOLUME [$WORKDIR, $LOGDIR]
 | 
			
		||||
WORKDIR $WORKDIR
 | 
			
		||||
ENV PWD $WORKDIR
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["vuls"]
 | 
			
		||||
CMD ["--help"]
 | 
			
		||||
							
								
								
									
										125
									
								
								setup/docker/vuls/latest/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								setup/docker/vuls/latest/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
			
		||||
# Vuls-Docker
 | 
			
		||||
 | 
			
		||||
This is the Git repo of the official Docker image for vuls.
 | 
			
		||||
See the [Hub page](https://hub.docker.com/r/vuls/vuls/) for the full readme on how to use the Docker image and for information regarding contributing and issues.
 | 
			
		||||
 | 
			
		||||
# Supported tags and respective `Dockerfile` links
 | 
			
		||||
 | 
			
		||||
- [`latest` (*vuls:latest Dockerfile*)](https://github.com/future-architect/vuls/blob/master/setup/docker/vuls/latest/Dockerfile)
 | 
			
		||||
 | 
			
		||||
# Caution
 | 
			
		||||
 | 
			
		||||
This image is built per commit.
 | 
			
		||||
If you want to use the latest docker image, you should remove the existing image, and pull it once again.
 | 
			
		||||
 | 
			
		||||
- Remove old docker image
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker rmi vuls/vuls
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- Pull new docker image
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker pull vuls/vuls
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# What is Vuls?
 | 
			
		||||
 | 
			
		||||
Vuls is the Vulnerability scanner for Linux/FreeBSD, agentless, written in golang.
 | 
			
		||||
Please see the [Documentation](https://github.com/future-architect/vuls)
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
# How to use this image
 | 
			
		||||
 | 
			
		||||
## check vuls version
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker run  --rm  vuls/vuls -v
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## config
 | 
			
		||||
 | 
			
		||||
Create config.toml referring to [this](https://github.com/future-architect/vuls#configuration).
 | 
			
		||||
 | 
			
		||||
```toml
 | 
			
		||||
[servers]
 | 
			
		||||
 | 
			
		||||
[servers.amazon]
 | 
			
		||||
host         = "54.249.93.16"
 | 
			
		||||
port        = "22"
 | 
			
		||||
user        = "vuls-user"
 | 
			
		||||
keyPath     = "/root/.ssh/id_rsa"  # path to ssh private key in docker
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## configtest
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run --rm -it \
 | 
			
		||||
    -v ~/.ssh:/root/.ssh:ro \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/vuls-log:/var/log/vuls \
 | 
			
		||||
    vuls/vuls configtest \
 | 
			
		||||
    -config=./config.toml # path to config.toml in docker
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## scan
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run --rm -it \
 | 
			
		||||
    -v ~/.ssh:/root/.ssh:ro \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/vuls-log:/var/log/vuls \
 | 
			
		||||
    -v /etc/localtime:/etc/localtime:ro \
 | 
			
		||||
    vuls/vuls scan \
 | 
			
		||||
    -config=./config.toml # path to config.toml in docker
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Report
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run --rm -it \
 | 
			
		||||
    -v ~/.ssh:/root/.ssh:ro \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/vuls-log:/var/log/vuls \
 | 
			
		||||
    -v /etc/localtime:/etc/localtime:ro \
 | 
			
		||||
    vuls/vuls report \
 | 
			
		||||
    -cvedb-path=/vuls/cve.sqlite3 \
 | 
			
		||||
    -format-short-text \
 | 
			
		||||
    -config=./config.toml # path to config.toml in docker
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## tui
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run --rm -it \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/vuls-log:/var/log/vuls \
 | 
			
		||||
    vuls/vuls tui \
 | 
			
		||||
    -cvedb-path=/vuls/cve.sqlite3 
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## vulsrepo
 | 
			
		||||
 | 
			
		||||
Prease refer to [this](https://hub.docker.com/r/vuls/vulsrepo/).
 | 
			
		||||
 | 
			
		||||
# User Feedback
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
Documentation for this image is stored in the [`docker/` directory](https://github.com/future-architect/vuls/tree/master/setup/docker) of the [`future-architect/vuls` GitHub repo](https://github.com/future-architect/vuls). 
 | 
			
		||||
 | 
			
		||||
## Issues
 | 
			
		||||
 | 
			
		||||
If you have any problems with or questions about this image, please contact us through a [GitHub issue](https://github.com/future-architect/vuls/issues). 
 | 
			
		||||
 | 
			
		||||
## Contributing
 | 
			
		||||
 | 
			
		||||
1. fork a repository: github.com/future-architect/vuls to github.com/you/repo
 | 
			
		||||
1. get original code: go get github.com/future-architect/vuls
 | 
			
		||||
1. work on original code
 | 
			
		||||
1. add remote to your repo: git remote add myfork https://github.com/you/repo.git
 | 
			
		||||
1. push your changes: git push myfork
 | 
			
		||||
1. create a new Pull Request
 | 
			
		||||
							
								
								
									
										31
									
								
								setup/docker/vulsrepo/latest/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								setup/docker/vulsrepo/latest/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
FROM httpd:2.4
 | 
			
		||||
 | 
			
		||||
MAINTAINER hikachan sadayuki-matsuno
 | 
			
		||||
# install packages
 | 
			
		||||
RUN apt-get update \
 | 
			
		||||
        && apt-get install -y --no-install-recommends \
 | 
			
		||||
      	  ca-certificates \
 | 
			
		||||
          vim \
 | 
			
		||||
          git \
 | 
			
		||||
          libcgi-pm-perl \
 | 
			
		||||
          libjson-perl \
 | 
			
		||||
        && rm -r /var/lib/apt/lists/*
 | 
			
		||||
 | 
			
		||||
# env
 | 
			
		||||
ENV HTTPD_PREFIX /usr/local/apache2
 | 
			
		||||
 | 
			
		||||
VOLUME /vuls
 | 
			
		||||
 | 
			
		||||
WORKDIR ${HTTPD_PREFIX}/htdocs
 | 
			
		||||
RUN git clone https://github.com/usiusi360/vulsrepo.git \
 | 
			
		||||
     && echo "LoadModule cgid_module modules/mod_cgid.so" >> $HTTPD_PREFIX/conf/httpd.conf \
 | 
			
		||||
     && echo "<Directory \"$HTTPD_PREFIX/htdocs/vulsrepo/dist/cgi\">" >> $HTTPD_PREFIX/conf/httpd.conf \
 | 
			
		||||
     && echo "  Options +ExecCGI +FollowSymLinks" >> $HTTPD_PREFIX/conf/httpd.conf \
 | 
			
		||||
     && echo "  AddHandler cgi-script cgi" >> $HTTPD_PREFIX/conf/httpd.conf \
 | 
			
		||||
     && echo "</Directory>" >> $HTTPD_PREFIX/conf/httpd.conf \
 | 
			
		||||
     && sed -i -e 's/User daemon/#User/g' $HTTPD_PREFIX/conf/httpd.conf \
 | 
			
		||||
     && sed -i -e 's/Group daemon/#Group/g' $HTTPD_PREFIX/conf/httpd.conf \
 | 
			
		||||
     && ln -snf /vuls/results /usr/local/apache2/htdocs/vulsrepo/results
 | 
			
		||||
 | 
			
		||||
EXPOSE 80
 | 
			
		||||
CMD ["httpd-foreground"]
 | 
			
		||||
							
								
								
									
										47
									
								
								setup/docker/vulsrepo/latest/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								setup/docker/vulsrepo/latest/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
# VulsRepo-Docker
 | 
			
		||||
 | 
			
		||||
This is the Git repo of the official Docker image for vulsrepo.
 | 
			
		||||
See the [Hub page](https://hub.docker.com/r/vuls/vulsrepo/) for the full readme on how to use the Docker image and for information regarding contributing and issues.
 | 
			
		||||
 | 
			
		||||
# Supported tags and respective `Dockerfile` links
 | 
			
		||||
 | 
			
		||||
- [`latest` (*vulsrepo:latest Dockerfile*)](https://github.com/future-architect/vuls/blob/master/setup/docker/vulsrepo/latest/Dockerfile)
 | 
			
		||||
 | 
			
		||||
# Caution
 | 
			
		||||
 | 
			
		||||
This image is built per commit.
 | 
			
		||||
If you want to use the latest docker image, you should remove the existing image, and pull it once again.
 | 
			
		||||
 | 
			
		||||
# What is vulsrepo?
 | 
			
		||||
 | 
			
		||||
VulsRepo is visualized based on the json report output in [vuls](https://github.com/future-architect/vuls).
 | 
			
		||||
 | 
			
		||||
# How to use this image
 | 
			
		||||
 | 
			
		||||
## vulsrepo
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$docker run -dt \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -p 80:80 \
 | 
			
		||||
    vuls/vulsrepo
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# User Feedback
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
Documentation for this image is stored in the [`docker/` directory](https://github.com/future-architect/vuls/tree/master/setup/docker) of the [`future-architect/vuls` GitHub repo](https://github.com/future-architect/vuls). 
 | 
			
		||||
 | 
			
		||||
## Issues
 | 
			
		||||
 | 
			
		||||
If you have any problems with or questions about this image, please contact us through a [GitHub issue](https://github.com/future-architect/vuls/issues). 
 | 
			
		||||
 | 
			
		||||
## Contributing
 | 
			
		||||
 | 
			
		||||
1. fork a repository: github.com/future-architect/vuls to github.com/you/repo
 | 
			
		||||
1. get original code: go get github.com/future-architect/vuls
 | 
			
		||||
1. work on original code
 | 
			
		||||
1. add remote to your repo: git remote add myfork https://github.com/you/repo.git
 | 
			
		||||
1. push your changes: git push myfork
 | 
			
		||||
1. create a new Pull Request
 | 
			
		||||
@@ -18,7 +18,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
@@ -30,6 +29,9 @@ import (
 | 
			
		||||
	formatter "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Log for localhsot
 | 
			
		||||
var Log *logrus.Entry
 | 
			
		||||
 | 
			
		||||
// NewCustomLogger creates logrus
 | 
			
		||||
func NewCustomLogger(c config.ServerInfo) *logrus.Entry {
 | 
			
		||||
	log := logrus.New()
 | 
			
		||||
@@ -41,24 +43,20 @@ func NewCustomLogger(c config.ServerInfo) *logrus.Entry {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// File output
 | 
			
		||||
	logDir := "/var/log/vuls"
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		logDir = filepath.Join(os.Getenv("APPDATA"), "vuls")
 | 
			
		||||
	logDir := GetDefaultLogDir()
 | 
			
		||||
	if 0 < len(config.Conf.LogDir) {
 | 
			
		||||
		logDir = config.Conf.LogDir
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := os.Stat(logDir); os.IsNotExist(err) {
 | 
			
		||||
		if err := os.Mkdir(logDir, 0666); err != nil {
 | 
			
		||||
			logrus.Errorf("Failed to create log directory: %s", err)
 | 
			
		||||
		if err := os.Mkdir(logDir, 0700); err != nil {
 | 
			
		||||
			log.Errorf("Failed to create log directory: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	whereami := "localhost"
 | 
			
		||||
	if 0 < len(c.ServerName) {
 | 
			
		||||
		if 0 < len(c.Container.ContainerID) {
 | 
			
		||||
			whereami = fmt.Sprintf(
 | 
			
		||||
				"%s_%s", c.ServerName, c.Container.Name)
 | 
			
		||||
		} else {
 | 
			
		||||
			whereami = fmt.Sprintf("%s", c.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
		whereami = c.GetServerName()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := os.Stat(logDir); err == nil {
 | 
			
		||||
@@ -76,3 +74,12 @@ func NewCustomLogger(c config.ServerInfo) *logrus.Entry {
 | 
			
		||||
	fields := logrus.Fields{"prefix": whereami}
 | 
			
		||||
	return log.WithFields(fields)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDefaultLogDir returns default log directory
 | 
			
		||||
func GetDefaultLogDir() string {
 | 
			
		||||
	defaultLogDir := "/var/log/vuls"
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		defaultLogDir = filepath.Join(os.Getenv("APPDATA"), "vuls")
 | 
			
		||||
	}
 | 
			
		||||
	return defaultLogDir
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								util/util.go
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								util/util.go
									
									
									
									
									
								
							@@ -95,7 +95,7 @@ func URLPathParamJoin(baseURL string, paths []string, params map[string]string)
 | 
			
		||||
 | 
			
		||||
// ProxyEnv returns shell environment variables to set proxy
 | 
			
		||||
func ProxyEnv() string {
 | 
			
		||||
	httpProxyEnv := "env"
 | 
			
		||||
	httpProxyEnv := ""
 | 
			
		||||
	keys := []string{
 | 
			
		||||
		"http_proxy",
 | 
			
		||||
		"https_proxy",
 | 
			
		||||
@@ -111,7 +111,7 @@ func ProxyEnv() string {
 | 
			
		||||
 | 
			
		||||
// PrependProxyEnv prepends proxy enviroment variable
 | 
			
		||||
func PrependProxyEnv(cmd string) string {
 | 
			
		||||
	if config.Conf.HTTPProxy == "" {
 | 
			
		||||
	if len(config.Conf.HTTPProxy) == 0 {
 | 
			
		||||
		return cmd
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s %s", ProxyEnv(), cmd)
 | 
			
		||||
@@ -124,3 +124,14 @@ func PrependProxyEnv(cmd string) string {
 | 
			
		||||
//      }
 | 
			
		||||
//      return time.Unix(i, 0), nil
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// Truncate truncates string to the length
 | 
			
		||||
func Truncate(str string, length int) string {
 | 
			
		||||
	if length < 0 {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
	if length <= len(str) {
 | 
			
		||||
		return str[:length]
 | 
			
		||||
	}
 | 
			
		||||
	return str
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -105,14 +105,14 @@ func TestPrependHTTPProxyEnv(t *testing.T) {
 | 
			
		||||
				"http://proxy.co.jp:8080",
 | 
			
		||||
				"yum check-update",
 | 
			
		||||
			},
 | 
			
		||||
			`env http_proxy="http://proxy.co.jp:8080" https_proxy="http://proxy.co.jp:8080" HTTP_PROXY="http://proxy.co.jp:8080" HTTPS_PROXY="http://proxy.co.jp:8080" yum check-update`,
 | 
			
		||||
			` http_proxy="http://proxy.co.jp:8080" https_proxy="http://proxy.co.jp:8080" HTTP_PROXY="http://proxy.co.jp:8080" HTTPS_PROXY="http://proxy.co.jp:8080" yum check-update`,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]string{
 | 
			
		||||
				"http://proxy.co.jp:8080",
 | 
			
		||||
				"",
 | 
			
		||||
			},
 | 
			
		||||
			`env http_proxy="http://proxy.co.jp:8080" https_proxy="http://proxy.co.jp:8080" HTTP_PROXY="http://proxy.co.jp:8080" HTTPS_PROXY="http://proxy.co.jp:8080" `,
 | 
			
		||||
			` http_proxy="http://proxy.co.jp:8080" https_proxy="http://proxy.co.jp:8080" HTTP_PROXY="http://proxy.co.jp:8080" HTTPS_PROXY="http://proxy.co.jp:8080" `,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]string{
 | 
			
		||||
@@ -131,3 +131,43 @@ func TestPrependHTTPProxyEnv(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTruncate(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in     string
 | 
			
		||||
		length int
 | 
			
		||||
		out    string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in:     "abcde",
 | 
			
		||||
			length: 3,
 | 
			
		||||
			out:    "abc",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:     "abcdefg",
 | 
			
		||||
			length: 5,
 | 
			
		||||
			out:    "abcde",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:     "abcdefg",
 | 
			
		||||
			length: 10,
 | 
			
		||||
			out:    "abcdefg",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:     "abcdefg",
 | 
			
		||||
			length: 0,
 | 
			
		||||
			out:    "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:     "abcdefg",
 | 
			
		||||
			length: -1,
 | 
			
		||||
			out:    "abcdefg",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := Truncate(tt.in, tt.length)
 | 
			
		||||
		if actual != tt.out {
 | 
			
		||||
			t.Errorf("\nexpected: %s\n  actual: %s", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user