Compare commits
	
		
			400 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					082b10a15b | ||
| 
						 | 
					1a6bcd82b0 | ||
| 
						 | 
					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 | ||
| 
						 | 
					8b264a564a | ||
| 
						 | 
					227da93c13 | ||
| 
						 | 
					f939041606 | ||
| 
						 | 
					e5b1a0bef8 | ||
| 
						 | 
					b9404d0880 | ||
| 
						 | 
					d6f12868be | ||
| 
						 | 
					b79e96f6cf | ||
| 
						 | 
					b066cc819e | ||
| 
						 | 
					4b669a0d49 | ||
| 
						 | 
					5e9de5d91a | ||
| 
						 | 
					da68b061e3 | ||
| 
						 | 
					6c3802071f | ||
| 
						 | 
					ad84f09bce | ||
| 
						 | 
					04166632d3 | ||
| 
						 | 
					376238b1ad | ||
| 
						 | 
					4f0dbff059 | ||
| 
						 | 
					f506e2b50a | ||
| 
						 | 
					88d2fbf5e2 | ||
| 
						 | 
					7fd8cc5449 | ||
| 
						 | 
					d033463b34 | ||
| 
						 | 
					740208cf74 | ||
| 
						 | 
					0036c0b10e | ||
| 
						 | 
					834c832390 | ||
| 
						 | 
					5bc99dfd25 | ||
| 
						 | 
					c92d2d064a | ||
| 
						 | 
					a60c21323c | ||
| 
						 | 
					34d6d6e709 | ||
| 
						 | 
					f2ddafc718 | ||
| 
						 | 
					267afdd15d | ||
| 
						 | 
					48b7b82e33 | ||
| 
						 | 
					84e5e5432e | ||
| 
						 | 
					201e18eac2 | ||
| 
						 | 
					3f3f0b1fec | ||
| 
						 | 
					ca697c5038 | ||
| 
						 | 
					5aeeb4e8b4 | ||
| 
						 | 
					c285f9f587 | ||
| 
						 | 
					d046608426 | ||
| 
						 | 
					b91ed9cff5 | ||
| 
						 | 
					185d85bfdd | ||
| 
						 | 
					44b2c1464a | ||
| 
						 | 
					a0762a0a6c | ||
| 
						 | 
					2ad7660c09 | ||
| 
						 | 
					d8b8c38182 | ||
| 
						 | 
					1d50e5126a | ||
| 
						 | 
					aa55e30358 | ||
| 
						 | 
					f662de50db | ||
| 
						 | 
					24c798ad3a | ||
| 
						 | 
					0e304ae546 | ||
| 
						 | 
					cd604cbfe7 | ||
| 
						 | 
					b8e66d9df0 | ||
| 
						 | 
					a2c738e57b | ||
| 
						 | 
					ae16cd708c | ||
| 
						 | 
					2ed0443f88 | ||
| 
						 | 
					38f1c5075d | ||
| 
						 | 
					55043a6348 | ||
| 
						 | 
					1f6eb55b86 | ||
| 
						 | 
					d9d8500484 | ||
| 
						 | 
					0fca75c2db | ||
| 
						 | 
					a7dcccbdf9 | ||
| 
						 | 
					396eb5aec2 | ||
| 
						 | 
					79d2076e09 | ||
| 
						 | 
					693dca4ca2 | ||
| 
						 | 
					4047076033 | ||
| 
						 | 
					acb0b71f1b | ||
| 
						 | 
					32d9352048 | ||
| 
						 | 
					0246556f7c | ||
| 
						 | 
					a17284681f | ||
| 
						 | 
					adb66e3298 | ||
| 
						 | 
					ad062d777d | ||
| 
						 | 
					ffe1ff73a5 | ||
| 
						 | 
					54f9202d74 | ||
| 
						 | 
					ef3e173fb2 | ||
| 
						 | 
					1aeec2ae51 | ||
| 
						 | 
					1f50bfd801 | ||
| 
						 | 
					d3466eabe5 | ||
| 
						 | 
					8aff1af939 | ||
| 
						 | 
					af35303432 | ||
| 
						 | 
					0ef1a5a3ce | ||
| 
						 | 
					e958bc8212 | ||
| 
						 | 
					e0ca6e89d1 | ||
| 
						 | 
					55d8ae124a | ||
| 
						 | 
					5e28ec22e1 | ||
| 
						 | 
					c3deb93489 | ||
| 
						 | 
					a9aca94848 | ||
| 
						 | 
					f3c06890dd | ||
| 
						 | 
					d9d0e629fd | ||
| 
						 | 
					17181405e3 | ||
| 
						 | 
					c209564945 | ||
| 
						 | 
					2da01db438 | ||
| 
						 | 
					8c4913d411 | ||
| 
						 | 
					e7ffc24844 | ||
| 
						 | 
					259f23f6ee | ||
| 
						 | 
					0de38b99c2 | ||
| 
						 | 
					1044fb8574 | ||
| 
						 | 
					e5bfa1bd6f | ||
| 
						 | 
					a29b2a2ad9 | ||
| 
						 | 
					b6899ce461 | ||
| 
						 | 
					32c11af07c | ||
| 
						 | 
					6ff55d24d0 | ||
| 
						 | 
					055aacd7f6 | ||
| 
						 | 
					5ecf58fd56 | ||
| 
						 | 
					8a9106052f | ||
| 
						 | 
					91264547c9 | ||
| 
						 | 
					3190b877ae | ||
| 
						 | 
					f8a8cc4676 | ||
| 
						 | 
					93ee329315 | ||
| 
						 | 
					b45163388d | ||
| 
						 | 
					6029784f76 | ||
| 
						 | 
					058ab55a6f | ||
| 
						 | 
					1005d241b8 | ||
| 
						 | 
					33b1ccba67 | ||
| 
						 | 
					a5549fb500 | ||
| 
						 | 
					b057ed3e77 | ||
| 
						 | 
					1e88cc10e7 | ||
| 
						 | 
					2f8634383e | ||
| 
						 | 
					86f9e5ce96 | ||
| 
						 | 
					9ae42d647c | ||
| 
						 | 
					54d6217b93 | ||
| 
						 | 
					150b1c2406 | ||
| 
						 | 
					51b6f1b5f3 | ||
| 
						 | 
					3eae14cef6 | ||
| 
						 | 
					cc6dc1ca69 | ||
| 
						 | 
					7f2361f58c | ||
| 
						 | 
					7cb02d77ae | ||
| 
						 | 
					52cc9b0cc0 | ||
| 
						 | 
					d91bf61038 | ||
| 
						 | 
					d5f81674f8 | ||
| 
						 | 
					9381883835 | ||
| 
						 | 
					f82e5a281d | ||
| 
						 | 
					904e6241e4 | ||
| 
						 | 
					ce39a3daf9 | ||
| 
						 | 
					f2c7f74beb | ||
| 
						 | 
					20db997fc2 | ||
| 
						 | 
					7188e97444 | ||
| 
						 | 
					6d528e741d | ||
| 
						 | 
					d356e8370d | ||
| 
						 | 
					5e336b5928 | ||
| 
						 | 
					787ad0629b | ||
| 
						 | 
					53e4adf24e | ||
| 
						 | 
					6af811d63e | ||
| 
						 | 
					359dab3380 | ||
| 
						 | 
					97a8e6e965 | ||
| 
						 | 
					8ea699aa08 | ||
| 
						 | 
					7d924d2b0c | ||
| 
						 | 
					3c85613ada | ||
| 
						 | 
					c536d26db3 | ||
| 
						 | 
					4350ff2692 | ||
| 
						 | 
					0b9a1e7bb4 | ||
| 
						 | 
					714ad18fa0 | ||
| 
						 | 
					f81f785813 | ||
| 
						 | 
					76c32af46f | ||
| 
						 | 
					cd108263e1 | ||
| 
						 | 
					093c47b59c | ||
| 
						 | 
					56a40ec51a | ||
| 
						 | 
					1337be2b84 | ||
| 
						 | 
					eecd2c60f5 | ||
| 
						 | 
					da071cb120 | ||
| 
						 | 
					012cfa3cbe | ||
| 
						 | 
					21180847dc | ||
| 
						 | 
					9e9e538846 | ||
| 
						 | 
					66025b1ae2 | ||
| 
						 | 
					5999361358 | ||
| 
						 | 
					e8699d1cb7 | ||
| 
						 | 
					9292448e73 | ||
| 
						 | 
					d7e156613d | ||
| 
						 | 
					c3604aa66d | ||
| 
						 | 
					49dd12fef3 | ||
| 
						 | 
					5e037b1743 | ||
| 
						 | 
					ebc79805ed | ||
| 
						 | 
					c37e56e51d | ||
| 
						 | 
					28a93c02e6 | ||
| 
						 | 
					0996c58894 | ||
| 
						 | 
					56ecf32565 | ||
| 
						 | 
					416fb3c937 | ||
| 
						 | 
					d48b8315c9 | ||
| 
						 | 
					7c6d1eb585 | ||
| 
						 | 
					fae04dce81 | ||
| 
						 | 
					86a5433312 | ||
| 
						 | 
					d9cf63a9fe | ||
| 
						 | 
					88bf643363 | ||
| 
						 | 
					e0b680b305 | ||
| 
						 | 
					d6356408b8 | ||
| 
						 | 
					4d28de17b4 | ||
| 
						 | 
					fdd918d970 | ||
| 
						 | 
					da16f9673e | ||
| 
						 | 
					b02b7c9081 | ||
| 
						 | 
					ea82149dbe | ||
| 
						 | 
					9d64f039ab | ||
| 
						 | 
					cd9cbd795b | ||
| 
						 | 
					929d561de8 | ||
| 
						 | 
					245abe5b6b | ||
| 
						 | 
					768364fc77 | ||
| 
						 | 
					60a3e9532a | ||
| 
						 | 
					dcd6ba0a82 | ||
| 
						 | 
					9f2dc2c6a3 | ||
| 
						 | 
					7498a540d4 | ||
| 
						 | 
					26ae01d960 | ||
| 
						 | 
					f72781c30c | ||
| 
						 | 
					21e957159d | ||
| 
						 | 
					a66b425da0 | ||
| 
						 | 
					804fffd009 | ||
| 
						 | 
					ac77cc1f87 | ||
| 
						 | 
					d0d360a6e7 | ||
| 
						 | 
					9708533565 | ||
| 
						 | 
					ac98b908e3 | ||
| 
						 | 
					9bacd98577 | ||
| 
						 | 
					d750205f31 | ||
| 
						 | 
					b4d0aa7532 | ||
| 
						 | 
					3e846233a3 | ||
| 
						 | 
					1a943776c3 | ||
| 
						 | 
					57ef45ebcd | ||
| 
						 | 
					b64115f283 | ||
| 
						 | 
					018eb29ce5 | ||
| 
						 | 
					77c7d2fe26 | ||
| 
						 | 
					336b72bbca | ||
| 
						 | 
					0deb1032cd | ||
| 
						 | 
					34c5644e63 | ||
| 
						 | 
					1f80738bef | ||
| 
						 | 
					66501663a0 | ||
| 
						 | 
					f677939975 | ||
| 
						 | 
					c465faeb6c | ||
| 
						 | 
					6a6c7bf8a4 | ||
| 
						 | 
					d19afe665f | ||
| 
						 | 
					c62ca7c645 | ||
| 
						 | 
					855b48f0c9 | ||
| 
						 | 
					555e34d035 | ||
| 
						 | 
					6b12ff35cd | ||
| 
						 | 
					d9813e822f | ||
| 
						 | 
					26273e7387 | 
							
								
								
									
										14
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,9 +1,15 @@
 | 
			
		||||
vuls
 | 
			
		||||
.vscode
 | 
			
		||||
*.txt
 | 
			
		||||
*.json
 | 
			
		||||
*.sqlite3*
 | 
			
		||||
*.db
 | 
			
		||||
tags
 | 
			
		||||
.gitmodules
 | 
			
		||||
coverage.out
 | 
			
		||||
issues/
 | 
			
		||||
*.txt
 | 
			
		||||
vendor/
 | 
			
		||||
log/
 | 
			
		||||
.gitmodules
 | 
			
		||||
vuls
 | 
			
		||||
*.sqlite3
 | 
			
		||||
results/
 | 
			
		||||
*config.toml
 | 
			
		||||
!setup/docker/*
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										285
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										285
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,5 +1,286 @@
 | 
			
		||||
# Change Log
 | 
			
		||||
 | 
			
		||||
0.1.0 (2013-03-23)
 | 
			
		||||
## [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)
 | 
			
		||||
 | 
			
		||||
Initial public release
 | 
			
		||||
**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:**
 | 
			
		||||
 | 
			
		||||
- 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)
 | 
			
		||||
 | 
			
		||||
**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)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- Enable to scan without running go-cve-dictionary as server mode [\#84](https://github.com/future-architect/vuls/issues/84)
 | 
			
		||||
- Support high-speed scanning for CentOS [\#138](https://github.com/future-architect/vuls/pull/138) ([tai-ga](https://github.com/tai-ga))
 | 
			
		||||
- Add configtest subcommand. skip un-ssh-able servers. [\#134](https://github.com/future-architect/vuls/pull/134) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Support -report-azure-blob option [\#130](https://github.com/future-architect/vuls/pull/130) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add optional key-values that will be outputted to JSON in config [\#117](https://github.com/future-architect/vuls/pull/117) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Change dir structure [\#115](https://github.com/future-architect/vuls/pull/115) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add some validation of loading config. user, host and port [\#113](https://github.com/future-architect/vuls/pull/113) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Support scanning with external ssh command [\#101](https://github.com/future-architect/vuls/pull/101) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Detect Platform and get instance-id of amazon ec2 [\#95](https://github.com/future-architect/vuls/pull/95) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add -report-s3 option [\#92](https://github.com/future-architect/vuls/pull/92) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Added FreeBSD support. [\#90](https://github.com/future-architect/vuls/pull/90) ([justyntemme](https://github.com/justyntemme))
 | 
			
		||||
- Add glide files for vendoring [\#89](https://github.com/future-architect/vuls/pull/89) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix README, change -cvedbpath to -cve-dictionary-dbpath \#84 [\#85](https://github.com/future-architect/vuls/pull/85) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add option for it get cve detail from cve.sqlite3. [\#81](https://github.com/future-architect/vuls/pull/81) ([ymd38](https://github.com/ymd38))
 | 
			
		||||
- Add -report-text option, Fix small bug of report in japanese [\#78](https://github.com/future-architect/vuls/pull/78) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add JSONWriter, Fix CVE sort order of report [\#77](https://github.com/future-architect/vuls/pull/77) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Docker: Panic [\#76](https://github.com/future-architect/vuls/issues/76)
 | 
			
		||||
- Fix apt command to scan correctly when system locale is not english [\#149](https://github.com/future-architect/vuls/pull/149) ([kit494way](https://github.com/kit494way))
 | 
			
		||||
- Disable -ask-sudo-password for security reasons [\#148](https://github.com/future-architect/vuls/pull/148) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix no tty error while executing with -external-ssh option [\#143](https://github.com/future-architect/vuls/pull/143) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- wrong log packages [\#141](https://github.com/future-architect/vuls/pull/141) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Fix platform detection. [\#137](https://github.com/future-architect/vuls/pull/137) ([Rompei](https://github.com/Rompei))
 | 
			
		||||
- Fix nil pointer when scan with -cve-dictionary-dbpath and cpeNames [\#111](https://github.com/future-architect/vuls/pull/111) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Remove vulndb file before pkg audit [\#110](https://github.com/future-architect/vuls/pull/110) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add error handling when unable to connect via ssh. status code: 255 [\#108](https://github.com/future-architect/vuls/pull/108) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Enable to detect vulnerabilities on FreeBSD [\#98](https://github.com/future-architect/vuls/pull/98) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix unknown format err while check-update on RHEL6.5 [\#93](https://github.com/future-architect/vuls/pull/93) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Fix type of SMTP Port of discovery command's output [\#88](https://github.com/future-architect/vuls/pull/88) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix error msg when go-cve-dictionary is unavailable \#84 [\#86](https://github.com/future-architect/vuls/pull/86) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix error handling to avoid nil pointer err on debian [\#83](https://github.com/future-architect/vuls/pull/83) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix nil pointer while doing apt-cache policy on ubuntu \#76 [\#82](https://github.com/future-architect/vuls/pull/82) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix log import url [\#79](https://github.com/future-architect/vuls/pull/79) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Fix error handling of gorequest [\#75](https://github.com/future-architect/vuls/pull/75) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix freezing forever when no args specified in TUI mode [\#73](https://github.com/future-architect/vuls/pull/73) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- mv version.go version/version.go to run main.go without compile [\#71](https://github.com/future-architect/vuls/pull/71) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
 | 
			
		||||
**Closed issues:**
 | 
			
		||||
 | 
			
		||||
- SSh password authentication failed on FreeBSD [\#99](https://github.com/future-architect/vuls/issues/99)
 | 
			
		||||
- BUG: -o pipefail is not work on FreeBSD's /bin/sh. because it isn't bash [\#91](https://github.com/future-architect/vuls/issues/91)
 | 
			
		||||
- Use ~/.ssh/config [\#62](https://github.com/future-architect/vuls/issues/62)
 | 
			
		||||
- SSH ciphers [\#37](https://github.com/future-architect/vuls/issues/37)
 | 
			
		||||
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- Update README \#138 [\#144](https://github.com/future-architect/vuls/pull/144) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix a typo [\#142](https://github.com/future-architect/vuls/pull/142) ([dtan4](https://github.com/dtan4))
 | 
			
		||||
- Remove unnecessary step in readme of docker setup [\#140](https://github.com/future-architect/vuls/pull/140) ([mikkame](https://github.com/mikkame))
 | 
			
		||||
- Update logo [\#139](https://github.com/future-architect/vuls/pull/139) ([chanomaru](https://github.com/chanomaru))
 | 
			
		||||
- Update README.ja.md to fix wrong tips. [\#135](https://github.com/future-architect/vuls/pull/135) ([a2atsu](https://github.com/a2atsu))
 | 
			
		||||
- add tips about NVD JVN issue [\#133](https://github.com/future-architect/vuls/pull/133) ([a2atsu](https://github.com/a2atsu))
 | 
			
		||||
- Fix README wrong links [\#129](https://github.com/future-architect/vuls/pull/129) ([aomoriringo](https://github.com/aomoriringo))
 | 
			
		||||
- Add logo [\#126](https://github.com/future-architect/vuls/pull/126) ([chanomaru](https://github.com/chanomaru))
 | 
			
		||||
- Improve setup/docker [\#125](https://github.com/future-architect/vuls/pull/125) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix scan command help [\#124](https://github.com/future-architect/vuls/pull/124) ([aomoriringo](https://github.com/aomoriringo))
 | 
			
		||||
- added dockernized-vuls with vulsrepo [\#121](https://github.com/future-architect/vuls/pull/121) ([hikachan](https://github.com/hikachan))
 | 
			
		||||
- Fix detect platform on azure and degital ocean [\#119](https://github.com/future-architect/vuls/pull/119) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Remove json marshall-indent [\#118](https://github.com/future-architect/vuls/pull/118) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Improve Readme.ja [\#116](https://github.com/future-architect/vuls/pull/116) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add architecture diag to README.md [\#114](https://github.com/future-architect/vuls/pull/114) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Rename linux.go to base.go [\#100](https://github.com/future-architect/vuls/pull/100) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Update README.md [\#74](https://github.com/future-architect/vuls/pull/74) ([yoshi-taka](https://github.com/yoshi-taka))
 | 
			
		||||
- Refactoring debian.go [\#72](https://github.com/future-architect/vuls/pull/72) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
## [v0.1.4](https://github.com/future-architect/vuls/tree/v0.1.4) (2016-05-24)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.3...v0.1.4)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- Initial fetch from NVD is too heavy \(2.3 GB of memory consumed\) [\#27](https://github.com/future-architect/vuls/issues/27)
 | 
			
		||||
- Enable to show previous scan result [\#69](https://github.com/future-architect/vuls/pull/69) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add ignore-unscored-cves option [\#68](https://github.com/future-architect/vuls/pull/68) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Support dynamic scanning docker container [\#67](https://github.com/future-architect/vuls/pull/67) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add version flag [\#65](https://github.com/future-architect/vuls/pull/65) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Update Dockerfile [\#57](https://github.com/future-architect/vuls/pull/57) ([theonlydoo](https://github.com/theonlydoo))
 | 
			
		||||
- Update run.sh [\#56](https://github.com/future-architect/vuls/pull/56) ([theonlydoo](https://github.com/theonlydoo))
 | 
			
		||||
- Support Windows [\#33](https://github.com/future-architect/vuls/pull/33) ([mattn](https://github.com/mattn))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- vuls scan -cvss-over does not work. [\#59](https://github.com/future-architect/vuls/issues/59)
 | 
			
		||||
- `panic: runtime error: invalid memory address or nil pointer dereference` when scan CentOS5.5 [\#58](https://github.com/future-architect/vuls/issues/58)
 | 
			
		||||
-  It rans out of memory. [\#47](https://github.com/future-architect/vuls/issues/47)
 | 
			
		||||
- BUG: vuls scan on CentOS with Japanese environment. [\#43](https://github.com/future-architect/vuls/issues/43)
 | 
			
		||||
- yum --color=never [\#36](https://github.com/future-architect/vuls/issues/36)
 | 
			
		||||
- Failed to parse yum check-update [\#32](https://github.com/future-architect/vuls/issues/32)
 | 
			
		||||
- Pointless sudo [\#29](https://github.com/future-architect/vuls/issues/29)
 | 
			
		||||
- Can't init database in a path having blanks [\#26](https://github.com/future-architect/vuls/issues/26)
 | 
			
		||||
- Fix pointless sudo in debian.go \#29 [\#66](https://github.com/future-architect/vuls/pull/66) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix error handling of httpGet in cve-client \#58 [\#64](https://github.com/future-architect/vuls/pull/64) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix nil pointer at error handling of cve\_client \#58 [\#63](https://github.com/future-architect/vuls/pull/63) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Set language en\_US. [\#61](https://github.com/future-architect/vuls/pull/61) ([pabroff](https://github.com/pabroff))
 | 
			
		||||
- Fix -cvss-over flag \#59 [\#60](https://github.com/future-architect/vuls/pull/60) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix scan on Japanese environment. [\#55](https://github.com/future-architect/vuls/pull/55) ([pabroff](https://github.com/pabroff))
 | 
			
		||||
- Fix a typo: replace Depricated by Deprecated. [\#54](https://github.com/future-architect/vuls/pull/54) ([jody-frankowski](https://github.com/jody-frankowski))
 | 
			
		||||
- Fix yes no infinite loop while doing yum update --changelog on root@CentOS \#47 [\#50](https://github.com/future-architect/vuls/pull/50) ([pabroff](https://github.com/pabroff))
 | 
			
		||||
- Fix $servername in output of discover command [\#45](https://github.com/future-architect/vuls/pull/45) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
## [v0.1.3](https://github.com/future-architect/vuls/tree/v0.1.3) (2016-04-21)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.2...v0.1.3)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- Add sudo support for prepare [\#11](https://github.com/future-architect/vuls/issues/11)
 | 
			
		||||
- Dockerfile? [\#10](https://github.com/future-architect/vuls/issues/10)
 | 
			
		||||
- Update README [\#41](https://github.com/future-architect/vuls/pull/41) ([theonlydoo](https://github.com/theonlydoo))
 | 
			
		||||
- Sparse dockerization [\#38](https://github.com/future-architect/vuls/pull/38) ([theonlydoo](https://github.com/theonlydoo))
 | 
			
		||||
- No password in config [\#35](https://github.com/future-architect/vuls/pull/35) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fr readme translation [\#23](https://github.com/future-architect/vuls/pull/23) ([novakin](https://github.com/novakin))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Issues updating CVE database behind https proxy [\#39](https://github.com/future-architect/vuls/issues/39)
 | 
			
		||||
- Vuls failed to parse yum check-update [\#24](https://github.com/future-architect/vuls/issues/24)
 | 
			
		||||
- Fix yum to yum --color=never \#36 [\#42](https://github.com/future-architect/vuls/pull/42) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix parse yum check update [\#40](https://github.com/future-architect/vuls/pull/40) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix typo [\#31](https://github.com/future-architect/vuls/pull/31) ([blue119](https://github.com/blue119))
 | 
			
		||||
- Fix error while parsing yum check-update \#24 [\#30](https://github.com/future-architect/vuls/pull/30) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
**Closed issues:**
 | 
			
		||||
 | 
			
		||||
- Unable to scan on ubuntu because changelog.ubuntu.com is down... [\#21](https://github.com/future-architect/vuls/issues/21)
 | 
			
		||||
- err: Not initialize\(d\) yet.. [\#16](https://github.com/future-architect/vuls/issues/16)
 | 
			
		||||
- Errors when using fish shell [\#8](https://github.com/future-architect/vuls/issues/8)
 | 
			
		||||
 | 
			
		||||
## [v0.1.2](https://github.com/future-architect/vuls/tree/v0.1.2) (2016-04-12)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.1...v0.1.2)
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Maximum 6 nodes available to scan [\#12](https://github.com/future-architect/vuls/issues/12)
 | 
			
		||||
- panic: runtime error: index out of range [\#5](https://github.com/future-architect/vuls/issues/5)
 | 
			
		||||
- Fix sudo option on RedHat like Linux and change some messages. [\#20](https://github.com/future-architect/vuls/pull/20) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Typo fix and updated readme [\#19](https://github.com/future-architect/vuls/pull/19) ([Euan-Kerr](https://github.com/Euan-Kerr))
 | 
			
		||||
- remove a period at the end of error messages. [\#18](https://github.com/future-architect/vuls/pull/18) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix error while yum updateinfo --security update on rhel@aws [\#17](https://github.com/future-architect/vuls/pull/17) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fixed typos [\#15](https://github.com/future-architect/vuls/pull/15) ([radarhere](https://github.com/radarhere))
 | 
			
		||||
- Typo fix in error messages [\#14](https://github.com/future-architect/vuls/pull/14) ([Bregor](https://github.com/Bregor))
 | 
			
		||||
- Fix index out of range error when the number of servers is over 6. \#12 [\#13](https://github.com/future-architect/vuls/pull/13) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Revise small grammar mistakes in serverapi.go [\#9](https://github.com/future-architect/vuls/pull/9) ([cpobrien](https://github.com/cpobrien))
 | 
			
		||||
- Fix error handling in HTTP backoff function [\#7](https://github.com/future-architect/vuls/pull/7) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
## [v0.1.1](https://github.com/future-architect/vuls/tree/v0.1.1) (2016-04-06)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.0...v0.1.1)
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Typo in Exapmle [\#6](https://github.com/future-architect/vuls/pull/6) ([toli](https://github.com/toli))
 | 
			
		||||
 | 
			
		||||
## [v0.1.0](https://github.com/future-architect/vuls/tree/v0.1.0) (2016-04-04)
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- English translation [\#4](https://github.com/future-architect/vuls/pull/4) ([hikachan](https://github.com/hikachan))
 | 
			
		||||
- English translation [\#3](https://github.com/future-architect/vuls/pull/3) ([chewyinping](https://github.com/chewyinping))
 | 
			
		||||
- Add a Bitdeli Badge to README [\#2](https://github.com/future-architect/vuls/pull/2) ([bitdeli-chef](https://github.com/bitdeli-chef))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
 | 
			
		||||
							
								
								
									
										36
									
								
								ISSUE_TEMPLATE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								ISSUE_TEMPLATE
									
									
									
									
									
										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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										35
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								Makefile
									
									
									
									
									
								
							@@ -1,4 +1,9 @@
 | 
			
		||||
.PHONY: \
 | 
			
		||||
	glide \
 | 
			
		||||
	deps \
 | 
			
		||||
	update \
 | 
			
		||||
	build \
 | 
			
		||||
	install \
 | 
			
		||||
	all \
 | 
			
		||||
	vendor \
 | 
			
		||||
	lint \
 | 
			
		||||
@@ -7,25 +12,39 @@
 | 
			
		||||
	fmtcheck \
 | 
			
		||||
	pretest \
 | 
			
		||||
	test \
 | 
			
		||||
	integration \
 | 
			
		||||
	cov \
 | 
			
		||||
	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)'
 | 
			
		||||
 | 
			
		||||
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)"
 | 
			
		||||
 | 
			
		||||
all: test
 | 
			
		||||
 | 
			
		||||
vendor:
 | 
			
		||||
	@ go get -v github.com/mjibson/party
 | 
			
		||||
	party -d external -c -u
 | 
			
		||||
 | 
			
		||||
lint:
 | 
			
		||||
	@ go get -v github.com/golang/lint/golint
 | 
			
		||||
	$(foreach file,$(SRCS),golint $(file) || exit;)
 | 
			
		||||
 | 
			
		||||
vet:
 | 
			
		||||
	@-go get -v golang.org/x/tools/cmd/vet
 | 
			
		||||
	#  @-go get -v golang.org/x/tools/cmd/vet
 | 
			
		||||
	$(foreach pkg,$(PKGS),go vet $(pkg);)
 | 
			
		||||
 | 
			
		||||
fmt:
 | 
			
		||||
@@ -37,7 +56,7 @@ fmtcheck:
 | 
			
		||||
pretest: lint vet fmtcheck
 | 
			
		||||
 | 
			
		||||
test: pretest
 | 
			
		||||
	$(foreach pkg,$(PKGS),go test -v $(pkg) || exit;)
 | 
			
		||||
	$(foreach pkg,$(PKGS),go test -cover -v $(pkg) || exit;)
 | 
			
		||||
 | 
			
		||||
unused :
 | 
			
		||||
	$(foreach pkg,$(PKGS),unused $(pkg);)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										224
									
								
								README.fr.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								README.fr.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,224 @@
 | 
			
		||||
 | 
			
		||||
# Vuls: VULnerability Scanner
 | 
			
		||||
 | 
			
		||||
[](http://goo.gl/forms/xm5KFo35tu)
 | 
			
		||||
 | 
			
		||||
Scanneur de vulnérabilité Linux, sans agent, écrit en golang
 | 
			
		||||
 | 
			
		||||
Nous avons une équipe Slack. [Rejoignez notre Slack Team](http://goo.gl/forms/xm5KFo35tu)  
 | 
			
		||||
 | 
			
		||||
[README en English](https://github.com/future-architect/vuls/blob/master/README.md)  
 | 
			
		||||
[README en Japonais](https://github.com/future-architect/vuls/blob/master/README.ja.md)  
 | 
			
		||||
 | 
			
		||||
[](https://asciinema.org/a/3y9zrf950agiko7klg8abvyck)
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Résumé
 | 
			
		||||
 | 
			
		||||
Effectuer des recherches de vulnérabilités et des mises à jour quotidiennes peut etre un fardeau pour un administrateur système.
 | 
			
		||||
Afin d'éviter des interruptions systèmes dans un environnement de production, il est fréquent pour un administrateur système de choisir de ne pas utiliser la fonction de mise à jour automatique proposée par le gestionnaire de paquets et d'effecter ces mises à jour manuellement.
 | 
			
		||||
Ce qui implique les problèmes suivants :
 | 
			
		||||
- L'administrateur système devra surveiller constamment toutes les nouvelles vulnérabilités dans NVD (National Vulnerability Database) etc.
 | 
			
		||||
- Il pourrait être impossible pour un administrateur système de surveiller tous les logiciels installés sur un serveur.
 | 
			
		||||
- Il est coûteux d'effectuer une analyse pour déterminer quels sont les serveurs affectés par de nouvelles vulnérabilités. La possibilité de négliger un serveur ou deux est bien présente.
 | 
			
		||||
 | 
			
		||||
Vuls est un outil crée pour palier aux problèmes listés ci-dessus. Voici ses caractéristiques.
 | 
			
		||||
- Informer les utilisateurs des vulnérabilités système.
 | 
			
		||||
- Informer les utilisateurs des systèmes concernés. 
 | 
			
		||||
- La détection de vulnérabilités est effectuée automatiquement pour éviter toute négligence.
 | 
			
		||||
- Les rapports sont générés régulièrement via CRON pour mieux gérer ces vulnérabilités.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Caractéristiques principales
 | 
			
		||||
 | 
			
		||||
- Recherche de vulnérabilités sur des serveurs Linux
 | 
			
		||||
    - Supporte Ubuntu, Debian, CentOS, Amazon Linux, RHEL
 | 
			
		||||
    - Cloud, auto-hébergement, Docker
 | 
			
		||||
- Scan d'intergiciels non inclus dans le gestionnaire de paquets de l'OS
 | 
			
		||||
    - Scan d'intergiciels, de libraries de language de programmation et framework pour des vulnérabilités
 | 
			
		||||
    - Supporte les logiciels inscrits au CPE
 | 
			
		||||
- Architecture sans agent
 | 
			
		||||
    - L'utilisateur doit seulement mettre en place VULS sur une seule machine qui se connectera aux autres via SSH
 | 
			
		||||
- Génération automatique des fichiers de configuration
 | 
			
		||||
    - Auto detection de serveurs via CIDR et génération de configuration
 | 
			
		||||
- Email et notification Slack possibles (supporte le Japonais) 
 | 
			
		||||
- Les résultats d'un scan sont accessibles dans un shell via TUI Viewer terminal.
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Ce que Vuls ne fait pas
 | 
			
		||||
 | 
			
		||||
- Vuls ne met pas à jour les programmes affectés par les vulnérabilités découvertes.
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Hello Vuls 
 | 
			
		||||
 | 
			
		||||
Ce tutoriel décrit la recherche de vulnérabilités sur une machine locale avec Vuls.
 | 
			
		||||
Voici les étapes à suivre. 
 | 
			
		||||
 | 
			
		||||
1. Démrarrage d'Amazon Linux
 | 
			
		||||
1. Autoriser les connexions SSH depuis localhost
 | 
			
		||||
1. Installation des prérequis
 | 
			
		||||
1. Déploiement de go-cve-dictionary
 | 
			
		||||
1. Deploiement de Vuls
 | 
			
		||||
1. Configuration
 | 
			
		||||
1. Préparation
 | 
			
		||||
1. Scan
 | 
			
		||||
1. TUI(Terminal-Based User Interface)
 | 
			
		||||
 | 
			
		||||
## Step1. Démrarrage d'Amazon Linux
 | 
			
		||||
 | 
			
		||||
- Nous utilisons dans cette exemple une vieille AMI (amzn-ami-hvm-2015.09.1.x86_64-gp2 - ami-383c1956)
 | 
			
		||||
- Taille de l'instance : t2.medium
 | 
			
		||||
    - La première fois, t2.medium et plus sont requis pour la récupération des CVE depuis NVD (2.3GB de mémoire utilisé)
 | 
			
		||||
    - Une fois la récupération initiale des données NVD terminée vous pouvez passer sur une instance t2.nano.
 | 
			
		||||
- Ajoutez la configuration suivante au cloud-init, afin d'éviter une mise à jour automatique lors du premier démarrage.
 | 
			
		||||
 | 
			
		||||
    - [Q: How do I disable the automatic installation of critical and important security updates on initial launch?](https://aws.amazon.com/amazon-linux-ami/faqs/?nc1=h_ls)
 | 
			
		||||
    ```
 | 
			
		||||
    #cloud-config
 | 
			
		||||
    repo_upgrade: none
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
## Step2. Paramètres SSH
 | 
			
		||||
 | 
			
		||||
Il est obligatoire que le serveur puisse se connecter à son propre serveur SSH
 | 
			
		||||
 | 
			
		||||
Générez une paire de clés SSH et ajoutez la clé publique dans le fichier authorized_keys
 | 
			
		||||
```bash
 | 
			
		||||
$ ssh-keygen -t rsa
 | 
			
		||||
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
 | 
			
		||||
$ chmod 600 ~/.ssh/authorized_keys
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step3. Installation des prérequis
 | 
			
		||||
 | 
			
		||||
Vuls requiert l'installation des paquets suivants : 
 | 
			
		||||
 | 
			
		||||
- sqlite
 | 
			
		||||
- git
 | 
			
		||||
- gcc
 | 
			
		||||
- go v1.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.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
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
export GOROOT=/usr/local/go
 | 
			
		||||
export GOPATH=$HOME/go
 | 
			
		||||
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Ajoutons ces nouvelles variables d’environnement au shell
 | 
			
		||||
```bash
 | 
			
		||||
$ source /etc/profile.d/goenv.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step4. Déploiement de [go-cve-dictionary](https://github.com/kotakanbe/go-cve-dictionary)
 | 
			
		||||
 | 
			
		||||
go get
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
$ sudo mkdir /var/log/vuls
 | 
			
		||||
$ sudo chown ec2-user /var/log/vuls
 | 
			
		||||
$ sudo chmod 700 /var/log/vuls
 | 
			
		||||
$ go get github.com/kotakanbe/go-cve-dictionary
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Démarrez go-cve-dictionary en mode serveur.
 | 
			
		||||
Lors de son premier démarrage go-cve-dictionary récupère la liste des vulnérabilités depuis NVD
 | 
			
		||||
Cette opération prend environ 10 minutes (sur AWS).  
 | 
			
		||||
 | 
			
		||||
## Step5. Déploiement de Vuls
 | 
			
		||||
 | 
			
		||||
Ouvrez un second terminal, connectez vous à l'instance ec2 via SSH
 | 
			
		||||
 | 
			
		||||
go get
 | 
			
		||||
```
 | 
			
		||||
$ go get github.com/future-architect/vuls
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step6. Configuration
 | 
			
		||||
 | 
			
		||||
Créez un fichier de configuration (TOML format).
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ cat config.toml
 | 
			
		||||
[servers]
 | 
			
		||||
 | 
			
		||||
[servers.172-31-4-82]
 | 
			
		||||
host         = "172.31.4.82"
 | 
			
		||||
port        = "22"
 | 
			
		||||
user        = "ec2-user"
 | 
			
		||||
keyPath     = "/home/ec2-user/.ssh/id_rsa"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step7. Configuration des serveurs cibles vuls  
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls prepare
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step8. Scan
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls scan -cve-dictionary-dbpath=$PWD/cve.sqlite3
 | 
			
		||||
INFO[0000] Begin scanning (config: /home/ec2-user/config.toml)
 | 
			
		||||
 | 
			
		||||
... snip ...
 | 
			
		||||
 | 
			
		||||
172-31-4-82 (amazon 2015.09)
 | 
			
		||||
============================
 | 
			
		||||
CVE-2016-0494   10.0    Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle
 | 
			
		||||
                        Java SE 6u105, 7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to
 | 
			
		||||
                        affect confidentiality, integrity, and availability via unknown vectors related to
 | 
			
		||||
                        2D.
 | 
			
		||||
... snip ...
 | 
			
		||||
 | 
			
		||||
CVE-2016-0494
 | 
			
		||||
-------------
 | 
			
		||||
Score           10.0 (High)
 | 
			
		||||
Vector          (AV:N/AC:L/Au:N/C:C/I:C/A:C)
 | 
			
		||||
Summary         Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle Java SE 6u105,
 | 
			
		||||
                7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to affect confidentiality,
 | 
			
		||||
                integrity, and availability via unknown vectors related to 2D.
 | 
			
		||||
NVD             https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0494
 | 
			
		||||
MITRE           https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0494
 | 
			
		||||
CVE Details     http://www.cvedetails.com/cve/CVE-2016-0494
 | 
			
		||||
CVSS 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
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step9. TUI
 | 
			
		||||
 | 
			
		||||
Les résultats de Vuls peuvent etre affichés dans un Shell via TUI (Terminal-Based User Interface).
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls tui
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
For more information see [README in English](https://github.com/future-architect/vuls/blob/master/README.md)  
 | 
			
		||||
							
								
								
									
										1254
									
								
								README.ja.md
									
									
									
									
									
								
							
							
						
						
									
										1254
									
								
								README.ja.md
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										173
									
								
								cache/bolt.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								cache/bolt.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,173 @@
 | 
			
		||||
/* 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"
 | 
			
		||||
 | 
			
		||||
	"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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 debuging
 | 
			
		||||
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("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
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										134
									
								
								cache/bolt_test.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								cache/bolt_test.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,134 @@
 | 
			
		||||
/* 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 !reflect.DeepEqual(meta, m) {
 | 
			
		||||
		t.Errorf("expected %v, actual %v", meta, m)
 | 
			
		||||
	}
 | 
			
		||||
	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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								cache/db.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								cache/db.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
/* 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 (
 | 
			
		||||
	"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)
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								commands/cmdutil.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								commands/cmdutil.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/howeyc/gopass"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func getPasswd(prompt string) (string, error) {
 | 
			
		||||
	for {
 | 
			
		||||
		fmt.Print(prompt)
 | 
			
		||||
		pass, err := gopass.GetPasswdMasked()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", fmt.Errorf("Failed to read password")
 | 
			
		||||
		}
 | 
			
		||||
		if 0 < len(pass) {
 | 
			
		||||
			return string(pass[:]), nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										166
									
								
								commands/configtest.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								commands/configtest.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,166 @@
 | 
			
		||||
/* 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"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/scan"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ConfigtestCmd is Subcommand
 | 
			
		||||
type ConfigtestCmd struct {
 | 
			
		||||
	configPath     string
 | 
			
		||||
	askKeyPassword bool
 | 
			
		||||
	sshExternal    bool
 | 
			
		||||
 | 
			
		||||
	debug bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*ConfigtestCmd) Name() string { return "configtest" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*ConfigtestCmd) Synopsis() string { return "Test configuration" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*ConfigtestCmd) Usage() string {
 | 
			
		||||
	return `configtest:
 | 
			
		||||
	configtest
 | 
			
		||||
		        [-config=/path/to/config.toml]
 | 
			
		||||
	        	[-ask-key-password]
 | 
			
		||||
	        	[-ssh-external]
 | 
			
		||||
		        [-debug]
 | 
			
		||||
 | 
			
		||||
		        [SERVER]...
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.sshExternal,
 | 
			
		||||
		"ssh-external",
 | 
			
		||||
		false,
 | 
			
		||||
		"Use external ssh command. Default: Use the Go native implementation")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.SSHExternal = p.sshExternal
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var servernames []string
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		servernames = f.Args()
 | 
			
		||||
	} else {
 | 
			
		||||
		stat, _ := os.Stdin.Stat()
 | 
			
		||||
		if (stat.Mode() & os.ModeCharDevice) == 0 {
 | 
			
		||||
			bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			fields := strings.Fields(string(bytes))
 | 
			
		||||
			if 0 < len(fields) {
 | 
			
		||||
				servernames = fields
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
	for _, arg := range servernames {
 | 
			
		||||
		found := false
 | 
			
		||||
		for servername, info := range c.Conf.Servers {
 | 
			
		||||
			if servername == arg {
 | 
			
		||||
				target[servername] = info
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			logrus.Errorf("%s is not in config", arg)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(servernames) {
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// logger
 | 
			
		||||
	Log := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	Log.Info("Validating Config...")
 | 
			
		||||
	if !c.Conf.ValidateOnConfigtest() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting Server/Contianer OS... ")
 | 
			
		||||
	if err := scan.InitServers(Log); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to init servers: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Checking sudo configuration... ")
 | 
			
		||||
	if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	scan.PrintSSHableServerNames()
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -39,7 +39,7 @@ type DiscoverCmd struct {
 | 
			
		||||
func (*DiscoverCmd) Name() string { return "discover" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*DiscoverCmd) Synopsis() string { return "Host discovery in the CIDR." }
 | 
			
		||||
func (*DiscoverCmd) Synopsis() string { return "Host discovery in the CIDR" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*DiscoverCmd) Usage() string {
 | 
			
		||||
@@ -77,7 +77,7 @@ func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(hosts) < 1 {
 | 
			
		||||
			logrus.Errorf("Active hosts not found in %s.", cidr)
 | 
			
		||||
			logrus.Errorf("Active hosts not found in %s", cidr)
 | 
			
		||||
			return subcommands.ExitSuccess
 | 
			
		||||
		} else if err := printConfigToml(hosts); err != nil {
 | 
			
		||||
			logrus.Errorf("Failed to parse template. err: %s", err)
 | 
			
		||||
@@ -93,14 +93,14 @@ func printConfigToml(ips []string) (err error) {
 | 
			
		||||
[slack]
 | 
			
		||||
hookURL      = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
 | 
			
		||||
channel      = "#channel-name"
 | 
			
		||||
#channel      = "#{servername}"
 | 
			
		||||
#channel      = "${servername}"
 | 
			
		||||
iconEmoji    = ":ghost:"
 | 
			
		||||
authUser     = "username"
 | 
			
		||||
notifyUsers  = ["@username"]
 | 
			
		||||
 | 
			
		||||
[mail]
 | 
			
		||||
[email]
 | 
			
		||||
smtpAddr      = "smtp.gmail.com"
 | 
			
		||||
smtpPort      = 465
 | 
			
		||||
smtpPort      = "587"
 | 
			
		||||
user          = "username"
 | 
			
		||||
password      = "password"
 | 
			
		||||
from          = "from@address.com"
 | 
			
		||||
@@ -111,9 +111,16 @@ subjectPrefix = "[vuls]"
 | 
			
		||||
[default]
 | 
			
		||||
#port        = "22"
 | 
			
		||||
#user        = "username"
 | 
			
		||||
#password    = "password"
 | 
			
		||||
#keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
#keyPassword = "password"
 | 
			
		||||
#cpeNames = [
 | 
			
		||||
#  "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
#]
 | 
			
		||||
#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml"
 | 
			
		||||
#containers = ["${running}"]
 | 
			
		||||
#ignoreCves = ["CVE-2014-6271"]
 | 
			
		||||
#optional = [
 | 
			
		||||
#    ["key", "value"],
 | 
			
		||||
#]
 | 
			
		||||
 | 
			
		||||
[servers]
 | 
			
		||||
{{- $names:=  .Names}}
 | 
			
		||||
@@ -122,12 +129,16 @@ subjectPrefix = "[vuls]"
 | 
			
		||||
host         = "{{$ip}}"
 | 
			
		||||
#port        = "22"
 | 
			
		||||
#user        = "root"
 | 
			
		||||
#password    = "password"
 | 
			
		||||
#keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
#keyPassword = "password"
 | 
			
		||||
#cpeNames = [
 | 
			
		||||
#  "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
#]
 | 
			
		||||
#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml"
 | 
			
		||||
#containers = ["${running}"]
 | 
			
		||||
#ignoreCves = ["CVE-2014-0160"]
 | 
			
		||||
#optional = [
 | 
			
		||||
#    ["key", "value"],
 | 
			
		||||
#]
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										98
									
								
								commands/history.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								commands/history.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
/* 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"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HistoryCmd is Subcommand of list scanned results
 | 
			
		||||
type HistoryCmd struct {
 | 
			
		||||
	debug      bool
 | 
			
		||||
	debugSQL   bool
 | 
			
		||||
	resultsDir string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*HistoryCmd) Name() string { return "history" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*HistoryCmd) Synopsis() string {
 | 
			
		||||
	return `List history of scanning.`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*HistoryCmd) Usage() string {
 | 
			
		||||
	return `history:
 | 
			
		||||
	history
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
	`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *HistoryCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	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.ResultsDir = p.resultsDir
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	var dirs jsonDirs
 | 
			
		||||
	if dirs, err = lsValidJSONDirs(); err != nil {
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	for _, d := range dirs {
 | 
			
		||||
		var files []os.FileInfo
 | 
			
		||||
		if files, err = ioutil.ReadDir(d); err != nil {
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		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
 | 
			
		||||
}
 | 
			
		||||
@@ -18,15 +18,16 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"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
 | 
			
		||||
@@ -34,7 +35,11 @@ type PrepareCmd struct {
 | 
			
		||||
	debug      bool
 | 
			
		||||
	configPath string
 | 
			
		||||
 | 
			
		||||
	useUnattendedUpgrades bool
 | 
			
		||||
	askSudoPassword bool
 | 
			
		||||
	askKeyPassword  bool
 | 
			
		||||
 | 
			
		||||
	sshExternal bool
 | 
			
		||||
	assumeYes   bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
@@ -42,12 +47,12 @@ 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
 | 
			
		||||
				RHEL:   None
 | 
			
		||||
				Ubuntu: None
 | 
			
		||||
				Debian: aptitude
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
}
 | 
			
		||||
@@ -55,8 +60,14 @@ func (*PrepareCmd) Synopsis() string {
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*PrepareCmd) Usage() string {
 | 
			
		||||
	return `prepare:
 | 
			
		||||
	prepare [-config=/path/to/config.toml] [-debug]
 | 
			
		||||
	prepare
 | 
			
		||||
			[-config=/path/to/config.toml]
 | 
			
		||||
			[-ask-key-password]
 | 
			
		||||
			[-assume-yes]
 | 
			
		||||
			[-debug]
 | 
			
		||||
			[-ssh-external]
 | 
			
		||||
 | 
			
		||||
			[SERVER]...
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -65,27 +76,62 @@ func (p *PrepareCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := os.Getenv("PWD") + "/config.toml"
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useUnattendedUpgrades,
 | 
			
		||||
		"use-unattended-upgrades",
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Depricated] For Ubuntu, install unattended-upgrades",
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askSudoPassword,
 | 
			
		||||
		"ask-sudo-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on target servers and use SSH key-based authentication",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.sshExternal,
 | 
			
		||||
		"ssh-external",
 | 
			
		||||
		false,
 | 
			
		||||
		"Use external ssh command. Default: Use the Go native implementation")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.assumeYes,
 | 
			
		||||
		"assume-yes",
 | 
			
		||||
		false,
 | 
			
		||||
		"Assume any dependencies should be installed")
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	logrus.Infof("Begin Preparing (config: %s)", p.configPath)
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if p.askSudoPassword {
 | 
			
		||||
		logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on target servers and use SSH key-based authentication")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := c.Load(p.configPath)
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Infof("Start Preparing (config: %s)", p.configPath)
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
	for _, arg := range f.Args() {
 | 
			
		||||
		found := false
 | 
			
		||||
@@ -106,26 +152,34 @@ func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
 | 
			
		||||
	c.Conf.SSHExternal = p.sshExternal
 | 
			
		||||
	c.Conf.AssumeYes = p.assumeYes
 | 
			
		||||
 | 
			
		||||
	logrus.Info("Validating Config...")
 | 
			
		||||
	if !c.Conf.ValidateOnPrepare() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
	// Set up custom logger
 | 
			
		||||
	logger := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	logger.Info("Detecting OS... ")
 | 
			
		||||
	err = scan.InitServers(logger)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Errorf("Failed to init servers. err: %s", err)
 | 
			
		||||
	if err := scan.InitServers(logger); err != nil {
 | 
			
		||||
		logger.Errorf("Failed to init servers: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Info("Checking sudo configuration... ")
 | 
			
		||||
	if err := scan.CheckIfSudoNoPasswd(logger); err != nil {
 | 
			
		||||
		logger.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Info("Installing...")
 | 
			
		||||
	if errs := scan.Prepare(); 0 < len(errs) {
 | 
			
		||||
		for _, e := range errs {
 | 
			
		||||
			logger.Errorf("Failed: %s.", e)
 | 
			
		||||
			logger.Errorf("Failed to prepare: %s", e)
 | 
			
		||||
		}
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Info("Success")
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										386
									
								
								commands/report.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										386
									
								
								commands/report.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,386 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"github.com/kotakanbe/go-cve-dictionary/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ReportCmd is subcommand for reporting
 | 
			
		||||
type ReportCmd struct {
 | 
			
		||||
	lang       string
 | 
			
		||||
	debug      bool
 | 
			
		||||
	debugSQL   bool
 | 
			
		||||
	configPath string
 | 
			
		||||
	resultsDir string
 | 
			
		||||
	refreshCve bool
 | 
			
		||||
 | 
			
		||||
	cvssScoreOver      float64
 | 
			
		||||
	ignoreUnscoredCves bool
 | 
			
		||||
	httpProxy          string
 | 
			
		||||
 | 
			
		||||
	cvedbtype        string
 | 
			
		||||
	cvedbpath        string
 | 
			
		||||
	cveDictionaryURL string
 | 
			
		||||
 | 
			
		||||
	toSlack     bool
 | 
			
		||||
	toEMail     bool
 | 
			
		||||
	toLocalFile bool
 | 
			
		||||
	toS3        bool
 | 
			
		||||
	toAzureBlob bool
 | 
			
		||||
 | 
			
		||||
	formatJSON        bool
 | 
			
		||||
	formatXML         bool
 | 
			
		||||
	formatOneLineText bool
 | 
			
		||||
	formatShortText   bool
 | 
			
		||||
	formatFullText    bool
 | 
			
		||||
 | 
			
		||||
	gzip bool
 | 
			
		||||
 | 
			
		||||
	awsProfile  string
 | 
			
		||||
	awsS3Bucket string
 | 
			
		||||
	awsRegion   string
 | 
			
		||||
 | 
			
		||||
	azureAccount   string
 | 
			
		||||
	azureKey       string
 | 
			
		||||
	azureContainer string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*ReportCmd) Name() string { return "report" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*ReportCmd) Synopsis() string { return "Reporting" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*ReportCmd) Usage() string {
 | 
			
		||||
	return `report:
 | 
			
		||||
	report
 | 
			
		||||
		[-lang=en|ja]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-refresh-cve]
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql]
 | 
			
		||||
		[-cvedb-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or mysql connection string]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-to-email]
 | 
			
		||||
		[-to-slack]
 | 
			
		||||
		[-to-localfile]
 | 
			
		||||
		[-to-s3]
 | 
			
		||||
		[-to-azure-blob]
 | 
			
		||||
		[-format-json]
 | 
			
		||||
		[-format-xml]
 | 
			
		||||
		[-format-one-line-text]
 | 
			
		||||
		[-format-short-text]
 | 
			
		||||
		[-format-full-text]
 | 
			
		||||
		[-gzip]
 | 
			
		||||
		[-aws-profile=default]
 | 
			
		||||
		[-aws-region=us-west-2]
 | 
			
		||||
		[-aws-s3-bucket=bucket_name]
 | 
			
		||||
		[-azure-account=accout]
 | 
			
		||||
		[-azure-key=key]
 | 
			
		||||
		[-azure-container=container]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
 | 
			
		||||
		[SERVER]...
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.refreshCve,
 | 
			
		||||
		"refresh-cve",
 | 
			
		||||
		false,
 | 
			
		||||
		"Refresh CVE information in JSON file under results dir")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbtype,
 | 
			
		||||
		"cvedb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching CVE dictionary (sqlite3 or mysql)")
 | 
			
		||||
 | 
			
		||||
	defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbpath,
 | 
			
		||||
		"cvedb-path",
 | 
			
		||||
		defaultCveDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDictionaryURL,
 | 
			
		||||
		"cvedb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://cve-dictionary.com:8080 or mysql connection string")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(
 | 
			
		||||
		&p.cvssScoreOver,
 | 
			
		||||
		"cvss-over",
 | 
			
		||||
		0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.ignoreUnscoredCves,
 | 
			
		||||
		"ignore-unscored-cves",
 | 
			
		||||
		false,
 | 
			
		||||
		"Don't report the unscored CVEs")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.httpProxy,
 | 
			
		||||
		"http-proxy",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://proxy-url:port (default: empty)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatJSON,
 | 
			
		||||
		"format-json",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("JSON format"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatXML,
 | 
			
		||||
		"format-xml",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("XML format"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatOneLineText,
 | 
			
		||||
		"format-one-line-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("One line summary in plain text"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatShortText,
 | 
			
		||||
		"format-short-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Summary in plain text"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatFullText,
 | 
			
		||||
		"format-full-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Detail report in plain text"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.gzip, "gzip", false, "gzip compression")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toSlack, "to-slack", false, "Send report via Slack")
 | 
			
		||||
	f.BoolVar(&p.toEMail, "to-email", false, "Send report via Email")
 | 
			
		||||
	f.BoolVar(&p.toLocalFile,
 | 
			
		||||
		"to-localfile",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Write report to localfile"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toS3,
 | 
			
		||||
		"to-s3",
 | 
			
		||||
		false,
 | 
			
		||||
		"Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/xml/txt)")
 | 
			
		||||
	f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
 | 
			
		||||
	f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
 | 
			
		||||
	f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toAzureBlob,
 | 
			
		||||
		"to-azure-blob",
 | 
			
		||||
		false,
 | 
			
		||||
		"Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/xml/txt)")
 | 
			
		||||
	f.StringVar(&p.azureAccount,
 | 
			
		||||
		"azure-account",
 | 
			
		||||
		"",
 | 
			
		||||
		"Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureKey,
 | 
			
		||||
		"azure-key",
 | 
			
		||||
		"",
 | 
			
		||||
		"Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureContainer, "azure-container", "", "Azure storage container name")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	Log := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	if err := c.Load(p.configPath, ""); err != nil {
 | 
			
		||||
		Log.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Lang = p.lang
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
	c.Conf.CveDBType = p.cvedbtype
 | 
			
		||||
	c.Conf.CveDBPath = p.cvedbpath
 | 
			
		||||
	c.Conf.CveDictionaryURL = p.cveDictionaryURL
 | 
			
		||||
	c.Conf.CvssScoreOver = p.cvssScoreOver
 | 
			
		||||
	c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
 | 
			
		||||
	jsonDir, err := jsonDir(f.Args())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Errorf("Failed to read from JSON: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.FormatXML = p.formatXML
 | 
			
		||||
	c.Conf.FormatJSON = p.formatJSON
 | 
			
		||||
	c.Conf.FormatOneLineText = p.formatOneLineText
 | 
			
		||||
	c.Conf.FormatShortText = p.formatShortText
 | 
			
		||||
	c.Conf.FormatFullText = p.formatFullText
 | 
			
		||||
 | 
			
		||||
	c.Conf.GZIP = p.gzip
 | 
			
		||||
 | 
			
		||||
	// report
 | 
			
		||||
	reports := []report.ResultWriter{
 | 
			
		||||
		report.StdoutWriter{},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toSlack {
 | 
			
		||||
		reports = append(reports, report.SlackWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toEMail {
 | 
			
		||||
		reports = append(reports, report.EMailWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toLocalFile {
 | 
			
		||||
		reports = append(reports, report.LocalFileWriter{
 | 
			
		||||
			CurrentDir: jsonDir,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toS3 {
 | 
			
		||||
		c.Conf.AwsRegion = p.awsRegion
 | 
			
		||||
		c.Conf.AwsProfile = p.awsProfile
 | 
			
		||||
		c.Conf.S3Bucket = p.awsS3Bucket
 | 
			
		||||
		if err := report.CheckIfBucketExists(); err != nil {
 | 
			
		||||
			Log.Errorf("Check if there is a bucket beforehand: %s, err: %s", c.Conf.S3Bucket, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.S3Writer{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toAzureBlob {
 | 
			
		||||
		c.Conf.AzureAccount = p.azureAccount
 | 
			
		||||
		if len(c.Conf.AzureAccount) == 0 {
 | 
			
		||||
			c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureKey = p.azureKey
 | 
			
		||||
		if len(c.Conf.AzureKey) == 0 {
 | 
			
		||||
			c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureContainer = p.azureContainer
 | 
			
		||||
		if len(c.Conf.AzureContainer) == 0 {
 | 
			
		||||
			Log.Error("Azure storage container name is requied with --azure-container option")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		if err := report.CheckIfAzureContainerExists(); err != nil {
 | 
			
		||||
			Log.Errorf("Check if there is a container beforehand: %s, err: %s", c.Conf.AzureContainer, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.AzureBlobWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !(p.formatJSON || p.formatOneLineText ||
 | 
			
		||||
		p.formatShortText || p.formatFullText || p.formatXML) {
 | 
			
		||||
		c.Conf.FormatShortText = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Validating Config...")
 | 
			
		||||
	if !c.Conf.ValidateOnReport() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
	if ok, err := cveapi.CveClient.CheckHealth(); !ok {
 | 
			
		||||
		Log.Errorf("CVE HTTP server is not running. err: %s", err)
 | 
			
		||||
		Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with --cvedb-path option")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	if c.Conf.CveDictionaryURL != "" {
 | 
			
		||||
		Log.Infof("cve-dictionary: %s", c.Conf.CveDictionaryURL)
 | 
			
		||||
	} else {
 | 
			
		||||
		if c.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
			Log.Infof("cve-dictionary: %s", c.Conf.CveDBPath)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	history, err := loadOneScanHistory(jsonDir)
 | 
			
		||||
 | 
			
		||||
	var results []models.ScanResult
 | 
			
		||||
	for _, r := range history.ScanResults {
 | 
			
		||||
		if p.refreshCve || needToRefreshCve(r) {
 | 
			
		||||
			Log.Debugf("need to refresh")
 | 
			
		||||
			if c.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
				if _, err := os.Stat(c.Conf.CveDBPath); os.IsNotExist(err) {
 | 
			
		||||
					log.Errorf("SQLite3 DB(CVE-Dictionary) is not exist: %s",
 | 
			
		||||
						c.Conf.CveDBPath)
 | 
			
		||||
					return subcommands.ExitFailure
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			filled, err := fillCveInfoFromCveDB(r)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				Log.Errorf("Failed to fill CVE information: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			filled.Lang = c.Conf.Lang
 | 
			
		||||
 | 
			
		||||
			if err := overwriteJSONFile(jsonDir, filled); err != nil {
 | 
			
		||||
				Log.Errorf("Failed to write JSON: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			results = append(results, filled)
 | 
			
		||||
		} else {
 | 
			
		||||
			Log.Debugf("no need to refresh")
 | 
			
		||||
			results = append(results, r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res models.ScanResults
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		res = append(res, r.FilterByCvssOver())
 | 
			
		||||
	}
 | 
			
		||||
	for _, w := range reports {
 | 
			
		||||
		if err := w.Write(res...); err != nil {
 | 
			
		||||
			Log.Errorf("Failed to report: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										229
									
								
								commands/scan.go
									
									
									
									
									
								
							
							
						
						
									
										229
									
								
								commands/scan.go
									
									
									
									
									
								
							@@ -18,88 +18,95 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"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
 | 
			
		||||
	cveDictionaryURL string
 | 
			
		||||
	cvssScoreOver    float64
 | 
			
		||||
	httpProxy        string
 | 
			
		||||
 | 
			
		||||
	useYumPluginSecurity  bool
 | 
			
		||||
	useUnattendedUpgrades bool
 | 
			
		||||
 | 
			
		||||
	// reporting
 | 
			
		||||
	reportSlack bool
 | 
			
		||||
	reportMail  bool
 | 
			
		||||
	debug          bool
 | 
			
		||||
	configPath     string
 | 
			
		||||
	resultsDir     string
 | 
			
		||||
	cacheDBPath    string
 | 
			
		||||
	httpProxy      string
 | 
			
		||||
	askKeyPassword bool
 | 
			
		||||
	containersOnly bool
 | 
			
		||||
	skipBroken     bool
 | 
			
		||||
	sshExternal    bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*ScanCmd) Name() string { return "scan" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities." }
 | 
			
		||||
func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*ScanCmd) Usage() string {
 | 
			
		||||
	return `scan:
 | 
			
		||||
	scan
 | 
			
		||||
		[-lang=en|ja]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-dbpath=/path/to/vuls.sqlite3]
 | 
			
		||||
		[-cve-dictionary-url=http://127.0.0.1:1323]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-report-slack]
 | 
			
		||||
		[-report-mail]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-cachedb-path=/path/to/cache.db]
 | 
			
		||||
		[-ssh-external]
 | 
			
		||||
		[-containers-only]
 | 
			
		||||
		[-skip-broken]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-ask-key-password]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
 | 
			
		||||
		[SERVER]...
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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")
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := os.Getenv("PWD") + "/config.toml"
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultDBPath := os.Getenv("PWD") + "/vuls.sqlite3"
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3")
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultURL := "http://127.0.0.1:1323"
 | 
			
		||||
	defaultCacheDBPath := filepath.Join(wd, "cache.db")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDictionaryURL,
 | 
			
		||||
		"cve-dictionary-url",
 | 
			
		||||
		defaultURL,
 | 
			
		||||
		"http://CVE.Dictionary")
 | 
			
		||||
		&p.cacheDBPath,
 | 
			
		||||
		"cachedb-path",
 | 
			
		||||
		defaultCacheDBPath,
 | 
			
		||||
		"/path/to/cache.db (local cache of changelog for Ubuntu/Debian)")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(
 | 
			
		||||
		&p.cvssScoreOver,
 | 
			
		||||
		"cvss-over",
 | 
			
		||||
		0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.sshExternal,
 | 
			
		||||
		"ssh-external",
 | 
			
		||||
		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,
 | 
			
		||||
@@ -108,37 +115,56 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		"http://proxy-url:port (default: empty)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.reportSlack, "report-slack", false, "Slack report")
 | 
			
		||||
	f.BoolVar(&p.reportMail, "report-mail", false, "Email report")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useYumPluginSecurity,
 | 
			
		||||
		"use-yum-plugin-security",
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Depricated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)",
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useUnattendedUpgrades,
 | 
			
		||||
		"use-unattended-upgrades",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Depricated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Infof("Begin scannig (config: %s)", p.configPath)
 | 
			
		||||
	err := c.Load(p.configPath)
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Info("Start scanning")
 | 
			
		||||
	logrus.Infof("config: %s", p.configPath)
 | 
			
		||||
 | 
			
		||||
	var servernames []string
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		servernames = f.Args()
 | 
			
		||||
	} else {
 | 
			
		||||
		stat, _ := os.Stdin.Stat()
 | 
			
		||||
		if (stat.Mode() & os.ModeCharDevice) == 0 {
 | 
			
		||||
			bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			fields := strings.Fields(string(bytes))
 | 
			
		||||
			if 0 < len(fields) {
 | 
			
		||||
				servernames = fields
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
	for _, arg := range f.Args() {
 | 
			
		||||
	for _, arg := range servernames {
 | 
			
		||||
		found := false
 | 
			
		||||
		for servername, info := range c.Conf.Servers {
 | 
			
		||||
			if servername == arg {
 | 
			
		||||
@@ -152,90 +178,51 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
	if 0 < len(servernames) {
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Lang = p.lang
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	logrus.Debugf("%s", pp.Sprintf("%v", target))
 | 
			
		||||
 | 
			
		||||
	// logger
 | 
			
		||||
	Log := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	// report
 | 
			
		||||
	reports := []report.ResultWriter{
 | 
			
		||||
		report.TextWriter{},
 | 
			
		||||
		report.LogrusWriter{},
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportSlack {
 | 
			
		||||
		reports = append(reports, report.SlackWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportMail {
 | 
			
		||||
		reports = append(reports, report.MailWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.DBPath = p.dbpath
 | 
			
		||||
	c.Conf.CveDictionaryURL = p.cveDictionaryURL
 | 
			
		||||
	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() {
 | 
			
		||||
	if !c.Conf.ValidateOnScan() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ok, err := cveapi.CveClient.CheckHealth(); !ok {
 | 
			
		||||
		Log.Errorf("CVE HTTP server is not running. %#v", cveapi.CveClient)
 | 
			
		||||
		Log.Fatal(err)
 | 
			
		||||
	Log.Info("Detecting Server/Contianer OS... ")
 | 
			
		||||
	if err := scan.InitServers(Log); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to init servers: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting OS... ")
 | 
			
		||||
	err = scan.InitServers(Log)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		Log.Errorf("Failed to init servers. err: %s", err)
 | 
			
		||||
	Log.Info("Checking sudo configuration... ")
 | 
			
		||||
	if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting Platforms... ")
 | 
			
		||||
	scan.DetectPlatforms(Log)
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerabilities... ")
 | 
			
		||||
	if errs := scan.Scan(); 0 < len(errs) {
 | 
			
		||||
		for _, e := range errs {
 | 
			
		||||
			Log.Errorf("Failed to scan. err: %s.", e)
 | 
			
		||||
			Log.Errorf("Failed to scan. err: %s", e)
 | 
			
		||||
		}
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scanResults, err := scan.GetScanResults()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		Log.Fatal(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Reporting...")
 | 
			
		||||
	filtered := scanResults.FilterByCvssOver()
 | 
			
		||||
	for _, w := range reports {
 | 
			
		||||
		if err := w.Write(filtered); err != nil {
 | 
			
		||||
			Log.Fatalf("Failed to output report, err: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Insert to DB...")
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
		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
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										110
									
								
								commands/tui.go
									
									
									
									
									
								
							
							
						
						
									
										110
									
								
								commands/tui.go
									
									
									
									
									
								
							@@ -18,33 +18,46 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TuiCmd is Subcommand of host discovery mode
 | 
			
		||||
type TuiCmd struct {
 | 
			
		||||
	lang     string
 | 
			
		||||
	debugSQL bool
 | 
			
		||||
	dbpath   string
 | 
			
		||||
	lang       string
 | 
			
		||||
	debugSQL   bool
 | 
			
		||||
	resultsDir string
 | 
			
		||||
 | 
			
		||||
	refreshCve       bool
 | 
			
		||||
	cvedbtype        string
 | 
			
		||||
	cvedbpath        string
 | 
			
		||||
	cveDictionaryURL string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*TuiCmd) Name() string { return "tui" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*TuiCmd) Synopsis() string { return "Run Tui view to anayze vulnerabilites." }
 | 
			
		||||
func (*TuiCmd) Synopsis() string { return "Run Tui view to anayze vulnerabilites" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*TuiCmd) Usage() string {
 | 
			
		||||
	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]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-refresh-cve]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
@@ -54,15 +67,88 @@ func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	//  f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "debug SQL")
 | 
			
		||||
 | 
			
		||||
	defaultDBPath := os.Getenv("PWD") + "/vuls.sqlite3"
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath,
 | 
			
		||||
		fmt.Sprintf("/path/to/sqlite3 (default: %s)", defaultDBPath))
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.refreshCve,
 | 
			
		||||
		"refresh-cve",
 | 
			
		||||
		false,
 | 
			
		||||
		"Refresh CVE information in JSON file under results dir")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbtype,
 | 
			
		||||
		"cvedb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching CVE dictionary (sqlite3 or mysql)")
 | 
			
		||||
 | 
			
		||||
	defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbpath,
 | 
			
		||||
		"cvedb-path",
 | 
			
		||||
		defaultCveDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDictionaryURL,
 | 
			
		||||
		"cvedb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://cve-dictionary.com:8080 or mysql connection string")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
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
 | 
			
		||||
	return report.RunTui()
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
	c.Conf.CveDBType = p.cvedbtype
 | 
			
		||||
	c.Conf.CveDBPath = p.cvedbpath
 | 
			
		||||
	c.Conf.CveDictionaryURL = p.cveDictionaryURL
 | 
			
		||||
 | 
			
		||||
	log.Info("Validating Config...")
 | 
			
		||||
	if !c.Conf.ValidateOnTui() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	jsonDir, err := jsonDir(f.Args())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Errorf("Failed to read json dir under results: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	history, err := loadOneScanHistory(jsonDir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Errorf("Failed to read from JSON: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var results []models.ScanResult
 | 
			
		||||
	for _, r := range history.ScanResults {
 | 
			
		||||
		if p.refreshCve || needToRefreshCve(r) {
 | 
			
		||||
			if c.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
				if _, err := os.Stat(c.Conf.CveDBPath); os.IsNotExist(err) {
 | 
			
		||||
					log.Errorf("SQLite3 DB(CVE-Dictionary) is not exist: %s",
 | 
			
		||||
						c.Conf.CveDBPath)
 | 
			
		||||
					return subcommands.ExitFailure
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			filled, err := fillCveInfoFromCveDB(r)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Errorf("Failed to fill CVE information: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	history.ScanResults = results
 | 
			
		||||
	return report.RunTui(history)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										225
									
								
								commands/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								commands/util.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,225 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// jsonDirPattern is file name pattern of JSON directory
 | 
			
		||||
// 2016-11-16T10:43:28+09:00
 | 
			
		||||
// 2016-11-16T10:43:28Z
 | 
			
		||||
var jsonDirPattern = regexp.MustCompile(
 | 
			
		||||
	`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$`)
 | 
			
		||||
 | 
			
		||||
// JSONDirs is array of json files path.
 | 
			
		||||
type jsonDirs []string
 | 
			
		||||
 | 
			
		||||
// sort as recent directories are at the head
 | 
			
		||||
func (d jsonDirs) Len() int {
 | 
			
		||||
	return len(d)
 | 
			
		||||
}
 | 
			
		||||
func (d jsonDirs) Swap(i, j int) {
 | 
			
		||||
	d[i], d[j] = d[j], d[i]
 | 
			
		||||
}
 | 
			
		||||
func (d jsonDirs) Less(i, j int) bool {
 | 
			
		||||
	return d[j] < d[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getValidJSONDirs return valid json directory as array
 | 
			
		||||
// Returned array is sorted so that recent directories are at the head
 | 
			
		||||
func lsValidJSONDirs() (dirs jsonDirs, err error) {
 | 
			
		||||
	var dirInfo []os.FileInfo
 | 
			
		||||
	if dirInfo, err = ioutil.ReadDir(c.Conf.ResultsDir); err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to read %s: %s", c.Conf.ResultsDir, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, d := range dirInfo {
 | 
			
		||||
		if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
 | 
			
		||||
			jsonDir := filepath.Join(c.Conf.ResultsDir, d.Name())
 | 
			
		||||
			dirs = append(dirs, jsonDir)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(dirs)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// jsonDir returns
 | 
			
		||||
// If there is an arg, check if it is a valid format and return the corresponding path under results.
 | 
			
		||||
// If passed via PIPE (such as history subcommand), return that path.
 | 
			
		||||
// Otherwise, returns the path of the latest directory
 | 
			
		||||
func jsonDir(args []string) (string, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	var dirs jsonDirs
 | 
			
		||||
 | 
			
		||||
	if 0 < len(args) {
 | 
			
		||||
		if dirs, err = lsValidJSONDirs(); err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		path := filepath.Join(c.Conf.ResultsDir, args[0])
 | 
			
		||||
		for _, d := range dirs {
 | 
			
		||||
			ss := strings.Split(d, string(os.PathSeparator))
 | 
			
		||||
			timedir := ss[len(ss)-1]
 | 
			
		||||
			if timedir == args[0] {
 | 
			
		||||
				return path, nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return "", fmt.Errorf("Invalid path: %s", path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// PIPE
 | 
			
		||||
	stat, _ := os.Stdin.Stat()
 | 
			
		||||
	if (stat.Mode() & os.ModeCharDevice) == 0 {
 | 
			
		||||
		bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", fmt.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		fields := strings.Fields(string(bytes))
 | 
			
		||||
		if 0 < len(fields) {
 | 
			
		||||
			return filepath.Join(c.Conf.ResultsDir, fields[0]), nil
 | 
			
		||||
		}
 | 
			
		||||
		return "", fmt.Errorf("Stdin is invalid: %s", string(bytes))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// returns latest dir when no args or no PIPE
 | 
			
		||||
	if dirs, err = lsValidJSONDirs(); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if len(dirs) == 0 {
 | 
			
		||||
		return "", fmt.Errorf("No results under %s",
 | 
			
		||||
			c.Conf.ResultsDir)
 | 
			
		||||
	}
 | 
			
		||||
	return dirs[0], nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// loadOneScanHistory read JSON data
 | 
			
		||||
func loadOneScanHistory(jsonDir string) (scanHistory models.ScanHistory, err error) {
 | 
			
		||||
	var results []models.ScanResult
 | 
			
		||||
	var files []os.FileInfo
 | 
			
		||||
	if files, err = ioutil.ReadDir(jsonDir); err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to read %s: %s", jsonDir, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, f := range files {
 | 
			
		||||
		if filepath.Ext(f.Name()) != ".json" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		var r models.ScanResult
 | 
			
		||||
		var data []byte
 | 
			
		||||
		path := filepath.Join(jsonDir, f.Name())
 | 
			
		||||
		if data, err = ioutil.ReadFile(path); err != nil {
 | 
			
		||||
			err = fmt.Errorf("Failed to read %s: %s", path, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if json.Unmarshal(data, &r) != nil {
 | 
			
		||||
			err = fmt.Errorf("Failed to parse %s: %s", path, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		results = append(results, r)
 | 
			
		||||
	}
 | 
			
		||||
	if len(results) == 0 {
 | 
			
		||||
		err = fmt.Errorf("There is no json file under %s", jsonDir)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scanHistory = models.ScanHistory{
 | 
			
		||||
		ScanResults: results,
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fillCveInfoFromCveDB(r models.ScanResult) (filled models.ScanResult, err error) {
 | 
			
		||||
	sInfo := c.Conf.Servers[r.ServerName]
 | 
			
		||||
	vs, err := scanVulnByCpeNames(sInfo.CpeNames, r.ScannedCves)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	r.ScannedCves = vs
 | 
			
		||||
	filled, err = r.FillCveDetail()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func overwriteJSONFile(dir string, r models.ScanResult) error {
 | 
			
		||||
	before := c.Conf.FormatJSON
 | 
			
		||||
	c.Conf.FormatJSON = true
 | 
			
		||||
	w := report.LocalFileWriter{CurrentDir: dir}
 | 
			
		||||
	if err := w.Write(r); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to write summary report: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	c.Conf.FormatJSON = before
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanVulnByCpeNames(cpeNames []string, scannedVulns []models.VulnInfo) ([]models.VulnInfo,
 | 
			
		||||
	error) {
 | 
			
		||||
	// To remove duplicate
 | 
			
		||||
	set := map[string]models.VulnInfo{}
 | 
			
		||||
	for _, v := range scannedVulns {
 | 
			
		||||
		set[v.CveID] = v
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, name := range cpeNames {
 | 
			
		||||
		details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		for _, detail := range details {
 | 
			
		||||
			if val, ok := set[detail.CveID]; ok {
 | 
			
		||||
				names := val.CpeNames
 | 
			
		||||
				names = util.AppendIfMissing(names, name)
 | 
			
		||||
				val.CpeNames = names
 | 
			
		||||
				set[detail.CveID] = val
 | 
			
		||||
			} else {
 | 
			
		||||
				set[detail.CveID] = models.VulnInfo{
 | 
			
		||||
					CveID:    detail.CveID,
 | 
			
		||||
					CpeNames: []string{name},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vinfos := []models.VulnInfo{}
 | 
			
		||||
	for key := range set {
 | 
			
		||||
		vinfos = append(vinfos, set[key])
 | 
			
		||||
	}
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func needToRefreshCve(r models.ScanResult) bool {
 | 
			
		||||
	return r.Lang != c.Conf.Lang || len(r.KnownCves) == 0 &&
 | 
			
		||||
		len(r.UnknownCves) == 0 &&
 | 
			
		||||
		len(r.IgnoredCves) == 0
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										226
									
								
								config/config.go
									
									
									
									
									
								
							
							
						
						
									
										226
									
								
								config/config.go
									
									
									
									
									
								
							@@ -19,6 +19,7 @@ package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
@@ -34,30 +35,95 @@ 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
 | 
			
		||||
	HTTPProxy     string `valid:"url"`
 | 
			
		||||
	DBPath        string
 | 
			
		||||
	//  CpeNames      []string
 | 
			
		||||
	//  SummaryMode          bool
 | 
			
		||||
	UseYumPluginSecurity  bool
 | 
			
		||||
	UseUnattendedUpgrades bool
 | 
			
		||||
	CvssScoreOver      float64
 | 
			
		||||
	IgnoreUnscoredCves bool
 | 
			
		||||
 | 
			
		||||
	AssumeYes      bool
 | 
			
		||||
	SSHExternal    bool
 | 
			
		||||
	ContainersOnly bool
 | 
			
		||||
	SkipBroken     bool
 | 
			
		||||
 | 
			
		||||
	HTTPProxy   string `valid:"url"`
 | 
			
		||||
	ResultsDir  string
 | 
			
		||||
	CveDBType   string
 | 
			
		||||
	CveDBPath   string
 | 
			
		||||
	CacheDBPath string
 | 
			
		||||
 | 
			
		||||
	FormatXML         bool
 | 
			
		||||
	FormatJSON        bool
 | 
			
		||||
	FormatOneLineText bool
 | 
			
		||||
	FormatShortText   bool
 | 
			
		||||
	FormatFullText    bool
 | 
			
		||||
 | 
			
		||||
	GZIP bool
 | 
			
		||||
 | 
			
		||||
	AwsProfile string
 | 
			
		||||
	AwsRegion  string
 | 
			
		||||
	S3Bucket   string
 | 
			
		||||
 | 
			
		||||
	AzureAccount   string
 | 
			
		||||
	AzureKey       string
 | 
			
		||||
	AzureContainer string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 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(
 | 
			
		||||
				"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))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -66,7 +132,42 @@ func (c Config) Validate() bool {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mailerrs := c.Mail.Validate(); 0 < len(mailerrs) {
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnReport validates configuration
 | 
			
		||||
func (c Config) ValidateOnReport() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.CveDBType != "sqlite3" && c.CveDBType != "mysql" {
 | 
			
		||||
		errs = append(errs, fmt.Errorf(
 | 
			
		||||
			"CVE DB type must be either 'sqlite3' or 'mysql'.  -cve-dictionary-dbtype: %s", c.CveDBType))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.CveDBType == "sqlite3" {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB(CVE-Dictionary) path must be a *Absolute* file path. -cve-dictionary-dbpath: %s", c.CveDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := valid.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mailerrs := c.EMail.Validate(); 0 < len(mailerrs) {
 | 
			
		||||
		errs = append(errs, mailerrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -81,6 +182,36 @@ func (c Config) Validate() bool {
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnTui validates configuration
 | 
			
		||||
func (c Config) ValidateOnTui() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.CveDBType != "sqlite3" && c.CveDBType != "mysql" {
 | 
			
		||||
		errs = append(errs, fmt.Errorf(
 | 
			
		||||
			"CVE DB type must be either 'sqlite3' or 'mysql'.  -cve-dictionary-dbtype: %s", c.CveDBType))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.CveDBType == "sqlite3" {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB(CVE-Dictionary) path must be a *Absolute* file path. -cve-dictionary-dbpath: %s", c.CveDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// smtpConf is smtp config
 | 
			
		||||
type smtpConf struct {
 | 
			
		||||
	SMTPAddr string
 | 
			
		||||
@@ -160,7 +291,6 @@ type SlackConf struct {
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *SlackConf) Validate() (errs []error) {
 | 
			
		||||
 | 
			
		||||
	if !c.UseThisTime {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -188,8 +318,6 @@ func (c *SlackConf) Validate() (errs []error) {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO check if slack configration is valid
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -197,25 +325,63 @@ func (c *SlackConf) Validate() (errs []error) {
 | 
			
		||||
type ServerInfo struct {
 | 
			
		||||
	ServerName  string
 | 
			
		||||
	User        string
 | 
			
		||||
	Password    string
 | 
			
		||||
	Host        string
 | 
			
		||||
	Port        string
 | 
			
		||||
	KeyPath     string
 | 
			
		||||
	KeyPassword string
 | 
			
		||||
	SudoOpt     SudoOption
 | 
			
		||||
 | 
			
		||||
	CpeNames []string
 | 
			
		||||
	CpeNames               []string
 | 
			
		||||
	DependencyCheckXMLPath string
 | 
			
		||||
 | 
			
		||||
	// DebugLog Color
 | 
			
		||||
	LogMsgAnsiColor string
 | 
			
		||||
	// Container Names or IDs
 | 
			
		||||
	Containers []string
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	Distro          Distro
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SudoOption is flag of sudo option.
 | 
			
		||||
type SudoOption struct {
 | 
			
		||||
 | 
			
		||||
	// echo pass | sudo -S ls
 | 
			
		||||
	ExecBySudo bool
 | 
			
		||||
 | 
			
		||||
	// echo pass | sudo sh -C 'ls'
 | 
			
		||||
	ExecBySudoSh bool
 | 
			
		||||
// 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsContainer returns whether this ServerInfo is about container
 | 
			
		||||
func (s ServerInfo) IsContainer() bool {
 | 
			
		||||
	return 0 < len(s.Container.ContainerID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetContainer set container
 | 
			
		||||
func (s *ServerInfo) SetContainer(d Container) {
 | 
			
		||||
	s.Container = d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Container has Container information.
 | 
			
		||||
type Container struct {
 | 
			
		||||
	ContainerID string
 | 
			
		||||
	Name        string
 | 
			
		||||
	Type        string
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,6 @@ type JSONLoader struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load load the configuraiton JSON file specified by path arg.
 | 
			
		||||
func (c JSONLoader) Load(path string) (err error) {
 | 
			
		||||
func (c JSONLoader) Load(path, sudoPass, keyPass string) (err error) {
 | 
			
		||||
	return fmt.Errorf("Not implement yet")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,16 +18,13 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
// Load loads configuration
 | 
			
		||||
func Load(path string) error {
 | 
			
		||||
 | 
			
		||||
	//TODO if path's suffix .toml
 | 
			
		||||
func Load(path, keyPass string) error {
 | 
			
		||||
	var loader Loader
 | 
			
		||||
	loader = TOMLLoader{}
 | 
			
		||||
 | 
			
		||||
	return loader.Load(path)
 | 
			
		||||
	return loader.Load(path, keyPass)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Loader is interface of concrete loader
 | 
			
		||||
type Loader interface {
 | 
			
		||||
	Load(string) error
 | 
			
		||||
	Load(string, string) error
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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,53 +32,73 @@ type TOMLLoader struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load load the configuraiton TOML file specified by path arg.
 | 
			
		||||
func (c TOMLLoader) Load(pathToToml string) (err error) {
 | 
			
		||||
func (c TOMLLoader) Load(pathToToml, keyPass string) 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)
 | 
			
		||||
		log.Error("Load config failed", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Conf.Mail = conf.Mail
 | 
			
		||||
	Conf.EMail = conf.EMail
 | 
			
		||||
	Conf.Slack = conf.Slack
 | 
			
		||||
 | 
			
		||||
	d := conf.Default
 | 
			
		||||
	Conf.Default = d
 | 
			
		||||
	servers := make(map[string]ServerInfo)
 | 
			
		||||
 | 
			
		||||
	if keyPass != "" {
 | 
			
		||||
		d.KeyPassword = keyPass
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	i := 0
 | 
			
		||||
	for name, v := range conf.Servers {
 | 
			
		||||
		s := ServerInfo{ServerName: name}
 | 
			
		||||
		s.User = v.User
 | 
			
		||||
		if s.User == "" {
 | 
			
		||||
			s.User = d.User
 | 
			
		||||
		if 0 < len(v.KeyPassword) {
 | 
			
		||||
			log.Warn("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE.")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Password = v.Password
 | 
			
		||||
		if s.Password == "" {
 | 
			
		||||
			s.Password = d.Password
 | 
			
		||||
		s := ServerInfo{ServerName: name}
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case v.User != "":
 | 
			
		||||
			s.User = v.User
 | 
			
		||||
		case d.User != "":
 | 
			
		||||
			s.User = d.User
 | 
			
		||||
		default:
 | 
			
		||||
			return fmt.Errorf("%s is invalid. User is empty", name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Host = v.Host
 | 
			
		||||
		if len(s.Host) == 0 {
 | 
			
		||||
			return fmt.Errorf("%s is invalid. host is empty", name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Port = v.Port
 | 
			
		||||
		if s.Port == "" {
 | 
			
		||||
		switch {
 | 
			
		||||
		case v.Port != "":
 | 
			
		||||
			s.Port = v.Port
 | 
			
		||||
		case d.Port != "":
 | 
			
		||||
			s.Port = d.Port
 | 
			
		||||
		default:
 | 
			
		||||
			s.Port = "22"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.KeyPath = v.KeyPath
 | 
			
		||||
		if s.KeyPath == "" {
 | 
			
		||||
		if len(s.KeyPath) == 0 {
 | 
			
		||||
			s.KeyPath = d.KeyPath
 | 
			
		||||
		}
 | 
			
		||||
		if s.KeyPath != "" {
 | 
			
		||||
			if _, err := os.Stat(s.KeyPath); err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"config.toml is invalid. keypath: %s not exists", s.KeyPath)
 | 
			
		||||
					"%s is invalid. keypath: %s not exists", name, s.KeyPath)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//  s.KeyPassword = keyPass
 | 
			
		||||
		s.KeyPassword = v.KeyPassword
 | 
			
		||||
		if s.KeyPassword == "" {
 | 
			
		||||
		if len(s.KeyPassword) == 0 {
 | 
			
		||||
			s.KeyPassword = d.KeyPassword
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -86,13 +107,78 @@ func (c TOMLLoader) Load(pathToToml string) (err error) {
 | 
			
		||||
			s.CpeNames = d.CpeNames
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.LogMsgAnsiColor = Colors[i%len(conf.Servers)]
 | 
			
		||||
		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 {
 | 
			
		||||
			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
 | 
			
		||||
			for _, kv := range s.Optional {
 | 
			
		||||
				if dkv[0] == kv[0] {
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !found {
 | 
			
		||||
				s.Optional = append(s.Optional, dkv)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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
 | 
			
		||||
}
 | 
			
		||||
@@ -30,6 +30,8 @@ import (
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	cveconfig "github.com/kotakanbe/go-cve-dictionary/config"
 | 
			
		||||
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -46,17 +48,19 @@ func (api *cvedictClient) initialize() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) CheckHealth() (ok bool, err error) {
 | 
			
		||||
	if config.Conf.CveDictionaryURL == "" {
 | 
			
		||||
		log.Debugf("get cve-dictionary from %s", config.Conf.CveDBType)
 | 
			
		||||
		return true, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.initialize()
 | 
			
		||||
	url := fmt.Sprintf("%s/health", api.baseURL)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if len(errs) > 0 || resp.StatusCode != 200 {
 | 
			
		||||
		return false, fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v",
 | 
			
		||||
			url,
 | 
			
		||||
			errs,
 | 
			
		||||
		)
 | 
			
		||||
	if 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
 | 
			
		||||
}
 | 
			
		||||
@@ -67,6 +71,10 @@ type response struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDetails, err error) {
 | 
			
		||||
	if config.Conf.CveDictionaryURL == "" {
 | 
			
		||||
		return api.FetchCveDetailsFromCveDB(cveIDs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
	reqChan := make(chan string, len(cveIDs))
 | 
			
		||||
	resChan := make(chan response, len(cveIDs))
 | 
			
		||||
@@ -121,25 +129,53 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDet
 | 
			
		||||
			fmt.Errorf("Failed to fetch CVE. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Sort(cveDetails)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails cve.CveDetails, err error) {
 | 
			
		||||
	log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
 | 
			
		||||
	cveconfig.Conf.DBType = config.Conf.CveDBType
 | 
			
		||||
	if config.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	} else {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDictionaryURL
 | 
			
		||||
	}
 | 
			
		||||
	cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
 | 
			
		||||
	if err := cvedb.OpenDB(); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to open DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	for _, cveID := range cveIDs {
 | 
			
		||||
		cveDetail := cvedb.Get(cveID)
 | 
			
		||||
		if len(cveDetail.CveID) == 0 {
 | 
			
		||||
			cveDetails = append(cveDetails, cve.CveDetail{
 | 
			
		||||
				CveID: cveID,
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			cveDetails = append(cveDetails, cveDetail)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// order by CVE ID desc
 | 
			
		||||
	sort.Sort(cveDetails)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
 | 
			
		||||
	var body string
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
		if len(errs) > 0 || resp.StatusCode != 200 {
 | 
			
		||||
			errChan <- fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
 | 
			
		||||
		//  resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v", errs, url, resp)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		log.Warnf("Failed to get. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
		log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -155,49 +191,17 @@ 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) {
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
	if config.Conf.CveDictionaryURL == "" {
 | 
			
		||||
		return api.FetchCveDetailsByCpeNameFromDB(cpeName)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
	url, err := util.URLPathJoin(api.baseURL, "cpes")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []cve.CveDetail{}, err
 | 
			
		||||
@@ -218,13 +222,13 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
 | 
			
		||||
			req = req.Send(fmt.Sprintf("%s=%s", key, query[key])).Type("json")
 | 
			
		||||
		}
 | 
			
		||||
		resp, body, errs = req.End()
 | 
			
		||||
		if len(errs) > 0 || resp.StatusCode != 200 {
 | 
			
		||||
			return fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
 | 
			
		||||
		if 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 get. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
		log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -238,3 +242,16 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
 | 
			
		||||
	}
 | 
			
		||||
	return cveDetails, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeNameFromDB(cpeName string) ([]cve.CveDetail, error) {
 | 
			
		||||
	log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
 | 
			
		||||
	cveconfig.Conf.DBType = config.Conf.CveDBType
 | 
			
		||||
	cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
 | 
			
		||||
 | 
			
		||||
	if err := cvedb.OpenDB(); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to open DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return cvedb.GetByCpeName(cpeName), nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										272
									
								
								db/db.go
									
									
									
									
									
								
							
							
						
						
									
										272
									
								
								db/db.go
									
									
									
									
									
								
							@@ -1,272 +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"
 | 
			
		||||
	"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.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.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_detail_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.CveDetail{}).
 | 
			
		||||
		AddIndex("idx_cve_detail_cveid", "cve_id").Error; err != nil {
 | 
			
		||||
		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
 | 
			
		||||
		}
 | 
			
		||||
		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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SelectLatestScanHistory select latest scan history from DB
 | 
			
		||||
func SelectLatestScanHistory() (m.ScanHistory, error) {
 | 
			
		||||
	scanHistory := m.ScanHistory{}
 | 
			
		||||
	db.Order("scanned_at desc").First(&scanHistory)
 | 
			
		||||
 | 
			
		||||
	if scanHistory.ID == 0 {
 | 
			
		||||
		return m.ScanHistory{}, fmt.Errorf("No scanHistory records.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	results := []m.ScanResult{}
 | 
			
		||||
	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
 | 
			
		||||
 | 
			
		||||
		knownCves := selectCveInfos(&r, "KnownCves")
 | 
			
		||||
		sort.Sort(m.CveInfos(knownCves))
 | 
			
		||||
		scanHistory.ScanResults[i].KnownCves = knownCves
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										123
									
								
								glide.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								glide.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
hash: c3167d83e68562cd7ef73f138ce60cb9e60b72b50394e8615388d1f3ba9fbef2
 | 
			
		||||
updated: 2017-01-02T09:37:09.437363123+09:00
 | 
			
		||||
imports:
 | 
			
		||||
- name: github.com/asaskevich/govalidator
 | 
			
		||||
  version: 7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877
 | 
			
		||||
- name: github.com/aws/aws-sdk-go
 | 
			
		||||
  version: 5b341290c488aa6bd76b335d819b4a68516ec3ab
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - aws
 | 
			
		||||
  - aws/awserr
 | 
			
		||||
  - aws/awsutil
 | 
			
		||||
  - aws/client
 | 
			
		||||
  - aws/client/metadata
 | 
			
		||||
  - aws/corehandlers
 | 
			
		||||
  - aws/credentials
 | 
			
		||||
  - aws/credentials/ec2rolecreds
 | 
			
		||||
  - aws/credentials/endpointcreds
 | 
			
		||||
  - aws/credentials/stscreds
 | 
			
		||||
  - aws/defaults
 | 
			
		||||
  - aws/ec2metadata
 | 
			
		||||
  - aws/request
 | 
			
		||||
  - aws/session
 | 
			
		||||
  - aws/signer/v4
 | 
			
		||||
  - private/endpoints
 | 
			
		||||
  - private/protocol
 | 
			
		||||
  - private/protocol/query
 | 
			
		||||
  - 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: 27ae5c8b5bc5d90ab0540b4c5d0f2632c8db8b57
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - storage
 | 
			
		||||
- name: github.com/boltdb/bolt
 | 
			
		||||
  version: 315c65d4cf4f5278c73477a35fb1f9b08365d340
 | 
			
		||||
- name: github.com/BurntSushi/toml
 | 
			
		||||
  version: 99064174e013895bbd9b025c31100bd1d9b590ca
 | 
			
		||||
- name: github.com/cenkalti/backoff
 | 
			
		||||
  version: 8edc80b07f38c27352fb186d971c628a6c32552b
 | 
			
		||||
- name: github.com/cheggaaa/pb
 | 
			
		||||
  version: ad4efe000aa550bb54918c06ebbadc0ff17687b9
 | 
			
		||||
- name: github.com/go-ini/ini
 | 
			
		||||
  version: 6e4869b434bd001f6983749881c7ead3545887d8
 | 
			
		||||
- name: github.com/go-sql-driver/mysql
 | 
			
		||||
  version: d512f204a577a4ab037a1816604c48c9c13210be
 | 
			
		||||
- name: github.com/google/subcommands
 | 
			
		||||
  version: a71b91e238406bd68766ee52db63bebedce0e9f6
 | 
			
		||||
- name: github.com/gosuri/uitable
 | 
			
		||||
  version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - util/strutil
 | 
			
		||||
  - util/wordwrap
 | 
			
		||||
- name: github.com/howeyc/gopass
 | 
			
		||||
  version: f5387c492211eb133053880d23dfae62aa14123d
 | 
			
		||||
- name: github.com/jinzhu/gorm
 | 
			
		||||
  version: 39165d498058a823126af3cbf4d2a3b0e1acf11e
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - dialects/mysql
 | 
			
		||||
  - dialects/sqlite
 | 
			
		||||
- name: github.com/jinzhu/inflection
 | 
			
		||||
  version: 74387dc39a75e970e7a3ae6a3386b5bd2e5c5cff
 | 
			
		||||
- name: github.com/jmespath/go-jmespath
 | 
			
		||||
  version: bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
 | 
			
		||||
- name: github.com/jroimartin/gocui
 | 
			
		||||
  version: ba396278de0a3c63658bbaba13d2d2fa392edb11
 | 
			
		||||
- name: github.com/k0kubun/pp
 | 
			
		||||
  version: f5dce6ed0ccf6c350f1679964ff6b61f3d6d2033
 | 
			
		||||
- name: github.com/kotakanbe/go-cve-dictionary
 | 
			
		||||
  version: 7eb1f1a2e7e436177570bf234e21c2ed9489d3fb
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - config
 | 
			
		||||
  - db
 | 
			
		||||
  - 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: 6c903ff4aa50920ca86087a280590b36b3152b9c
 | 
			
		||||
- name: github.com/mattn/go-isatty
 | 
			
		||||
  version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
 | 
			
		||||
- name: github.com/mattn/go-runewidth
 | 
			
		||||
  version: 737072b4e32b7a5018b4a7125da8d12de90e8045
 | 
			
		||||
- name: github.com/mattn/go-sqlite3
 | 
			
		||||
  version: 5510da399572b4962c020184bb291120c0a412e2
 | 
			
		||||
- name: github.com/mgutz/ansi
 | 
			
		||||
  version: c286dcecd19ff979eeb73ea444e479b903f2cfcb
 | 
			
		||||
- name: github.com/moul/http2curl
 | 
			
		||||
  version: b1479103caacaa39319f75e7f57fc545287fca0d
 | 
			
		||||
- name: github.com/nsf/termbox-go
 | 
			
		||||
  version: b6acae516ace002cb8105a89024544a1480655a5
 | 
			
		||||
- name: github.com/parnurzeal/gorequest
 | 
			
		||||
  version: e37b9d1efacf7c94820b29b75dd7d0c2996b3fb1
 | 
			
		||||
- name: github.com/rifflock/lfshook
 | 
			
		||||
  version: 3f9d976bd7402de39b46357069fb6325a974572e
 | 
			
		||||
- name: github.com/Sirupsen/logrus
 | 
			
		||||
  version: 3ec0642a7fb6488f65b06f9040adc67e3990296a
 | 
			
		||||
- name: golang.org/x/crypto
 | 
			
		||||
  version: 9477e0b78b9ac3d0b03822fd95422e2fe07627cd
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - curve25519
 | 
			
		||||
  - ed25519
 | 
			
		||||
  - ed25519/internal/edwards25519
 | 
			
		||||
  - ssh
 | 
			
		||||
  - ssh/agent
 | 
			
		||||
  - ssh/terminal
 | 
			
		||||
- name: golang.org/x/net
 | 
			
		||||
  version: 1d7a0b2100da090d8b02afcfb42f97e2c77e71a4
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - publicsuffix
 | 
			
		||||
- name: golang.org/x/sys
 | 
			
		||||
  version: 9bb9f0998d48b31547d975974935ae9b48c7a03c
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - unix
 | 
			
		||||
testImports: []
 | 
			
		||||
							
								
								
									
										36
									
								
								glide.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								glide.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
package: github.com/future-architect/vuls
 | 
			
		||||
import:
 | 
			
		||||
- package: github.com/Azure/azure-sdk-for-go
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - storage
 | 
			
		||||
- package: github.com/BurntSushi/toml
 | 
			
		||||
- package: github.com/Sirupsen/logrus
 | 
			
		||||
- package: github.com/asaskevich/govalidator
 | 
			
		||||
- package: github.com/aws/aws-sdk-go
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - aws
 | 
			
		||||
  - aws/credentials
 | 
			
		||||
  - aws/session
 | 
			
		||||
  - service/s3
 | 
			
		||||
- package: github.com/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/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
 | 
			
		||||
- package: github.com/mattn/go-sqlite3
 | 
			
		||||
- package: github.com/parnurzeal/gorequest
 | 
			
		||||
- package: github.com/rifflock/lfshook
 | 
			
		||||
- package: golang.org/x/crypto
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - ssh
 | 
			
		||||
  - ssh/agent
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 91 KiB  | 
							
								
								
									
										417
									
								
								img/vuls-scan-flow.graphml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										417
									
								
								img/vuls-scan-flow.graphml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,417 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
 | 
			
		||||
  <!--Created by yEd 3.14.2-->
 | 
			
		||||
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
 | 
			
		||||
  <key for="port" id="d1" yfiles.type="portgraphics"/>
 | 
			
		||||
  <key for="port" id="d2" yfiles.type="portgeometry"/>
 | 
			
		||||
  <key for="port" id="d3" yfiles.type="portuserdata"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="node" id="d4"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="node" id="d5"/>
 | 
			
		||||
  <key for="node" id="d6" yfiles.type="nodegraphics"/>
 | 
			
		||||
  <key for="graphml" id="d7" yfiles.type="resources"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
 | 
			
		||||
  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
 | 
			
		||||
  <graph edgedefault="directed" id="G">
 | 
			
		||||
    <data key="d0"/>
 | 
			
		||||
    <node id="n0">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="0.0"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="83.482421875" x="92.2587890625" y="18.93359375">Detect the OS<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n1">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.decision">
 | 
			
		||||
          <y:Geometry height="40.0" width="80.0" x="403.6849206349206" y="206.44247787610618"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="38.0" y="18.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n2">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="90.44247787610618" width="268.0" x="309.6849206349206" y="86.0"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="60.53125" modelName="custom" textColor="#000000" visible="true" width="170.763671875" x="48.61816406250006" y="14.95561393805309">Get installed packages
 | 
			
		||||
Debian/Ubuntu: dpkg-query
 | 
			
		||||
Amazon/RHEL/CentOS: rpm
 | 
			
		||||
FreeBSD: pkg<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n3">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="10.0" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="260.83984375" x="3.580078125" y="11.8671875">Check upgradable packages
 | 
			
		||||
Debian/Ubuntu: apt-get upgrade --dry-run<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n4">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.loopLimit">
 | 
			
		||||
          <y:Geometry height="51.10998735777497" width="137.19216182048035" x="75.40391908975982" y="376.28592169721867"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="131.751953125" x="2.7201043477401754" y="9.422181178887513">foreach 
 | 
			
		||||
upgradable  packages<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="5.551115123125783E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n5">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="10.0" y="459.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="213.619140625" x="27.1904296875" y="11.8671875">Parse changelog and get  CVE IDs
 | 
			
		||||
Debian/Ubuntu: aptitude changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n6">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.loopLimitEnd">
 | 
			
		||||
          <y:Geometry height="50.0" width="137.0" x="75.5" y="545.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="55.24609375" x="40.876953125" y="15.93359375">end loop<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n7">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="625.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="194.904296875" x="36.5478515625" y="18.93359375">Select the CVE detail information<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n8">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" modelName="custom" textColor="#000000" visible="true" width="232.744140625" x="17.6279296875" y="4.80078125">Get CVE IDs by using package manager
 | 
			
		||||
Amazon/RHEL: yum plugin security
 | 
			
		||||
FreeBSD: pkg audit<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n9">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
          <y:Geometry height="65.22882427307195" width="136.83944374209864" x="411.5802781289507" y="687.385587863464"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="117.970703125" x="9.434370308549205" y="23.548005886535975">CVE DB (NVD / JVN)<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-8.326672684688674E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="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="152.634765625" x="57.6826171875" y="11.8671875">Write results to JSON files
 | 
			
		||||
Reporting<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="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="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>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="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"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="205.52734375" x="31.236328125000057" y="18.93359375">Parse changelogs and get CVE IDs <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n2" target="n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="45.22123893805309" tx="0.0" ty="-20.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e1" source="n1" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="-40.0" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="144.0" y="226.44247787610618"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="46.697265625" x="-56.79057374984495" y="-34.26562148912808">Debian
 | 
			
		||||
Ubuntu<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="right" ratio="0.02215389573439544" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e2" source="n3" target="n4">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-25.554993678887485"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e3" source="n4" target="n5">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="25.554993678887485" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e4" source="n5" target="n6">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-25.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e5" source="n6" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="68.5" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="570.8409153761062"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e6" source="n1" target="n8">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="40.0" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="226.44247787610618"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="51.806640625" x="10.125014629061297" y="-48.39843398912805">Amazon
 | 
			
		||||
RHEL
 | 
			
		||||
FreeBSD<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="left" ratio="0.022401276994204813" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e7" source="n8" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e8" source="n0" target="n2">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-45.22123893805309"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e9" source="n7" target="n10">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e10" source="n7" target="n9">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="-134.01566143419018" sy="6.159084623893818" tx="0.0" ty="-29.333162136535975">
 | 
			
		||||
            <y:Point x="480.0" y="660.0"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e11" source="n1" target="n11">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="20.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="46.708984375" x="-53.35447755843876" y="11.632816010871807">CentOS<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e12" source="n11" target="n12">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e13" source="n12" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="134.00000000000006" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="401.8409153761062"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
  </graph>
 | 
			
		||||
  <data key="d7">
 | 
			
		||||
    <y:Resources/>
 | 
			
		||||
  </data>
 | 
			
		||||
</graphml>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								img/vuls-scan-flow.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								img/vuls-scan-flow.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 72 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								img/vuls_icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								img/vuls_icon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 19 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								img/vuls_logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								img/vuls_logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 8.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								img/vuls_logo_large.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								img/vuls_logo_large.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 56 KiB  | 
							
								
								
									
										20
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								main.go
									
									
									
									
									
								
							@@ -19,9 +19,10 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/commands"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
@@ -29,6 +30,12 @@ import (
 | 
			
		||||
	_ "github.com/mattn/go-sqlite3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Version of Vuls
 | 
			
		||||
var version = "0.2.0"
 | 
			
		||||
 | 
			
		||||
// Revision of Git
 | 
			
		||||
var revision string
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	subcommands.Register(subcommands.HelpCommand(), "")
 | 
			
		||||
	subcommands.Register(subcommands.FlagsCommand(), "")
 | 
			
		||||
@@ -37,8 +44,19 @@ func main() {
 | 
			
		||||
	subcommands.Register(&commands.TuiCmd{}, "tui")
 | 
			
		||||
	subcommands.Register(&commands.ScanCmd{}, "scan")
 | 
			
		||||
	subcommands.Register(&commands.PrepareCmd{}, "prepare")
 | 
			
		||||
	subcommands.Register(&commands.HistoryCmd{}, "history")
 | 
			
		||||
	subcommands.Register(&commands.ReportCmd{}, "report")
 | 
			
		||||
	subcommands.Register(&commands.ConfigtestCmd{}, "configtest")
 | 
			
		||||
 | 
			
		||||
	var v = flag.Bool("v", false, "Show version")
 | 
			
		||||
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	if *v {
 | 
			
		||||
		fmt.Printf("vuls %s %s\n", version, revision)
 | 
			
		||||
		os.Exit(int(subcommands.ExitSuccess))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	os.Exit(int(subcommands.Execute(ctx)))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										345
									
								
								models/models.go
									
									
									
									
									
								
							
							
						
						
									
										345
									
								
								models/models.go
									
									
									
									
									
								
							@@ -23,84 +23,274 @@ 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 []ScanResult
 | 
			
		||||
	ScannedAt   time.Time
 | 
			
		||||
	ScanResults ScanResults
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResults is slice of ScanResult.
 | 
			
		||||
type ScanResults []ScanResult
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver is filter function.
 | 
			
		||||
func (results ScanResults) FilterByCvssOver() (filtered ScanResults) {
 | 
			
		||||
	for _, result := range results {
 | 
			
		||||
		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)
 | 
			
		||||
// Len implement Sort Interface
 | 
			
		||||
func (s ScanResults) Len() int {
 | 
			
		||||
	return len(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Swap implement Sort Interface
 | 
			
		||||
func (s ScanResults) Swap(i, j int) {
 | 
			
		||||
	s[i], s[j] = s[j], s[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Less implement Sort Interface
 | 
			
		||||
func (s ScanResults) Less(i, j int) bool {
 | 
			
		||||
	if s[i].ServerName == s[j].ServerName {
 | 
			
		||||
		return s[i].Container.ContainerID < s[i].Container.ContainerID
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
	return s[i].ServerName < s[j].ServerName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResult has the result of scanned CVE information.
 | 
			
		||||
type ScanResult struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	ScanHistoryID uint
 | 
			
		||||
	ScannedAt time.Time
 | 
			
		||||
 | 
			
		||||
	Lang       string
 | 
			
		||||
	ServerName string // TOML Section key
 | 
			
		||||
	//  Hostname    string
 | 
			
		||||
	Family  string
 | 
			
		||||
	Release string
 | 
			
		||||
	//  Fqdn        string
 | 
			
		||||
	//  NWLinks     []NWLink
 | 
			
		||||
	Family     string
 | 
			
		||||
	Release    string
 | 
			
		||||
	Container  Container
 | 
			
		||||
	Platform   Platform
 | 
			
		||||
 | 
			
		||||
	// Scanned Vulns via SSH + CPE Vulns
 | 
			
		||||
	ScannedCves []VulnInfo
 | 
			
		||||
 | 
			
		||||
	KnownCves   []CveInfo
 | 
			
		||||
	UnknownCves []CveInfo
 | 
			
		||||
	IgnoredCves []CveInfo
 | 
			
		||||
 | 
			
		||||
	Packages PackageInfoList
 | 
			
		||||
 | 
			
		||||
	Optional [][]interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillCveDetail fetches CVE detailed information from
 | 
			
		||||
// CVE Database, and then set to fields.
 | 
			
		||||
func (r ScanResult) FillCveDetail() (ScanResult, error) {
 | 
			
		||||
	set := map[string]VulnInfo{}
 | 
			
		||||
	var cveIDs []string
 | 
			
		||||
	for _, v := range r.ScannedCves {
 | 
			
		||||
		set[v.CveID] = v
 | 
			
		||||
		cveIDs = append(cveIDs, v.CveID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ds, err := cveapi.CveClient.FetchCveDetails(cveIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return r, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	icves := config.Conf.Servers[r.ServerName].IgnoreCves
 | 
			
		||||
 | 
			
		||||
	var known, unknown, ignored CveInfos
 | 
			
		||||
	for _, d := range ds {
 | 
			
		||||
		cinfo := CveInfo{
 | 
			
		||||
			CveDetail: d,
 | 
			
		||||
			VulnInfo:  set[d.CveID],
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// ignored
 | 
			
		||||
		found := false
 | 
			
		||||
		for _, icve := range icves {
 | 
			
		||||
			if icve == d.CveID {
 | 
			
		||||
				ignored = append(ignored, cinfo)
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if found {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// unknown
 | 
			
		||||
		if d.CvssScore(config.Conf.Lang) <= 0 {
 | 
			
		||||
			unknown = append(unknown, cinfo)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// known
 | 
			
		||||
		known = append(known, cinfo)
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(known)
 | 
			
		||||
	sort.Sort(unknown)
 | 
			
		||||
	sort.Sort(ignored)
 | 
			
		||||
	r.KnownCves = known
 | 
			
		||||
	r.UnknownCves = unknown
 | 
			
		||||
	r.IgnoredCves = ignored
 | 
			
		||||
	return r, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver is filter function.
 | 
			
		||||
func (r ScanResult) FilterByCvssOver() ScanResult {
 | 
			
		||||
	cveInfos := []CveInfo{}
 | 
			
		||||
	for _, cveInfo := range r.KnownCves {
 | 
			
		||||
		if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
 | 
			
		||||
			cveInfos = append(cveInfos, cveInfo)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	r.KnownCves = cveInfos
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReportFileName returns the filename on localhost without extention
 | 
			
		||||
func (r ScanResult) ReportFileName() (name string) {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s", r.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReportKeyName returns the name of key on S3, Azure-Blob without extention
 | 
			
		||||
func (r ScanResult) ReportKeyName() (name string) {
 | 
			
		||||
	timestr := r.ScannedAt.Format(time.RFC3339)
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s/%s", timestr, r.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s/%s@%s", timestr, r.Container.Name, r.ServerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfo returns server name one line
 | 
			
		||||
func (r ScanResult) ServerInfo() string {
 | 
			
		||||
	hostinfo := ""
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"%s (%s%s)",
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
		)
 | 
			
		||||
	} else {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"%s / %s (%s%s) on %s",
 | 
			
		||||
			r.Container.Name,
 | 
			
		||||
			r.Container.ContainerID,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	return hostinfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfoTui returns server infromation for TUI sidebar
 | 
			
		||||
func (r ScanResult) ServerInfoTui() string {
 | 
			
		||||
	hostinfo := ""
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"%s (%s%s)",
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
		)
 | 
			
		||||
	} else {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"|-- %s (%s%s)",
 | 
			
		||||
			r.Container.Name,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
			//  r.Container.ContainerID,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	return hostinfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveSummary summarize the number of CVEs group by CVSSv2 Severity
 | 
			
		||||
func (r ScanResult) CveSummary() string {
 | 
			
		||||
	var high, middle, low, unknown int
 | 
			
		||||
	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:
 | 
			
		||||
			unknown++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d ?:%d)",
 | 
			
		||||
		high+middle+low+unknown,
 | 
			
		||||
		high, middle, low, unknown,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)",
 | 
			
		||||
			high+medium+low, high, medium, low)
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
	ScanResultID uint
 | 
			
		||||
 | 
			
		||||
	IPAddress string
 | 
			
		||||
	Netmask   string
 | 
			
		||||
	DevName   string
 | 
			
		||||
	LinkState string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VulnInfos is VulnInfo list, getter/setter, sortable methods.
 | 
			
		||||
type VulnInfos []VulnInfo
 | 
			
		||||
 | 
			
		||||
// VulnInfo holds a vulnerability information and unsecure packages
 | 
			
		||||
type VulnInfo struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	Packages         PackageInfoList
 | 
			
		||||
	DistroAdvisories []DistroAdvisory // for Aamazon, RHEL, FreeBSD
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindByCveID find by CVEID
 | 
			
		||||
func (s VulnInfos) FindByCveID(cveID string) (VulnInfo, bool) {
 | 
			
		||||
	for _, p := range s {
 | 
			
		||||
		if cveID == p.CveID {
 | 
			
		||||
			return p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return VulnInfo{CveID: cveID}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// immutable
 | 
			
		||||
func (s VulnInfos) set(cveID string, v VulnInfo) VulnInfos {
 | 
			
		||||
	for i, p := range s {
 | 
			
		||||
		if cveID == p.CveID {
 | 
			
		||||
			s[i] = v
 | 
			
		||||
			return s
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return append(s, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Len implement Sort Interface
 | 
			
		||||
func (s VulnInfos) Len() int {
 | 
			
		||||
	return len(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Swap implement Sort Interface
 | 
			
		||||
func (s VulnInfos) Swap(i, j int) {
 | 
			
		||||
	s[i], s[j] = s[j], s[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Less implement Sort Interface
 | 
			
		||||
func (s VulnInfos) Less(i, j int) bool {
 | 
			
		||||
	return s[i].CveID < s[j].CveID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveInfos is for sorting
 | 
			
		||||
type CveInfos []CveInfo
 | 
			
		||||
 | 
			
		||||
@@ -114,26 +304,16 @@ func (c CveInfos) Swap(i, j int) {
 | 
			
		||||
 | 
			
		||||
func (c CveInfos) Less(i, j int) bool {
 | 
			
		||||
	lang := config.Conf.Lang
 | 
			
		||||
	return c[i].CveDetail.CvssScore(lang) > c[j].CveDetail.CvssScore(lang)
 | 
			
		||||
	if c[i].CveDetail.CvssScore(lang) == c[j].CveDetail.CvssScore(lang) {
 | 
			
		||||
		return c[i].CveDetail.CveID < c[j].CveDetail.CveID
 | 
			
		||||
	}
 | 
			
		||||
	return c[j].CveDetail.CvssScore(lang) < c[i].CveDetail.CvssScore(lang)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveInfo has Cve Information.
 | 
			
		||||
type CveInfo struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	ScanResultID uint
 | 
			
		||||
 | 
			
		||||
	CveDetail        cve.CveDetail
 | 
			
		||||
	Packages         []PackageInfo
 | 
			
		||||
	DistroAdvisories []DistroAdvisory
 | 
			
		||||
	CpeNames         []CpeName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CpeName has CPE name
 | 
			
		||||
type CpeName struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	CveInfoID uint
 | 
			
		||||
 | 
			
		||||
	Name string
 | 
			
		||||
	CveDetail cve.CveDetail
 | 
			
		||||
	VulnInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageInfoList is slice of PackageInfo
 | 
			
		||||
@@ -177,6 +357,34 @@ func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found boo
 | 
			
		||||
	return PackageInfo{}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MergeNewVersion merges candidate version information to the receiver struct
 | 
			
		||||
func (ps PackageInfoList) MergeNewVersion(as PackageInfoList) {
 | 
			
		||||
	for _, a := range as {
 | 
			
		||||
		for i, p := range ps {
 | 
			
		||||
			if p.Name == a.Name {
 | 
			
		||||
				ps[i].NewVersion = a.NewVersion
 | 
			
		||||
				ps[i].NewRelease = a.NewRelease
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ps PackageInfoList) countUpdatablePacks() int {
 | 
			
		||||
	count := 0
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if len(p.NewVersion) != 0 {
 | 
			
		||||
			count++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return count
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToUpdatablePacksSummary returns a summary of updatable packages
 | 
			
		||||
func (ps PackageInfoList) ToUpdatablePacksSummary() string {
 | 
			
		||||
	return fmt.Sprintf("%d updatable packages",
 | 
			
		||||
		ps.countUpdatablePacks())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Find search PackageInfo by name-version-release
 | 
			
		||||
//  func (ps PackageInfoList) find(nameVersionRelease string) (PackageInfo, bool) {
 | 
			
		||||
//      for _, p := range ps {
 | 
			
		||||
@@ -194,17 +402,22 @@ 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
 | 
			
		||||
	CveInfoID uint
 | 
			
		||||
 | 
			
		||||
	Name    string
 | 
			
		||||
	Version string
 | 
			
		||||
	Release string
 | 
			
		||||
 | 
			
		||||
	Name       string
 | 
			
		||||
	Version    string
 | 
			
		||||
	Release    string
 | 
			
		||||
	NewVersion string
 | 
			
		||||
	NewRelease string
 | 
			
		||||
	Repository string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToStringCurrentVersion returns package name-version-release
 | 
			
		||||
@@ -231,14 +444,22 @@ func (p PackageInfo) ToStringNewVersion() string {
 | 
			
		||||
	return str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DistroAdvisory has Amazon Linux AMI Security Advisory information.
 | 
			
		||||
//TODO Rename to DistroAdvisory
 | 
			
		||||
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
 | 
			
		||||
type DistroAdvisory struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	CveInfoID uint
 | 
			
		||||
 | 
			
		||||
	AdvisoryID string
 | 
			
		||||
	Severity   string
 | 
			
		||||
	Issued     time.Time
 | 
			
		||||
	Updated    time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Container has Container information
 | 
			
		||||
type Container struct {
 | 
			
		||||
	ContainerID string
 | 
			
		||||
	Name        string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Platform has platform information
 | 
			
		||||
type Platform struct {
 | 
			
		||||
	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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										145
									
								
								report/azureblob.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								report/azureblob.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,145 @@
 | 
			
		||||
/* 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"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Azure/azure-sdk-for-go/storage"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AzureBlobWriter writes results to AzureBlob
 | 
			
		||||
type AzureBlobWriter struct{}
 | 
			
		||||
 | 
			
		||||
// Write results to Azure Blob storage
 | 
			
		||||
func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	if len(rs) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cli, err := getBlobClient()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.FormatOneLineText {
 | 
			
		||||
		timestr := rs[0].ScannedAt.Format(time.RFC3339)
 | 
			
		||||
		k := fmt.Sprintf(timestr + "/summary.txt")
 | 
			
		||||
		text := toOneLineSummary(rs...)
 | 
			
		||||
		b := []byte(text)
 | 
			
		||||
		if err := createBlockBlob(cli, k, b); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		key := r.ReportKeyName()
 | 
			
		||||
		if c.Conf.FormatJSON {
 | 
			
		||||
			k := key + ".json"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = json.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			if err := createBlockBlob(cli, k, b); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatShortText {
 | 
			
		||||
			k := key + "_short.txt"
 | 
			
		||||
			b := []byte(toShortPlainText(r))
 | 
			
		||||
			if err := createBlockBlob(cli, k, b); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatFullText {
 | 
			
		||||
			k := key + "_full.txt"
 | 
			
		||||
			b := []byte(toFullPlainText(r))
 | 
			
		||||
			if err := createBlockBlob(cli, k, b); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatXML {
 | 
			
		||||
			k := key + ".xml"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = xml.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to XML: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
 | 
			
		||||
			if err := createBlockBlob(cli, k, allBytes); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfAzureContainerExists check the existence of Azure storage container
 | 
			
		||||
func CheckIfAzureContainerExists() error {
 | 
			
		||||
	cli, err := getBlobClient()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	ok, err := cli.ContainerExists(c.Conf.AzureContainer)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf("Container not found. Container: %s", c.Conf.AzureContainer)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getBlobClient() (storage.BlobStorageClient, error) {
 | 
			
		||||
	api, err := storage.NewBasicClient(c.Conf.AzureAccount, c.Conf.AzureKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return storage.BlobStorageClient{}, err
 | 
			
		||||
	}
 | 
			
		||||
	return api.GetBlobService(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								report/email.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								report/email.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
/* 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
 | 
			
		||||
	to := strings.Join(conf.EMail.To[:], ", ")
 | 
			
		||||
	cc := strings.Join(conf.EMail.Cc[:], ", ")
 | 
			
		||||
	mailAddresses := append(conf.EMail.To, conf.EMail.Cc...)
 | 
			
		||||
	if _, err := mail.ParseAddressList(strings.Join(mailAddresses[:], ", ")); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to parse email addresses: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		subject := fmt.Sprintf("%s%s %s",
 | 
			
		||||
			conf.EMail.SubjectPrefix,
 | 
			
		||||
			r.ServerInfo(),
 | 
			
		||||
			r.CveSummary(),
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		headers := make(map[string]string)
 | 
			
		||||
		headers["From"] = conf.EMail.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 message string
 | 
			
		||||
		for k, v := range headers {
 | 
			
		||||
			message += fmt.Sprintf("%s: %s\r\n", k, v)
 | 
			
		||||
		}
 | 
			
		||||
		message += "\r\n" + toFullPlainText(r)
 | 
			
		||||
 | 
			
		||||
		smtpServer := net.JoinHostPort(conf.EMail.SMTPAddr, conf.EMail.SMTPPort)
 | 
			
		||||
		err = smtp.SendMail(
 | 
			
		||||
			smtpServer,
 | 
			
		||||
			smtp.PlainAuth(
 | 
			
		||||
				"",
 | 
			
		||||
				conf.EMail.User,
 | 
			
		||||
				conf.EMail.Password,
 | 
			
		||||
				conf.EMail.SMTPAddr,
 | 
			
		||||
			),
 | 
			
		||||
			conf.EMail.From,
 | 
			
		||||
			conf.EMail.To,
 | 
			
		||||
			[]byte(message),
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to send emails: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,37 +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"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// JSONWriter writes report as JSON format
 | 
			
		||||
type JSONWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
	var j []byte
 | 
			
		||||
	if j, err = json.MarshalIndent(scanResults, "", "  "); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(string(j))
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										111
									
								
								report/localfile.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								report/localfile.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LocalFileWriter writes results to a local file.
 | 
			
		||||
type LocalFileWriter struct {
 | 
			
		||||
	CurrentDir string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	if c.Conf.FormatOneLineText {
 | 
			
		||||
		path := filepath.Join(w.CurrentDir, "summary.txt")
 | 
			
		||||
		text := toOneLineSummary(rs...)
 | 
			
		||||
		if err := writeFile(path, []byte(text), 0600); err != nil {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"Failed to write to file. path: %s, err: %s",
 | 
			
		||||
				path, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		path := filepath.Join(w.CurrentDir, r.ReportFileName())
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatJSON {
 | 
			
		||||
			p := path + ".json"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = json.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			if err := writeFile(p, b, 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to write JSON. path: %s, err: %s", p, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatShortText {
 | 
			
		||||
			p := path + "_short.txt"
 | 
			
		||||
			if err := writeFile(
 | 
			
		||||
				p, []byte(toShortPlainText(r)), 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"Failed to write text files. path: %s, err: %s", p, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatFullText {
 | 
			
		||||
			p := path + "_full.txt"
 | 
			
		||||
			if err := writeFile(
 | 
			
		||||
				p, []byte(toFullPlainText(r)), 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"Failed to write text files. path: %s, err: %s", p, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatXML {
 | 
			
		||||
			p := path + ".xml"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = xml.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to XML: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
 | 
			
		||||
			if err := writeFile(p, allBytes, 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to write XML. path: %s, err: %s", p, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func writeFile(path string, data []byte, perm os.FileMode) error {
 | 
			
		||||
	var err error
 | 
			
		||||
	if c.Conf.GZIP {
 | 
			
		||||
		if data, err = gz(data); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		path = path + ".gz"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ioutil.WriteFile(
 | 
			
		||||
		path, []byte(data), perm); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,51 +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"
 | 
			
		||||
 | 
			
		||||
	"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"
 | 
			
		||||
	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.ServerName,
 | 
			
		||||
			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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										151
									
								
								report/s3.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								report/s3.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
			
		||||
/* 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"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/credentials"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/session"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/service/s3"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// S3Writer writes results to S3
 | 
			
		||||
type S3Writer struct{}
 | 
			
		||||
 | 
			
		||||
func getS3() *s3.S3 {
 | 
			
		||||
	return s3.New(session.New(&aws.Config{
 | 
			
		||||
		Region:      aws.String(c.Conf.AwsRegion),
 | 
			
		||||
		Credentials: credentials.NewSharedCredentials("", c.Conf.AwsProfile),
 | 
			
		||||
	}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write results to S3
 | 
			
		||||
// http://docs.aws.amazon.com/sdk-for-go/latest/v1/developerguide/common-examples.title.html
 | 
			
		||||
func (w S3Writer) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	if len(rs) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	svc := getS3()
 | 
			
		||||
 | 
			
		||||
	if c.Conf.FormatOneLineText {
 | 
			
		||||
		timestr := rs[0].ScannedAt.Format(time.RFC3339)
 | 
			
		||||
		k := fmt.Sprintf(timestr + "/summary.txt")
 | 
			
		||||
		text := toOneLineSummary(rs...)
 | 
			
		||||
		if err := putObject(svc, k, []byte(text)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		key := r.ReportKeyName()
 | 
			
		||||
		if c.Conf.FormatJSON {
 | 
			
		||||
			k := key + ".json"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = json.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			if err := putObject(svc, k, b); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatShortText {
 | 
			
		||||
			k := key + "_short.txt"
 | 
			
		||||
			text := toShortPlainText(r)
 | 
			
		||||
			if err := putObject(svc, k, []byte(text)); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatFullText {
 | 
			
		||||
			k := key + "_full.txt"
 | 
			
		||||
			text := toFullPlainText(r)
 | 
			
		||||
			if err := putObject(svc, k, []byte(text)); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatXML {
 | 
			
		||||
			k := key + ".xml"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = xml.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to XML: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
 | 
			
		||||
			if err := putObject(svc, k, allBytes); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfBucketExists check the existence of S3 bucket
 | 
			
		||||
func CheckIfBucketExists() error {
 | 
			
		||||
	svc := getS3()
 | 
			
		||||
	result, err := svc.ListBuckets(&s3.ListBucketsInput{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"Failed to list buckets. err: %s, profile: %s, region: %s",
 | 
			
		||||
			err, c.Conf.AwsProfile, c.Conf.AwsRegion)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	found := false
 | 
			
		||||
	for _, bucket := range result.Buckets {
 | 
			
		||||
		if *bucket.Name == c.Conf.S3Bucket {
 | 
			
		||||
			found = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"Failed to find the buckets. profile: %s, region: %s, bukdet: %s",
 | 
			
		||||
			c.Conf.AwsProfile, c.Conf.AwsRegion, c.Conf.S3Bucket)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := svc.PutObject(&s3.PutObjectInput{
 | 
			
		||||
		Bucket: &c.Conf.S3Bucket,
 | 
			
		||||
		Key:    &k,
 | 
			
		||||
		Body:   bytes.NewReader(b),
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to upload data to %s/%s, %s",
 | 
			
		||||
			c.Conf.S3Bucket, k, err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -56,37 +56,36 @@ type message struct {
 | 
			
		||||
// SlackWriter send report to slack
 | 
			
		||||
type SlackWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w SlackWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
func (w SlackWriter) Write(rs ...models.ScanResult) error {
 | 
			
		||||
	conf := config.Conf.Slack
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
	channel := conf.Channel
 | 
			
		||||
 | 
			
		||||
		channel := conf.Channel
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		if channel == "${servername}" {
 | 
			
		||||
			channel = fmt.Sprintf("#%s", s.ServerName)
 | 
			
		||||
			channel = fmt.Sprintf("#%s", r.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		msg := message{
 | 
			
		||||
			Text:        msgText(s),
 | 
			
		||||
			Text:        msgText(r),
 | 
			
		||||
			Username:    conf.AuthUser,
 | 
			
		||||
			IconEmoji:   conf.IconEmoji,
 | 
			
		||||
			Channel:     channel,
 | 
			
		||||
			Attachments: toSlackAttachments(s),
 | 
			
		||||
			Attachments: toSlackAttachments(r),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bytes, _ := json.Marshal(msg)
 | 
			
		||||
		jsonBody := string(bytes)
 | 
			
		||||
		f := func() (err error) {
 | 
			
		||||
			resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).
 | 
			
		||||
				Send(string(jsonBody)).End()
 | 
			
		||||
			if resp.StatusCode != 200 {
 | 
			
		||||
				log.Errorf("Resonse body: %s", body)
 | 
			
		||||
				if len(errs) > 0 {
 | 
			
		||||
					return errs[0]
 | 
			
		||||
				}
 | 
			
		||||
			resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).Send(string(jsonBody)).End()
 | 
			
		||||
			if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"HTTP POST error: %v, url: %s, resp: %v, body: %s",
 | 
			
		||||
					errs, conf.HookURL, resp, body)
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		notify := func(err error, t time.Duration) {
 | 
			
		||||
			log.Warn("Error %s", err)
 | 
			
		||||
			log.Warn("Retrying in ", t)
 | 
			
		||||
		}
 | 
			
		||||
		if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
 | 
			
		||||
@@ -97,33 +96,30 @@ func (w SlackWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func msgText(r models.ScanResult) string {
 | 
			
		||||
 | 
			
		||||
	notifyUsers := ""
 | 
			
		||||
	if 0 < len(r.KnownCves) || 0 < len(r.UnknownCves) {
 | 
			
		||||
		notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hostinfo := fmt.Sprintf(
 | 
			
		||||
		"*%s* (%s %s)",
 | 
			
		||||
		r.ServerName,
 | 
			
		||||
		r.Family,
 | 
			
		||||
		r.Release,
 | 
			
		||||
	)
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, hostinfo, r.CveSummary())
 | 
			
		||||
	serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, serverInfo, r.CveSummary())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
 | 
			
		||||
	cves := scanResult.KnownCves
 | 
			
		||||
	if !config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		cves = append(cves, scanResult.UnknownCves...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scanResult.KnownCves = append(scanResult.KnownCves, scanResult.UnknownCves...)
 | 
			
		||||
	for _, cveInfo := range scanResult.KnownCves {
 | 
			
		||||
	for _, cveInfo := range cves {
 | 
			
		||||
		cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
 | 
			
		||||
		curentPackages := []string{}
 | 
			
		||||
		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{}
 | 
			
		||||
@@ -176,16 +172,15 @@ func attachmentText(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case config.Conf.Lang == "ja" &&
 | 
			
		||||
		cveInfo.CveDetail.Jvn.ID != 0 &&
 | 
			
		||||
		0 < cveInfo.CveDetail.CvssScore("ja"):
 | 
			
		||||
		0 < cveInfo.CveDetail.Jvn.CvssScore():
 | 
			
		||||
 | 
			
		||||
		jvn := cveInfo.CveDetail.Jvn
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
 | 
			
		||||
			cveInfo.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
			jvn.Severity,
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, jvn.Vector),
 | 
			
		||||
			jvn.Vector,
 | 
			
		||||
			jvn.Title,
 | 
			
		||||
			jvn.CvssSeverity(),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, jvn.CvssVector()),
 | 
			
		||||
			jvn.CvssVector(),
 | 
			
		||||
			jvn.CveTitle(),
 | 
			
		||||
			linkText,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
@@ -193,20 +188,31 @@ func attachmentText(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
 | 
			
		||||
			cveInfo.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
			nvd.Severity(),
 | 
			
		||||
			nvd.CvssSeverity(),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, nvd.CvssVector()),
 | 
			
		||||
			nvd.CvssVector(),
 | 
			
		||||
			nvd.Summary,
 | 
			
		||||
			nvd.CveSummary(),
 | 
			
		||||
			linkText,
 | 
			
		||||
		)
 | 
			
		||||
	default:
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		return fmt.Sprintf("?\n%s\n%s", nvd.Summary, linkText)
 | 
			
		||||
		return fmt.Sprintf("?\n%s\n%s", nvd.CveSummary(), linkText)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TextWriter write to stdout
 | 
			
		||||
type TextWriter struct{}
 | 
			
		||||
// StdoutWriter write to stdout
 | 
			
		||||
type StdoutWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w TextWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		text, err := toPlainText(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
// WriteScanSummary prints Scan summary at the end of scan
 | 
			
		||||
func (w StdoutWriter) WriteScanSummary(rs ...models.ScanResult) {
 | 
			
		||||
	fmt.Printf("\n\n")
 | 
			
		||||
	fmt.Printf("Scan Summary\n")
 | 
			
		||||
	fmt.Printf("============\n")
 | 
			
		||||
	fmt.Printf("%s\n", toScanSummary(rs...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w StdoutWriter) Write(rs ...models.ScanResult) error {
 | 
			
		||||
	if c.Conf.FormatOneLineText {
 | 
			
		||||
		fmt.Print("\n\n")
 | 
			
		||||
		fmt.Println("One Line Summary")
 | 
			
		||||
		fmt.Println("================")
 | 
			
		||||
		fmt.Println(toOneLineSummary(rs...))
 | 
			
		||||
		fmt.Print("\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.FormatShortText {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			fmt.Println(toShortPlainText(r))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.FormatFullText {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			fmt.Println(toFullPlainText(r))
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println(text)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										150
									
								
								report/tui.go
									
									
									
									
									
								
							
							
						
						
									
										150
									
								
								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() subcommands.ExitStatus {
 | 
			
		||||
	var err error
 | 
			
		||||
	scanHistory, err = latestScanHistory()
 | 
			
		||||
	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 latestScanHistory() (latest models.ScanHistory, err error) {
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		return latest, fmt.Errorf(
 | 
			
		||||
			"Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	latest, err = db.SelectLatestScanHistory()
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func keybindings(g *gocui.Gui) (err error) {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
@@ -163,35 +151,41 @@ 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("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")
 | 
			
		||||
	default:
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
 | 
			
		||||
@@ -203,7 +197,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
 | 
			
		||||
		}
 | 
			
		||||
@@ -332,7 +326,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 +354,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 +362,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 +372,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 +380,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
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -410,7 +404,7 @@ func changeHost(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	serverName := strings.TrimSpace(l)
 | 
			
		||||
 | 
			
		||||
	for _, r := range scanHistory.ScanResults {
 | 
			
		||||
		if serverName == r.ServerName {
 | 
			
		||||
		if serverName == strings.TrimSpace(r.ServerInfoTui()) {
 | 
			
		||||
			currentScanResult = r
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
@@ -451,7 +445,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 +467,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 +478,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
 | 
			
		||||
@@ -509,17 +503,20 @@ func layout(g *gocui.Gui) error {
 | 
			
		||||
 | 
			
		||||
func setSideLayout(g *gocui.Gui) error {
 | 
			
		||||
	_, maxY := g.Size()
 | 
			
		||||
	if v, err := g.SetView("side", -1, -1, 30, maxY); err != nil {
 | 
			
		||||
	if v, err := g.SetView("side", -1, -1, 40, maxY); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		v.Highlight = true
 | 
			
		||||
 | 
			
		||||
		for _, result := range scanHistory.ScanResults {
 | 
			
		||||
			fmt.Fprintln(v, result.ServerName)
 | 
			
		||||
			fmt.Fprintln(v, result.ServerInfoTui())
 | 
			
		||||
		}
 | 
			
		||||
		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
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -528,12 +525,12 @@ func setSideLayout(g *gocui.Gui) error {
 | 
			
		||||
 | 
			
		||||
func setSummaryLayout(g *gocui.Gui) error {
 | 
			
		||||
	maxX, maxY := g.Size()
 | 
			
		||||
	if v, err := g.SetView("summary", 30, -1, maxX, int(float64(maxY)*0.2)); err != nil {
 | 
			
		||||
	if v, err := g.SetView("summary", 40, -1, maxX, int(float64(maxY)*0.2)); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lines := summaryLines(currentScanResult)
 | 
			
		||||
		lines := summaryLines()
 | 
			
		||||
		fmt.Fprintf(v, lines)
 | 
			
		||||
 | 
			
		||||
		v.Highlight = true
 | 
			
		||||
@@ -543,40 +540,40 @@ 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
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
		//      packs = append(packs, pack.Name)
 | 
			
		||||
		//  }
 | 
			
		||||
		if config.Conf.Lang == "ja" && 0 < d.CveDetail.Jvn.CvssScore() {
 | 
			
		||||
			summary := d.CveDetail.Jvn.Title
 | 
			
		||||
			summary := d.CveDetail.Jvn.CveTitle()
 | 
			
		||||
			cols = []string{
 | 
			
		||||
				fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("|  %-4.1f(%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Jvn.Severity,
 | 
			
		||||
					d.CveDetail.Jvn.CvssSeverity(),
 | 
			
		||||
				),
 | 
			
		||||
				//  strings.Join(packs, ","),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			summary := d.CveDetail.Nvd.Summary
 | 
			
		||||
			summary := d.CveDetail.Nvd.CveSummary()
 | 
			
		||||
 | 
			
		||||
			var cvssScore string
 | 
			
		||||
			if d.CveDetail.CvssScore("en") <= 0 {
 | 
			
		||||
@@ -584,7 +581,7 @@ func summaryLines(data models.ScanResult) string {
 | 
			
		||||
			} else {
 | 
			
		||||
				cvssScore = fmt.Sprintf("| %-4.1f(%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Nvd.Severity(),
 | 
			
		||||
					d.CveDetail.Nvd.CvssSeverity(),
 | 
			
		||||
				)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@@ -616,7 +613,7 @@ func setDetailLayout(g *gocui.Gui) error {
 | 
			
		||||
	_, oy := summaryView.Origin()
 | 
			
		||||
	currentCveInfo = cy + oy
 | 
			
		||||
 | 
			
		||||
	if v, err := g.SetView("detail", 30, int(float64(maxY)*0.2), maxX, maxY); err != nil {
 | 
			
		||||
	if v, err := g.SetView("detail", 40, int(float64(maxY)*0.2), maxX, maxY); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
@@ -643,20 +640,21 @@ type dataForTmpl struct {
 | 
			
		||||
	CvssVector       string
 | 
			
		||||
	CvssSeverity     string
 | 
			
		||||
	Summary          string
 | 
			
		||||
	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.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())
 | 
			
		||||
@@ -670,18 +668,20 @@ func detailLines() (string, error) {
 | 
			
		||||
	case config.Conf.Lang == "ja" &&
 | 
			
		||||
		0 < cveInfo.CveDetail.Jvn.CvssScore():
 | 
			
		||||
		jvn := cveInfo.CveDetail.Jvn
 | 
			
		||||
		cvssSeverity = jvn.Severity
 | 
			
		||||
		cvssVector = jvn.Vector
 | 
			
		||||
		summary = fmt.Sprintf("%s\n%s", jvn.Title, jvn.Summary)
 | 
			
		||||
		refs = jvn.References
 | 
			
		||||
		cvssSeverity = jvn.CvssSeverity()
 | 
			
		||||
		cvssVector = jvn.CvssVector()
 | 
			
		||||
		summary = fmt.Sprintf("%s\n%s", jvn.CveTitle(), jvn.CveSummary())
 | 
			
		||||
		refs = jvn.VulnSiteReferences()
 | 
			
		||||
	default:
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		cvssSeverity = nvd.Severity()
 | 
			
		||||
		cvssSeverity = nvd.CvssSeverity()
 | 
			
		||||
		cvssVector = nvd.CvssVector()
 | 
			
		||||
		summary = nvd.Summary
 | 
			
		||||
		refs = nvd.References
 | 
			
		||||
		summary = nvd.CveSummary()
 | 
			
		||||
		refs = nvd.VulnSiteReferences()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 +715,7 @@ func detailLines() (string, error) {
 | 
			
		||||
		CvssSeverity:  cvssSeverity,
 | 
			
		||||
		CvssVector:    cvssVector,
 | 
			
		||||
		Summary:       summary,
 | 
			
		||||
		CweURL:        cweURL,
 | 
			
		||||
		VulnSiteLinks: links,
 | 
			
		||||
		References:    refs,
 | 
			
		||||
		Packages:      packages,
 | 
			
		||||
@@ -746,14 +747,19 @@ Summary
 | 
			
		||||
 | 
			
		||||
 {{.Summary }}
 | 
			
		||||
 | 
			
		||||
CWE
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
 {{.CweURL }}
 | 
			
		||||
 | 
			
		||||
Package/CPE
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range $pack := .Packages -}}
 | 
			
		||||
* {{$pack}}
 | 
			
		||||
{{end -}}
 | 
			
		||||
{{range .CpeNames -}}
 | 
			
		||||
* {{.Name}}
 | 
			
		||||
{{range $name := .CpeNames -}}
 | 
			
		||||
* {{$name}}
 | 
			
		||||
{{end}}
 | 
			
		||||
Links
 | 
			
		||||
--------------
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										253
									
								
								report/util.go
									
									
									
									
									
								
							
							
						
						
									
										253
									
								
								report/util.go
									
									
									
									
									
								
							@@ -27,83 +27,120 @@ import (
 | 
			
		||||
	"github.com/gosuri/uitable"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func toPlainText(scanResult models.ScanResult) (string, error) {
 | 
			
		||||
	hostinfo := fmt.Sprintf(
 | 
			
		||||
		"%s (%s %s)",
 | 
			
		||||
		scanResult.ServerName,
 | 
			
		||||
		scanResult.Family,
 | 
			
		||||
		scanResult.Release,
 | 
			
		||||
	)
 | 
			
		||||
const maxColWidth = 80
 | 
			
		||||
 | 
			
		||||
	var buffer bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(hostinfo); i++ {
 | 
			
		||||
		buffer.WriteString("=")
 | 
			
		||||
func toScanSummary(rs ...models.ScanResult) string {
 | 
			
		||||
	table := uitable.New()
 | 
			
		||||
	table.MaxColWidth = maxColWidth
 | 
			
		||||
	table.Wrap = true
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		cols := []interface{}{
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
			fmt.Sprintf("%s%s", r.Family, r.Release),
 | 
			
		||||
			fmt.Sprintf("%d CVEs", len(r.ScannedCves)),
 | 
			
		||||
			r.Packages.ToUpdatablePacksSummary(),
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow(cols...)
 | 
			
		||||
	}
 | 
			
		||||
	header := fmt.Sprintf("%s\n%s", hostinfo, 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 := 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 {
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = 84
 | 
			
		||||
	stable.Wrap = true
 | 
			
		||||
	cves := append(r.KnownCves, r.UnknownCves...)
 | 
			
		||||
	for _, d := range cves {
 | 
			
		||||
		var scols []string
 | 
			
		||||
func toOneLineSummary(rs ...models.ScanResult) string {
 | 
			
		||||
	table := uitable.New()
 | 
			
		||||
	table.MaxColWidth = maxColWidth
 | 
			
		||||
	table.Wrap = true
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		cols := []interface{}{
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
			r.CveSummary(),
 | 
			
		||||
			r.Packages.ToUpdatablePacksSummary(),
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow(cols...)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s\n", table)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toShortPlainText(r models.ScanResult) string {
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = maxColWidth
 | 
			
		||||
	stable.Wrap = true
 | 
			
		||||
 | 
			
		||||
	cves := r.KnownCves
 | 
			
		||||
	if !config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		cves = append(cves, r.UnknownCves...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(r.ServerInfo()); i++ {
 | 
			
		||||
		buf.WriteString("=")
 | 
			
		||||
	}
 | 
			
		||||
	header := fmt.Sprintf("%s\n%s\n%s\t%s\n\n",
 | 
			
		||||
		r.ServerInfo(),
 | 
			
		||||
		buf.String(),
 | 
			
		||||
		r.CveSummary(),
 | 
			
		||||
		r.Packages.ToUpdatablePacksSummary(),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if len(cves) == 0 {
 | 
			
		||||
		return fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
No CVE-IDs are found in updatable packages.
 | 
			
		||||
%s
 | 
			
		||||
`, header, r.Packages.ToUpdatablePacksSummary())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, d := range cves {
 | 
			
		||||
		var packsVer string
 | 
			
		||||
		for _, p := range d.Packages {
 | 
			
		||||
			packsVer += fmt.Sprintf(
 | 
			
		||||
				"%s -> %s\n", p.ToStringCurrentVersion(), p.ToStringNewVersion())
 | 
			
		||||
		}
 | 
			
		||||
		for _, n := range d.CpeNames {
 | 
			
		||||
			packsVer += n
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var scols []string
 | 
			
		||||
		switch {
 | 
			
		||||
		case config.Conf.Lang == "ja" &&
 | 
			
		||||
			d.CveDetail.Jvn.ID != 0 &&
 | 
			
		||||
			0 < d.CveDetail.CvssScore("ja"):
 | 
			
		||||
 | 
			
		||||
			summary := d.CveDetail.Jvn.Title
 | 
			
		||||
			0 < d.CveDetail.Jvn.CvssScore():
 | 
			
		||||
			summary := fmt.Sprintf("%s\n%s\n%s\n%s",
 | 
			
		||||
				d.CveDetail.Jvn.CveTitle(),
 | 
			
		||||
				d.CveDetail.Jvn.Link(),
 | 
			
		||||
				distroLinks(d, r.Family)[0].url,
 | 
			
		||||
				packsVer,
 | 
			
		||||
			)
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("%-4.1f (%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Jvn.Severity,
 | 
			
		||||
					d.CveDetail.Jvn.CvssSeverity(),
 | 
			
		||||
				),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case 0 < d.CveDetail.CvssScore("en"):
 | 
			
		||||
			summary := d.CveDetail.Nvd.Summary
 | 
			
		||||
			summary := fmt.Sprintf("%s\n%s/%s\n%s\n%s",
 | 
			
		||||
				d.CveDetail.Nvd.CveSummary(),
 | 
			
		||||
				cveDetailsBaseURL,
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				distroLinks(d, r.Family)[0].url,
 | 
			
		||||
				packsVer,
 | 
			
		||||
			)
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("%-4.1f",
 | 
			
		||||
				fmt.Sprintf("%-4.1f (%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Nvd.CvssSeverity(),
 | 
			
		||||
				),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			summary := fmt.Sprintf("%s\n%s",
 | 
			
		||||
				distroLinks(d, r.Family)[0].url, packsVer)
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				"?",
 | 
			
		||||
				d.CveDetail.Nvd.Summary,
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -112,16 +149,58 @@ 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO Distro Advisory
 | 
			
		||||
func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
 | 
			
		||||
	for _, cve := range data.KnownCves {
 | 
			
		||||
func toFullPlainText(r models.ScanResult) string {
 | 
			
		||||
	serverInfo := r.ServerInfo()
 | 
			
		||||
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(serverInfo); i++ {
 | 
			
		||||
		buf.WriteString("=")
 | 
			
		||||
	}
 | 
			
		||||
	header := fmt.Sprintf("%s\n%s\n%s\t%s\n",
 | 
			
		||||
		r.ServerInfo(),
 | 
			
		||||
		buf.String(),
 | 
			
		||||
		r.CveSummary(),
 | 
			
		||||
		r.Packages.ToUpdatablePacksSummary(),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if len(r.KnownCves) == 0 && len(r.UnknownCves) == 0 {
 | 
			
		||||
		return fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
No CVE-IDs are found in updatable packages.
 | 
			
		||||
%s
 | 
			
		||||
`, header, r.Packages.ToUpdatablePacksSummary())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scoredReport, unscoredReport := []string{}, []string{}
 | 
			
		||||
	scoredReport, unscoredReport = toPlainTextDetails(r, r.Family)
 | 
			
		||||
 | 
			
		||||
	unscored := ""
 | 
			
		||||
	if !config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		unscored = strings.Join(unscoredReport, "\n\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scored := strings.Join(scoredReport, "\n\n")
 | 
			
		||||
	detail := fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
 | 
			
		||||
%s
 | 
			
		||||
`,
 | 
			
		||||
		scored,
 | 
			
		||||
		unscored,
 | 
			
		||||
	)
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n", header, detail)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toPlainTextDetails(r models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
 | 
			
		||||
	for _, cve := range r.KnownCves {
 | 
			
		||||
		switch config.Conf.Lang {
 | 
			
		||||
		case "en":
 | 
			
		||||
			if cve.CveDetail.Nvd.ID != 0 {
 | 
			
		||||
			if 0 < cve.CveDetail.Nvd.CvssScore() {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
			} else {
 | 
			
		||||
@@ -129,10 +208,10 @@ func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport,
 | 
			
		||||
					scoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
			}
 | 
			
		||||
		case "ja":
 | 
			
		||||
			if cve.CveDetail.Jvn.ID != 0 {
 | 
			
		||||
			if 0 < cve.CveDetail.Jvn.CvssScore() {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangJa(cve, osFamily))
 | 
			
		||||
			} else if cve.CveDetail.Nvd.ID != 0 {
 | 
			
		||||
			} else if 0 < cve.CveDetail.Nvd.CvssScore() {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
			} else {
 | 
			
		||||
@@ -141,7 +220,7 @@ func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cve := range data.UnknownCves {
 | 
			
		||||
	for _, cve := range r.UnknownCves {
 | 
			
		||||
		unscoredReport = append(
 | 
			
		||||
			unscoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
	}
 | 
			
		||||
@@ -151,7 +230,7 @@ func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport,
 | 
			
		||||
func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
	dtable := uitable.New()
 | 
			
		||||
	dtable.MaxColWidth = 100
 | 
			
		||||
	dtable.MaxColWidth = maxColWidth
 | 
			
		||||
	dtable.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
@@ -165,19 +244,19 @@ func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	for _, link := range dlinks {
 | 
			
		||||
		dtable.AddRow(link.title, link.url)
 | 
			
		||||
	}
 | 
			
		||||
	dtable = addPackageInfos(dtable, cveInfo.Packages)
 | 
			
		||||
	dtable = addCpeNames(dtable, cveInfo.CpeNames)
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s", dtable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toPlainTextDetailsLangJa(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("-------------")
 | 
			
		||||
@@ -185,14 +264,16 @@ func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
		dtable.AddRow("Score",
 | 
			
		||||
			fmt.Sprintf("%4.1f (%s)",
 | 
			
		||||
				cveDetail.Jvn.CvssScore(),
 | 
			
		||||
				jvn.Severity,
 | 
			
		||||
				jvn.CvssSeverity(),
 | 
			
		||||
			))
 | 
			
		||||
	} else {
 | 
			
		||||
		dtable.AddRow("Score", "?")
 | 
			
		||||
	}
 | 
			
		||||
	dtable.AddRow("Vector", jvn.Vector)
 | 
			
		||||
	dtable.AddRow("Title", jvn.Title)
 | 
			
		||||
	dtable.AddRow("Description", jvn.Summary)
 | 
			
		||||
	dtable.AddRow("Vector", jvn.CvssVector())
 | 
			
		||||
	dtable.AddRow("Title", jvn.CveTitle())
 | 
			
		||||
	dtable.AddRow("Description", jvn.CveSummary())
 | 
			
		||||
	dtable.AddRow(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))
 | 
			
		||||
@@ -217,8 +298,7 @@ func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
 | 
			
		||||
	nvd := cveDetail.Nvd
 | 
			
		||||
 | 
			
		||||
	dtable := uitable.New()
 | 
			
		||||
	//TODO resize
 | 
			
		||||
	dtable.MaxColWidth = 100
 | 
			
		||||
	dtable.MaxColWidth = maxColWidth
 | 
			
		||||
	dtable.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
@@ -227,14 +307,16 @@ func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
 | 
			
		||||
		dtable.AddRow("Score",
 | 
			
		||||
			fmt.Sprintf("%4.1f (%s)",
 | 
			
		||||
				cveDetail.Nvd.CvssScore(),
 | 
			
		||||
				nvd.Severity(),
 | 
			
		||||
				nvd.CvssSeverity(),
 | 
			
		||||
			))
 | 
			
		||||
	} else {
 | 
			
		||||
		dtable.AddRow("Score", "?")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dtable.AddRow("Vector", nvd.CvssVector())
 | 
			
		||||
	dtable.AddRow("Summary", nvd.Summary)
 | 
			
		||||
	dtable.AddRow("Summary", nvd.CveSummary())
 | 
			
		||||
	dtable.AddRow("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))
 | 
			
		||||
@@ -306,18 +388,26 @@ func distroLinks(cveInfo models.CveInfo, osFamily string) []distroLink {
 | 
			
		||||
			},
 | 
			
		||||
			//  TODO Debian dsa
 | 
			
		||||
		}
 | 
			
		||||
	case "FreeBSD":
 | 
			
		||||
		links := []distroLink{}
 | 
			
		||||
		for _, advisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			links = append(links, distroLink{
 | 
			
		||||
				"FreeBSD-VuXML",
 | 
			
		||||
				fmt.Sprintf(freeBSDVuXMLBaseURL, advisory.AdvisoryID),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	default:
 | 
			
		||||
		return []distroLink{}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//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())
 | 
			
		||||
@@ -326,9 +416,18 @@ 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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -31,9 +36,29 @@ const (
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										255
									
								
								scan/base.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								scan/base.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,255 @@
 | 
			
		||||
/* 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"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type base struct {
 | 
			
		||||
	ServerInfo config.ServerInfo
 | 
			
		||||
	Distro     config.Distro
 | 
			
		||||
	Platform   models.Platform
 | 
			
		||||
 | 
			
		||||
	lackDependencies []string
 | 
			
		||||
	osPackages
 | 
			
		||||
 | 
			
		||||
	log  *logrus.Entry
 | 
			
		||||
	errs []error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) ssh(cmd string, sudo bool) sshResult {
 | 
			
		||||
	return sshExec(l.ServerInfo, cmd, sudo, l.log)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setServerInfo(c config.ServerInfo) {
 | 
			
		||||
	l.ServerInfo = c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getServerInfo() config.ServerInfo {
 | 
			
		||||
	return l.ServerInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) 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) getDistro() config.Distro {
 | 
			
		||||
	return l.Distro
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setPlatform(p models.Platform) {
 | 
			
		||||
	l.Platform = p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getPlatform() models.Platform {
 | 
			
		||||
	return l.Platform
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getLackDependencies() []string {
 | 
			
		||||
	return l.lackDependencies
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) allContainers() (containers []config.Container, err error) {
 | 
			
		||||
	switch l.ServerInfo.Container.Type {
 | 
			
		||||
	case "", "docker":
 | 
			
		||||
		stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}}'")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseDockerPs(stdout)
 | 
			
		||||
	default:
 | 
			
		||||
		return containers, fmt.Errorf(
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Container.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) runningContainers() (containers []config.Container, err error) {
 | 
			
		||||
	switch l.ServerInfo.Container.Type {
 | 
			
		||||
	case "", "docker":
 | 
			
		||||
		stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}}'")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseDockerPs(stdout)
 | 
			
		||||
	default:
 | 
			
		||||
		return containers, fmt.Errorf(
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Container.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) exitedContainers() (containers []config.Container, err error) {
 | 
			
		||||
	switch l.ServerInfo.Container.Type {
 | 
			
		||||
	case "", "docker":
 | 
			
		||||
		stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}}'")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseDockerPs(stdout)
 | 
			
		||||
	default:
 | 
			
		||||
		return containers, fmt.Errorf(
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Container.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) dockerPs(option string) (string, error) {
 | 
			
		||||
	cmd := fmt.Sprintf("docker ps %s", option)
 | 
			
		||||
	r := l.ssh(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return "", fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return r.Stdout, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) parseDockerPs(stdout string) (containers []config.Container, err error) {
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		fields := strings.Fields(line)
 | 
			
		||||
		if len(fields) == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if len(fields) != 2 {
 | 
			
		||||
			return containers, fmt.Errorf("Unknown format: %s", line)
 | 
			
		||||
		}
 | 
			
		||||
		containers = append(containers, config.Container{
 | 
			
		||||
			ContainerID: fields[0],
 | 
			
		||||
			Name:        fields[1],
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) detectPlatform() error {
 | 
			
		||||
	ok, instanceID, err := l.detectRunningOnAws()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if ok {
 | 
			
		||||
		l.setPlatform(models.Platform{
 | 
			
		||||
			Name:       "aws",
 | 
			
		||||
			InstanceID: instanceID,
 | 
			
		||||
		})
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//TODO Azure, GCP...
 | 
			
		||||
	l.setPlatform(models.Platform{
 | 
			
		||||
		Name: "other",
 | 
			
		||||
	})
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) detectRunningOnAws() (ok bool, instanceID string, err error) {
 | 
			
		||||
	if r := l.ssh("type curl", noSudo); r.isSuccess() {
 | 
			
		||||
		cmd := "curl --max-time 1 --retry 3 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id"
 | 
			
		||||
		r := l.ssh(cmd, noSudo)
 | 
			
		||||
		if r.isSuccess() {
 | 
			
		||||
			id := strings.TrimSpace(r.Stdout)
 | 
			
		||||
			if !l.isAwsInstanceID(id) {
 | 
			
		||||
				return false, "", nil
 | 
			
		||||
			}
 | 
			
		||||
			return true, id, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch r.ExitStatus {
 | 
			
		||||
		case 28, 7:
 | 
			
		||||
			// Not running on AWS
 | 
			
		||||
			//  7   Failed to connect to host.
 | 
			
		||||
			// 28  operation timeout.
 | 
			
		||||
			return false, "", nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := l.ssh("type wget", noSudo); r.isSuccess() {
 | 
			
		||||
		cmd := "wget --tries=3 --timeout=1 --no-proxy -q -O - http://169.254.169.254/latest/meta-data/instance-id"
 | 
			
		||||
		r := l.ssh(cmd, noSudo)
 | 
			
		||||
		if r.isSuccess() {
 | 
			
		||||
			id := strings.TrimSpace(r.Stdout)
 | 
			
		||||
			if !l.isAwsInstanceID(id) {
 | 
			
		||||
				return false, "", nil
 | 
			
		||||
			}
 | 
			
		||||
			return true, id, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch r.ExitStatus {
 | 
			
		||||
		case 4, 8:
 | 
			
		||||
			// Not running on AWS
 | 
			
		||||
			// 4   Network failure
 | 
			
		||||
			// 8   Server issued an error response.
 | 
			
		||||
			return false, "", nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false, "", fmt.Errorf(
 | 
			
		||||
		"Failed to curl or wget to AWS instance metadata on %s. container: %s",
 | 
			
		||||
		l.ServerInfo.ServerName, l.ServerInfo.Container.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/resource-ids.html
 | 
			
		||||
var awsInstanceIDPattern = regexp.MustCompile(`^i-[0-9a-f]+$`)
 | 
			
		||||
 | 
			
		||||
func (l base) isAwsInstanceID(str string) bool {
 | 
			
		||||
	return awsInstanceIDPattern.MatchString(str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) convertToModel() (models.ScanResult, error) {
 | 
			
		||||
	for _, p := range l.VulnInfos {
 | 
			
		||||
		sort.Sort(models.PackageInfosByName(p.Packages))
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(l.VulnInfos)
 | 
			
		||||
 | 
			
		||||
	container := models.Container{
 | 
			
		||||
		ContainerID: l.ServerInfo.Container.ContainerID,
 | 
			
		||||
		Name:        l.ServerInfo.Container.Name,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return models.ScanResult{
 | 
			
		||||
		ServerName:  l.ServerInfo.ServerName,
 | 
			
		||||
		ScannedAt:   time.Now(),
 | 
			
		||||
		Family:      l.Distro.Family,
 | 
			
		||||
		Release:     l.Distro.Release,
 | 
			
		||||
		Container:   container,
 | 
			
		||||
		Platform:    l.Platform,
 | 
			
		||||
		ScannedCves: l.VulnInfos,
 | 
			
		||||
		Packages:    l.Packages,
 | 
			
		||||
		Optional:    l.ServerInfo.Optional,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setErrs(errs []error) {
 | 
			
		||||
	l.errs = errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getErrs() []error {
 | 
			
		||||
	return l.errs
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										80
									
								
								scan/base_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								scan/base_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseDockerPs(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []config.Container
 | 
			
		||||
	}{
 | 
			
		||||
		`c7ca0992415a romantic_goldberg
 | 
			
		||||
f570ae647edc agitated_lovelace`,
 | 
			
		||||
		[]config.Container{
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "c7ca0992415a",
 | 
			
		||||
				Name:        "romantic_goldberg",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "f570ae647edc",
 | 
			
		||||
				Name:        "agitated_lovelace",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	actual, err := r.parseDockerPs(test.in)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Error occurred. in: %s, err: %s", test.in, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for i, e := range test.expected {
 | 
			
		||||
		if !reflect.DeepEqual(e, actual[i]) {
 | 
			
		||||
			t.Errorf("expected %v, actual %v", e, actual[i])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										546
									
								
								scan/debian.go
									
									
									
									
									
								
							
							
						
						
									
										546
									
								
								scan/debian.go
									
									
									
									
									
								
							@@ -20,42 +20,44 @@ package scan
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type debian struct {
 | 
			
		||||
	linux
 | 
			
		||||
	base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewDebian is constructor
 | 
			
		||||
func newDebian(c config.ServerInfo) *debian {
 | 
			
		||||
	d := &debian{}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ubuntu, Debian
 | 
			
		||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/debian.rb
 | 
			
		||||
func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface) {
 | 
			
		||||
 | 
			
		||||
func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err error) {
 | 
			
		||||
	deb = newDebian(c)
 | 
			
		||||
 | 
			
		||||
	// set sudo option flag
 | 
			
		||||
	c.SudoOpt = config.SudoOption{ExecBySudo: true}
 | 
			
		||||
	deb.setServerInfo(c)
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
 | 
			
		||||
		Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return false, deb
 | 
			
		||||
		if r.Error != nil {
 | 
			
		||||
			return false, deb, r.Error
 | 
			
		||||
		}
 | 
			
		||||
		if r.ExitStatus == 255 {
 | 
			
		||||
			return false, deb, fmt.Errorf(
 | 
			
		||||
				"Unable to connect via SSH. Check SSH settings. %s", r)
 | 
			
		||||
		}
 | 
			
		||||
		Log.Debugf("Not Debian like Linux. %s", r)
 | 
			
		||||
		return false, deb, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "lsb_release -ir", noSudo); r.isSuccess() {
 | 
			
		||||
@@ -63,20 +65,18 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface) {
 | 
			
		||||
		//  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")
 | 
			
		||||
			deb.setDistro("debian/ubuntu", "unknown")
 | 
			
		||||
			Log.Warnf(
 | 
			
		||||
				"Unknown Debian/Ubuntu version. lsb_release -ir: %s, Host: %s:%s",
 | 
			
		||||
				r.Stdout, c.Host, c.Port)
 | 
			
		||||
				"Unknown Debian/Ubuntu version. lsb_release -ir: %s", r)
 | 
			
		||||
		} else {
 | 
			
		||||
			distro := strings.ToLower(trim(result[1]))
 | 
			
		||||
			deb.setDistributionInfo(distro, trim(result[2]))
 | 
			
		||||
			deb.setDistro(distro, trim(result[2]))
 | 
			
		||||
		}
 | 
			
		||||
		return true, deb
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
 | 
			
		||||
@@ -85,81 +85,87 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface) {
 | 
			
		||||
		//  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(
 | 
			
		||||
				"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s, Host: %s:%s",
 | 
			
		||||
				r.Stdout, c.Host, c.Port)
 | 
			
		||||
			deb.setDistributionInfo("debian/ubuntu", "unknown")
 | 
			
		||||
				"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s", r)
 | 
			
		||||
			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
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Debian
 | 
			
		||||
	cmd := "cat /etc/debian_version"
 | 
			
		||||
	if r := sshExec(c, cmd, noSudo); r.isSuccess() {
 | 
			
		||||
		deb.setDistributionInfo("debian", trim(r.Stdout))
 | 
			
		||||
		return true, deb
 | 
			
		||||
		deb.setDistro("debian", trim(r.Stdout))
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
	return false, deb
 | 
			
		||||
	Log.Debugf("Not Debian like Linux: %s", c.ServerName)
 | 
			
		||||
	return false, deb, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func trim(str string) string {
 | 
			
		||||
	return strings.TrimSpace(str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) checkIfSudoNoPasswd() error {
 | 
			
		||||
	r := o.ssh("apt-get -v", sudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		o.log.Errorf("sudo error on %s", r)
 | 
			
		||||
		return fmt.Errorf("Failed to sudo: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Infof("sudo ... OK")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) checkDependencies() error {
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		return nil
 | 
			
		||||
 | 
			
		||||
	case "debian":
 | 
			
		||||
		// Debian needs aptitude to get changelogs.
 | 
			
		||||
		// Because unable to get changelogs via apt-get changelog on Debian.
 | 
			
		||||
		name := "aptitude"
 | 
			
		||||
		cmd := name + " -h"
 | 
			
		||||
		if r := o.ssh(cmd, noSudo); !r.isSuccess() {
 | 
			
		||||
			o.lackDependencies = []string{name}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("Not implemented yet: %s", o.Distro)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) install() error {
 | 
			
		||||
	if len(o.lackDependencies) == 0 {
 | 
			
		||||
		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 %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		msg := fmt.Sprintf("Failed to SSH: %s", r)
 | 
			
		||||
		o.log.Errorf(msg)
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.Family == "debian" {
 | 
			
		||||
		// install aptitude
 | 
			
		||||
		cmd = util.PrependProxyEnv("apt-get install --force-yes -y aptitude")
 | 
			
		||||
	for _, name := range o.lackDependencies {
 | 
			
		||||
		cmd = util.PrependProxyEnv("apt-get install -y " + name)
 | 
			
		||||
		if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
			msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
				cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
			msg := fmt.Sprintf("Failed to SSH: %s", r)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
		o.log.Infof("Installed: aptitude")
 | 
			
		||||
		o.log.Infof("Installed: " + name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// install unattended-upgrades
 | 
			
		||||
	if !config.Conf.UseUnattendedUpgrades {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := o.ssh("type unattended-upgrade", noSudo); r.isSuccess() {
 | 
			
		||||
		o.log.Infof(
 | 
			
		||||
			"Ignored: unattended-upgrade already installed")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd = util.PrependProxyEnv(
 | 
			
		||||
		"apt-get install --force-yes -y unattended-upgrades")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		o.log.Errorf(msg)
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Infof("Installed: unattended-upgrades")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -172,21 +178,19 @@ 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 valnerable packages")
 | 
			
		||||
		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)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return packs, fmt.Errorf(
 | 
			
		||||
			"Failed to scan packages. status: %d, stdout:%s, stderr: %s",
 | 
			
		||||
			r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		return packs, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//  e.g.
 | 
			
		||||
@@ -195,7 +199,7 @@ func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error)
 | 
			
		||||
	lines := strings.Split(r.Stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
 | 
			
		||||
			name, version, err := o.parseScanedPackagesLine(trimmed)
 | 
			
		||||
			name, version, err := o.parseScannedPackagesLine(trimmed)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, fmt.Errorf(
 | 
			
		||||
					"Debian: Failed to parse package line: %s", line)
 | 
			
		||||
@@ -209,12 +213,16 @@ func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) parseScanedPackagesLine(line string) (name, version string, err error) {
 | 
			
		||||
	re, _ := regexp.Compile(`^([^\t']+)\t(.+)$`)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
var packageLinePattern = regexp.MustCompile(`^([^\t']+)\t(.+)$`)
 | 
			
		||||
 | 
			
		||||
func (o *debian) parseScannedPackagesLine(line string) (name, version string, err error) {
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
@@ -222,174 +230,126 @@ func (o *debian) parseScanedPackagesLine(line string) (name, version string, err
 | 
			
		||||
	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", sudo); !r.isSuccess() {
 | 
			
		||||
			msg := "aptitude is not installed"
 | 
			
		||||
	if o.Distro.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 := "unattended-upgrade is not installed"
 | 
			
		||||
		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() {
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr,
 | 
			
		||||
		)
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var upgradablePackNames []string
 | 
			
		||||
	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)
 | 
			
		||||
	if 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)
 | 
			
		||||
	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("apt-cache policy %s", p.Name)
 | 
			
		||||
					r := o.ssh(cmd, sudo)
 | 
			
		||||
					if !r.isSuccess() {
 | 
			
		||||
						errChan <- fmt.Errorf(
 | 
			
		||||
							"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
							cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
						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)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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:
 | 
			
		||||
			return nil, err
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return nil, fmt.Errorf("Timeout fillCandidateVersion.")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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) error {
 | 
			
		||||
	// Search from cache
 | 
			
		||||
	old, 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)
 | 
			
		||||
		return fmt.Errorf("Failed to get meta. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	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)
 | 
			
		||||
	if !found {
 | 
			
		||||
		o.log.Debugf("Not found in meta: %s", current.Name)
 | 
			
		||||
		err = cache.DB.EnsureBuckets(current)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to ensure buckets. err: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if current.Distro.Family != old.Distro.Family ||
 | 
			
		||||
			current.Distro.Release != old.Distro.Release {
 | 
			
		||||
			o.log.Debugf("Need to refesh meta: %s", current.Name)
 | 
			
		||||
			err = cache.DB.EnsureBuckets(current)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to ensure buckets. err: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			o.log.Debugf("Reuse meta: %s", current.Name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config.Conf.Debug {
 | 
			
		||||
		cache.DB.PrettyPrint(current)
 | 
			
		||||
	}
 | 
			
		||||
	return 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.ssh(cmd, sudo)
 | 
			
		||||
	if r.isSuccess(0, 1) {
 | 
			
		||||
		packNames = strings.Split(strings.TrimSpace(r.Stdout), " ")
 | 
			
		||||
		return packNames, nil
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return packNames, fmt.Errorf(
 | 
			
		||||
		"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
		cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
	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("apt-get upgrade --dry-run")
 | 
			
		||||
	cmd := util.PrependProxyEnv("LANGUAGE=en_US.UTF-8 apt-get upgrade --dry-run")
 | 
			
		||||
	r := o.ssh(cmd, sudo)
 | 
			
		||||
	if r.isSuccess(0, 1) {
 | 
			
		||||
		return o.parseAptGetUpgrade(r.Stdout)
 | 
			
		||||
@@ -400,8 +360,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")
 | 
			
		||||
@@ -441,98 +401,127 @@ 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)
 | 
			
		||||
func (o *debian) scanVulnInfos(upgradablePacks []models.PackageInfo) (models.VulnInfos, error) {
 | 
			
		||||
	meta := cache.Meta{
 | 
			
		||||
		Name:   o.getServerInfo().GetServerName(),
 | 
			
		||||
		Distro: o.getServerInfo().Distro,
 | 
			
		||||
		Packs:  upgradablePacks,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type strarray []string
 | 
			
		||||
	resChan := make(chan struct {
 | 
			
		||||
		models.PackageInfo
 | 
			
		||||
		strarray
 | 
			
		||||
	}, len(unsecurePacks))
 | 
			
		||||
	errChan := make(chan error, len(unsecurePacks))
 | 
			
		||||
	reqChan := make(chan models.PackageInfo, len(unsecurePacks))
 | 
			
		||||
	}, len(upgradablePacks))
 | 
			
		||||
	errChan := make(chan error, len(upgradablePacks))
 | 
			
		||||
	reqChan := make(chan models.PackageInfo, len(upgradablePacks))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, pack := range unsecurePacks {
 | 
			
		||||
		for _, pack := range upgradablePacks {
 | 
			
		||||
			reqChan <- pack
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	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) {
 | 
			
		||||
					if cveIds, err := o.scanPackageCveIds(p); err != nil {
 | 
			
		||||
					changelog := o.getChangelogCache(meta, p)
 | 
			
		||||
					if 0 < len(changelog) {
 | 
			
		||||
						cveIDs := o.getCveIDFromChangelog(changelog, p.Name, p.Version)
 | 
			
		||||
						resChan <- struct {
 | 
			
		||||
							models.PackageInfo
 | 
			
		||||
							strarray
 | 
			
		||||
						}{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.
 | 
			
		||||
					if cveIDs, err := o.scanPackageCveIDs(p); err != nil {
 | 
			
		||||
						errChan <- err
 | 
			
		||||
					} else {
 | 
			
		||||
						resChan <- struct {
 | 
			
		||||
							models.PackageInfo
 | 
			
		||||
							strarray
 | 
			
		||||
						}{p, cveIds}
 | 
			
		||||
						}{p, cveIDs}
 | 
			
		||||
					}
 | 
			
		||||
				}(pack)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < len(unsecurePacks); i++ {
 | 
			
		||||
	// { CVE ID: [packageInfo] }
 | 
			
		||||
	cvePackages := make(map[string][]models.PackageInfo)
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
	for i := 0; i < len(upgradablePacks); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case pair := <-resChan:
 | 
			
		||||
			pack := pair.PackageInfo
 | 
			
		||||
			cveIds := pair.strarray
 | 
			
		||||
			for _, cveID := range cveIds {
 | 
			
		||||
			cveIDs := pair.strarray
 | 
			
		||||
			for _, cveID := range cveIDs {
 | 
			
		||||
				cvePackages[cveID] = appendPackIfMissing(cvePackages[cveID], pack)
 | 
			
		||||
			}
 | 
			
		||||
			o.log.Infof("(%d/%d) Scanned %s-%s : %s",
 | 
			
		||||
				i+1, len(unsecurePacks), pair.Name, pair.PackageInfo.Version, cveIds)
 | 
			
		||||
				i+1, len(upgradablePacks), pair.Name, pair.PackageInfo.Version, cveIDs)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return nil, fmt.Errorf("Timeout scanPackageCveIds.")
 | 
			
		||||
			errs = append(errs, fmt.Errorf("Timeout scanPackageCveIDs"))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		return nil, fmt.Errorf("%v", errs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cveIds []string
 | 
			
		||||
	var cveIDs []string
 | 
			
		||||
	for k := range cvePackages {
 | 
			
		||||
		cveIds = append(cveIds, k)
 | 
			
		||||
		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),
 | 
			
		||||
	o.log.Debugf("%d Cves are found. cves: %v", len(cveIDs), cveIDs)
 | 
			
		||||
	var vinfos models.VulnInfos
 | 
			
		||||
	for k, v := range cvePackages {
 | 
			
		||||
		vinfos = append(vinfos, models.VulnInfo{
 | 
			
		||||
			CveID:    k,
 | 
			
		||||
			Packages: v,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(CvePacksList(cvePacksList))
 | 
			
		||||
	return
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackageCveIds(pack models.PackageInfo) (cveIds []string, err error) {
 | 
			
		||||
func (o *debian) getChangelogCache(meta cache.Meta, pack models.PackageInfo) string {
 | 
			
		||||
	cachedPack, found := meta.FindPack(pack.Name)
 | 
			
		||||
	if !found {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	if 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 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Debugf("Cache hit: %s, len: %d, %s...",
 | 
			
		||||
		meta.Name, len(changelog), util.Truncate(changelog, 30))
 | 
			
		||||
	return changelog
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackageCveIDs(pack models.PackageInfo) ([]string, error) {
 | 
			
		||||
	cmd := ""
 | 
			
		||||
	switch o.Family {
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		cmd = fmt.Sprintf(`apt-get changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
 | 
			
		||||
	case "debian":
 | 
			
		||||
@@ -542,47 +531,45 @@ func (o *debian) scanPackageCveIds(pack models.PackageInfo) (cveIds []string, er
 | 
			
		||||
 | 
			
		||||
	r := o.ssh(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		o.log.Warnf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		o.log.Warnf("Failed to SSH: %s", r)
 | 
			
		||||
		// Ignore this Error.
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if 0 < len(strings.TrimSpace(r.Stdout)) {
 | 
			
		||||
		err := cache.DB.PutChangelog(o.getServerInfo().GetServerName(), pack.Name, r.Stdout)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("Failed to put changelog into cache")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	cveIds, err = o.getCveIDParsingChangelog(r.Stdout, pack.Name, pack.Version)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		trimUbuntu := strings.Split(pack.Version, "ubuntu")[0]
 | 
			
		||||
		return o.getCveIDParsingChangelog(r.Stdout, pack.Name, trimUbuntu)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
	// No error will be returned. Only logging.
 | 
			
		||||
	return o.getCveIDFromChangelog(r.Stdout, pack.Name, pack.Version), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) getCveIDParsingChangelog(changelog string,
 | 
			
		||||
	packName string, versionOrLater string) (cveIDs []string, err error) {
 | 
			
		||||
func (o *debian) getCveIDFromChangelog(changelog string,
 | 
			
		||||
	packName string, versionOrLater string) []string {
 | 
			
		||||
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, versionOrLater)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	if cveIDs, err := o.parseChangelog(changelog, packName, versionOrLater); err == nil {
 | 
			
		||||
		return cveIDs
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ver := strings.Split(versionOrLater, "ubuntu")[0]
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, ver)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	if cveIDs, err := o.parseChangelog(changelog, packName, ver); err == nil {
 | 
			
		||||
		return cveIDs
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	splittedByColon := strings.Split(versionOrLater, ":")
 | 
			
		||||
	if 1 < len(splittedByColon) {
 | 
			
		||||
		ver = splittedByColon[1]
 | 
			
		||||
	}
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, ver)
 | 
			
		||||
	cveIDs, err := o.parseChangelog(changelog, packName, ver)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
		return cveIDs
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//TODO report as unable to parse changelog.
 | 
			
		||||
	o.log.Warn(err)
 | 
			
		||||
	return []string{}, nil
 | 
			
		||||
	// Only logging the error.
 | 
			
		||||
	o.log.Error(err)
 | 
			
		||||
	return []string{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Collect CVE-IDs included in the changelog.
 | 
			
		||||
@@ -590,8 +577,8 @@ func (o *debian) getCveIDParsingChangelog(changelog string,
 | 
			
		||||
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)))
 | 
			
		||||
	cveRe := regexp.MustCompile(`(CVE-\d{4}-\d{4,})`)
 | 
			
		||||
	stopRe := regexp.MustCompile(fmt.Sprintf(`\(%s\)`, regexp.QuoteMeta(versionOrLater)))
 | 
			
		||||
	stopLineFound := false
 | 
			
		||||
	lines := strings.Split(changelog, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
@@ -599,7 +586,7 @@ func (o *debian) parseChangelog(changelog string,
 | 
			
		||||
			o.log.Debugf("Found the stop line. 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)
 | 
			
		||||
			}
 | 
			
		||||
@@ -615,6 +602,29 @@ func (o *debian) parseChangelog(changelog string,
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) splitAptCachePolicy(stdout string) map[string]string {
 | 
			
		||||
	//  re := regexp.MustCompile(`(?m:^[^ \t]+:$)`)
 | 
			
		||||
	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 {
 | 
			
		||||
	Name      string
 | 
			
		||||
	Installed string
 | 
			
		||||
 
 | 
			
		||||
@@ -18,14 +18,18 @@ 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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseScanedPackagesLineDebian(t *testing.T) {
 | 
			
		||||
func TestParseScannedPackagesLineDebian(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var packagetests = []struct {
 | 
			
		||||
		in      string
 | 
			
		||||
@@ -43,7 +47,7 @@ func TestParseScanedPackagesLineDebian(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range packagetests {
 | 
			
		||||
		n, v, _ := d.parseScanedPackagesLine(tt.in)
 | 
			
		||||
		n, v, _ := d.parseScannedPackagesLine(tt.in)
 | 
			
		||||
		if n != tt.name {
 | 
			
		||||
			t.Errorf("name: expected %s, actual %s", tt.name, n)
 | 
			
		||||
		}
 | 
			
		||||
@@ -54,7 +58,7 @@ func TestParseScanedPackagesLineDebian(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestgetCveIDParsingChangelog(t *testing.T) {
 | 
			
		||||
func TestGetCveIDParsingChangelog(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       []string
 | 
			
		||||
@@ -86,12 +90,11 @@ systemd (227-1) unstable; urgency=medium`,
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// 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
 | 
			
		||||
@@ -110,7 +113,6 @@ pcre3 (2:8.35-7) unstable; urgency=medium`,
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
@@ -151,7 +153,7 @@ sysvinit (2.88dsf-57) unstable; urgency=low`,
 | 
			
		||||
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() /
 | 
			
		||||
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
 | 
			
		||||
@@ -178,15 +180,17 @@ util-linux (2.26.2-6) unstable; urgency=medium`,
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
				"CVE-2016-1000000",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, _ := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], tt.in[1])
 | 
			
		||||
		actual := d.getCveIDFromChangelog(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)
 | 
			
		||||
			t.Errorf(pp.Sprintf("%s", tt.in))
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		for i := range tt.expected {
 | 
			
		||||
@@ -195,13 +199,6 @@ util-linux (2.26.2-6) unstable; urgency=medium`,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		_, err := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], "version number do'nt match case")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Returning error is unexpected.")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetUpdatablePackNames(t *testing.T) {
 | 
			
		||||
@@ -504,7 +501,7 @@ Calculating upgrade... Done
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, err := d.parseAptGetUpgrade(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Returning error is unexpected.")
 | 
			
		||||
			t.Errorf("Returning error is unexpected")
 | 
			
		||||
		}
 | 
			
		||||
		if len(tt.expected) != len(actual) {
 | 
			
		||||
			t.Errorf("Result length is not as same as expected. expected: %d, actual: %d", len(tt.expected), len(actual))
 | 
			
		||||
@@ -520,6 +517,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 {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										250
									
								
								scan/freebsd.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								scan/freebsd.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,250 @@
 | 
			
		||||
/* 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"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type bsd struct {
 | 
			
		||||
	base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewBSD constructor
 | 
			
		||||
func newBsd(c config.ServerInfo) *bsd {
 | 
			
		||||
	d := &bsd{}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	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)
 | 
			
		||||
 | 
			
		||||
	// Prevent from adding `set -o pipefail` option
 | 
			
		||||
	c.Distro = config.Distro{Family: "FreeBSD"}
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "uname", noSudo); r.isSuccess() {
 | 
			
		||||
		if strings.Contains(r.Stdout, "FreeBSD") == true {
 | 
			
		||||
			if b := sshExec(c, "uname -r", noSudo); b.isSuccess() {
 | 
			
		||||
				rel := strings.TrimSpace(b.Stdout)
 | 
			
		||||
				bsd.setDistro("FreeBSD", rel)
 | 
			
		||||
				return true, bsd
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	Log.Debugf("Not FreeBSD. servernam: %s", c.ServerName)
 | 
			
		||||
	return false, bsd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) checkIfSudoNoPasswd() error {
 | 
			
		||||
	// FreeBSD doesn't need root privilege
 | 
			
		||||
	o.log.Infof("sudo ... OK")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) checkDependencies() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) install() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) checkRequiredPackagesInstalled() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanPackages() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	var packs []models.PackageInfo
 | 
			
		||||
	if packs, err = o.scanInstalledPackages(); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setPackages(packs)
 | 
			
		||||
 | 
			
		||||
	var vinfos []models.VulnInfo
 | 
			
		||||
	if vinfos, err = o.scanUnsecurePackages(); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan vulnerable packages")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setVulnInfos(vinfos)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanInstalledPackages() ([]models.PackageInfo, error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("pkg version -v")
 | 
			
		||||
	r := o.ssh(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.parsePkgVersion(r.Stdout), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanUnsecurePackages() (vulnInfos []models.VulnInfo, err error) {
 | 
			
		||||
	const vulndbPath = "/tmp/vuln.db"
 | 
			
		||||
	cmd := "rm -f " + vulndbPath
 | 
			
		||||
	r := o.ssh(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess(0) {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd = util.PrependProxyEnv("pkg audit -F -r -f " + vulndbPath)
 | 
			
		||||
	r = o.ssh(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess(0, 1) {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	if r.ExitStatus == 0 {
 | 
			
		||||
		// no vulnerabilities
 | 
			
		||||
		return []models.VulnInfo{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var packAdtRslt []pkgAuditResult
 | 
			
		||||
	blocks := o.splitIntoBlocks(r.Stdout)
 | 
			
		||||
	for _, b := range blocks {
 | 
			
		||||
		name, cveIDs, vulnID := o.parseBlock(b)
 | 
			
		||||
		if len(cveIDs) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pack, found := o.Packages.FindByName(name)
 | 
			
		||||
		if !found {
 | 
			
		||||
			return nil, fmt.Errorf("Vulnerable package: %s is not found", name)
 | 
			
		||||
		}
 | 
			
		||||
		packAdtRslt = append(packAdtRslt, pkgAuditResult{
 | 
			
		||||
			pack: pack,
 | 
			
		||||
			vulnIDCveIDs: vulnIDCveIDs{
 | 
			
		||||
				vulnID: vulnID,
 | 
			
		||||
				cveIDs: cveIDs,
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// { CVE ID: []pkgAuditResult }
 | 
			
		||||
	cveIDAdtMap := make(map[string][]pkgAuditResult)
 | 
			
		||||
	for _, p := range packAdtRslt {
 | 
			
		||||
		for _, cid := range p.vulnIDCveIDs.cveIDs {
 | 
			
		||||
			cveIDAdtMap[cid] = append(cveIDAdtMap[cid], p)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for k := range cveIDAdtMap {
 | 
			
		||||
		packs := []models.PackageInfo{}
 | 
			
		||||
		for _, r := range cveIDAdtMap[k] {
 | 
			
		||||
			packs = append(packs, r.pack)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		disAdvs := []models.DistroAdvisory{}
 | 
			
		||||
		for _, r := range cveIDAdtMap[k] {
 | 
			
		||||
			disAdvs = append(disAdvs, models.DistroAdvisory{
 | 
			
		||||
				AdvisoryID: r.vulnIDCveIDs.vulnID,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		vulnInfos = append(vulnInfos, models.VulnInfo{
 | 
			
		||||
			CveID:            k,
 | 
			
		||||
			Packages:         packs,
 | 
			
		||||
			DistroAdvisories: disAdvs,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) parsePkgVersion(stdout string) (packs []models.PackageInfo) {
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, l := range lines {
 | 
			
		||||
		fields := strings.Fields(l)
 | 
			
		||||
		if len(fields) < 2 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		packVer := fields[0]
 | 
			
		||||
		splitted := strings.Split(packVer, "-")
 | 
			
		||||
		ver := splitted[len(splitted)-1]
 | 
			
		||||
		name := strings.Join(splitted[:len(splitted)-1], "-")
 | 
			
		||||
 | 
			
		||||
		switch fields[1] {
 | 
			
		||||
		case "?", "=":
 | 
			
		||||
			packs = append(packs, models.PackageInfo{
 | 
			
		||||
				Name:    name,
 | 
			
		||||
				Version: ver,
 | 
			
		||||
			})
 | 
			
		||||
		case "<":
 | 
			
		||||
			candidate := strings.TrimSuffix(fields[6], ")")
 | 
			
		||||
			packs = append(packs, models.PackageInfo{
 | 
			
		||||
				Name:       name,
 | 
			
		||||
				Version:    ver,
 | 
			
		||||
				NewVersion: candidate,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type vulnIDCveIDs struct {
 | 
			
		||||
	vulnID string
 | 
			
		||||
	cveIDs []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type pkgAuditResult struct {
 | 
			
		||||
	pack         models.PackageInfo
 | 
			
		||||
	vulnIDCveIDs vulnIDCveIDs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) splitIntoBlocks(stdout string) (blocks []string) {
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	block := []string{}
 | 
			
		||||
	for _, l := range lines {
 | 
			
		||||
		if len(strings.TrimSpace(l)) == 0 {
 | 
			
		||||
			if 0 < len(block) {
 | 
			
		||||
				blocks = append(blocks, strings.Join(block, "\n"))
 | 
			
		||||
				block = []string{}
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		block = append(block, strings.TrimSpace(l))
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(block) {
 | 
			
		||||
		blocks = append(blocks, strings.Join(block, "\n"))
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) parseBlock(block string) (packName string, cveIDs []string, vulnID string) {
 | 
			
		||||
	lines := strings.Split(block, "\n")
 | 
			
		||||
	for _, l := range lines {
 | 
			
		||||
		if strings.HasSuffix(l, " is vulnerable:") {
 | 
			
		||||
			packVer := strings.Fields(l)[0]
 | 
			
		||||
			splitted := strings.Split(packVer, "-")
 | 
			
		||||
			packName = strings.Join(splitted[:len(splitted)-1], "-")
 | 
			
		||||
		} else if strings.HasPrefix(l, "CVE:") {
 | 
			
		||||
			cveIDs = append(cveIDs, strings.Fields(l)[1])
 | 
			
		||||
		} else if strings.HasPrefix(l, "WWW:") {
 | 
			
		||||
			splitted := strings.Split(l, "/")
 | 
			
		||||
			vulnID = strings.TrimSuffix(splitted[len(splitted)-1], ".html")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										155
									
								
								scan/freebsd_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								scan/freebsd_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParsePkgVersion(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []models.PackageInfo
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			`Updating FreeBSD repository catalogue...
 | 
			
		||||
FreeBSD repository is up-to-date.
 | 
			
		||||
All repositories are up-to-date.
 | 
			
		||||
bash-4.2.45                        <   needs updating (remote has 4.3.42_1)
 | 
			
		||||
gettext-0.18.3.1                   <   needs updating (remote has 0.19.7)
 | 
			
		||||
tcl84-8.4.20_2,1                   =   up-to-date with remote
 | 
			
		||||
teTeX-base-3.0_25                  ?   orphaned: print/teTeX-base`,
 | 
			
		||||
 | 
			
		||||
			[]models.PackageInfo{
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "bash",
 | 
			
		||||
					Version:    "4.2.45",
 | 
			
		||||
					NewVersion: "4.3.42_1",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "gettext",
 | 
			
		||||
					Version:    "0.18.3.1",
 | 
			
		||||
					NewVersion: "0.19.7",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "tcl84",
 | 
			
		||||
					Version: "8.4.20_2,1",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "teTeX-base",
 | 
			
		||||
					Version: "3.0_25",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newBsd(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := d.parsePkgVersion(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(tt.expected, actual) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.expected)
 | 
			
		||||
			a := pp.Sprintf("%v", actual)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSplitIntoBlocks(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			`
 | 
			
		||||
block1
 | 
			
		||||
 | 
			
		||||
block2
 | 
			
		||||
block2
 | 
			
		||||
block2
 | 
			
		||||
 | 
			
		||||
block3
 | 
			
		||||
block3`,
 | 
			
		||||
			[]string{
 | 
			
		||||
				`block1`,
 | 
			
		||||
				"block2\nblock2\nblock2",
 | 
			
		||||
				"block3\nblock3",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newBsd(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := d.splitIntoBlocks(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(tt.expected, actual) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.expected)
 | 
			
		||||
			a := pp.Sprintf("%v", actual)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseBlock(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in     string
 | 
			
		||||
		name   string
 | 
			
		||||
		cveIDs []string
 | 
			
		||||
		vulnID string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
 | 
			
		||||
			in: `vulnxml file up-to-date
 | 
			
		||||
bind96-9.6.3.2.ESV.R10_2 is vulnerable:
 | 
			
		||||
bind -- denial of service vulnerability
 | 
			
		||||
CVE: CVE-2014-0591
 | 
			
		||||
WWW: https://vuxml.FreeBSD.org/freebsd/cb252f01-7c43-11e3-b0a6-005056a37f68.html`,
 | 
			
		||||
			name:   "bind96",
 | 
			
		||||
			cveIDs: []string{"CVE-2014-0591"},
 | 
			
		||||
			vulnID: "cb252f01-7c43-11e3-b0a6-005056a37f68",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: `bind96-9.6.3.2.ESV.R10_2 is vulnerable:
 | 
			
		||||
bind -- denial of service vulnerability
 | 
			
		||||
CVE: CVE-2014-8680
 | 
			
		||||
CVE: CVE-2014-8500
 | 
			
		||||
WWW: https://vuxml.FreeBSD.org/freebsd/ab3e98d9-8175-11e4-907d-d050992ecde8.html`,
 | 
			
		||||
			name:   "bind96",
 | 
			
		||||
			cveIDs: []string{"CVE-2014-8680", "CVE-2014-8500"},
 | 
			
		||||
			vulnID: "ab3e98d9-8175-11e4-907d-d050992ecde8",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: `hoge-hoge-9.6.3.2.ESV.R10_2 is vulnerable:
 | 
			
		||||
bind -- denial of service vulnerability
 | 
			
		||||
CVE: CVE-2014-8680
 | 
			
		||||
CVE: CVE-2014-8500
 | 
			
		||||
WWW: https://vuxml.FreeBSD.org/freebsd/ab3e98d9-8175-11e4-907d-d050992ecde8.html`,
 | 
			
		||||
			name:   "hoge-hoge",
 | 
			
		||||
			cveIDs: []string{"CVE-2014-8680", "CVE-2014-8500"},
 | 
			
		||||
			vulnID: "ab3e98d9-8175-11e4-907d-d050992ecde8",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:     `1 problem(s) in the installed packages found.`,
 | 
			
		||||
			cveIDs: []string{},
 | 
			
		||||
			vulnID: "",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newBsd(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		aName, aCveIDs, aVulnID := d.parseBlock(tt.in)
 | 
			
		||||
		if tt.name != aName {
 | 
			
		||||
			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 != aVulnID {
 | 
			
		||||
			t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVulnID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										129
									
								
								scan/linux.go
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								scan/linux.go
									
									
									
									
									
								
							@@ -1,129 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sort"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type linux struct {
 | 
			
		||||
	ServerInfo config.ServerInfo
 | 
			
		||||
 | 
			
		||||
	Family  string
 | 
			
		||||
	Release string
 | 
			
		||||
	osPackages
 | 
			
		||||
	log *logrus.Entry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *linux) ssh(cmd string, sudo bool) sshResult {
 | 
			
		||||
	return sshExec(l.ServerInfo, cmd, sudo, l.log)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *linux) setServerInfo(c config.ServerInfo) {
 | 
			
		||||
	l.ServerInfo = c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *linux) getServerInfo() config.ServerInfo {
 | 
			
		||||
	return l.ServerInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *linux) setDistributionInfo(fam, rel string) {
 | 
			
		||||
	l.Family = fam
 | 
			
		||||
	l.Release = rel
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *linux) convertToModel() (models.ScanResult, error) {
 | 
			
		||||
	var cves, unknownScoreCves []models.CveInfo
 | 
			
		||||
	for _, p := range l.UnsecurePackages {
 | 
			
		||||
		if p.CveDetail.CvssScore(config.Conf.Lang) < 0 {
 | 
			
		||||
			unknownScoreCves = append(unknownScoreCves, models.CveInfo{
 | 
			
		||||
				CveDetail:        p.CveDetail,
 | 
			
		||||
				Packages:         p.Packs,
 | 
			
		||||
				DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
 | 
			
		||||
			})
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cpenames := []models.CpeName{}
 | 
			
		||||
		for _, cpename := range p.CpeNames {
 | 
			
		||||
			cpenames = append(cpenames,
 | 
			
		||||
				models.CpeName{Name: cpename})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cve := models.CveInfo{
 | 
			
		||||
			CveDetail:        p.CveDetail,
 | 
			
		||||
			Packages:         p.Packs,
 | 
			
		||||
			DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
 | 
			
		||||
			CpeNames:         cpenames,
 | 
			
		||||
		}
 | 
			
		||||
		cves = append(cves, cve)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return models.ScanResult{
 | 
			
		||||
		ServerName:  l.ServerInfo.ServerName,
 | 
			
		||||
		Family:      l.Family,
 | 
			
		||||
		Release:     l.Release,
 | 
			
		||||
		KnownCves:   cves,
 | 
			
		||||
		UnknownCves: unknownScoreCves,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
 | 
			
		||||
func (l *linux) scanVulnByCpeName() error {
 | 
			
		||||
	unsecurePacks := CvePacksList{}
 | 
			
		||||
 | 
			
		||||
	serverInfo := l.getServerInfo()
 | 
			
		||||
	cpeNames := serverInfo.CpeNames
 | 
			
		||||
 | 
			
		||||
	// remove duplicate
 | 
			
		||||
	set := map[string]CvePacksInfo{}
 | 
			
		||||
 | 
			
		||||
	for _, name := range cpeNames {
 | 
			
		||||
		details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for _, detail := range details {
 | 
			
		||||
			if val, ok := set[detail.CveID]; ok {
 | 
			
		||||
				names := val.CpeNames
 | 
			
		||||
				names = append(names, name)
 | 
			
		||||
				val.CpeNames = names
 | 
			
		||||
				set[detail.CveID] = val
 | 
			
		||||
			} else {
 | 
			
		||||
				set[detail.CveID] = CvePacksInfo{
 | 
			
		||||
					CveID:     detail.CveID,
 | 
			
		||||
					CveDetail: detail,
 | 
			
		||||
					CpeNames:  []string{name},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for key := range set {
 | 
			
		||||
		unsecurePacks = append(unsecurePacks, set[key])
 | 
			
		||||
	}
 | 
			
		||||
	unsecurePacks = append(unsecurePacks, l.UnsecurePackages...)
 | 
			
		||||
	sort.Sort(CvePacksList(unsecurePacks))
 | 
			
		||||
	l.setUnsecurePackages(unsecurePacks)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
							
								
								
									
										564
									
								
								scan/redhat.go
									
									
									
									
									
								
							
							
						
						
									
										564
									
								
								scan/redhat.go
									
									
									
									
									
								
							@@ -26,7 +26,6 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
 | 
			
		||||
@@ -35,28 +34,24 @@ import (
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type redhat struct {
 | 
			
		||||
	linux
 | 
			
		||||
	base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRedhat is constructor
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
	// set sudo option flag
 | 
			
		||||
	c.SudoOpt = config.SudoOption{ExecBySudoSh: true}
 | 
			
		||||
	red.setServerInfo(c)
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
 | 
			
		||||
		red.setDistributionInfo("fedora", "unknown")
 | 
			
		||||
		Log.Warn("Fedora not tested yet. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		red.setDistro("fedora", "unknown")
 | 
			
		||||
		Log.Warn("Fedora not tested yet: %s", r)
 | 
			
		||||
		return true, red
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -66,21 +61,19 @@ func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
 | 
			
		||||
		// $ 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.]*)`)
 | 
			
		||||
			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. stdout: %s, Host: %s:%s",
 | 
			
		||||
					r.Stdout, c.Host, c.Port)
 | 
			
		||||
				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
 | 
			
		||||
		}
 | 
			
		||||
@@ -96,115 +89,84 @@ func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
 | 
			
		||||
				release = fields[4]
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		red.setDistributionInfo(family, release)
 | 
			
		||||
		red.setDistro(family, release)
 | 
			
		||||
		return true, red
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Debugf("Not RedHat like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
	Log.Debugf("Not RedHat like Linux. servername: %s", c.ServerName)
 | 
			
		||||
	return false, red
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := o.installYumPluginSecurity(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return o.installYumChangelog()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := util.PrependProxyEnv("yum install -y yum-plugin-security")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
func (o *redhat) checkIfSudoNoPasswd() error {
 | 
			
		||||
	r := o.ssh("yum --version", o.sudo())
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		o.log.Errorf("sudo error on %s", r)
 | 
			
		||||
		return fmt.Errorf("Failed to sudo: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Infof("sudo ... OK")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) installYumChangelog() error {
 | 
			
		||||
	o.log.Info("Installing yum-plugin-security...")
 | 
			
		||||
// CentOS 5 ... yum-changelog
 | 
			
		||||
// CentOS 6 ... yum-plugin-changelog
 | 
			
		||||
// CentOS 7 ... yum-plugin-changelog
 | 
			
		||||
// RHEL, Amazon ... no additinal packages needed
 | 
			
		||||
func (o *redhat) checkDependencies() error {
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "rhel", "amazon":
 | 
			
		||||
		//  o.log.Infof("Nothing to do")
 | 
			
		||||
		return nil
 | 
			
		||||
 | 
			
		||||
	if o.Family == "centos" {
 | 
			
		||||
	case "centos":
 | 
			
		||||
		var majorVersion int
 | 
			
		||||
		if 0 < len(o.Release) {
 | 
			
		||||
			majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
 | 
			
		||||
		if 0 < len(o.Distro.Release) {
 | 
			
		||||
			majorVersion, _ = strconv.Atoi(strings.Split(o.Distro.Release, ".")[0])
 | 
			
		||||
		} else {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"Not implemented yet. family: %s, release: %s",
 | 
			
		||||
				o.Family, o.Release)
 | 
			
		||||
			return fmt.Errorf("Not implemented yet: %s", o.Distro)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var packName = ""
 | 
			
		||||
		var name = "yum-plugin-changelog"
 | 
			
		||||
		if majorVersion < 6 {
 | 
			
		||||
			packName = "yum-changelog"
 | 
			
		||||
		} else {
 | 
			
		||||
			packName = "yum-plugin-changelog"
 | 
			
		||||
			name = "yum-changelog"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cmd := "rpm -q " + packName
 | 
			
		||||
		cmd := "rpm -q " + name
 | 
			
		||||
		if r := o.ssh(cmd, noSudo); r.isSuccess() {
 | 
			
		||||
			o.log.Infof("Ignored: %s already installed.", packName)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		o.lackDependencies = []string{name}
 | 
			
		||||
		return nil
 | 
			
		||||
 | 
			
		||||
		cmd = util.PrependProxyEnv("yum install -y " + packName)
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("Not implemented yet: %s", o.Distro)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) install() error {
 | 
			
		||||
	for _, name := range o.lackDependencies {
 | 
			
		||||
		cmd := util.PrependProxyEnv("yum install -y " + name)
 | 
			
		||||
		if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"Failed to install %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
				packName, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
			return fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
		}
 | 
			
		||||
		o.log.Infof("Installed: %s.", packName)
 | 
			
		||||
		o.log.Infof("Installed: %s", name)
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
		cmd := "rpm -q yum-plugin-security"
 | 
			
		||||
		if o.Family == "centos" {
 | 
			
		||||
			if r := o.ssh(cmd, noSudo); !r.isSuccess() {
 | 
			
		||||
				msg := "yum-plugin-security is not installed"
 | 
			
		||||
				o.log.Errorf(msg)
 | 
			
		||||
				return fmt.Errorf(msg)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.Family == "centos" {
 | 
			
		||||
	if o.Distro.Family == "centos" {
 | 
			
		||||
		var majorVersion int
 | 
			
		||||
		if 0 < len(o.Release) {
 | 
			
		||||
			majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
 | 
			
		||||
		if 0 < len(o.Distro.Release) {
 | 
			
		||||
			majorVersion, _ = strconv.Atoi(strings.Split(o.Distro.Release, ".")[0])
 | 
			
		||||
		} else {
 | 
			
		||||
			msg := fmt.Sprintf("Not implemented yet. family: %s, release: %s", o.Family, o.Release)
 | 
			
		||||
			msg := fmt.Sprintf("Not implemented yet: %s", o.Distro)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var packName = ""
 | 
			
		||||
		var packName = "yum-plugin-changelog"
 | 
			
		||||
		if majorVersion < 6 {
 | 
			
		||||
			packName = "yum-changelog"
 | 
			
		||||
		} else {
 | 
			
		||||
			packName = "yum-plugin-changelog"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cmd := "rpm -q " + packName
 | 
			
		||||
@@ -226,12 +188,12 @@ func (o *redhat) scanPackages() error {
 | 
			
		||||
	}
 | 
			
		||||
	o.setPackages(packs)
 | 
			
		||||
 | 
			
		||||
	var unsecurePacks []CvePacksInfo
 | 
			
		||||
	if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan valnerable packages")
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -245,7 +207,7 @@ func (o *redhat) scanInstalledPackages() (installedPackages models.PackageInfoLi
 | 
			
		||||
		for _, line := range lines {
 | 
			
		||||
			if trimed := strings.TrimSpace(line); len(trimed) != 0 {
 | 
			
		||||
				var packinfo models.PackageInfo
 | 
			
		||||
				if packinfo, err = o.parseScanedPackagesLine(line); err != nil {
 | 
			
		||||
				if packinfo, err = o.parseScannedPackagesLine(line); err != nil {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				installedPackages = append(installedPackages, packinfo)
 | 
			
		||||
@@ -259,21 +221,21 @@ func (o *redhat) scanInstalledPackages() (installedPackages models.PackageInfoLi
 | 
			
		||||
		r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseScanedPackagesLine(line string) (pack models.PackageInfo, err error) {
 | 
			
		||||
	re, _ := regexp.Compile(`^([^\t']+)\t([^\t]+)\t(.+)$`)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) == 4 {
 | 
			
		||||
		pack.Name = result[1]
 | 
			
		||||
		pack.Version = result[2]
 | 
			
		||||
		pack.Release = strings.TrimSpace(result[3])
 | 
			
		||||
	} else {
 | 
			
		||||
		err = fmt.Errorf("redhat: Failed to parse package line: %s", line)
 | 
			
		||||
func (o *redhat) parseScannedPackagesLine(line string) (models.PackageInfo, error) {
 | 
			
		||||
	fields := strings.Fields(line)
 | 
			
		||||
	if len(fields) != 3 {
 | 
			
		||||
		return models.PackageInfo{},
 | 
			
		||||
			fmt.Errorf("Failed to parse package line: %s", line)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
	return models.PackageInfo{
 | 
			
		||||
		Name:    fields[0],
 | 
			
		||||
		Version: fields[1],
 | 
			
		||||
		Release: fields[2],
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
 | 
			
		||||
	if o.Family != "centos" || config.Conf.UseYumPluginSecurity {
 | 
			
		||||
func (o *redhat) scanVulnInfos() ([]models.VulnInfo, error) {
 | 
			
		||||
	if o.Distro.Family != "centos" {
 | 
			
		||||
		// Amazon, RHEL has yum updateinfo as default
 | 
			
		||||
		// yum updateinfo can collenct vendor advisory information.
 | 
			
		||||
		return o.scanUnsecurePackagesUsingYumPluginSecurity()
 | 
			
		||||
@@ -283,16 +245,19 @@ 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 := "yum check-update"
 | 
			
		||||
	r := o.ssh(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	if !r.isSuccess(0, 100) {
 | 
			
		||||
		//returns an exit code of 100 if there are available updates.
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get Updateble package name, installed, candidate version.
 | 
			
		||||
@@ -300,20 +265,33 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Debugf("%s", pp.Sprintf("%s", packInfoList))
 | 
			
		||||
	o.log.Debugf("%s", pp.Sprintf("%v", packInfoList))
 | 
			
		||||
 | 
			
		||||
	// set candidate version info
 | 
			
		||||
	o.Packages.MergeNewVersion(packInfoList)
 | 
			
		||||
 | 
			
		||||
	// Collect CVE-IDs in changelog
 | 
			
		||||
	type PackInfoCveIDs struct {
 | 
			
		||||
		PackInfo models.PackageInfo
 | 
			
		||||
		CveIDs   []string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	allChangelog, err := o.getAllChangelog(packInfoList)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to getAllchangelog. err: %s", err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// { packageName: changelog-lines }
 | 
			
		||||
	var rpm2changelog map[string]*string
 | 
			
		||||
	rpm2changelog, err = o.parseAllChangelog(allChangelog)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to parseAllChangelog. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var results []PackInfoCveIDs
 | 
			
		||||
	for i, packInfo := range packInfoList {
 | 
			
		||||
		changelog, err := o.getChangelog(packInfo.Name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Errorf("Failed to collect CVE. err: %s", err)
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		changelog := o.getChangelogCVELines(rpm2changelog, packInfo)
 | 
			
		||||
 | 
			
		||||
		// Collect unique set of CVE-ID in each changelog
 | 
			
		||||
		uniqueCveIDMap := make(map[string]bool)
 | 
			
		||||
@@ -362,40 +340,20 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error)
 | 
			
		||||
	cveIDPackInfoMap := make(map[string][]models.PackageInfo)
 | 
			
		||||
	for _, res := range results {
 | 
			
		||||
		for _, cveID := range res.CveIDs {
 | 
			
		||||
			//  packInfo, found := o.Packages.FindByName(res.Packname)
 | 
			
		||||
			//  if !found {
 | 
			
		||||
			//      return CvePacksList{}, fmt.Errorf(
 | 
			
		||||
			//          "Faild to transform data structure: %v", res.Packname)
 | 
			
		||||
			//  }
 | 
			
		||||
			cveIDPackInfoMap[cveID] = append(cveIDPackInfoMap[cveID], res.PackInfo)
 | 
			
		||||
			cveIDPackInfoMap[cveID] = append(
 | 
			
		||||
				cveIDPackInfoMap[cveID], res.PackInfo)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var uniqueCveIDs []string
 | 
			
		||||
	for cveID := range cveIDPackInfoMap {
 | 
			
		||||
		uniqueCveIDs = append(uniqueCveIDs, cveID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// cveIDs => []cve.CveInfo
 | 
			
		||||
	o.log.Info("Fetching CVE details...")
 | 
			
		||||
	cveDetails, err := cveapi.CveClient.FetchCveDetails(uniqueCveIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Info("Done")
 | 
			
		||||
 | 
			
		||||
	cvePacksList := []CvePacksInfo{}
 | 
			
		||||
	for _, detail := range cveDetails {
 | 
			
		||||
	vinfos := []models.VulnInfo{}
 | 
			
		||||
	for k, v := range cveIDPackInfoMap {
 | 
			
		||||
		// Amazon, RHEL do not use this method, so VendorAdvisory do not set.
 | 
			
		||||
		cvePacksList = append(cvePacksList, CvePacksInfo{
 | 
			
		||||
			CveID:     detail.CveID,
 | 
			
		||||
			CveDetail: detail,
 | 
			
		||||
			Packs:     cveIDPackInfoMap[detail.CveID],
 | 
			
		||||
			//  CvssScore: cinfo.CvssScore(conf.Lang),
 | 
			
		||||
		vinfos = append(vinfos, models.VulnInfo{
 | 
			
		||||
			CveID:    k,
 | 
			
		||||
			Packages: v,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(CvePacksList(cvePacksList))
 | 
			
		||||
	return cvePacksList, nil
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseYumCheckUpdateLines parse yum check-update to get package name, candidate version
 | 
			
		||||
@@ -409,19 +367,26 @@ func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.Package
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if needToParse {
 | 
			
		||||
			if strings.HasPrefix(line, "Obsoleting") ||
 | 
			
		||||
				strings.HasPrefix(line, "Security:") {
 | 
			
		||||
				// see https://github.com/future-architect/vuls/issues/165
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			candidate, err := o.parseYumCheckUpdateLine(line)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return models.PackageInfoList{}, err
 | 
			
		||||
				return results, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			installed, found := o.Packages.FindByName(candidate.Name)
 | 
			
		||||
			if !found {
 | 
			
		||||
				return models.PackageInfoList{}, fmt.Errorf(
 | 
			
		||||
					"Failed to parse yum check update line: %s-%s-%s",
 | 
			
		||||
				o.log.Warnf("Not found the package in rpm -qa. candidate: %s-%s-%s",
 | 
			
		||||
					candidate.Name, candidate.Version, candidate.Release)
 | 
			
		||||
				results = append(results, candidate)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			installed.NewVersion = candidate.NewVersion
 | 
			
		||||
			installed.NewRelease = candidate.NewRelease
 | 
			
		||||
			installed.Repository = candidate.Repository
 | 
			
		||||
			results = append(results, installed)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -430,7 +395,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], ".")
 | 
			
		||||
@@ -441,25 +406,146 @@ 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 := fields[0]
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) getChangelog(packageNames string) (stdout string, err error) {
 | 
			
		||||
func (o *redhat) mkPstring() *string {
 | 
			
		||||
	str := ""
 | 
			
		||||
	return &str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) regexpReplace(src string, pat string, rep string) string {
 | 
			
		||||
	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 {
 | 
			
		||||
			if changeLogCVEPattern.MatchString(line) {
 | 
			
		||||
				retLine += fmt.Sprintf("%s\n", line)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return retLine
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseAllChangelog(allChangelog string) (map[string]*string, error) {
 | 
			
		||||
	var majorVersion int
 | 
			
		||||
	if 0 < len(o.Distro.Release) && o.Distro.Family == "centos" {
 | 
			
		||||
		majorVersion, _ = strconv.Atoi(strings.Split(o.Distro.Release, ".")[0])
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil, fmt.Errorf("Not implemented yet: %s", o.getDistro())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 0 < i {
 | 
			
		||||
				prev, err = o.isRpmPackageNameLine(orglines[i-1])
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			now, err = o.isRpmPackageNameLine(orglines[i])
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			if prev && now {
 | 
			
		||||
				tmpline = fmt.Sprintf("%s, %s", tmpline, orglines[i])
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if !prev && now {
 | 
			
		||||
				tmpline = fmt.Sprintf("%s%s", tmpline, orglines[i])
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if tmpline != "" {
 | 
			
		||||
				lines = append(lines, fmt.Sprintf("%s", tmpline))
 | 
			
		||||
				tmpline = ""
 | 
			
		||||
			}
 | 
			
		||||
			lines = append(lines, fmt.Sprintf("%s", orglines[i]))
 | 
			
		||||
		} else {
 | 
			
		||||
			/* for CentOS6,7 (yum-util >= 1.1.20) */
 | 
			
		||||
			line := orglines[i]
 | 
			
		||||
			line = o.regexpReplace(line, `^ChangeLog for: `, "")
 | 
			
		||||
			line = o.regexpReplace(line, `^\*\*\sNo\sChangeLog\sfor:.*`, "")
 | 
			
		||||
			lines = append(lines, line)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rpm2changelog := make(map[string]*string)
 | 
			
		||||
	writePointer := o.mkPstring()
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		match, err := o.isRpmPackageNameLine(line)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		if match {
 | 
			
		||||
			rpms := strings.Split(line, ",")
 | 
			
		||||
			pNewString := o.mkPstring()
 | 
			
		||||
			writePointer = pNewString
 | 
			
		||||
			for _, rpm := range rpms {
 | 
			
		||||
				rpm = strings.TrimSpace(rpm)
 | 
			
		||||
				rpm = o.regexpReplace(rpm, `^[0-9]+:`, "")
 | 
			
		||||
				rpm = o.regexpReplace(rpm, `\.(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)$`, "")
 | 
			
		||||
				rpm2changelog[rpm] = pNewString
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if strings.HasPrefix(line, "Dependencies Resolved") {
 | 
			
		||||
				return rpm2changelog, nil
 | 
			
		||||
			}
 | 
			
		||||
			*writePointer += fmt.Sprintf("%s\n", line)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return rpm2changelog, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CentOS
 | 
			
		||||
func (o *redhat) getAllChangelog(packInfoList models.PackageInfoList) (stdout string, err error) {
 | 
			
		||||
	packageNames := ""
 | 
			
		||||
	for _, packInfo := range packInfoList {
 | 
			
		||||
		packageNames += fmt.Sprintf("%s ", packInfo.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	command := "echo N | "
 | 
			
		||||
	if 0 < len(config.Conf.HTTPProxy) {
 | 
			
		||||
		command += util.ProxyEnv()
 | 
			
		||||
	}
 | 
			
		||||
	command += fmt.Sprintf(" yum update --changelog %s | grep CVE", packageNames)
 | 
			
		||||
 | 
			
		||||
	yumopts := ""
 | 
			
		||||
	if o.getServerInfo().Enablerepo != "" {
 | 
			
		||||
		yumopts = " --enablerepo=" + o.getServerInfo().Enablerepo
 | 
			
		||||
	}
 | 
			
		||||
	if config.Conf.SkipBroken {
 | 
			
		||||
		yumopts += " --skip-broken"
 | 
			
		||||
	}
 | 
			
		||||
	// yum update --changelog doesn't have --color option.
 | 
			
		||||
	command += fmt.Sprintf(" LANGUAGE=en_US.UTF-8 yum %s --changelog update ", yumopts) + packageNames
 | 
			
		||||
 | 
			
		||||
	r := o.ssh(command, sudo)
 | 
			
		||||
	if !r.isSuccess(0, 1) {
 | 
			
		||||
@@ -476,62 +562,51 @@ 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
 | 
			
		||||
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 repolist"
 | 
			
		||||
	r := o.ssh(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	cmd := "yum --color=never repolist"
 | 
			
		||||
	r := o.ssh(util.PrependProxyEnv(cmd), o.sudo())
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get advisoryID(RHSA, ALAS) - package name,version
 | 
			
		||||
	cmd = "yum updateinfo list available --security"
 | 
			
		||||
	r = o.ssh(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	cmd = "yum --color=never updateinfo list available --security"
 | 
			
		||||
	r = o.ssh(util.PrependProxyEnv(cmd), o.sudo())
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	advIDPackNamesList, err := o.parseYumUpdateinfoListAvailable(r.Stdout)
 | 
			
		||||
 | 
			
		||||
	// get package name, version, rel to be upgrade.
 | 
			
		||||
	cmd = "yum check-update --security"
 | 
			
		||||
	r = o.ssh(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	//  cmd = "yum check-update --security"
 | 
			
		||||
	cmd = "LANGUAGE=en_US.UTF-8 yum --color=never check-update"
 | 
			
		||||
	r = o.ssh(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 %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	vulnerablePackInfoList, err := o.parseYumCheckUpdateLines(r.Stdout)
 | 
			
		||||
	updatable, err := o.parseYumCheckUpdateLines(r.Stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Debugf("%s", pp.Sprintf("%s", vulnerablePackInfoList))
 | 
			
		||||
	for i, packInfo := range vulnerablePackInfoList {
 | 
			
		||||
		installedPack, found := o.Packages.FindByName(packInfo.Name)
 | 
			
		||||
		if !found {
 | 
			
		||||
			return nil, fmt.Errorf(
 | 
			
		||||
				"Parsed package not found. packInfo: %#v", packInfo)
 | 
			
		||||
		}
 | 
			
		||||
		vulnerablePackInfoList[i].Version = installedPack.Version
 | 
			
		||||
		vulnerablePackInfoList[i].Release = installedPack.Release
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Debugf("%s", pp.Sprintf("%v", updatable))
 | 
			
		||||
 | 
			
		||||
	// set candidate version info
 | 
			
		||||
	o.Packages.MergeNewVersion(updatable)
 | 
			
		||||
 | 
			
		||||
	dict := map[string][]models.PackageInfo{}
 | 
			
		||||
	for _, advIDPackNames := range advIDPackNamesList {
 | 
			
		||||
		packInfoList := models.PackageInfoList{}
 | 
			
		||||
		for _, packName := range advIDPackNames.PackNames {
 | 
			
		||||
			packInfo, found := vulnerablePackInfoList.FindByName(packName)
 | 
			
		||||
			packInfo, found := updatable.FindByName(packName)
 | 
			
		||||
			if !found {
 | 
			
		||||
				return nil, fmt.Errorf(
 | 
			
		||||
					"PackInfo not found. packInfo: %#v", packName)
 | 
			
		||||
@@ -543,59 +618,52 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get advisoryID(RHSA, ALAS) - CVE IDs
 | 
			
		||||
	cmd = "yum updateinfo --security update"
 | 
			
		||||
	r = o.ssh(util.PrependProxyEnv(cmd), noSudo)
 | 
			
		||||
	cmd = "yum --color=never updateinfo --security update"
 | 
			
		||||
	r = o.ssh(util.PrependProxyEnv(cmd), o.sudo())
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	advisoryCveIDsList, err := o.parseYumUpdateinfo(r.Stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return CvePacksList{}, err
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	//  pp.Println(advisoryCveIDsList)
 | 
			
		||||
 | 
			
		||||
	// All information collected.
 | 
			
		||||
	// Convert to CvePacksList.
 | 
			
		||||
	// Convert to VulnInfos.
 | 
			
		||||
	o.log.Info("Fetching CVE details...")
 | 
			
		||||
	result := CvePacksList{}
 | 
			
		||||
	vinfos := models.VulnInfos{}
 | 
			
		||||
	for _, advIDCveIDs := range advisoryCveIDsList {
 | 
			
		||||
		cveDetails, err :=
 | 
			
		||||
			cveapi.CveClient.FetchCveDetails(advIDCveIDs.CveIDs)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, cveDetail := range cveDetails {
 | 
			
		||||
		for _, cveID := range advIDCveIDs.CveIDs {
 | 
			
		||||
			found := false
 | 
			
		||||
			for i, p := range result {
 | 
			
		||||
				if cveDetail.CveID == p.CveID {
 | 
			
		||||
			for i, p := range vinfos {
 | 
			
		||||
				if cveID == p.CveID {
 | 
			
		||||
					advAppended := append(p.DistroAdvisories, advIDCveIDs.DistroAdvisory)
 | 
			
		||||
					result[i].DistroAdvisories = advAppended
 | 
			
		||||
					vinfos[i].DistroAdvisories = advAppended
 | 
			
		||||
 | 
			
		||||
					packs := dict[advIDCveIDs.DistroAdvisory.AdvisoryID]
 | 
			
		||||
					result[i].Packs = append(result[i].Packs, packs...)
 | 
			
		||||
					vinfos[i].Packages = append(vinfos[i].Packages, packs...)
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if !found {
 | 
			
		||||
				cpinfo := CvePacksInfo{
 | 
			
		||||
					CveID:            cveDetail.CveID,
 | 
			
		||||
					CveDetail:        cveDetail,
 | 
			
		||||
				cpinfo := models.VulnInfo{
 | 
			
		||||
					CveID:            cveID,
 | 
			
		||||
					DistroAdvisories: []models.DistroAdvisory{advIDCveIDs.DistroAdvisory},
 | 
			
		||||
					Packs:            dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
 | 
			
		||||
					Packages:         dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
 | 
			
		||||
				}
 | 
			
		||||
				result = append(result, cpinfo)
 | 
			
		||||
				vinfos = append(vinfos, cpinfo)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Info("Done")
 | 
			
		||||
	return result, nil
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var horizontalRulePattern = regexp.MustCompile(`^=+$`)
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveIDs, err error) {
 | 
			
		||||
	sectionState := Outside
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
@@ -613,7 +681,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 {
 | 
			
		||||
@@ -640,7 +708,7 @@ 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
 | 
			
		||||
@@ -707,8 +775,22 @@ 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: ")
 | 
			
		||||
	ss := strings.Split(s, ", ")
 | 
			
		||||
	if len(ss) == 0 {
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
	for _, s := range ss {
 | 
			
		||||
		s = strings.TrimRight(s, " \r\n")
 | 
			
		||||
		if !rpmPackageArchPattern.MatchString(s) {
 | 
			
		||||
			return false, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// see test case
 | 
			
		||||
@@ -728,9 +810,10 @@ func (o *redhat) parseYumUpdateinfoHeaderCentOS(line string) (packs []models.Pac
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var yumHeaderPattern = regexp.MustCompile(`(ALAS-.+): (.+) priority package update for (.+)$`)
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
	result := yumHeaderPattern.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) == 4 {
 | 
			
		||||
		a.AdvisoryID = result[1]
 | 
			
		||||
		a.Severity = result[2]
 | 
			
		||||
@@ -742,31 +825,36 @@ func (o *redhat) parseYumUpdateinfoHeaderAmazon(line string) (a models.DistroAdv
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
@@ -777,14 +865,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
 | 
			
		||||
	}
 | 
			
		||||
@@ -819,7 +909,6 @@ func (o *redhat) extractPackNameVerRel(nameVerRel string) (name, ver, rel string
 | 
			
		||||
 | 
			
		||||
// parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS), packages
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacksList, error) {
 | 
			
		||||
 | 
			
		||||
	result := []advisoryIDPacks{}
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
@@ -859,3 +948,16 @@ func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacks
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) clone() osTypeInterface {
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) sudo() bool {
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "amazon":
 | 
			
		||||
		return false
 | 
			
		||||
	default:
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -48,10 +48,18 @@ func TestParseScanedPackagesLineRedhat(t *testing.T) {
 | 
			
		||||
				Release: "30.el6.11",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"Percona-Server-shared-56 5.6.19 rel67.0.el6",
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:    "Percona-Server-shared-56",
 | 
			
		||||
				Version: "5.6.19",
 | 
			
		||||
				Release: "rel67.0.el6",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range packagetests {
 | 
			
		||||
		p, _ := r.parseScanedPackagesLine(tt.in)
 | 
			
		||||
		p, _ := r.parseScannedPackagesLine(tt.in)
 | 
			
		||||
		if p.Name != tt.pack.Name {
 | 
			
		||||
			t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
 | 
			
		||||
		}
 | 
			
		||||
@@ -135,11 +143,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",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@@ -294,6 +302,54 @@ func TestIsDescriptionLine(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsRpmPackageNameLine(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in    string
 | 
			
		||||
		found bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"stunnel-4.15-2.el5.2.i386",
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"iproute-2.6.18-15.el5.i386",
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"1:yum-updatesd-0.9-6.el5_10.noarch",
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"glibc-2.12-1.192.el6.x86_64",
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			" glibc-2.12-1.192.el6.x86_64",
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"glibc-2.12-1.192.el6.x86_64, iproute-2.6.18-15.el5.i386",
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"k6 hoge.i386",
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"triathlon",
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		found, err := r.isRpmPackageNameLine(tt.in)
 | 
			
		||||
		if tt.found != found {
 | 
			
		||||
			t.Errorf("[%d] line: %s, expected %t, actual %t, err %v", i, tt.in, tt.found, found, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoToGetSeverity(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
@@ -395,7 +451,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
 | 
			
		||||
@@ -455,7 +511,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")
 | 
			
		||||
@@ -545,7 +601,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
 | 
			
		||||
@@ -556,7 +612,13 @@ Loading mirror speeds from cached hostfile
 | 
			
		||||
 | 
			
		||||
audit-libs.x86_64              2.3.7-5.el6                   base
 | 
			
		||||
bash.x86_64                    4.1.2-33.el6_7.1              updates
 | 
			
		||||
	`
 | 
			
		||||
Obsoleting Packages
 | 
			
		||||
python-libs.i686    2.6.6-64.el6   rhui-REGION-rhel-server-releases
 | 
			
		||||
    python-ordereddict.noarch     1.1-3.el6ev    installed
 | 
			
		||||
bind-utils.x86_64                       30:9.3.6-25.P1.el5_11.8          updates
 | 
			
		||||
pytalloc.x86_64                 2.0.7-2.el6                      @CentOS 6.5/6.5
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
	r.Packages = []models.PackageInfo{
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "audit-libs",
 | 
			
		||||
@@ -568,6 +630,26 @@ bash.x86_64                    4.1.2-33.el6_7.1              updates
 | 
			
		||||
			Version: "4.1.1",
 | 
			
		||||
			Release: "33",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "python-libs",
 | 
			
		||||
			Version: "2.6.0",
 | 
			
		||||
			Release: "1.1-0",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "python-ordereddict",
 | 
			
		||||
			Version: "1.0",
 | 
			
		||||
			Release: "1",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "bind-utils",
 | 
			
		||||
			Version: "1.0",
 | 
			
		||||
			Release: "1",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "pytalloc",
 | 
			
		||||
			Version: "2.0.1",
 | 
			
		||||
			Release: "0",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
@@ -582,6 +664,7 @@ bash.x86_64                    4.1.2-33.el6_7.1              updates
 | 
			
		||||
					Release:    "4.el6",
 | 
			
		||||
					NewVersion: "2.3.7",
 | 
			
		||||
					NewRelease: "5.el6",
 | 
			
		||||
					Repository: "base",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "bash",
 | 
			
		||||
@@ -589,6 +672,39 @@ bash.x86_64                    4.1.2-33.el6_7.1              updates
 | 
			
		||||
					Release:    "33",
 | 
			
		||||
					NewVersion: "4.1.2",
 | 
			
		||||
					NewRelease: "33.el6_7.1",
 | 
			
		||||
					Repository: "updates",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "python-libs",
 | 
			
		||||
					Version:    "2.6.0",
 | 
			
		||||
					Release:    "1.1-0",
 | 
			
		||||
					NewVersion: "2.6.6",
 | 
			
		||||
					NewRelease: "64.el6",
 | 
			
		||||
					Repository: "rhui-REGION-rhel-server-releases",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "python-ordereddict",
 | 
			
		||||
					Version:    "1.0",
 | 
			
		||||
					Release:    "1",
 | 
			
		||||
					NewVersion: "1.1",
 | 
			
		||||
					NewRelease: "3.el6ev",
 | 
			
		||||
					Repository: "installed",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "bind-utils",
 | 
			
		||||
					Version:    "1.0",
 | 
			
		||||
					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",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
@@ -612,23 +728,23 @@ bash.x86_64                    4.1.2-33.el6_7.1              updates
 | 
			
		||||
 | 
			
		||||
func TestParseYumCheckUpdateLinesAmazon(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "amzon"
 | 
			
		||||
	r.Distro = config.Distro{Family: "amazon"}
 | 
			
		||||
	stdout := `Loaded plugins: priorities, update-motd, upgrade-helper
 | 
			
		||||
34 package(s) needed for security, out of 71 available
 | 
			
		||||
 | 
			
		||||
bind-libs.x86_64           32:9.8.2-0.37.rc1.45.amzn1      amzn-main
 | 
			
		||||
java-1.7.0-openjdk.x86_64  1:1.7.0.95-2.6.4.0.65.amzn1     amzn-main
 | 
			
		||||
java-1.7.0-openjdk.x86_64  1.7.0.95-2.6.4.0.65.amzn1     amzn-main
 | 
			
		||||
if-not-architecture        100-200                         amzn-main
 | 
			
		||||
`
 | 
			
		||||
	r.Packages = []models.PackageInfo{
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "bind-libs",
 | 
			
		||||
			Version: "32:9.8.0",
 | 
			
		||||
			Version: "9.8.0",
 | 
			
		||||
			Release: "0.33.rc1.45.amzn1",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "java-1.7.0-openjdk",
 | 
			
		||||
			Version: "1:1.7.0.0",
 | 
			
		||||
			Version: "1.7.0.0",
 | 
			
		||||
			Release: "2.6.4.0.0.amzn1",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
@@ -646,17 +762,19 @@ if-not-architecture        100-200                         amzn-main
 | 
			
		||||
			models.PackageInfoList{
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "bind-libs",
 | 
			
		||||
					Version:    "32:9.8.0",
 | 
			
		||||
					Version:    "9.8.0",
 | 
			
		||||
					Release:    "0.33.rc1.45.amzn1",
 | 
			
		||||
					NewVersion: "32:9.8.2",
 | 
			
		||||
					NewVersion: "9.8.2",
 | 
			
		||||
					NewRelease: "0.37.rc1.45.amzn1",
 | 
			
		||||
					Repository: "amzn-main",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "java-1.7.0-openjdk",
 | 
			
		||||
					Version:    "1:1.7.0.0",
 | 
			
		||||
					Version:    "1.7.0.0",
 | 
			
		||||
					Release:    "2.6.4.0.0.amzn1",
 | 
			
		||||
					NewVersion: "1:1.7.0.95",
 | 
			
		||||
					NewVersion: "1.7.0.95",
 | 
			
		||||
					NewRelease: "2.6.4.0.65.amzn1",
 | 
			
		||||
					Repository: "amzn-main",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "if-not-architecture",
 | 
			
		||||
@@ -664,6 +782,7 @@ if-not-architecture        100-200                         amzn-main
 | 
			
		||||
					Release:    "20",
 | 
			
		||||
					NewVersion: "100",
 | 
			
		||||
					NewRelease: "200",
 | 
			
		||||
					Repository: "amzn-main",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
@@ -774,39 +893,6 @@ updateinfo list done`
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoToGetUpdateID(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	var packagetests = []struct {
 | 
			
		||||
		in   string
 | 
			
		||||
		pack models.PackageInfo
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"openssl	1.0.1e	30.el6.11",
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:    "openssl",
 | 
			
		||||
				Version: "1.0.1e",
 | 
			
		||||
				Release: "30.el6.11",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range packagetests {
 | 
			
		||||
		p, _ := r.parseScanedPackagesLine(tt.in)
 | 
			
		||||
		if p.Name != tt.pack.Name {
 | 
			
		||||
			t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
 | 
			
		||||
		}
 | 
			
		||||
		if p.Version != tt.pack.Version {
 | 
			
		||||
			t.Errorf("version: expected %s, actual %s", tt.pack.Version, p.Version)
 | 
			
		||||
		}
 | 
			
		||||
		if p.Release != tt.pack.Release {
 | 
			
		||||
			t.Errorf("release: expected %s, actual %s", tt.pack.Release, p.Release)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExtractPackNameVerRel(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
@@ -841,3 +927,309 @@ func TestExtractPackNameVerRel(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	/* for CentOS6,7 (yum-util >= 1.1.20) */
 | 
			
		||||
	stdoutCentos6 = `---> Package libaio.x86_64 0:0.3.107-10.el6 will be installed
 | 
			
		||||
--> Finished Dependency Resolution
 | 
			
		||||
 | 
			
		||||
Changes in packages about to be updated:
 | 
			
		||||
 | 
			
		||||
ChangeLog for: binutils-2.20.51.0.2-5.44.el6.x86_64
 | 
			
		||||
* Mon Dec  7 21:00:00 2015 Nick Clifton <nickc@redhat.com> - 2.20.51.0.2-5.44
 | 
			
		||||
- Backport upstream RELRO fixes. (#1227839)
 | 
			
		||||
 | 
			
		||||
** No ChangeLog for: chkconfig-1.3.49.5-1.el6.x86_64
 | 
			
		||||
 | 
			
		||||
ChangeLog for: coreutils-8.4-43.el6.x86_64, coreutils-libs-8.4-43.el6.x86_64
 | 
			
		||||
* Wed Feb 10 21:00:00 2016 Ondrej Vasik <ovasik@redhat.com> - 8.4-43
 | 
			
		||||
- sed should actually be /bin/sed (related #1222140)
 | 
			
		||||
 | 
			
		||||
* Wed Jan  6 21:00:00 2016 Ondrej Vasik <ovasik@redhat.com> - 8.4-41
 | 
			
		||||
- colorls.sh,colorls.csh - call utilities with complete path (#1222140)
 | 
			
		||||
- mkdir, mkfifo, mknod - respect default umask/acls when
 | 
			
		||||
  COREUTILS_CHILD_DEFAULT_ACLS envvar is set (to match rhel 7 behaviour,
 | 
			
		||||
 | 
			
		||||
ChangeLog for: centos-release-6-8.el6.centos.12.3.x86_64
 | 
			
		||||
* Wed May 18 21:00:00 2016 Johnny Hughes <johnny@centos.org> 6-8.el6.centos.12.3
 | 
			
		||||
- CentOS-6.8 Released
 | 
			
		||||
- TESTSTRING CVE-0000-0000
 | 
			
		||||
 | 
			
		||||
ChangeLog for: 12:dhclient-4.1.1-51.P1.el6.centos.x86_64, 12:dhcp-common-4.1.1-51.P1.el6.centos.x86_64
 | 
			
		||||
* Tue May 10 21:00:00 2016 Johnny Hughes <johnny@centos.org> - 12:4.1.1-51.P1
 | 
			
		||||
- created patch 1000 for CentOS Branding
 | 
			
		||||
- replaced vvendor variable with CentOS in the SPEC file
 | 
			
		||||
- TESTSTRING CVE-1111-1111
 | 
			
		||||
 | 
			
		||||
* Mon Jan 11 21:00:00 2016 Jiri Popelka <jpopelka@redhat.com> - 12:4.1.1-51.P1
 | 
			
		||||
- send unicast request/release via correct interface (#1297445)
 | 
			
		||||
 | 
			
		||||
* Thu Dec  3 21:00:00 2015 Jiri Popelka <jpopelka@redhat.com> - 12:4.1.1-50.P1
 | 
			
		||||
- Lease table overflow crash. (#1133917)
 | 
			
		||||
- Add ignore-client-uids option. (#1196768)
 | 
			
		||||
- dhclient-script: it's OK if the arping reply comes from our system. (#1204095)
 | 
			
		||||
- VLAN ID is only bottom 12-bits of TCI. (#1259552)
 | 
			
		||||
- dhclient: Make sure link-local address is ready in stateless mode. (#1263466)
 | 
			
		||||
- dhclient-script: make_resolv_conf(): Keep old nameservers
 | 
			
		||||
  if server sends domain-name/search, but no nameservers. (#1269595)
 | 
			
		||||
 | 
			
		||||
ChangeLog for: file-5.04-30.el6.x86_64, file-libs-5.04-30.el6.x86_64
 | 
			
		||||
* Tue Feb 16 21:00:00 2016 Jan Kaluza <jkaluza@redhat.com> 5.04-30
 | 
			
		||||
- fix CVE-2014-3538 (unrestricted regular expression matching)
 | 
			
		||||
 | 
			
		||||
* Tue Jan  5 21:00:00 2016 Jan Kaluza <jkaluza@redhat.com> 5.04-29
 | 
			
		||||
- fix #1284826 - try to read ELF header to detect corrupted one
 | 
			
		||||
 | 
			
		||||
* Wed Dec 16 21:00:00 2015 Jan Kaluza <jkaluza@redhat.com> 5.04-28
 | 
			
		||||
- fix #1263987 - fix bugs found by coverity in the patch
 | 
			
		||||
 | 
			
		||||
* Thu Nov 26 21:00:00 2015 Jan Kaluza <jkaluza@redhat.com> 5.04-27
 | 
			
		||||
- fix CVE-2014-3587 (incomplete fix for CVE-2012-1571)
 | 
			
		||||
- fix CVE-2014-3710 (out-of-bounds read in elf note headers)
 | 
			
		||||
- fix CVE-2014-8116 (multiple DoS issues (resource consumption))
 | 
			
		||||
- fix CVE-2014-8117 (denial of service issue (resource consumption))
 | 
			
		||||
- fix CVE-2014-9620 (limit the number of ELF notes processed)
 | 
			
		||||
- fix CVE-2014-9653 (malformed elf file causes access to uninitialized memory)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Dependencies Resolved
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
	/* for CentOS5 (yum-util < 1.1.20) */
 | 
			
		||||
	stdoutCentos5 = `---> Package portmap.i386 0:4.0-65.2.2.1 set to be updated
 | 
			
		||||
--> Finished Dependency Resolution
 | 
			
		||||
 | 
			
		||||
Changes in packages about to be updated:
 | 
			
		||||
 | 
			
		||||
libuser-0.54.7-3.el5.i386
 | 
			
		||||
nss_db-2.2-38.el5_11.i386
 | 
			
		||||
* Thu Nov 20 23:00:00 2014 Nalin Dahyabhai <nalin@redhat.com> - 2.2-38
 | 
			
		||||
- build without strict aliasing (internal build tooling)
 | 
			
		||||
 | 
			
		||||
* Sat Nov 15 23:00:00 2014 Nalin Dahyabhai <nalin@redhat.com> - 2.2-37
 | 
			
		||||
- pull in fix for a memory leak in nss_db (#1163493)
 | 
			
		||||
 | 
			
		||||
acpid-1.0.4-12.el5.i386
 | 
			
		||||
* Thu Oct  6 00:00:00 2011 Jiri Skala <jskala@redhat.com> - 1.0.4-12
 | 
			
		||||
- Resolves: #729769 - acpid dumping useless info to log
 | 
			
		||||
 | 
			
		||||
nash-5.1.19.6-82.el5.i386, mkinitrd-5.1.19.6-82.el5.i386
 | 
			
		||||
* Tue Apr 15 00:00:00 2014 Brian C. Lane <bcl@redhat.com> 5.1.19.6-82
 | 
			
		||||
- Use ! instead of / when searching sysfs for ccis device
 | 
			
		||||
  Resolves: rhbz#988020
 | 
			
		||||
- Always include ahci module (except on s390) (bcl)
 | 
			
		||||
  Resolves: rhbz#978245
 | 
			
		||||
- Prompt to recreate default initrd (karsten)
 | 
			
		||||
  Resolves: rhbz#472764
 | 
			
		||||
 | 
			
		||||
util-linux-2.13-0.59.el5_8.i386
 | 
			
		||||
* Wed Oct 17 00:00:00 2012 Karel Zak <kzak@redhat.com> 2.13-0.59.el5_8
 | 
			
		||||
- fix #865791 - fdisk fails to partition disk not in use
 | 
			
		||||
 | 
			
		||||
* Wed Dec 21 23:00:00 2011 Karel Zak <kzak@redhat.com> 2.13-0.59
 | 
			
		||||
- fix #768382 - CVE-2011-1675 CVE-2011-1677 util-linux various flaws
 | 
			
		||||
 | 
			
		||||
* Wed Oct 26 00:00:00 2011 Karel Zak <kzak@redhat.com> 2.13-0.58
 | 
			
		||||
- fix #677452 - util-linux fails to build with gettext-0.17
 | 
			
		||||
 | 
			
		||||
30:bind-utils-9.3.6-25.P1.el5_11.8.i386, 30:bind-libs-9.3.6-25.P1.el5_11.8.i386
 | 
			
		||||
* Mon Mar 14 23:00:00 2016 Tomas Hozza <thozza@redhat.com> - 30:9.3.6-25.P1.8
 | 
			
		||||
- Fix issue with patch for CVE-2016-1285 and CVE-2016-1286 found by test suite
 | 
			
		||||
 | 
			
		||||
* Wed Mar  9 23:00:00 2016 Tomas Hozza <thozza@redhat.com> - 30:9.3.6-25.P1.7
 | 
			
		||||
- Fix CVE-2016-1285 and CVE-2016-1286
 | 
			
		||||
 | 
			
		||||
* Mon Jan 18 23:00:00 2016 Tomas Hozza <thozza@redhat.com> - 30:9.3.6-25.P1.6
 | 
			
		||||
- Fix CVE-2015-8704
 | 
			
		||||
 | 
			
		||||
* Thu Sep  3 00:00:00 2015 Tomas Hozza <thozza@redhat.com> - 30:9.3.6-25.P1.5
 | 
			
		||||
- Fix CVE-2015-8000
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Dependencies Resolved
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetChangelogCVELines(t *testing.T) {
 | 
			
		||||
	var testsCentos6 = []struct {
 | 
			
		||||
		in  models.PackageInfo
 | 
			
		||||
		out string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "binutils",
 | 
			
		||||
				NewVersion: "2.20.51.0.2",
 | 
			
		||||
				NewRelease: "5.44.el6",
 | 
			
		||||
			},
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "centos-release",
 | 
			
		||||
				NewVersion: "6",
 | 
			
		||||
				NewRelease: "8.el6.centos.12.3",
 | 
			
		||||
			},
 | 
			
		||||
			`- TESTSTRING CVE-0000-0000
 | 
			
		||||
`,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "dhclient",
 | 
			
		||||
				NewVersion: "4.1.1",
 | 
			
		||||
				NewRelease: "51.P1.el6.centos",
 | 
			
		||||
			},
 | 
			
		||||
			`- TESTSTRING CVE-1111-1111
 | 
			
		||||
`,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "dhcp-common",
 | 
			
		||||
				NewVersion: "4.1.1",
 | 
			
		||||
				NewRelease: "51.P1.el6.centos",
 | 
			
		||||
			},
 | 
			
		||||
			`- TESTSTRING CVE-1111-1111
 | 
			
		||||
`,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "coreutils-libs",
 | 
			
		||||
				NewVersion: "8.4",
 | 
			
		||||
				NewRelease: "43.el6",
 | 
			
		||||
			},
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "file",
 | 
			
		||||
				NewVersion: "5.04",
 | 
			
		||||
				NewRelease: "30.el6",
 | 
			
		||||
			},
 | 
			
		||||
			`- fix CVE-2014-3538 (unrestricted regular expression matching)
 | 
			
		||||
- fix CVE-2014-3587 (incomplete fix for CVE-2012-1571)
 | 
			
		||||
- fix CVE-2014-3710 (out-of-bounds read in elf note headers)
 | 
			
		||||
- fix CVE-2014-8116 (multiple DoS issues (resource consumption))
 | 
			
		||||
- fix CVE-2014-8117 (denial of service issue (resource consumption))
 | 
			
		||||
- fix CVE-2014-9620 (limit the number of ELF notes processed)
 | 
			
		||||
- fix CVE-2014-9653 (malformed elf file causes access to uninitialized memory)
 | 
			
		||||
`,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "file-libs",
 | 
			
		||||
				NewVersion: "5.04",
 | 
			
		||||
				NewRelease: "30.el6",
 | 
			
		||||
			},
 | 
			
		||||
			`- fix CVE-2014-3538 (unrestricted regular expression matching)
 | 
			
		||||
- fix CVE-2014-3587 (incomplete fix for CVE-2012-1571)
 | 
			
		||||
- fix CVE-2014-3710 (out-of-bounds read in elf note headers)
 | 
			
		||||
- fix CVE-2014-8116 (multiple DoS issues (resource consumption))
 | 
			
		||||
- fix CVE-2014-8117 (denial of service issue (resource consumption))
 | 
			
		||||
- fix CVE-2014-9620 (limit the number of ELF notes processed)
 | 
			
		||||
- fix CVE-2014-9653 (malformed elf file causes access to uninitialized memory)
 | 
			
		||||
`,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{
 | 
			
		||||
		Family:  "centos",
 | 
			
		||||
		Release: "6.7",
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range testsCentos6 {
 | 
			
		||||
		rpm2changelog, err := r.parseAllChangelog(stdoutCentos6)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("err: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		changelog := r.getChangelogCVELines(rpm2changelog, tt.in)
 | 
			
		||||
		if tt.out != changelog {
 | 
			
		||||
			t.Errorf("line: expected %s, actual %s, tt: %#v", tt.out, changelog, tt)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var testsCentos5 = []struct {
 | 
			
		||||
		in  models.PackageInfo
 | 
			
		||||
		out string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "libuser",
 | 
			
		||||
				NewVersion: "0.54.7",
 | 
			
		||||
				NewRelease: "3.el5",
 | 
			
		||||
			},
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "nss_db",
 | 
			
		||||
				NewVersion: "2.2",
 | 
			
		||||
				NewRelease: "38.el5_11",
 | 
			
		||||
			},
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "acpid",
 | 
			
		||||
				NewVersion: "1.0.4",
 | 
			
		||||
				NewRelease: "82.el5",
 | 
			
		||||
			},
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "mkinitrd",
 | 
			
		||||
				NewVersion: "5.1.19.6",
 | 
			
		||||
				NewRelease: "82.el5",
 | 
			
		||||
			},
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "util-linux",
 | 
			
		||||
				NewVersion: "2.13",
 | 
			
		||||
				NewRelease: "0.59.el5_8",
 | 
			
		||||
			},
 | 
			
		||||
			`- fix #768382 - CVE-2011-1675 CVE-2011-1677 util-linux various flaws
 | 
			
		||||
`,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "bind-libs",
 | 
			
		||||
				NewVersion: "9.3.6",
 | 
			
		||||
				NewRelease: "25.P1.el5_11.8",
 | 
			
		||||
			},
 | 
			
		||||
			`- Fix issue with patch for CVE-2016-1285 and CVE-2016-1286 found by test suite
 | 
			
		||||
- Fix CVE-2016-1285 and CVE-2016-1286
 | 
			
		||||
- Fix CVE-2015-8704
 | 
			
		||||
- Fix CVE-2015-8000
 | 
			
		||||
`,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:       "bind-utils",
 | 
			
		||||
				NewVersion: "9.3.6",
 | 
			
		||||
				NewRelease: "25.P1.el5_11.8",
 | 
			
		||||
			},
 | 
			
		||||
			`- Fix issue with patch for CVE-2016-1285 and CVE-2016-1286 found by test suite
 | 
			
		||||
- Fix CVE-2016-1285 and CVE-2016-1286
 | 
			
		||||
- Fix CVE-2015-8704
 | 
			
		||||
- Fix CVE-2015-8000
 | 
			
		||||
`,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.Distro = config.Distro{
 | 
			
		||||
		Family:  "centos",
 | 
			
		||||
		Release: "5.6",
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range testsCentos5 {
 | 
			
		||||
		rpm2changelog, err := r.parseAllChangelog(stdoutCentos5)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("err: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		changelog := r.getChangelogCVELines(rpm2changelog, tt.in)
 | 
			
		||||
		if tt.out != changelog {
 | 
			
		||||
			t.Errorf("line: expected %s, actual %s, tt: %#v", tt.out, changelog, tt)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,35 @@
 | 
			
		||||
/* 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 (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"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"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Log for localhsot
 | 
			
		||||
@@ -16,142 +37,419 @@ var Log *logrus.Entry
 | 
			
		||||
 | 
			
		||||
var servers []osTypeInterface
 | 
			
		||||
 | 
			
		||||
// Base Interface of redhat, debian
 | 
			
		||||
// Base Interface of redhat, debian, freebsd
 | 
			
		||||
type osTypeInterface interface {
 | 
			
		||||
	setServerInfo(config.ServerInfo)
 | 
			
		||||
	getServerInfo() config.ServerInfo
 | 
			
		||||
	setDistributionInfo(string, string)
 | 
			
		||||
 | 
			
		||||
	setDistro(string, string)
 | 
			
		||||
	getDistro() config.Distro
 | 
			
		||||
 | 
			
		||||
	// checkDependencies checks if dependencies are installed on the target server.
 | 
			
		||||
	checkDependencies() error
 | 
			
		||||
	getLackDependencies() []string
 | 
			
		||||
 | 
			
		||||
	checkIfSudoNoPasswd() error
 | 
			
		||||
	detectPlatform() error
 | 
			
		||||
	getPlatform() models.Platform
 | 
			
		||||
 | 
			
		||||
	checkRequiredPackagesInstalled() error
 | 
			
		||||
	scanPackages() error
 | 
			
		||||
	scanVulnByCpeName() error
 | 
			
		||||
	install() error
 | 
			
		||||
	convertToModel() (models.ScanResult, error)
 | 
			
		||||
 | 
			
		||||
	runningContainers() ([]config.Container, error)
 | 
			
		||||
	exitedContainers() ([]config.Container, error)
 | 
			
		||||
	allContainers() ([]config.Container, error)
 | 
			
		||||
 | 
			
		||||
	getErrs() []error
 | 
			
		||||
	setErrs([]error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// osPackages included by linux struct
 | 
			
		||||
// 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
 | 
			
		||||
func (p *osPackages) setVulnInfos(vi []models.VulnInfo) {
 | 
			
		||||
	p.VulnInfos = vi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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.PackageInfo
 | 
			
		||||
	DistroAdvisories []models.DistroAdvisory // for Aamazon, RHEL
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
	//  CvssScore float64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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("en") > s[j].CveDetail.CvssScore("en")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectOs(c config.ServerInfo) (osType osTypeInterface) {
 | 
			
		||||
func detectOS(c config.ServerInfo) (osType osTypeInterface) {
 | 
			
		||||
	var itsMe bool
 | 
			
		||||
	itsMe, osType = detectDebian(c)
 | 
			
		||||
	if itsMe {
 | 
			
		||||
	var fatalErr error
 | 
			
		||||
 | 
			
		||||
	itsMe, osType, fatalErr = detectDebian(c)
 | 
			
		||||
	if fatalErr != nil {
 | 
			
		||||
		osType.setServerInfo(c)
 | 
			
		||||
		osType.setErrs([]error{fatalErr})
 | 
			
		||||
		return
 | 
			
		||||
	} else if itsMe {
 | 
			
		||||
		Log.Debugf("Debian like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	itsMe, osType = detectRedhat(c)
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType = detectRedhat(c); itsMe {
 | 
			
		||||
		Log.Debugf("Redhat like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if itsMe, osType = detectFreebsd(c); itsMe {
 | 
			
		||||
		Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	osType.setServerInfo(c)
 | 
			
		||||
	osType.setErrs([]error{fmt.Errorf("Unknown OS Type")})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrintSSHableServerNames print SSH-able servernames
 | 
			
		||||
func PrintSSHableServerNames() {
 | 
			
		||||
	Log.Info("SSH-able servers are below...")
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		if s.getServerInfo().IsContainer() {
 | 
			
		||||
			fmt.Printf("%s@%s ",
 | 
			
		||||
				s.getServerInfo().Container.Name,
 | 
			
		||||
				s.getServerInfo().ServerName,
 | 
			
		||||
			)
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Printf("%s ", s.getServerInfo().ServerName)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Printf("\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InitServers detect the kind of OS distribution of target servers
 | 
			
		||||
func InitServers(localLogger *logrus.Entry) (err error) {
 | 
			
		||||
func InitServers(localLogger *logrus.Entry) error {
 | 
			
		||||
	Log = localLogger
 | 
			
		||||
	if servers, err = detectServersOS(); err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to detect OS")
 | 
			
		||||
	} else {
 | 
			
		||||
		Log.Debugf("%s", pp.Sprintf("%s", servers))
 | 
			
		||||
	servers = detectServerOSes()
 | 
			
		||||
	if len(servers) == 0 {
 | 
			
		||||
		return fmt.Errorf("No scannable servers")
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
 | 
			
		||||
	containers := detectContainerOSes()
 | 
			
		||||
	if config.Conf.ContainersOnly {
 | 
			
		||||
		servers = containers
 | 
			
		||||
	} else {
 | 
			
		||||
		servers = append(servers, containers...)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectServersOS() (osi []osTypeInterface, err error) {
 | 
			
		||||
func detectServerOSes() (sshAbleOses []osTypeInterface) {
 | 
			
		||||
	Log.Info("Detecting OS of servers... ")
 | 
			
		||||
	osTypeChan := make(chan osTypeInterface, len(config.Conf.Servers))
 | 
			
		||||
	defer close(osTypeChan)
 | 
			
		||||
	for _, s := range config.Conf.Servers {
 | 
			
		||||
		go func(s config.ServerInfo) {
 | 
			
		||||
			osTypeChan <- detectOs(s)
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if p := recover(); p != nil {
 | 
			
		||||
					Log.Debugf("Panic: %s on %s", p, s.ServerName)
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			osTypeChan <- detectOS(s)
 | 
			
		||||
		}(s)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(60 * time.Second)
 | 
			
		||||
	var oses []osTypeInterface
 | 
			
		||||
	timeout := time.After(30 * time.Second)
 | 
			
		||||
	for i := 0; i < len(config.Conf.Servers); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-osTypeChan:
 | 
			
		||||
			osi = append(osi, res)
 | 
			
		||||
			oses = append(oses, res)
 | 
			
		||||
			if 0 < len(res.getErrs()) {
 | 
			
		||||
				Log.Errorf("(%d/%d) Failed: %s, err: %s",
 | 
			
		||||
					i+1, len(config.Conf.Servers),
 | 
			
		||||
					res.getServerInfo().ServerName,
 | 
			
		||||
					res.getErrs())
 | 
			
		||||
			} else {
 | 
			
		||||
				Log.Infof("(%d/%d) Detected: %s: %s",
 | 
			
		||||
					i+1, len(config.Conf.Servers),
 | 
			
		||||
					res.getServerInfo().ServerName,
 | 
			
		||||
					res.getDistro())
 | 
			
		||||
			}
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			Log.Error("Timeout Occured while detecting OS.")
 | 
			
		||||
			err = fmt.Errorf("Timeout!")
 | 
			
		||||
			return
 | 
			
		||||
			msg := "Timed out while detecting servers"
 | 
			
		||||
			Log.Error(msg)
 | 
			
		||||
			for servername := range config.Conf.Servers {
 | 
			
		||||
				found := false
 | 
			
		||||
				for _, o := range oses {
 | 
			
		||||
					if servername == o.getServerInfo().ServerName {
 | 
			
		||||
						found = true
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if !found {
 | 
			
		||||
					Log.Errorf("(%d/%d) Timed out: %s",
 | 
			
		||||
						i+1, len(config.Conf.Servers),
 | 
			
		||||
						servername)
 | 
			
		||||
					i++
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, o := range oses {
 | 
			
		||||
		if len(o.getErrs()) == 0 {
 | 
			
		||||
			sshAbleOses = append(sshAbleOses, o)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectContainerOSes() (actives []osTypeInterface) {
 | 
			
		||||
	Log.Info("Detecting OS of containers... ")
 | 
			
		||||
	osTypesChan := make(chan []osTypeInterface, len(servers))
 | 
			
		||||
	defer close(osTypesChan)
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		go func(s osTypeInterface) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if p := recover(); p != nil {
 | 
			
		||||
					Log.Debugf("Panic: %s on %s",
 | 
			
		||||
						p, s.getServerInfo().GetServerName())
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			osTypesChan <- detectContainerOSesOnServer(s)
 | 
			
		||||
		}(s)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var oses []osTypeInterface
 | 
			
		||||
	timeout := time.After(30 * time.Second)
 | 
			
		||||
	for i := 0; i < len(servers); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-osTypesChan:
 | 
			
		||||
			for _, osi := range res {
 | 
			
		||||
				sinfo := osi.getServerInfo()
 | 
			
		||||
				if 0 < len(osi.getErrs()) {
 | 
			
		||||
					Log.Errorf("Failed: %s err: %s", sinfo.ServerName, osi.getErrs())
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				oses = append(oses, osi)
 | 
			
		||||
				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 {
 | 
			
		||||
				found := false
 | 
			
		||||
				for _, o := range oses {
 | 
			
		||||
					if servername == o.getServerInfo().ServerName {
 | 
			
		||||
						found = true
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if !found {
 | 
			
		||||
					Log.Errorf("Timed out: %s", servername)
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, o := range oses {
 | 
			
		||||
		if len(o.getErrs()) == 0 {
 | 
			
		||||
			actives = append(actives, o)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeInterface) {
 | 
			
		||||
	containerHostInfo := containerHost.getServerInfo()
 | 
			
		||||
	if len(containerHostInfo.Containers) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	running, err := containerHost.runningContainers()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		containerHost.setErrs([]error{fmt.Errorf(
 | 
			
		||||
			"Failed to get running containers on %s. err: %s",
 | 
			
		||||
			containerHost.getServerInfo().ServerName, err)})
 | 
			
		||||
		return append(oses, containerHost)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if containerHostInfo.Containers[0] == "${running}" {
 | 
			
		||||
		for _, containerInfo := range running {
 | 
			
		||||
			copied := containerHostInfo
 | 
			
		||||
			copied.SetContainer(config.Container{
 | 
			
		||||
				ContainerID: containerInfo.ContainerID,
 | 
			
		||||
				Name:        containerInfo.Name,
 | 
			
		||||
			})
 | 
			
		||||
			os := detectOS(copied)
 | 
			
		||||
			oses = append(oses, os)
 | 
			
		||||
		}
 | 
			
		||||
		return oses
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	exitedContainers, err := containerHost.exitedContainers()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		containerHost.setErrs([]error{fmt.Errorf(
 | 
			
		||||
			"Failed to get exited containers on %s. err: %s",
 | 
			
		||||
			containerHost.getServerInfo().ServerName, err)})
 | 
			
		||||
		return append(oses, containerHost)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var exited, unknown []string
 | 
			
		||||
	for _, container := range containerHostInfo.Containers {
 | 
			
		||||
		found := false
 | 
			
		||||
		for _, c := range running {
 | 
			
		||||
			if c.ContainerID == container || c.Name == container {
 | 
			
		||||
				copied := containerHostInfo
 | 
			
		||||
				copied.SetContainer(c)
 | 
			
		||||
				os := detectOS(copied)
 | 
			
		||||
				oses = append(oses, os)
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !found {
 | 
			
		||||
			foundInExitedContainers := false
 | 
			
		||||
			for _, c := range exitedContainers {
 | 
			
		||||
				if c.ContainerID == container || c.Name == container {
 | 
			
		||||
					exited = append(exited, container)
 | 
			
		||||
					foundInExitedContainers = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !foundInExitedContainers {
 | 
			
		||||
				unknown = append(unknown, container)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(exited) || 0 < len(unknown) {
 | 
			
		||||
		containerHost.setErrs([]error{fmt.Errorf(
 | 
			
		||||
			"Some containers on %s are exited or unknown. exited: %s, unknown: %s",
 | 
			
		||||
			containerHost.getServerInfo().ServerName, exited, unknown)})
 | 
			
		||||
		return append(oses, containerHost)
 | 
			
		||||
	}
 | 
			
		||||
	return oses
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfSudoNoPasswd checks whether vuls can sudo with nopassword via SSH
 | 
			
		||||
func CheckIfSudoNoPasswd(localLogger *logrus.Entry) error {
 | 
			
		||||
	timeoutSec := 15
 | 
			
		||||
	errs := parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.checkIfSudoNoPasswd()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		return fmt.Errorf(fmt.Sprintf("%s", errs))
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectPlatforms detects the platform of each servers.
 | 
			
		||||
func DetectPlatforms(localLogger *logrus.Entry) {
 | 
			
		||||
	errs := detectPlatforms()
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		// Only logging
 | 
			
		||||
		Log.Warnf("Failed to detect platforms. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
	for i, s := range servers {
 | 
			
		||||
		if s.getServerInfo().IsContainer() {
 | 
			
		||||
			Log.Infof("(%d/%d) %s on %s is running on %s",
 | 
			
		||||
				i+1, len(servers),
 | 
			
		||||
				s.getServerInfo().Container.Name,
 | 
			
		||||
				s.getServerInfo().ServerName,
 | 
			
		||||
				s.getPlatform().Name,
 | 
			
		||||
			)
 | 
			
		||||
 | 
			
		||||
		} else {
 | 
			
		||||
			Log.Infof("(%d/%d) %s is running on %s",
 | 
			
		||||
				i+1, len(servers),
 | 
			
		||||
				s.getServerInfo().ServerName,
 | 
			
		||||
				s.getPlatform().Name,
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectPlatforms() []error {
 | 
			
		||||
	timeoutSec := 1 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.detectPlatform()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Prepare installs requred packages to scan vulnerabilities.
 | 
			
		||||
func Prepare() []error {
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
	errs := parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		if err := o.checkDependencies(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return errs
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var targets []osTypeInterface
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		deps := s.getLackDependencies()
 | 
			
		||||
		if len(deps) != 0 {
 | 
			
		||||
			targets = append(targets, s)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(targets) == 0 {
 | 
			
		||||
		Log.Info("No need to install dependencies")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("The following servers need dependencies installed")
 | 
			
		||||
	for _, s := range targets {
 | 
			
		||||
		for _, d := range s.getLackDependencies() {
 | 
			
		||||
			Log.Infof("  - %s on %s", d, s.getServerInfo().GetServerName())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !config.Conf.AssumeYes {
 | 
			
		||||
		Log.Info("Is this ok to install dependencies on the servers? [y/N]")
 | 
			
		||||
 | 
			
		||||
		reader := bufio.NewReader(os.Stdin)
 | 
			
		||||
		for {
 | 
			
		||||
			text, err := reader.ReadString('\n')
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return []error{err}
 | 
			
		||||
			}
 | 
			
		||||
			switch strings.TrimSpace(text) {
 | 
			
		||||
			case "", "N", "n":
 | 
			
		||||
				return nil
 | 
			
		||||
			case "y", "Y":
 | 
			
		||||
				goto yes
 | 
			
		||||
			default:
 | 
			
		||||
				Log.Info("Please enter y or N")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
yes:
 | 
			
		||||
	servers = targets
 | 
			
		||||
	errs = parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		if err := o.install(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return errs
 | 
			
		||||
	}
 | 
			
		||||
	Log.Info("All dependencies were installed correctly")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scan scan
 | 
			
		||||
func Scan() []error {
 | 
			
		||||
	if len(servers) == 0 {
 | 
			
		||||
		return []error{fmt.Errorf("Not initialize yet.")}
 | 
			
		||||
		return []error{fmt.Errorf("No server defined. Check the configuration")}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Check required packages for scanning...")
 | 
			
		||||
@@ -160,14 +458,42 @@ func Scan() []error {
 | 
			
		||||
		return errs
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vuluneable OS packages...")
 | 
			
		||||
	if errs := scanPackages(); errs != nil {
 | 
			
		||||
	if err := setupCangelogCache(); err != nil {
 | 
			
		||||
		return []error{err}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if cache.DB != nil {
 | 
			
		||||
			cache.DB.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerable OS packages...")
 | 
			
		||||
	scannedAt := time.Now()
 | 
			
		||||
	dir, err := ensureResultDir(scannedAt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []error{err}
 | 
			
		||||
	}
 | 
			
		||||
	if errs := scanVulns(dir, scannedAt); errs != nil {
 | 
			
		||||
		return errs
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerable software specified in CPE...")
 | 
			
		||||
	if errs := scanVulnByCpeName(); errs != nil {
 | 
			
		||||
		return errs
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setupCangelogCache() error {
 | 
			
		||||
	needToSetupCache := false
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		switch s.getDistro().Family {
 | 
			
		||||
		case "ubuntu", "debian":
 | 
			
		||||
			needToSetupCache = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if needToSetupCache {
 | 
			
		||||
		if err := cache.SetupBolt(config.Conf.CacheDBPath, Log); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -179,31 +505,67 @@ func checkRequiredPackagesInstalled() []error {
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanPackages() []error {
 | 
			
		||||
	timeoutSec := 30 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.scanPackages()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
 | 
			
		||||
func scanVulnByCpeName() []error {
 | 
			
		||||
	timeoutSec := 30 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.scanVulnByCpeName()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetScanResults returns Scan Resutls
 | 
			
		||||
func GetScanResults() (results models.ScanResults, err error) {
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		r, err := s.convertToModel()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return results, fmt.Errorf("Failed converting to model: %s.", err)
 | 
			
		||||
func scanVulns(jsonDir string, scannedAt time.Time) []error {
 | 
			
		||||
	var results models.ScanResults
 | 
			
		||||
	timeoutSec := 120 * 60
 | 
			
		||||
	errs := parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		if err := o.scanPackages(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		r, err := o.convertToModel()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		r.ScannedAt = scannedAt
 | 
			
		||||
		results = append(results, r)
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
 | 
			
		||||
	config.Conf.FormatJSON = true
 | 
			
		||||
	ws := []report.ResultWriter{
 | 
			
		||||
		report.LocalFileWriter{CurrentDir: jsonDir},
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
	for _, w := range ws {
 | 
			
		||||
		if err := w.Write(results...); err != nil {
 | 
			
		||||
			return []error{
 | 
			
		||||
				fmt.Errorf("Failed to write summary report: %s", err),
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if errs != nil {
 | 
			
		||||
		return errs
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	report.StdoutWriter{}.WriteScanSummary(results...)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ensureResultDir(scannedAt time.Time) (currentDir string, err error) {
 | 
			
		||||
	jsonDirName := scannedAt.Format(time.RFC3339)
 | 
			
		||||
 | 
			
		||||
	resultsDir := config.Conf.ResultsDir
 | 
			
		||||
	if len(resultsDir) == 0 {
 | 
			
		||||
		wd, _ := os.Getwd()
 | 
			
		||||
		resultsDir = filepath.Join(wd, "results")
 | 
			
		||||
	}
 | 
			
		||||
	jsonDir := filepath.Join(resultsDir, jsonDirName)
 | 
			
		||||
	if err := os.MkdirAll(jsonDir, 0700); err != nil {
 | 
			
		||||
		return "", fmt.Errorf("Failed to create dir: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	symlinkPath := filepath.Join(resultsDir, "current")
 | 
			
		||||
	if _, err := os.Lstat(symlinkPath); err == nil {
 | 
			
		||||
		if err := os.Remove(symlinkPath); err != nil {
 | 
			
		||||
			return "", fmt.Errorf(
 | 
			
		||||
				"Failed to remove symlink. path: %s, err: %s", symlinkPath, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := os.Symlink(jsonDir, symlinkPath); err != nil {
 | 
			
		||||
		return "", fmt.Errorf(
 | 
			
		||||
			"Failed to create symlink: path: %s, err: %s", symlinkPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	return jsonDir, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										247
									
								
								scan/sshutil.go
									
									
									
									
									
								
							
							
						
						
									
										247
									
								
								scan/sshutil.go
									
									
									
									
									
								
							@@ -25,7 +25,9 @@ import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/crypto/ssh"
 | 
			
		||||
@@ -34,18 +36,30 @@ import (
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	conf "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type sshResult struct {
 | 
			
		||||
	Servername string
 | 
			
		||||
	Host       string
 | 
			
		||||
	Port       string
 | 
			
		||||
	Cmd        string
 | 
			
		||||
	Stdout     string
 | 
			
		||||
	Stderr     string
 | 
			
		||||
	ExitStatus int
 | 
			
		||||
	Error      error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s sshResult) String() string {
 | 
			
		||||
	return fmt.Sprintf(
 | 
			
		||||
		"SSHResult: servername: %s, cmd: %s, exitstatus: %d, stdout: %s, stderr: %s, err: %s",
 | 
			
		||||
		s.Servername, s.Cmd, s.ExitStatus, s.Stdout, s.Stderr, s.Error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s sshResult) isSuccess(expectedStatusCodes ...int) bool {
 | 
			
		||||
	if s.Error != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if len(expectedStatusCodes) == 0 {
 | 
			
		||||
		return s.ExitStatus == 0
 | 
			
		||||
	}
 | 
			
		||||
@@ -64,10 +78,19 @@ const sudo = true
 | 
			
		||||
const noSudo = false
 | 
			
		||||
 | 
			
		||||
func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []error) {
 | 
			
		||||
	resChan := make(chan string, len(servers))
 | 
			
		||||
	errChan := make(chan error, len(servers))
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		go func(s osTypeInterface) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if p := recover(); p != nil {
 | 
			
		||||
					logrus.Debugf("Panic: %s on %s",
 | 
			
		||||
						p, s.getServerInfo().GetServerName())
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			if err := fn(s); err != nil {
 | 
			
		||||
				errChan <- fmt.Errorf("%s@%s:%s: %s",
 | 
			
		||||
					s.getServerInfo().User,
 | 
			
		||||
@@ -76,7 +99,7 @@ func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []
 | 
			
		||||
					err,
 | 
			
		||||
				)
 | 
			
		||||
			} else {
 | 
			
		||||
				errChan <- nil
 | 
			
		||||
				resChan <- s.getServerInfo().GetServerName()
 | 
			
		||||
			}
 | 
			
		||||
		}(s)
 | 
			
		||||
	}
 | 
			
		||||
@@ -88,66 +111,74 @@ func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []
 | 
			
		||||
		timeout = timeoutSec[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var snames []string
 | 
			
		||||
	isTimedout := false
 | 
			
		||||
	for i := 0; i < len(servers); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case s := <-resChan:
 | 
			
		||||
			snames = append(snames, s)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				errs = append(errs, err)
 | 
			
		||||
			} else {
 | 
			
		||||
				logrus.Debug("Parallel SSH Success.")
 | 
			
		||||
			}
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-time.After(time.Duration(timeout) * time.Second):
 | 
			
		||||
			logrus.Errorf("Parallel SSH Timeout.")
 | 
			
		||||
			errs = append(errs, fmt.Errorf("Timed out!"))
 | 
			
		||||
			isTimedout = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// collect timed out servernames
 | 
			
		||||
	var timedoutSnames []string
 | 
			
		||||
	if isTimedout {
 | 
			
		||||
		for _, s := range servers {
 | 
			
		||||
			name := s.getServerInfo().GetServerName()
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, t := range snames {
 | 
			
		||||
				if name == t {
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !found {
 | 
			
		||||
				timedoutSnames = append(timedoutSnames, name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if isTimedout {
 | 
			
		||||
		errs = append(errs, fmt.Errorf(
 | 
			
		||||
			"Timed out: %s", timedoutSnames))
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result sshResult) {
 | 
			
		||||
	// Setup Logger
 | 
			
		||||
	var logger *logrus.Entry
 | 
			
		||||
	if len(log) == 0 {
 | 
			
		||||
		level := logrus.InfoLevel
 | 
			
		||||
		if conf.Conf.Debug == true {
 | 
			
		||||
			level = logrus.DebugLevel
 | 
			
		||||
		}
 | 
			
		||||
		l := &logrus.Logger{
 | 
			
		||||
			Out:       os.Stderr,
 | 
			
		||||
			Formatter: new(logrus.TextFormatter),
 | 
			
		||||
			Hooks:     make(logrus.LevelHooks),
 | 
			
		||||
			Level:     level,
 | 
			
		||||
		}
 | 
			
		||||
		logger = logrus.NewEntry(l)
 | 
			
		||||
	if conf.Conf.SSHExternal {
 | 
			
		||||
		result = sshExecExternal(c, cmd, sudo)
 | 
			
		||||
	} else {
 | 
			
		||||
		logger = log[0]
 | 
			
		||||
		result = sshExecNative(c, cmd, sudo)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	if sudo && c.User != "root" {
 | 
			
		||||
		switch {
 | 
			
		||||
		case c.SudoOpt.ExecBySudo:
 | 
			
		||||
			cmd = fmt.Sprintf("echo %s | sudo -S %s", c.Password, cmd)
 | 
			
		||||
		case c.SudoOpt.ExecBySudoSh:
 | 
			
		||||
			cmd = fmt.Sprintf("echo %s | sudo sh -c '%s'", c.Password, cmd)
 | 
			
		||||
		default:
 | 
			
		||||
			logger.Panicf("sudoOpt is invalid. SudoOpt: %v", c.SudoOpt)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// set pipefail option.
 | 
			
		||||
	// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
 | 
			
		||||
	cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
 | 
			
		||||
	logger.Debugf("Command: %s", strings.Replace(cmd, "\n", "", -1))
 | 
			
		||||
	logger := getSSHLogger(log...)
 | 
			
		||||
	logger.Debug(result)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult) {
 | 
			
		||||
	result.Servername = c.ServerName
 | 
			
		||||
	result.Host = c.Host
 | 
			
		||||
	result.Port = c.Port
 | 
			
		||||
 | 
			
		||||
	var client *ssh.Client
 | 
			
		||||
	client, err = sshConnect(c)
 | 
			
		||||
	var err error
 | 
			
		||||
	if client, err = sshConnect(c); err != nil {
 | 
			
		||||
		result.Error = err
 | 
			
		||||
		result.ExitStatus = 999
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer client.Close()
 | 
			
		||||
 | 
			
		||||
	var session *ssh.Session
 | 
			
		||||
	if session, err = client.NewSession(); err != nil {
 | 
			
		||||
		logger.Errorf("Failed to new session. err: %s, c: %s",
 | 
			
		||||
			err,
 | 
			
		||||
			pp.Sprintf("%v", c))
 | 
			
		||||
		result.Error = fmt.Errorf(
 | 
			
		||||
			"Failed to create a new session. servername: %s, err: %s",
 | 
			
		||||
			c.ServerName, err)
 | 
			
		||||
		result.ExitStatus = 999
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -159,11 +190,10 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
 | 
			
		||||
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
 | 
			
		||||
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
 | 
			
		||||
	}
 | 
			
		||||
	if err = session.RequestPty("xterm", 400, 120, modes); err != nil {
 | 
			
		||||
		logger.Errorf("Failed to request for pseudo terminal. err: %s, c: %s",
 | 
			
		||||
			err,
 | 
			
		||||
			pp.Sprintf("%v", c))
 | 
			
		||||
 | 
			
		||||
	if err = session.RequestPty("xterm", 400, 256, modes); err != nil {
 | 
			
		||||
		result.Error = fmt.Errorf(
 | 
			
		||||
			"Failed to request for pseudo terminal. servername: %s, err: %s",
 | 
			
		||||
			c.ServerName, err)
 | 
			
		||||
		result.ExitStatus = 999
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -172,6 +202,7 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
 | 
			
		||||
	session.Stdout = &stdoutBuf
 | 
			
		||||
	session.Stderr = &stderrBuf
 | 
			
		||||
 | 
			
		||||
	cmd = decolateCmd(c, cmd, sudo)
 | 
			
		||||
	if err := session.Run(cmd); err != nil {
 | 
			
		||||
		if exitErr, ok := err.(*ssh.ExitError); ok {
 | 
			
		||||
			result.ExitStatus = exitErr.ExitStatus()
 | 
			
		||||
@@ -184,18 +215,106 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
 | 
			
		||||
 | 
			
		||||
	result.Stdout = stdoutBuf.String()
 | 
			
		||||
	result.Stderr = stderrBuf.String()
 | 
			
		||||
	result.Host = c.Host
 | 
			
		||||
	result.Port = c.Port
 | 
			
		||||
 | 
			
		||||
	logger.Debugf(
 | 
			
		||||
		"SSH executed. cmd: %s, status: %#v\nstdout: \n%s\nstderr: \n%s",
 | 
			
		||||
		cmd, err, result.Stdout, result.Stderr)
 | 
			
		||||
 | 
			
		||||
	result.Cmd = strings.Replace(cmd, "\n", "", -1)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result sshResult) {
 | 
			
		||||
	sshBinaryPath, err := exec.LookPath("ssh")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return sshExecNative(c, cmd, sudo)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defaultSSHArgs := []string{
 | 
			
		||||
		"-t",
 | 
			
		||||
		"-o", "StrictHostKeyChecking=no",
 | 
			
		||||
		"-o", "UserKnownHostsFile=/dev/null",
 | 
			
		||||
		"-o", "LogLevel=quiet",
 | 
			
		||||
		"-o", "ConnectionAttempts=3",
 | 
			
		||||
		"-o", "ConnectTimeout=10",
 | 
			
		||||
		"-o", "ControlMaster=no",
 | 
			
		||||
		"-o", "ControlPath=none",
 | 
			
		||||
 | 
			
		||||
		// TODO ssh session multiplexing
 | 
			
		||||
		//  "-o", "ControlMaster=auto",
 | 
			
		||||
		//  "-o", `ControlPath=~/.ssh/controlmaster-%r-%h.%p`,
 | 
			
		||||
		//  "-o", "Controlpersist=30m",
 | 
			
		||||
	}
 | 
			
		||||
	args := append(defaultSSHArgs, fmt.Sprintf("%s@%s", c.User, c.Host))
 | 
			
		||||
	args = append(args, "-p", c.Port)
 | 
			
		||||
 | 
			
		||||
	//  if conf.Conf.Debug {
 | 
			
		||||
	//      args = append(args, "-v")
 | 
			
		||||
	//  }
 | 
			
		||||
 | 
			
		||||
	if 0 < len(c.KeyPath) {
 | 
			
		||||
		args = append(args, "-i", c.KeyPath)
 | 
			
		||||
		args = append(args, "-o", "PasswordAuthentication=no")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd = decolateCmd(c, cmd, sudo)
 | 
			
		||||
	//  cmd = fmt.Sprintf("stty cols 256; set -o pipefail; %s", cmd)
 | 
			
		||||
 | 
			
		||||
	args = append(args, cmd)
 | 
			
		||||
	execCmd := exec.Command(sshBinaryPath, args...)
 | 
			
		||||
 | 
			
		||||
	var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
	execCmd.Stdout = &stdoutBuf
 | 
			
		||||
	execCmd.Stderr = &stderrBuf
 | 
			
		||||
	if err := execCmd.Run(); err != nil {
 | 
			
		||||
		if e, ok := err.(*exec.ExitError); ok {
 | 
			
		||||
			if s, ok := e.Sys().(syscall.WaitStatus); ok {
 | 
			
		||||
				result.ExitStatus = s.ExitStatus()
 | 
			
		||||
			} else {
 | 
			
		||||
				result.ExitStatus = 998
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			result.ExitStatus = 999
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		result.ExitStatus = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result.Stdout = stdoutBuf.String()
 | 
			
		||||
	result.Stderr = stderrBuf.String()
 | 
			
		||||
	result.Servername = c.ServerName
 | 
			
		||||
	result.Host = c.Host
 | 
			
		||||
	result.Port = c.Port
 | 
			
		||||
	result.Cmd = fmt.Sprintf("%s %s", sshBinaryPath, strings.Join(args, " "))
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getSSHLogger(log ...*logrus.Entry) *logrus.Entry {
 | 
			
		||||
	if len(log) == 0 {
 | 
			
		||||
		return util.NewCustomLogger(conf.ServerInfo{})
 | 
			
		||||
	}
 | 
			
		||||
	return log[0]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func decolateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
 | 
			
		||||
	if sudo && c.User != "root" && !c.IsContainer() {
 | 
			
		||||
		cmd = fmt.Sprintf("sudo -S %s", cmd)
 | 
			
		||||
		cmd = strings.Replace(cmd, "|", "| sudo ", -1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Distro.Family != "FreeBSD" {
 | 
			
		||||
		// set pipefail option. Bash only
 | 
			
		||||
		// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
 | 
			
		||||
		cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.IsContainer() {
 | 
			
		||||
		switch c.Container.Type {
 | 
			
		||||
		case "", "docker":
 | 
			
		||||
			cmd = fmt.Sprintf(`docker exec %s /bin/bash -c "%s"`, c.Container.ContainerID, cmd)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	//  cmd = fmt.Sprintf("set -x; %s", cmd)
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAgentAuth() (auth ssh.AuthMethod, ok bool) {
 | 
			
		||||
	if sock := os.Getenv("SSH_AUTH_SOCK"); len(sock) > 0 {
 | 
			
		||||
	if 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)
 | 
			
		||||
@@ -218,18 +337,13 @@ func tryAgentConnect(c conf.ServerInfo) *ssh.Client {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
 | 
			
		||||
 | 
			
		||||
	if client = tryAgentConnect(c); client != nil {
 | 
			
		||||
		return client, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var auths = []ssh.AuthMethod{}
 | 
			
		||||
	if auths, err = addKeyAuth(auths, c.KeyPath, c.KeyPassword); err != nil {
 | 
			
		||||
		logrus.Fatalf("Faild to add keyAuth. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Password != "" {
 | 
			
		||||
		auths = append(auths, ssh.Password(c.Password))
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// http://blog.ralch.com/tutorial/golang-ssh-connection/
 | 
			
		||||
@@ -237,12 +351,11 @@ func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
 | 
			
		||||
		User: c.User,
 | 
			
		||||
		Auth: auths,
 | 
			
		||||
	}
 | 
			
		||||
	//  log.Debugf("config: %s", pp.Sprintf("%v", config))
 | 
			
		||||
 | 
			
		||||
	notifyFunc := func(e error, t time.Duration) {
 | 
			
		||||
		logrus.Warnf("Faild to ssh %s@%s:%s. err: %s, Retrying in %s...",
 | 
			
		||||
			c.User, c.Host, c.Port, e, t)
 | 
			
		||||
		logrus.Debugf("sshConInfo: %s", pp.Sprintf("%v", c))
 | 
			
		||||
		logger := getSSHLogger()
 | 
			
		||||
		logger.Debugf("Failed to Dial to %s, err: %s, Retrying in %s...",
 | 
			
		||||
			c.ServerName, e, t)
 | 
			
		||||
	}
 | 
			
		||||
	err = backoff.RetryNotify(func() error {
 | 
			
		||||
		if client, err = ssh.Dial("tcp", c.Host+":"+c.Port, config); err != nil {
 | 
			
		||||
@@ -306,6 +419,6 @@ func parsePemBlock(block *pem.Block) (interface{}, error) {
 | 
			
		||||
	case "DSA PRIVATE KEY":
 | 
			
		||||
		return ssh.ParseDSAPrivateKey(block.Bytes)
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, fmt.Errorf("rtop: unsupported key type %q", block.Type)
 | 
			
		||||
		return nil, fmt.Errorf("Unsupported key type %q", block.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										195
									
								
								setup/docker/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								setup/docker/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,195 @@
 | 
			
		||||
# Vuls Docker components
 | 
			
		||||
 | 
			
		||||
This is the Git repo of the official Docker image for vuls.
 | 
			
		||||
 | 
			
		||||
# Supported tags and respective `Dockerfile` links
 | 
			
		||||
 | 
			
		||||
- go-cve-dictionary
 | 
			
		||||
  - [`latest` (*go-cve-dictionary:latest Dockerfile*)]()
 | 
			
		||||
- vuls
 | 
			
		||||
  - [`latest` (*vuls:latest Dockerfile*)]()
 | 
			
		||||
- vulsrepo
 | 
			
		||||
  - [`latest` (*vulsrepo:latest Dockerfile*)]()
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
1. Confirm your vuls version
 | 
			
		||||
 | 
			
		||||
- go-cve-dictionary
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run  --rm  vuls/go-cve-dictionary -v
 | 
			
		||||
 | 
			
		||||
go-cve-dictionary v0.0.xxx xxxx
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- vuls
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run  --rm  vuls/vuls -v
 | 
			
		||||
 | 
			
		||||
vuls v0.0.xxx xxxx
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
2. Remove your old docker images
 | 
			
		||||
 | 
			
		||||
- go-cve-dictionary
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker rmi vuls/go-cve-dictionary
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker rmi vuls/vuls
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- 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. prepare (vuls/vuls)
 | 
			
		||||
1. scan (vuls/vuls)
 | 
			
		||||
1. vulsrepo (vuls/vulsrepo)
 | 
			
		||||
 | 
			
		||||
## Step1. Fetch NVD
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ for i in {2002..2016}; 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. Prepare
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run --rm \
 | 
			
		||||
    -v ~/.ssh:/root/.ssh:ro \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/vuls-log:/var/log/vuls \
 | 
			
		||||
    vuls/vuls prepare \
 | 
			
		||||
    -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
 | 
			
		||||
							
								
								
									
										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 {2002..2016}; 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"]
 | 
			
		||||
							
								
								
									
										134
									
								
								setup/docker/vuls/latest/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								setup/docker/vuls/latest/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,134 @@
 | 
			
		||||
# 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
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## configtest
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## prepare
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run --rm \
 | 
			
		||||
    -v ~/.ssh:/root/.ssh:ro \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/vuls-log:/var/log/vuls \
 | 
			
		||||
    vuls/vuls prepare \
 | 
			
		||||
    -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,8 +18,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/rifflock/lfshook"
 | 
			
		||||
@@ -40,6 +41,9 @@ func NewCustomLogger(c config.ServerInfo) *logrus.Entry {
 | 
			
		||||
 | 
			
		||||
	// File output
 | 
			
		||||
	logDir := "/var/log/vuls"
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		logDir = filepath.Join(os.Getenv("APPDATA"), "vuls")
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := os.Stat(logDir); os.IsNotExist(err) {
 | 
			
		||||
		if err := os.Mkdir(logDir, 0666); err != nil {
 | 
			
		||||
			logrus.Errorf("Failed to create log directory: %s", err)
 | 
			
		||||
@@ -48,11 +52,11 @@ func NewCustomLogger(c config.ServerInfo) *logrus.Entry {
 | 
			
		||||
 | 
			
		||||
	whereami := "localhost"
 | 
			
		||||
	if 0 < len(c.ServerName) {
 | 
			
		||||
		whereami = fmt.Sprintf("%s:%s", c.ServerName, c.Port)
 | 
			
		||||
 | 
			
		||||
		whereami = c.GetServerName()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := os.Stat(logDir); err == nil {
 | 
			
		||||
		path := fmt.Sprintf("%s/%s.log", logDir, whereami)
 | 
			
		||||
		path := filepath.Join(logDir, whereami)
 | 
			
		||||
		log.Hooks.Add(lfshook.NewHook(lfshook.PathMap{
 | 
			
		||||
			logrus.DebugLevel: path,
 | 
			
		||||
			logrus.InfoLevel:  path,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								util/util.go
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								util/util.go
									
									
									
									
									
								
							@@ -31,6 +31,12 @@ func GenWorkers(num int) chan<- func() {
 | 
			
		||||
	tasks := make(chan func())
 | 
			
		||||
	for i := 0; i < num; i++ {
 | 
			
		||||
		go func() {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if p := recover(); p != nil {
 | 
			
		||||
					log := NewCustomLogger(config.ServerInfo{})
 | 
			
		||||
					log.Debugf("Panic: %s")
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			for f := range tasks {
 | 
			
		||||
				f()
 | 
			
		||||
			}
 | 
			
		||||
@@ -105,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)
 | 
			
		||||
@@ -118,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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								version.go
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								version.go
									
									
									
									
									
								
							@@ -1,24 +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 main
 | 
			
		||||
 | 
			
		||||
// Name is Vuls
 | 
			
		||||
const Name string = "vuls"
 | 
			
		||||
 | 
			
		||||
// Version of Vuls
 | 
			
		||||
const Version string = "0.1.1"
 | 
			
		||||
		Reference in New Issue
	
	Block a user