Compare commits
	
		
			484 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					e788e6a5ad | ||
| 
						 | 
					d00e912934 | ||
| 
						 | 
					8ebb663368 | ||
| 
						 | 
					445ffc4123 | ||
| 
						 | 
					6af49f4d55 | ||
| 
						 | 
					1de9e8c086 | ||
| 
						 | 
					59b0812adf | ||
| 
						 | 
					719785c1ed | ||
| 
						 | 
					8e5f627e59 | ||
| 
						 | 
					5ced3c72b8 | ||
| 
						 | 
					c002f0168c | ||
| 
						 | 
					00c690f516 | ||
| 
						 | 
					ab68ad5cc5 | ||
| 
						 | 
					5c84ebefab | ||
| 
						 | 
					eb2acaff22 | ||
| 
						 | 
					84d0655c52 | ||
| 
						 | 
					e137ebb9c2 | ||
| 
						 | 
					10d690d929 | ||
| 
						 | 
					14611d2fd9 | ||
| 
						 | 
					0665bfe15f | ||
| 
						 | 
					473096d35d | ||
| 
						 | 
					0eae26e261 | ||
| 
						 | 
					a32845f652 | ||
| 
						 | 
					15a0f7eadb | ||
| 
						 | 
					5a0a6abf11 | ||
| 
						 | 
					032b8d9572 | ||
| 
						 | 
					5798e3af83 | ||
| 
						 | 
					8e15b9ce1c | ||
| 
						 | 
					7a1f132c1f | ||
| 
						 | 
					a8483b2195 | ||
| 
						 | 
					83bbbd0cb0 | ||
| 
						 | 
					132432dce6 | ||
| 
						 | 
					e5eb8e42f5 | ||
| 
						 | 
					1095ebea24 | ||
| 
						 | 
					1541a602b2 | ||
| 
						 | 
					03a141c252 | ||
| 
						 | 
					5f2183fc8e | ||
| 
						 | 
					820831fa5d | ||
| 
						 | 
					6d2d767c52 | ||
| 
						 | 
					e0c3a728ae | ||
| 
						 | 
					ec92f7797f | ||
| 
						 | 
					0ba490c6df | ||
| 
						 | 
					cfd668e11d | ||
| 
						 | 
					a8bc25321e | ||
| 
						 | 
					fec13bcb86 | ||
| 
						 | 
					cb1c07f998 | ||
| 
						 | 
					6312b97faa | ||
| 
						 | 
					21f13b55eb | ||
| 
						 | 
					187598382b | ||
| 
						 | 
					551fdd5022 | ||
| 
						 | 
					58b0d03e28 | ||
| 
						 | 
					3790197699 | ||
| 
						 | 
					579fff122c | ||
| 
						 | 
					feb3f79a13 | ||
| 
						 | 
					b5cb08ac43 | ||
| 
						 | 
					4ac5d9e0da | ||
| 
						 | 
					93f741da35 | ||
| 
						 | 
					648a999514 | ||
| 
						 | 
					71490aebd9 | ||
| 
						 | 
					9e90c0f912 | ||
| 
						 | 
					de65073f61 | ||
| 
						 | 
					6129ac7bd4 | ||
| 
						 | 
					b5d4d27312 | ||
| 
						 | 
					823fcd91f4 | ||
| 
						 | 
					477e12d5cf | ||
| 
						 | 
					a36a226ae2 | ||
| 
						 | 
					886a21c633 | ||
| 
						 | 
					fd19fa2082 | ||
| 
						 | 
					843f1a462f | ||
| 
						 | 
					5c5b8a361d | ||
| 
						 | 
					417df0582d | ||
| 
						 | 
					999d8f5866 | ||
| 
						 | 
					47a444e795 | ||
| 
						 | 
					dbceca8780 | ||
| 
						 | 
					c66898e608 | ||
| 
						 | 
					ee20cb59a5 | ||
| 
						 | 
					5c51d83573 | ||
| 
						 | 
					47b3b3848b | ||
| 
						 | 
					95eb980f58 | ||
| 
						 | 
					f738622c28 | ||
| 
						 | 
					577509bbf9 | ||
| 
						 | 
					774c78add0 | ||
| 
						 | 
					b14406e329 | ||
| 
						 | 
					29cf4bb517 | ||
| 
						 | 
					a233e08929 | ||
| 
						 | 
					cbd1c12773 | ||
| 
						 | 
					0a3f0f9ffc | ||
| 
						 | 
					d3014025b0 | ||
| 
						 | 
					2887dc0d36 | ||
| 
						 | 
					5f49e7da8e | ||
| 
						 | 
					9e0032b258 | ||
| 
						 | 
					008da49b83 | ||
| 
						 | 
					9899cba816 | ||
| 
						 | 
					27724a2faf | ||
| 
						 | 
					8b6a283114 | ||
| 
						 | 
					4379b8bacf | ||
| 
						 | 
					56603dcfae | ||
| 
						 | 
					1752736714 | ||
| 
						 | 
					b1428b6758 | ||
| 
						 | 
					9b6d84def6 | ||
| 
						 | 
					ed162d7d6e | ||
| 
						 | 
					1aae425945 | ||
| 
						 | 
					26e447f11a | ||
| 
						 | 
					ffbaa0a508 | ||
| 
						 | 
					a9ebac3818 | ||
| 
						 | 
					738e9fb119 | ||
| 
						 | 
					7778783dd8 | ||
| 
						 | 
					c442a433b0 | ||
| 
						 | 
					f7aa85746d | ||
| 
						 | 
					1883da3b2a | ||
| 
						 | 
					997dd6022f | ||
| 
						 | 
					63394a2400 | ||
| 
						 | 
					a662b038dc | ||
| 
						 | 
					e9df2bfa01 | ||
| 
						 | 
					a7951b727c | ||
| 
						 | 
					c6ad9ea57a | ||
| 
						 | 
					a14810bbd4 | ||
| 
						 | 
					bc5a95ebb3 | ||
| 
						 | 
					306182e2ae | ||
| 
						 | 
					ad096196ee | ||
| 
						 | 
					af66e44427 | ||
| 
						 | 
					0a012273ec | ||
| 
						 | 
					73b011eba7 | ||
| 
						 | 
					a31974a3c0 | ||
| 
						 | 
					eb02bdd95a | ||
| 
						 | 
					74805c6be8 | ||
| 
						 | 
					d9bc4499a4 | ||
| 
						 | 
					9128e2748b | ||
| 
						 | 
					7f8c975bd7 | ||
| 
						 | 
					8b6c841b1e | ||
| 
						 | 
					4fcdea3ccb | ||
| 
						 | 
					3be11cf52f | ||
| 
						 | 
					b285cb0e57 | ||
| 
						 | 
					dd5a7920e5 | ||
| 
						 | 
					cfb848918f | ||
| 
						 | 
					b977558f38 | ||
| 
						 | 
					210e3dc990 | ||
| 
						 | 
					f36671784e | ||
| 
						 | 
					d626cc8a8b | ||
| 
						 | 
					f26b61d773 | ||
| 
						 | 
					12c2d3cbc6 | ||
| 
						 | 
					209ca704de | ||
| 
						 | 
					2e37d3adc1 | ||
| 
						 | 
					509fb045b6 | ||
| 
						 | 
					a2c364f9eb | ||
| 
						 | 
					17a4e532c1 | ||
| 
						 | 
					c103b79ec2 | ||
| 
						 | 
					b545b5d0a3 | ||
| 
						 | 
					342a1c6cff | ||
| 
						 | 
					aafbdcd34d | ||
| 
						 | 
					ec092501c3 | ||
| 
						 | 
					bb708db89f | ||
| 
						 | 
					085a9dcb79 | ||
| 
						 | 
					037e12b0bd | ||
| 
						 | 
					c9ab956f8f | ||
| 
						 | 
					587c87b3a0 | ||
| 
						 | 
					1a319859eb | ||
| 
						 | 
					c989c31aeb | ||
| 
						 | 
					e5d32c8764 | ||
| 
						 | 
					23c177ed4a | ||
| 
						 | 
					10a27042b5 | ||
| 
						 | 
					2cec20c7ee | ||
| 
						 | 
					7ecd09f497 | ||
| 
						 | 
					8bf7f6cac5 | ||
| 
						 | 
					067a2315df | ||
| 
						 | 
					fecd1ad464 | ||
| 
						 | 
					a3f2555bc1 | ||
| 
						 | 
					5bf4cd46ff | ||
| 
						 | 
					f878e225cc | ||
| 
						 | 
					eb2598f3b3 | ||
| 
						 | 
					e20a59b991 | ||
| 
						 | 
					703c142659 | ||
| 
						 | 
					8335b40368 | ||
| 
						 | 
					05884c2d29 | ||
| 
						 | 
					33b2aa2d52 | ||
| 
						 | 
					9ab0622886 | ||
| 
						 | 
					b33cd54916 | ||
| 
						 | 
					d4bec0dd9a | ||
| 
						 | 
					bdf6efeaac | ||
| 
						 | 
					74431ca63f | ||
| 
						 | 
					c90be385ef | ||
| 
						 | 
					b0d9c0b550 | ||
| 
						 | 
					9255132f9b | ||
| 
						 | 
					d5c0092fa3 | ||
| 
						 | 
					c7019debb9 | ||
| 
						 | 
					7131270cad | ||
| 
						 | 
					af5a1204bc | ||
| 
						 | 
					58afcfc49a | ||
| 
						 | 
					986762ca85 | ||
| 
						 | 
					6342cf79f5 | ||
| 
						 | 
					5fbf67f971 | ||
| 
						 | 
					e441e5a696 | ||
| 
						 | 
					d201efb029 | ||
| 
						 | 
					25960126c7 | ||
| 
						 | 
					63d5a6f584 | ||
| 
						 | 
					2030951a8f | ||
| 
						 | 
					cd841462cd | ||
| 
						 | 
					735aa835a6 | ||
| 
						 | 
					92e213ca32 | ||
| 
						 | 
					d077c29716 | ||
| 
						 | 
					d6eba48a50 | ||
| 
						 | 
					2a1608d1d2 | ||
| 
						 | 
					cc7d3dc2aa | ||
| 
						 | 
					a5c4c682f5 | ||
| 
						 | 
					688cfd6872 | ||
| 
						 | 
					7e268dbae1 | ||
| 
						 | 
					ce6a4231ef | ||
| 
						 | 
					e1de8ab626 | ||
| 
						 | 
					0058eaf357 | ||
| 
						 | 
					732d95098a | ||
| 
						 | 
					52f0943207 | ||
| 
						 | 
					41f99f2b65 | ||
| 
						 | 
					1f9e5c6263 | ||
| 
						 | 
					2f3eddd2ab | ||
| 
						 | 
					619a0ee700 | ||
| 
						 | 
					b1b5c2c9a0 | ||
| 
						 | 
					a86035c0bf | ||
| 
						 | 
					c66b0f4db4 | ||
| 
						 | 
					a4cf4bd314 | ||
| 
						 | 
					f1cd9383c1 | ||
| 
						 | 
					6fa57abe10 | ||
| 
						 | 
					6e77c714b5 | ||
| 
						 | 
					fbab020e6e | ||
| 
						 | 
					5581a5cce7 | ||
| 
						 | 
					b4be11775e | ||
| 
						 | 
					b079f5e52e | ||
| 
						 | 
					f9bf470a37 | ||
| 
						 | 
					9d783dd2ab | ||
| 
						 | 
					1b9aafbbaf | ||
| 
						 | 
					1d3ee6a241 | ||
| 
						 | 
					2f9c3071a6 | ||
| 
						 | 
					4b0be4f115 | ||
| 
						 | 
					1419c7c8c6 | ||
| 
						 | 
					851cecdd73 | ||
| 
						 | 
					753da3aad7 | ||
| 
						 | 
					65c10d6d8e | ||
| 
						 | 
					1b8b423131 | ||
| 
						 | 
					55b1264c7d | ||
| 
						 | 
					902a1888d4 | ||
| 
						 | 
					98151f7d0e | ||
| 
						 | 
					a6f0c559f8 | ||
| 
						 | 
					e7ec5b841d | ||
| 
						 | 
					d6f72ac0f3 | ||
| 
						 | 
					7e3a10025a | ||
| 
						 | 
					e16ec15226 | ||
| 
						 | 
					6935b56c9d | ||
| 
						 | 
					0e3a0b64e7 | ||
| 
						 | 
					74e6aee236 | ||
| 
						 | 
					db0602b7b8 | ||
| 
						 | 
					c9b7c3f179 | ||
| 
						 | 
					5bd9f4afb4 | ||
| 
						 | 
					9d2ba5912e | ||
| 
						 | 
					9986c4a6f3 | ||
| 
						 | 
					df2c9697ef | ||
| 
						 | 
					ab0388e882 | ||
| 
						 | 
					c05d8a36eb | ||
| 
						 | 
					492753d905 | ||
| 
						 | 
					6e08bd23f4 | ||
| 
						 | 
					a687c97808 | ||
| 
						 | 
					c6864289cb | ||
| 
						 | 
					97d85258c5 | ||
| 
						 | 
					bee25f5aa2 | ||
| 
						 | 
					386b97d2be | ||
| 
						 | 
					00660485b7 | ||
| 
						 | 
					1e8f24dedb | ||
| 
						 | 
					2be190f863 | ||
| 
						 | 
					ec7c6e6c85 | ||
| 
						 | 
					c52bc53fd8 | ||
| 
						 | 
					981631503a | ||
| 
						 | 
					48de3a6a4f | ||
| 
						 | 
					d1983a6978 | ||
| 
						 | 
					f821a26aec | ||
| 
						 | 
					3380e905de | ||
| 
						 | 
					b5c2718756 | ||
| 
						 | 
					a03a803b89 | ||
| 
						 | 
					e743177ae6 | ||
| 
						 | 
					6e12c69953 | ||
| 
						 | 
					019ab77466 | ||
| 
						 | 
					1730caf124 | ||
| 
						 | 
					59d1533795 | ||
| 
						 | 
					a6278ab7ea | ||
| 
						 | 
					42a6004c7d | ||
| 
						 | 
					6084c1b1d3 | ||
| 
						 | 
					c96fbc1dba | ||
| 
						 | 
					5546a8b093 | ||
| 
						 | 
					6b76b38dcd | ||
| 
						 | 
					941e50b460 | ||
| 
						 | 
					5a10e5c9ff | ||
| 
						 | 
					883fe13756 | ||
| 
						 | 
					2e7c34cf9f | ||
| 
						 | 
					9216efbd2f | ||
| 
						 | 
					6c8100e5b6 | ||
| 
						 | 
					e7ef50bedf | ||
| 
						 | 
					386ca3565a | ||
| 
						 | 
					2d854cd64d | ||
| 
						 | 
					49b4b8be22 | ||
| 
						 | 
					db975ebfee | ||
| 
						 | 
					d60a41139b | ||
| 
						 | 
					f62d869d27 | ||
| 
						 | 
					6cbe3cdb93 | ||
| 
						 | 
					b13e7b9da4 | ||
| 
						 | 
					8fe34c8474 | ||
| 
						 | 
					bef29be50f | ||
| 
						 | 
					20275a1063 | ||
| 
						 | 
					910385b084 | ||
| 
						 | 
					8e779374a7 | ||
| 
						 | 
					44fc6f728e | ||
| 
						 | 
					1f62dcf22a | ||
| 
						 | 
					0416c3b561 | ||
| 
						 | 
					a6912cae76 | ||
| 
						 | 
					63dfe8a952 | ||
| 
						 | 
					62d1b761bd | ||
| 
						 | 
					082b10a15b | ||
| 
						 | 
					1a6bcd82b0 | ||
| 
						 | 
					6ecd70220b | ||
| 
						 | 
					e9f55f5772 | ||
| 
						 | 
					155cadf901 | ||
| 
						 | 
					cb29289167 | ||
| 
						 | 
					e4db9d1d91 | ||
| 
						 | 
					7b2e2cb817 | ||
| 
						 | 
					c717f8d15d | ||
| 
						 | 
					8db147acab | ||
| 
						 | 
					e6de7aa9ca | ||
| 
						 | 
					46f96740a2 | ||
| 
						 | 
					8f9fb5c262 | ||
| 
						 | 
					171d6d6684 | ||
| 
						 | 
					f648b5ad0a | ||
| 
						 | 
					ef21376f0a | ||
| 
						 | 
					58958d68d8 | ||
| 
						 | 
					a06b565ee9 | ||
| 
						 | 
					a7db27ce5a | ||
| 
						 | 
					cda69dc7f0 | ||
| 
						 | 
					39f9594548 | ||
| 
						 | 
					6d82ad32a9 | ||
| 
						 | 
					cfcd8bf223 | ||
| 
						 | 
					8149ad00b5 | ||
| 
						 | 
					2310522806 | ||
| 
						 | 
					e40ef656d6 | ||
| 
						 | 
					e060d40a32 | ||
| 
						 | 
					a522218c4e | ||
| 
						 | 
					820455399c | ||
| 
						 | 
					959d612534 | ||
| 
						 | 
					cd81e6eab2 | ||
| 
						 | 
					e6ec6920ad | ||
| 
						 | 
					18a92fa1ca | ||
| 
						 | 
					f95af9897b | ||
| 
						 | 
					b61adcb1fd | ||
| 
						 | 
					1bbf320755 | ||
| 
						 | 
					159f26171c | ||
| 
						 | 
					8ac00f6c0d | ||
| 
						 | 
					ce2daf2493 | ||
| 
						 | 
					f014f8fd59 | ||
| 
						 | 
					f50a39a9e2 | ||
| 
						 | 
					e0d8147104 | ||
| 
						 | 
					c5cfac62da | ||
| 
						 | 
					83469ce5cc | ||
| 
						 | 
					7cd7b4a9a2 | ||
| 
						 | 
					7681b277cf | ||
| 
						 | 
					406efa96c0 | ||
| 
						 | 
					9a7a30c0bc | ||
| 
						 | 
					64bdfa0e80 | ||
| 
						 | 
					067089973c | ||
| 
						 | 
					85e6d753c7 | ||
| 
						 | 
					4094984642 | ||
| 
						 | 
					85c0009a43 | ||
| 
						 | 
					234e312ee2 | ||
| 
						 | 
					ce3ca64678 | ||
| 
						 | 
					b042a600c3 | ||
| 
						 | 
					686e9f07a9 | ||
| 
						 | 
					bb6725372b | ||
| 
						 | 
					6f012fc9c5 | ||
| 
						 | 
					4c82458481 | ||
| 
						 | 
					a0ac863998 | ||
| 
						 | 
					d23ef838f8 | ||
| 
						 | 
					f81ac197f5 | ||
| 
						 | 
					652b37e630 | ||
| 
						 | 
					c57e430393 | ||
| 
						 | 
					fff6047df9 | ||
| 
						 | 
					1e2b93d55b | ||
| 
						 | 
					66b27a7795 | ||
| 
						 | 
					63f0a272c4 | ||
| 
						 | 
					8d2180cf5a | ||
| 
						 | 
					1986f7e4dd | ||
| 
						 | 
					21beb396b4 | ||
| 
						 | 
					cb5a6f38d6 | ||
| 
						 | 
					67e4aaede0 | ||
| 
						 | 
					b42805d00c | ||
| 
						 | 
					95d6888c87 | ||
| 
						 | 
					549b315a65 | ||
| 
						 | 
					5b80b16684 | ||
| 
						 | 
					0cd0a4bf2b | ||
| 
						 | 
					b5cf06cad8 | ||
| 
						 | 
					b964d19d82 | ||
| 
						 | 
					cf7990d444 | ||
| 
						 | 
					738ccf7dbb | ||
| 
						 | 
					fc2ea48c1d | ||
| 
						 | 
					3af93b93d7 | ||
| 
						 | 
					f386c3be92 | ||
| 
						 | 
					239d910dbe | ||
| 
						 | 
					48929deabd | ||
| 
						 | 
					79523de1db | ||
| 
						 | 
					fbfc14dfeb | ||
| 
						 | 
					a8dc886f89 | ||
| 
						 | 
					cfc9e064b9 | ||
| 
						 | 
					e72fa3362a | ||
| 
						 | 
					26364421e8 | ||
| 
						 | 
					4a07974b54 | ||
| 
						 | 
					eaddc7f2ba | ||
| 
						 | 
					85056aaa00 | ||
| 
						 | 
					c077c740fa | ||
| 
						 | 
					c2eab87a3f | ||
| 
						 | 
					ea582d2d2e | ||
| 
						 | 
					2f89a24100 | ||
| 
						 | 
					73ebb94f67 | ||
| 
						 | 
					95bf387ecc | ||
| 
						 | 
					f17a8452f9 | ||
| 
						 | 
					920ffe1f33 | ||
| 
						 | 
					093bcb7477 | ||
| 
						 | 
					c06b3ec9eb | ||
| 
						 | 
					ac6fe6f9fc | ||
| 
						 | 
					2dffdaac42 | ||
| 
						 | 
					cb445c9504 | ||
| 
						 | 
					e3fc3aa9d1 | ||
| 
						 | 
					97c3f5d642 | ||
| 
						 | 
					0a52fc9a56 | ||
| 
						 | 
					c831339b0d | ||
| 
						 | 
					058ccf575f | ||
| 
						 | 
					92be12bc2f | ||
| 
						 | 
					1aa2f4b5b1 | ||
| 
						 | 
					bba9431985 | ||
| 
						 | 
					3c39f1e737 | ||
| 
						 | 
					e6f4d07a87 | ||
| 
						 | 
					e43358a0d2 | ||
| 
						 | 
					f0644e8a9d | ||
| 
						 | 
					11b010b281 | ||
| 
						 | 
					c751029127 | ||
| 
						 | 
					fb70d1b2f0 | ||
| 
						 | 
					3d68783b7f | ||
| 
						 | 
					0d77853912 | ||
| 
						 | 
					ea1b5dd8f7 | ||
| 
						 | 
					2dcb7d5ce1 | ||
| 
						 | 
					99cab34527 | ||
| 
						 | 
					f5eeed0bc2 | ||
| 
						 | 
					1b85e56961 | ||
| 
						 | 
					8a8ac5fd22 | ||
| 
						 | 
					00c0354a8e | ||
| 
						 | 
					a2a6973ba1 | ||
| 
						 | 
					dd1d3a05fa | ||
| 
						 | 
					2afe2d2640 | ||
| 
						 | 
					29678f9b59 | ||
| 
						 | 
					77edb251bb | ||
| 
						 | 
					29151fa267 | ||
| 
						 | 
					b3f13790bd | ||
| 
						 | 
					38857c3356 | ||
| 
						 | 
					d75990d9fd | ||
| 
						 | 
					ed063f6534 | ||
| 
						 | 
					c8a9bdc517 | ||
| 
						 | 
					595729cdf8 | ||
| 
						 | 
					6119f79748 | ||
| 
						 | 
					d4fb46c9ba | ||
| 
						 | 
					c41301afca | ||
| 
						 | 
					50fd80830e | ||
| 
						 | 
					1c203b4272 | ||
| 
						 | 
					c545e9045d | ||
| 
						 | 
					2721dc0647 | ||
| 
						 | 
					51d13f4234 | ||
| 
						 | 
					a60a5d6eab | ||
| 
						 | 
					5959235425 | ||
| 
						 | 
					d8e6d4e5fc | ||
| 
						 | 
					7dfc9815b3 | ||
| 
						 | 
					0c53b187a4 | ||
| 
						 | 
					42dadfed8f | ||
| 
						 | 
					a46c603c77 | ||
| 
						 | 
					ad0020d9a6 | ||
| 
						 | 
					a224f0bfd4 | ||
| 
						 | 
					d8dc3650d3 | ||
| 
						 | 
					30f7527f10 | ||
| 
						 | 
					b1f5bdd8b2 | ||
| 
						 | 
					c8e7c8b9fa | ||
| 
						 | 
					30bf3223f8 | ||
| 
						 | 
					886710ec30 | ||
| 
						 | 
					510dc8d828 | ||
| 
						 | 
					5ff7b2aab4 | ||
| 
						 | 
					1e33536205 | 
							
								
								
									
										36
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,36 @@
 | 
			
		||||
 | 
			
		||||
# Environment
 | 
			
		||||
 | 
			
		||||
## Vuls
 | 
			
		||||
 | 
			
		||||
Hash : ____
 | 
			
		||||
 | 
			
		||||
To check the commit hash of HEAD
 | 
			
		||||
$ vuls -v
 | 
			
		||||
 | 
			
		||||
or
 | 
			
		||||
$ cd $GOPATH/src/github.com/future-architect/vuls 
 | 
			
		||||
$ git rev-parse --short HEAD 
 | 
			
		||||
 | 
			
		||||
## OS
 | 
			
		||||
- Target Server: Write here
 | 
			
		||||
- Vuls Server: Write here
 | 
			
		||||
 | 
			
		||||
## Go
 | 
			
		||||
- Go version: here
 | 
			
		||||
 | 
			
		||||
# Current Output
 | 
			
		||||
 | 
			
		||||
Please re-run the command using ```-debug``` and provide the output below.
 | 
			
		||||
 | 
			
		||||
# Addition Details
 | 
			
		||||
 | 
			
		||||
Can you also please fill in each of the remaining sections.
 | 
			
		||||
 | 
			
		||||
## Expected Behavior
 | 
			
		||||
 | 
			
		||||
## Actual Behavior
 | 
			
		||||
 | 
			
		||||
## Steps to reproduce the behaviour
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										24
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,24 @@
 | 
			
		||||
## What did you implement:
 | 
			
		||||
 | 
			
		||||
Closes #XXXXX
 | 
			
		||||
 | 
			
		||||
## How did you implement it:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## How can we verify it:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Todos:
 | 
			
		||||
You don't have to satisfy all of the following.
 | 
			
		||||
 | 
			
		||||
- [ ] Write tests
 | 
			
		||||
- [ ] Write documentation
 | 
			
		||||
- [ ] Check that there aren't other open pull requests for the same issue/feature
 | 
			
		||||
- [ ] Format your source code by `make fmt`
 | 
			
		||||
- [ ] Pass the test by `make test`
 | 
			
		||||
- [ ] Provide verification config / commands
 | 
			
		||||
- [ ] Enable "Allow edits from maintainers" for this PR
 | 
			
		||||
- [ ] Update the messages below
 | 
			
		||||
 | 
			
		||||
***Is this ready for review?:*** NO  
 | 
			
		||||
***Is it a breaking change?:*** NO
 | 
			
		||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,8 +1,9 @@
 | 
			
		||||
vuls
 | 
			
		||||
.vscode
 | 
			
		||||
*.txt
 | 
			
		||||
*.json
 | 
			
		||||
*.sqlite3
 | 
			
		||||
*.sqlite3*
 | 
			
		||||
*.db
 | 
			
		||||
tags
 | 
			
		||||
.gitmodules
 | 
			
		||||
coverage.out
 | 
			
		||||
issues/
 | 
			
		||||
@@ -10,3 +11,4 @@ vendor/
 | 
			
		||||
log/
 | 
			
		||||
results/
 | 
			
		||||
*config.toml
 | 
			
		||||
!setup/docker/*
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								.goreleaser.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,24 @@
 | 
			
		||||
project_name: vuls
 | 
			
		||||
release:
 | 
			
		||||
  github:
 | 
			
		||||
    owner: future-architect
 | 
			
		||||
    name: vuls
 | 
			
		||||
builds:
 | 
			
		||||
- goos:
 | 
			
		||||
  - linux
 | 
			
		||||
  goarch:
 | 
			
		||||
  - amd64
 | 
			
		||||
  main: .
 | 
			
		||||
  ldflags: -s -w -X main.version={{.Version}} -X main.revision={{.Commit}}
 | 
			
		||||
  binary: vuls
 | 
			
		||||
archive:
 | 
			
		||||
  format: tar.gz
 | 
			
		||||
  name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{
 | 
			
		||||
    .Arm }}{{ end }}'
 | 
			
		||||
  files:
 | 
			
		||||
  - LICENSE
 | 
			
		||||
  - NOTICE
 | 
			
		||||
  - README*
 | 
			
		||||
  - CHANGELOG.md
 | 
			
		||||
snapshot:
 | 
			
		||||
  name_template: SNAPSHOT-{{ .Commit }}
 | 
			
		||||
							
								
								
									
										7
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,7 @@
 | 
			
		||||
language: go
 | 
			
		||||
 | 
			
		||||
go:
 | 
			
		||||
  - 1.8
 | 
			
		||||
 | 
			
		||||
after_success:
 | 
			
		||||
  - test -n "$TRAVIS_TAG" && curl -sL https://git.io/goreleaser | bash
 | 
			
		||||
							
								
								
									
										349
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						@@ -1,5 +1,350 @@
 | 
			
		||||
# Change Log
 | 
			
		||||
 | 
			
		||||
## v0.4.1 and later, see [GitHub release](https://github.com/future-architect/vuls/releases)
 | 
			
		||||
 | 
			
		||||
## [v0.4.0](https://github.com/future-architect/vuls/tree/v0.4.0) (2017-08-25)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.3.0...v0.4.0)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- Output changelog in report, TUI and JSON for RHEL [\#367](https://github.com/future-architect/vuls/issues/367)
 | 
			
		||||
- Output changelog in report, TUI and JSON for Amazon Linux [\#366](https://github.com/future-architect/vuls/issues/366)
 | 
			
		||||
- Improve scanning accuracy by checking package versions [\#256](https://github.com/future-architect/vuls/issues/256)
 | 
			
		||||
- Improve SSH [\#415](https://github.com/future-architect/vuls/issues/415)
 | 
			
		||||
- Enable to scan even if target server can not connect to the Internet [\#258](https://github.com/future-architect/vuls/issues/258)
 | 
			
		||||
- SSH Hostkey check [\#417](https://github.com/future-architect/vuls/pull/417) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- v0.4.0 [\#449](https://github.com/future-architect/vuls/pull/449) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Change default ssh method from go library to external command [\#416](https://github.com/future-architect/vuls/pull/416) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add containers-only option to configtest [\#411](https://github.com/future-architect/vuls/pull/411) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
-  Running Vuls tui before vuls report does not show vulnerabilities checked by CPE [\#396](https://github.com/future-architect/vuls/issues/396)
 | 
			
		||||
- With a long package name, Local shell mode \(stty dont' work\) [\#444](https://github.com/future-architect/vuls/issues/444)
 | 
			
		||||
- Improve SSH [\#415](https://github.com/future-architect/vuls/issues/415)
 | 
			
		||||
- Report that a vulnerability exists in the wrong package [\#408](https://github.com/future-architect/vuls/issues/408)
 | 
			
		||||
- With a long package name, a parse error occurs. [\#391](https://github.com/future-architect/vuls/issues/391)
 | 
			
		||||
- Ubuntu failed to scan vulnerable packages [\#205](https://github.com/future-architect/vuls/issues/205)
 | 
			
		||||
- CVE-ID in changelog can't be picked up. [\#154](https://github.com/future-architect/vuls/issues/154)
 | 
			
		||||
- v0.4.0 [\#449](https://github.com/future-architect/vuls/pull/449) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix SSH dial error [\#413](https://github.com/future-architect/vuls/pull/413) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Update deps, Change deps tool from glide to dep [\#412](https://github.com/future-architect/vuls/pull/412) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix report option Loaded error-info [\#406](https://github.com/future-architect/vuls/pull/406) ([hogehogehugahuga](https://github.com/hogehogehugahuga))
 | 
			
		||||
- Add --user root to docker exec command [\#389](https://github.com/future-architect/vuls/pull/389) ([PaulFurtado](https://github.com/PaulFurtado))
 | 
			
		||||
 | 
			
		||||
**Closed issues:**
 | 
			
		||||
 | 
			
		||||
- README.md.ja not include "Oracle Linux, FreeBSD"  [\#465](https://github.com/future-architect/vuls/issues/465)
 | 
			
		||||
- Can't scan remote server - \(centos 7 - updated\) [\#451](https://github.com/future-architect/vuls/issues/451)
 | 
			
		||||
- An abnormality in the result of vuls tui [\#439](https://github.com/future-architect/vuls/issues/439)
 | 
			
		||||
- compile faild [\#436](https://github.com/future-architect/vuls/issues/436)
 | 
			
		||||
- Can't install vuls on CentOS 7 [\#432](https://github.com/future-architect/vuls/issues/432)
 | 
			
		||||
- Vuls scan doesn't show severity score in any of the vulnerable packages [\#430](https://github.com/future-architect/vuls/issues/430)
 | 
			
		||||
- Load config failedtoml: cannot load TOML value of type string into a Go slice [\#429](https://github.com/future-architect/vuls/issues/429)
 | 
			
		||||
- vuls scan not running check-update with sudo for Centos 7 [\#428](https://github.com/future-architect/vuls/issues/428)
 | 
			
		||||
- options for configtest not being activated [\#422](https://github.com/future-architect/vuls/issues/422)
 | 
			
		||||
- "could not find project Gopkg.toml, use dep init to initiate a manifest" when installing vuls [\#420](https://github.com/future-architect/vuls/issues/420)
 | 
			
		||||
- go get not get  [\#407](https://github.com/future-architect/vuls/issues/407)
 | 
			
		||||
- Failed to scan via docker. err: Unknown format [\#404](https://github.com/future-architect/vuls/issues/404)
 | 
			
		||||
- Failed to scan - kernel-xxx is an installed security update [\#403](https://github.com/future-architect/vuls/issues/403)
 | 
			
		||||
- 169.254.169.254 port 80: Connection refused [\#402](https://github.com/future-architect/vuls/issues/402)
 | 
			
		||||
- vuls scan --debug cause `invalid memory address` error [\#397](https://github.com/future-architect/vuls/issues/397)
 | 
			
		||||
- Provide a command line flag that will automatically install aptitude on debian? [\#390](https://github.com/future-architect/vuls/issues/390)
 | 
			
		||||
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- export fill cve info [\#467](https://github.com/future-architect/vuls/pull/467) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- add oval docker [\#466](https://github.com/future-architect/vuls/pull/466) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- fix typos in commands. [\#464](https://github.com/future-architect/vuls/pull/464) ([ymomoi](https://github.com/ymomoi))
 | 
			
		||||
- Update README [\#463](https://github.com/future-architect/vuls/pull/463) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- export FillWithOval [\#462](https://github.com/future-architect/vuls/pull/462) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- add serveruuid field [\#458](https://github.com/future-architect/vuls/pull/458) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- add s3 dirctory option [\#457](https://github.com/future-architect/vuls/pull/457) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Extract Advisory.Description on RHEL, Amazon, Oracle [\#450](https://github.com/future-architect/vuls/pull/450) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- nosudo on CentOS and Fetch Changelogs on Amazon, RHEL [\#448](https://github.com/future-architect/vuls/pull/448) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- change logrus package to lowercase and update other packages [\#446](https://github.com/future-architect/vuls/pull/446) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- add db backend redis [\#445](https://github.com/future-architect/vuls/pull/445) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- fast test [\#435](https://github.com/future-architect/vuls/pull/435) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- fix typo [\#433](https://github.com/future-architect/vuls/pull/433) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Add support for PostgreSQL as a DB storage back-end [\#431](https://github.com/future-architect/vuls/pull/431) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- typo README.js.md [\#426](https://github.com/future-architect/vuls/pull/426) ([ryurock](https://github.com/ryurock))
 | 
			
		||||
- Add TOC to README [\#425](https://github.com/future-architect/vuls/pull/425) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fixing \#420 where lock and manifest have moved to TOML [\#421](https://github.com/future-architect/vuls/pull/421) ([elfgoh](https://github.com/elfgoh))
 | 
			
		||||
- Define timeout for vulnerabilities scan and platform detection [\#414](https://github.com/future-architect/vuls/pull/414) ([s7anley](https://github.com/s7anley))
 | 
			
		||||
- Enable -timeout option when detecting OS [\#410](https://github.com/future-architect/vuls/pull/410) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Remove duplicate command in README [\#401](https://github.com/future-architect/vuls/pull/401) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Fix to read config.toml at tui [\#441](https://github.com/future-architect/vuls/pull/441) ([usiusi360](https://github.com/usiusi360))
 | 
			
		||||
- Change NVD URL to new one [\#419](https://github.com/future-architect/vuls/pull/419) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add some testcases [\#418](https://github.com/future-architect/vuls/pull/418) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
## [v0.3.0](https://github.com/future-architect/vuls/tree/v0.3.0) (2017-03-24)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.2.0...v0.3.0)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- Changelog parsing fails when package maintainers aren't consistent regarding versions [\#327](https://github.com/future-architect/vuls/issues/327)
 | 
			
		||||
- Docker scan doesn't report image name [\#325](https://github.com/future-architect/vuls/issues/325)
 | 
			
		||||
- vuls report -to-email only one E-Mail [\#295](https://github.com/future-architect/vuls/issues/295)
 | 
			
		||||
- Support RHEL5 [\#286](https://github.com/future-architect/vuls/issues/286)
 | 
			
		||||
- Continue scanning even when some hosts have tech issues? [\#264](https://github.com/future-architect/vuls/issues/264)
 | 
			
		||||
- Normalization of JSON output [\#259](https://github.com/future-architect/vuls/issues/259)
 | 
			
		||||
- Add report subcommand, change scan subcommand options [\#239](https://github.com/future-architect/vuls/issues/239)
 | 
			
		||||
- scan localhost? [\#210](https://github.com/future-architect/vuls/issues/210)
 | 
			
		||||
- Can Vuls show details about updateable packages [\#341](https://github.com/future-architect/vuls/issues/341)
 | 
			
		||||
- Scan all containers except [\#285](https://github.com/future-architect/vuls/issues/285)
 | 
			
		||||
- Notify the difference from the previous scan result [\#255](https://github.com/future-architect/vuls/issues/255)
 | 
			
		||||
- EC2RoleCreds support? [\#250](https://github.com/future-architect/vuls/issues/250)
 | 
			
		||||
- Output confidence score of detection accuracy and detection method to JSON or Reporting [\#350](https://github.com/future-architect/vuls/pull/350) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Avoid null slice being null in JSON [\#345](https://github.com/future-architect/vuls/pull/345) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add -format-one-email option [\#331](https://github.com/future-architect/vuls/pull/331) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Support Raspbian [\#330](https://github.com/future-architect/vuls/pull/330) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Add leniancy to the version matching for debian to account for versio… [\#328](https://github.com/future-architect/vuls/pull/328) ([jsulinski](https://github.com/jsulinski))
 | 
			
		||||
- Add image information for docker containers [\#326](https://github.com/future-architect/vuls/pull/326) ([jsulinski](https://github.com/jsulinski))
 | 
			
		||||
- Continue scanning even when some hosts have tech issues [\#309](https://github.com/future-architect/vuls/pull/309) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add -log-dir option [\#301](https://github.com/future-architect/vuls/pull/301) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Use --assumeno option [\#300](https://github.com/future-architect/vuls/pull/300) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Add local scan mode\(Scan without SSH when target server is localhost\) [\#291](https://github.com/future-architect/vuls/pull/291) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Support RHEL5 [\#289](https://github.com/future-architect/vuls/pull/289) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add LXD support [\#288](https://github.com/future-architect/vuls/pull/288) ([jiazio](https://github.com/jiazio))
 | 
			
		||||
- Add timeout option to configtest [\#400](https://github.com/future-architect/vuls/pull/400) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Notify the difference from the previous scan result [\#392](https://github.com/future-architect/vuls/pull/392) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Add Oracle Linux support [\#386](https://github.com/future-architect/vuls/pull/386) ([Djelibeybi](https://github.com/Djelibeybi))
 | 
			
		||||
- Change container scan format in config.toml [\#381](https://github.com/future-architect/vuls/pull/381) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Obsolete CentOS5 support [\#378](https://github.com/future-architect/vuls/pull/378) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Deprecate prepare subcommand to minimize the root authority defined by /etc/sudoers [\#375](https://github.com/future-architect/vuls/pull/375) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Support IAM role for report to S3. [\#370](https://github.com/future-architect/vuls/pull/370) ([ohsawa0515](https://github.com/ohsawa0515))
 | 
			
		||||
- Add .travis.yml [\#363](https://github.com/future-architect/vuls/pull/363) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Output changelog in report, TUI and JSON for Ubuntu/Debian/CentOS [\#356](https://github.com/future-architect/vuls/pull/356) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Debian scans failing in docker [\#323](https://github.com/future-architect/vuls/issues/323)
 | 
			
		||||
- Local CVE DB is still checked, even if a CVE Dictionary URL is defined [\#316](https://github.com/future-architect/vuls/issues/316)
 | 
			
		||||
- vuls needs gmake. [\#313](https://github.com/future-architect/vuls/issues/313)
 | 
			
		||||
- patch request for FreeBSD [\#312](https://github.com/future-architect/vuls/issues/312)
 | 
			
		||||
- Report: failed to read from json \(Docker\) [\#294](https://github.com/future-architect/vuls/issues/294)
 | 
			
		||||
- -report-mail option does not output required mail header [\#282](https://github.com/future-architect/vuls/issues/282)
 | 
			
		||||
- PackInfo not found error when vuls scan. [\#281](https://github.com/future-architect/vuls/issues/281)
 | 
			
		||||
- Normalize character set [\#279](https://github.com/future-architect/vuls/issues/279)
 | 
			
		||||
- The number of Updatable Packages is different from the number of yum check-update [\#373](https://github.com/future-architect/vuls/issues/373)
 | 
			
		||||
- sudo is needed when exec yum check-update on RHEL7 [\#371](https://github.com/future-architect/vuls/issues/371)
 | 
			
		||||
- `123-3ubuntu4` should be marked as ChangelogLenientMatch [\#362](https://github.com/future-architect/vuls/issues/362)
 | 
			
		||||
- CentOS  multi package invalid result [\#360](https://github.com/future-architect/vuls/issues/360)
 | 
			
		||||
- Parse error after check-update. \(Unknown format\) [\#359](https://github.com/future-architect/vuls/issues/359)
 | 
			
		||||
- Fix candidate to confidence. [\#354](https://github.com/future-architect/vuls/pull/354) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Bug fix: not send e-mail to cc address [\#346](https://github.com/future-architect/vuls/pull/346) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Change the command used for os detection from uname to freebsd-version [\#340](https://github.com/future-architect/vuls/pull/340) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix error handling of detectOS [\#337](https://github.com/future-architect/vuls/pull/337) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix infinite retry at size overrun error in Slack report [\#329](https://github.com/future-architect/vuls/pull/329) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- aptitude changelog defaults to using more, which is not interactive a… [\#324](https://github.com/future-architect/vuls/pull/324) ([jsulinski](https://github.com/jsulinski))
 | 
			
		||||
- Do not use sudo when echo [\#322](https://github.com/future-architect/vuls/pull/322) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Reduce privilege requirements for commands that don't need sudo on Ubuntu/Debian [\#319](https://github.com/future-architect/vuls/pull/319) ([jsulinski](https://github.com/jsulinski))
 | 
			
		||||
- Don't check for a CVE DB when CVE Dictionary URL is defined [\#317](https://github.com/future-architect/vuls/pull/317) ([jsulinski](https://github.com/jsulinski))
 | 
			
		||||
- Fix typo contianer -\> container [\#314](https://github.com/future-architect/vuls/pull/314) ([justyns](https://github.com/justyns))
 | 
			
		||||
- Fix the changelog cache logic for ubuntu/debian [\#305](https://github.com/future-architect/vuls/pull/305) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix yum updateinfo options [\#304](https://github.com/future-architect/vuls/pull/304) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Update glide.lock to fix create-log-dir error. [\#303](https://github.com/future-architect/vuls/pull/303) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix a bug in logging \(file output\) at scan command [\#302](https://github.com/future-architect/vuls/pull/302) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add -pipe flag \#294 [\#299](https://github.com/future-architect/vuls/pull/299) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix RHEL5 scan stopped halfway [\#293](https://github.com/future-architect/vuls/pull/293) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix amazon linux scan stopped halfway [\#292](https://github.com/future-architect/vuls/pull/292) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix nil-ponter in TUI [\#388](https://github.com/future-architect/vuls/pull/388) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix Bug of Mysql Backend [\#384](https://github.com/future-architect/vuls/pull/384) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix scan confidence on Ubuntu/Debian/Raspbian \#362 [\#379](https://github.com/future-architect/vuls/pull/379) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix updatalbe packages count \#373 [\#374](https://github.com/future-architect/vuls/pull/374) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- sudo yum check-update on RHEL [\#372](https://github.com/future-architect/vuls/pull/372) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Change ssh option from -t to -tt [\#369](https://github.com/future-architect/vuls/pull/369) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Increase the width of RequestPty [\#364](https://github.com/future-architect/vuls/pull/364) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
 | 
			
		||||
**Closed issues:**
 | 
			
		||||
 | 
			
		||||
-  vuls configtest --debugがsudoのチェックで止まってしまう [\#395](https://github.com/future-architect/vuls/issues/395)
 | 
			
		||||
- Add support for Oracle Linux [\#385](https://github.com/future-architect/vuls/issues/385)
 | 
			
		||||
- error on install - Ubuntu 16.04 [\#376](https://github.com/future-architect/vuls/issues/376)
 | 
			
		||||
- Unknown OS Type [\#335](https://github.com/future-architect/vuls/issues/335)
 | 
			
		||||
- mac os 10.12.3 make install error [\#334](https://github.com/future-architect/vuls/issues/334)
 | 
			
		||||
- assumeYes doesn't work because there is no else condition [\#320](https://github.com/future-architect/vuls/issues/320)
 | 
			
		||||
- Debian scan uses sudo where unnecessary [\#318](https://github.com/future-architect/vuls/issues/318)
 | 
			
		||||
- Add FreeBSD 11 to supported OS on documents. [\#311](https://github.com/future-architect/vuls/issues/311)
 | 
			
		||||
- docker fetchnvd failing [\#274](https://github.com/future-architect/vuls/issues/274)
 | 
			
		||||
- Latest version of labstack echo breaks installation [\#268](https://github.com/future-architect/vuls/issues/268)
 | 
			
		||||
- fetchnvd Fails using example loop [\#267](https://github.com/future-architect/vuls/issues/267)
 | 
			
		||||
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- fix typo in README.ja.md [\#394](https://github.com/future-architect/vuls/pull/394) ([lv7777](https://github.com/lv7777))
 | 
			
		||||
- Update Tutorial in README [\#387](https://github.com/future-architect/vuls/pull/387) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix README [\#383](https://github.com/future-architect/vuls/pull/383) ([usiusi360](https://github.com/usiusi360))
 | 
			
		||||
- s/dictinary/dictionary typo [\#382](https://github.com/future-architect/vuls/pull/382) ([beuno](https://github.com/beuno))
 | 
			
		||||
- Fix Japanese typo [\#377](https://github.com/future-architect/vuls/pull/377) ([IMAI-Yuji](https://github.com/IMAI-Yuji))
 | 
			
		||||
- Improve kanji character [\#351](https://github.com/future-architect/vuls/pull/351) ([hasegawa-tomoki](https://github.com/hasegawa-tomoki))
 | 
			
		||||
- Add PULL\_REQUEST\_TEMPLATE.md [\#348](https://github.com/future-architect/vuls/pull/348) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Update README [\#347](https://github.com/future-architect/vuls/pull/347) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Fix test case [\#344](https://github.com/future-architect/vuls/pull/344) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix typo [\#343](https://github.com/future-architect/vuls/pull/343) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Rename Makefile to GNUmakefile \#313 [\#339](https://github.com/future-architect/vuls/pull/339) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Update README [\#338](https://github.com/future-architect/vuls/pull/338) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- add error handling [\#332](https://github.com/future-architect/vuls/pull/332) ([kazuminn](https://github.com/kazuminn))
 | 
			
		||||
- Update readme [\#308](https://github.com/future-architect/vuls/pull/308) ([lapthorn](https://github.com/lapthorn))
 | 
			
		||||
- Update glide.lock to fix import error [\#306](https://github.com/future-architect/vuls/pull/306) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Check whether echo is executable with nopasswd [\#298](https://github.com/future-architect/vuls/pull/298) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- Update docker README [\#297](https://github.com/future-architect/vuls/pull/297) ([knqyf263](https://github.com/knqyf263))
 | 
			
		||||
- update readme [\#296](https://github.com/future-architect/vuls/pull/296) ([galigalikun](https://github.com/galigalikun))
 | 
			
		||||
- remove unused import line. [\#358](https://github.com/future-architect/vuls/pull/358) ([ymomoi](https://github.com/ymomoi))
 | 
			
		||||
 | 
			
		||||
## [v0.2.0](https://github.com/future-architect/vuls/tree/v0.2.0) (2017-01-10)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.7...v0.2.0)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- Add report subcommand, change scan options. \#239 [\#270](https://github.com/future-architect/vuls/pull/270) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add --assume-yes to prepare \#260 [\#266](https://github.com/future-architect/vuls/pull/266) ([Code0x58](https://github.com/Code0x58))
 | 
			
		||||
- Use RFC3339 timestamps in the results [\#265](https://github.com/future-architect/vuls/pull/265) ([Code0x58](https://github.com/Code0x58))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- vuls prepare failed to centos7 [\#275](https://github.com/future-architect/vuls/issues/275)
 | 
			
		||||
- Failed to scan on RHEL5 [\#94](https://github.com/future-architect/vuls/issues/94)
 | 
			
		||||
- Fix container os detection [\#287](https://github.com/future-architect/vuls/pull/287) ([jiazio](https://github.com/jiazio))
 | 
			
		||||
- Add date header to report mail. [\#283](https://github.com/future-architect/vuls/pull/283) ([ymomoi](https://github.com/ymomoi))
 | 
			
		||||
- Add Content-Type header to report/mail.go . [\#280](https://github.com/future-architect/vuls/pull/280) ([hogehogehugahuga](https://github.com/hogehogehugahuga))
 | 
			
		||||
- Keep output of "vuls scan -report-\*" to be same every times [\#272](https://github.com/future-architect/vuls/pull/272) ([yoheimuta](https://github.com/yoheimuta))
 | 
			
		||||
- Fix JSON-dir regex pattern \#265 [\#271](https://github.com/future-architect/vuls/pull/271) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Stop quietly ignoring `--ssh-external` on Windows [\#263](https://github.com/future-architect/vuls/pull/263) ([Code0x58](https://github.com/Code0x58))
 | 
			
		||||
- Fix non-interactive `apt-get install` \#251 [\#253](https://github.com/future-architect/vuls/pull/253) ([Code0x58](https://github.com/Code0x58))
 | 
			
		||||
 | 
			
		||||
**Closed issues:**
 | 
			
		||||
 | 
			
		||||
- gocui.NewGui now takes a parameter [\#261](https://github.com/future-architect/vuls/issues/261)
 | 
			
		||||
- Add a `--yes` flag to bypass interactive prompt for `vuls prepare` [\#260](https://github.com/future-architect/vuls/issues/260)
 | 
			
		||||
- `vuls prepare` doesn't work on Debian host due to apt-get confirmation prompt [\#251](https://github.com/future-architect/vuls/issues/251)
 | 
			
		||||
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- Fix gocui.NewGui after signature change \#261 [\#262](https://github.com/future-architect/vuls/pull/262) ([Code0x58](https://github.com/Code0x58))
 | 
			
		||||
- Replace inconsistent tabs with spaces [\#254](https://github.com/future-architect/vuls/pull/254) ([Code0x58](https://github.com/Code0x58))
 | 
			
		||||
- Fix README [\#249](https://github.com/future-architect/vuls/pull/249) ([usiusi360](https://github.com/usiusi360))
 | 
			
		||||
 | 
			
		||||
## [v0.1.7](https://github.com/future-architect/vuls/tree/v0.1.7) (2016-11-08)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.6...v0.1.7)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- Enable to scan only docker container, without docker host [\#122](https://github.com/future-architect/vuls/issues/122)
 | 
			
		||||
- Add -skip-broken option \[CentOS only\] \#245 [\#248](https://github.com/future-architect/vuls/pull/248) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Display unknown CVEs to TUI [\#244](https://github.com/future-architect/vuls/pull/244) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add the XML output [\#240](https://github.com/future-architect/vuls/pull/240) ([gleentea](https://github.com/gleentea))
 | 
			
		||||
- add '-ssh-external' option to prepare subcommand [\#234](https://github.com/future-architect/vuls/pull/234) ([mykstmhr](https://github.com/mykstmhr))
 | 
			
		||||
- Integrate OWASP Dependency Check [\#232](https://github.com/future-architect/vuls/pull/232) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add support for reading CVE data from MySQL. [\#225](https://github.com/future-architect/vuls/pull/225) ([oswell](https://github.com/oswell))
 | 
			
		||||
- Remove base docker image, -v shows commit hash [\#223](https://github.com/future-architect/vuls/pull/223) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Support ignore CveIDs in config [\#222](https://github.com/future-architect/vuls/pull/222) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Confirm before installing dependencies on prepare [\#219](https://github.com/future-architect/vuls/pull/219) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Remove all.json [\#218](https://github.com/future-architect/vuls/pull/218) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add GitHub issue template [\#217](https://github.com/future-architect/vuls/pull/217) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Improve makefile, -version shows git hash, fix README [\#216](https://github.com/future-architect/vuls/pull/216) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- change e-mail package from gomail to net/smtp [\#211](https://github.com/future-architect/vuls/pull/211) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Add only-containers option to scan subcommand \#122 [\#190](https://github.com/future-architect/vuls/pull/190) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix -results-dir option of scan subcommand [\#185](https://github.com/future-architect/vuls/pull/185) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Show error when no scannable servers are detected. [\#177](https://github.com/future-architect/vuls/pull/177) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add sudo check to prepare subcommand [\#176](https://github.com/future-architect/vuls/pull/176) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Supports yum --enablerepo option \(supports only base,updates for now\) [\#147](https://github.com/future-architect/vuls/pull/147) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Debian 8.6 \(jessie\) scan does not show vulnerable packages [\#235](https://github.com/future-architect/vuls/issues/235)
 | 
			
		||||
- panic: runtime error: index out of range - ubuntu 16.04 + vuls history [\#180](https://github.com/future-architect/vuls/issues/180)
 | 
			
		||||
- Moved golang.org/x/net/context to context [\#243](https://github.com/future-architect/vuls/pull/243) ([yoheimuta](https://github.com/yoheimuta))
 | 
			
		||||
- Fix changelog cache bug on Ubuntu and Debian \#235 [\#238](https://github.com/future-architect/vuls/pull/238) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- add '-ssh-external' option to prepare subcommand [\#234](https://github.com/future-architect/vuls/pull/234) ([mykstmhr](https://github.com/mykstmhr))
 | 
			
		||||
- Fixed error for the latest version of gocui [\#231](https://github.com/future-architect/vuls/pull/231) ([ymd38](https://github.com/ymd38))
 | 
			
		||||
- Handle the refactored gocui SetCurrentView method. [\#229](https://github.com/future-architect/vuls/pull/229) ([oswell](https://github.com/oswell))
 | 
			
		||||
- Fix locale env var LANG to LANGUAGE [\#215](https://github.com/future-architect/vuls/pull/215) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fixed bug with parsing update line on CentOS/RHEL [\#206](https://github.com/future-architect/vuls/pull/206) ([andyone](https://github.com/andyone))
 | 
			
		||||
- Fix defer cache.DB.close [\#201](https://github.com/future-architect/vuls/pull/201) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix a help message of -report-azure-blob option [\#195](https://github.com/future-architect/vuls/pull/195) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix error handling in tui [\#193](https://github.com/future-architect/vuls/pull/193) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix not working changelog cache on Container [\#189](https://github.com/future-architect/vuls/pull/189) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix release version detection on FreeBSD [\#184](https://github.com/future-architect/vuls/pull/184) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix defer cahce.DB.close\(\) [\#183](https://github.com/future-architect/vuls/pull/183) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix a mode of files/dir \(report, log\) [\#182](https://github.com/future-architect/vuls/pull/182) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix a error when no json dirs are found under results \#180 [\#181](https://github.com/future-architect/vuls/pull/181) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- ssh-external option of configtest is not working \#178 [\#179](https://github.com/future-architect/vuls/pull/179) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
**Closed issues:**
 | 
			
		||||
 | 
			
		||||
- --enable-repos of yum option [\#246](https://github.com/future-architect/vuls/issues/246)
 | 
			
		||||
- --skip-broken at yum option [\#245](https://github.com/future-architect/vuls/issues/245)
 | 
			
		||||
- Recent changes to gobui cause build failures [\#228](https://github.com/future-architect/vuls/issues/228)
 | 
			
		||||
- https://hub.docker.com/r/vuls/go-cve-dictionary/ is empty [\#208](https://github.com/future-architect/vuls/issues/208)
 | 
			
		||||
- Not able to install gomail fails [\#202](https://github.com/future-architect/vuls/issues/202)
 | 
			
		||||
- No results file created - vuls tui failed [\#199](https://github.com/future-architect/vuls/issues/199)
 | 
			
		||||
- Wrong file permissions for results/\*.json in official Docker container [\#197](https://github.com/future-architect/vuls/issues/197)
 | 
			
		||||
- Failed: Unknown OS Type [\#196](https://github.com/future-architect/vuls/issues/196)
 | 
			
		||||
- Segmentation fault with configtest [\#192](https://github.com/future-architect/vuls/issues/192)
 | 
			
		||||
- Failed to scan. err: No server defined. Check the configuration [\#187](https://github.com/future-architect/vuls/issues/187)
 | 
			
		||||
- vuls configtest -ssh-external doesnt work [\#178](https://github.com/future-architect/vuls/issues/178)
 | 
			
		||||
- apt-get update: time out [\#175](https://github.com/future-architect/vuls/issues/175)
 | 
			
		||||
- scanning on Centos6, but vuls recognizes debian. [\#174](https://github.com/future-architect/vuls/issues/174)
 | 
			
		||||
- Fix READMEja  \#164  [\#173](https://github.com/future-architect/vuls/issues/173)
 | 
			
		||||
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- Update README \#225 [\#242](https://github.com/future-architect/vuls/pull/242) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix readme [\#241](https://github.com/future-architect/vuls/pull/241) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Fix README \#234 [\#237](https://github.com/future-architect/vuls/pull/237) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Update glide files [\#236](https://github.com/future-architect/vuls/pull/236) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix README [\#226](https://github.com/future-architect/vuls/pull/226) ([usiusi360](https://github.com/usiusi360))
 | 
			
		||||
- fix some misspelling. [\#221](https://github.com/future-architect/vuls/pull/221) ([ymomoi](https://github.com/ymomoi))
 | 
			
		||||
- fix docker readme [\#214](https://github.com/future-architect/vuls/pull/214) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Fix ja document about typo [\#213](https://github.com/future-architect/vuls/pull/213) ([shokohara](https://github.com/shokohara))
 | 
			
		||||
- fix readme [\#212](https://github.com/future-architect/vuls/pull/212) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- fix README [\#207](https://github.com/future-architect/vuls/pull/207) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- fix typo [\#204](https://github.com/future-architect/vuls/pull/204) ([usiusi360](https://github.com/usiusi360))
 | 
			
		||||
- fix gitignore [\#191](https://github.com/future-architect/vuls/pull/191) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Update glide.lock [\#188](https://github.com/future-architect/vuls/pull/188) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix path in setup/docker/README [\#186](https://github.com/future-architect/vuls/pull/186) ([dladuke](https://github.com/dladuke))
 | 
			
		||||
- Vuls and vulsrepo are now separated [\#163](https://github.com/future-architect/vuls/pull/163) ([hikachan](https://github.com/hikachan))
 | 
			
		||||
 | 
			
		||||
## [v0.1.6](https://github.com/future-architect/vuls/tree/v0.1.6) (2016-09-12)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.5...v0.1.6)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- High speed scan on Ubuntu/Debian [\#172](https://github.com/future-architect/vuls/pull/172) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Support CWE\(Common Weakness Enumeration\) [\#169](https://github.com/future-architect/vuls/pull/169) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Enable to scan without sudo on amazon linux [\#167](https://github.com/future-architect/vuls/pull/167) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Remove deprecated options -use-unattended-upgrades,-use-yum-plugin-security [\#161](https://github.com/future-architect/vuls/pull/161) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- delete sqlite3 [\#152](https://github.com/future-architect/vuls/pull/152) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Failed to setup vuls docker [\#170](https://github.com/future-architect/vuls/issues/170)
 | 
			
		||||
- yum check-update error occurred when no reboot after kernel updating [\#165](https://github.com/future-architect/vuls/issues/165)
 | 
			
		||||
- error thrown from 'docker build .' [\#157](https://github.com/future-architect/vuls/issues/157)
 | 
			
		||||
- CVE-ID is truncated to 4 digits [\#153](https://github.com/future-architect/vuls/issues/153)
 | 
			
		||||
- 'yum update --changelog' stalled in 'vuls scan'. if ssh user is not 'root'. [\#150](https://github.com/future-architect/vuls/issues/150)
 | 
			
		||||
- Panic on packet scan [\#131](https://github.com/future-architect/vuls/issues/131)
 | 
			
		||||
- Update glide.lock \#170 [\#171](https://github.com/future-architect/vuls/pull/171) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix detecting a platform on Azure [\#168](https://github.com/future-architect/vuls/pull/168) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix parse error for yum check-update \#165 [\#166](https://github.com/future-architect/vuls/pull/166) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix bug: Vuls on Docker [\#159](https://github.com/future-architect/vuls/pull/159) ([tjinjin](https://github.com/tjinjin))
 | 
			
		||||
- Fix CVE-ID is truncated to 4 digits [\#155](https://github.com/future-architect/vuls/pull/155) ([usiusi360](https://github.com/usiusi360))
 | 
			
		||||
- Fix yum update --changelog stalled when non-root ssh user on CentOS \#150 [\#151](https://github.com/future-architect/vuls/pull/151) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
**Closed issues:**
 | 
			
		||||
 | 
			
		||||
- Support su for root privilege escalation [\#44](https://github.com/future-architect/vuls/issues/44)
 | 
			
		||||
- Support FreeBSD [\#34](https://github.com/future-architect/vuls/issues/34)
 | 
			
		||||
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- Change scripts for data fetching from jvn [\#164](https://github.com/future-architect/vuls/pull/164) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix: setup vulsrepo [\#162](https://github.com/future-architect/vuls/pull/162) ([tjinjin](https://github.com/tjinjin))
 | 
			
		||||
- Fix-docker-vulsrepo-install [\#160](https://github.com/future-architect/vuls/pull/160) ([usiusi360](https://github.com/usiusi360))
 | 
			
		||||
- Reduce regular expression compilation [\#158](https://github.com/future-architect/vuls/pull/158) ([itchyny](https://github.com/itchyny))
 | 
			
		||||
- Add testcases for \#153 [\#156](https://github.com/future-architect/vuls/pull/156) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
## [v0.1.5](https://github.com/future-architect/vuls/tree/v0.1.5) (2016-08-16)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.4...v0.1.5)
 | 
			
		||||
 | 
			
		||||
@@ -141,7 +486,7 @@
 | 
			
		||||
- Maximum 6 nodes available to scan [\#12](https://github.com/future-architect/vuls/issues/12)
 | 
			
		||||
- panic: runtime error: index out of range [\#5](https://github.com/future-architect/vuls/issues/5)
 | 
			
		||||
- Fix sudo option on RedHat like Linux and change some messages. [\#20](https://github.com/future-architect/vuls/pull/20) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Typo fix and updated readme [\#19](https://github.com/future-architect/vuls/pull/19) ([Euan-Kerr](https://github.com/Euan-Kerr))
 | 
			
		||||
- Typo fix and updated readme [\#19](https://github.com/future-architect/vuls/pull/19) ([EuanKerr](https://github.com/EuanKerr))
 | 
			
		||||
- remove a period at the end of error messages. [\#18](https://github.com/future-architect/vuls/pull/18) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix error while yum updateinfo --security update on rhel@aws [\#17](https://github.com/future-architect/vuls/pull/17) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fixed typos [\#15](https://github.com/future-architect/vuls/pull/15) ([radarhere](https://github.com/radarhere))
 | 
			
		||||
@@ -166,4 +511,4 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
 | 
			
		||||
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										71
									
								
								GNUmakefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,71 @@
 | 
			
		||||
.PHONY: \
 | 
			
		||||
	dep \
 | 
			
		||||
	depup \
 | 
			
		||||
	build \
 | 
			
		||||
	install \
 | 
			
		||||
	all \
 | 
			
		||||
	vendor \
 | 
			
		||||
	lint \
 | 
			
		||||
	vet \
 | 
			
		||||
	fmt \
 | 
			
		||||
	fmtcheck \
 | 
			
		||||
	pretest \
 | 
			
		||||
	test \
 | 
			
		||||
	cov \
 | 
			
		||||
	clean
 | 
			
		||||
 | 
			
		||||
SRCS = $(shell git ls-files '*.go')
 | 
			
		||||
PKGS = ./. ./cache ./commands ./config ./models ./oval ./report ./scan ./util 
 | 
			
		||||
VERSION := $(shell git describe --tags --abbrev=0)
 | 
			
		||||
REVISION := $(shell git rev-parse --short HEAD)
 | 
			
		||||
LDFLAGS := -X 'main.version=$(VERSION)' \
 | 
			
		||||
	-X 'main.revision=$(REVISION)'
 | 
			
		||||
 | 
			
		||||
all: dep build test
 | 
			
		||||
 | 
			
		||||
dep:
 | 
			
		||||
	go get -u github.com/golang/dep/...
 | 
			
		||||
	dep ensure
 | 
			
		||||
 | 
			
		||||
depup:
 | 
			
		||||
	go get -u github.com/golang/dep/...
 | 
			
		||||
	dep ensure -update
 | 
			
		||||
 | 
			
		||||
build: main.go dep pretest
 | 
			
		||||
	go build -ldflags "$(LDFLAGS)" -o vuls $<
 | 
			
		||||
 | 
			
		||||
install: main.go dep pretest
 | 
			
		||||
	go install -ldflags "$(LDFLAGS)"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	echo $(PKGS) | xargs go vet || exit;
 | 
			
		||||
 | 
			
		||||
fmt:
 | 
			
		||||
	gofmt -s -w $(SRCS)
 | 
			
		||||
 | 
			
		||||
fmtcheck:
 | 
			
		||||
	$(foreach file,$(SRCS),gofmt -s -d $(file);)
 | 
			
		||||
 | 
			
		||||
pretest: lint vet fmtcheck
 | 
			
		||||
 | 
			
		||||
test: pretest
 | 
			
		||||
	go install
 | 
			
		||||
	echo $(PKGS) | xargs go test -cover -v || exit;
 | 
			
		||||
 | 
			
		||||
unused :
 | 
			
		||||
	$(foreach pkg,$(PKGS),unused $(pkg);)
 | 
			
		||||
 | 
			
		||||
cov:
 | 
			
		||||
	@ go get -v github.com/axw/gocov/gocov
 | 
			
		||||
	@ go get golang.org/x/tools/cmd/cover
 | 
			
		||||
	gocov test | gocov report
 | 
			
		||||
 | 
			
		||||
clean:
 | 
			
		||||
	echo $(PKGS) | xargs go clean || exit;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										278
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,278 @@
 | 
			
		||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/Azure/azure-sdk-for-go"
 | 
			
		||||
  packages = ["storage"]
 | 
			
		||||
  revision = "7692b0cef22674113fcf71cc17ac3ccc1a7fef48"
 | 
			
		||||
  version = "v11.2.2-beta"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/Azure/go-autorest"
 | 
			
		||||
  packages = ["autorest","autorest/adal","autorest/azure","autorest/date"]
 | 
			
		||||
  revision = "c67b24a8e30d876542a85022ebbdecf0e5a935e8"
 | 
			
		||||
  version = "v9.4.1"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/BurntSushi/toml"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "b26d9c308763d68093482582cea63d69be07a0f0"
 | 
			
		||||
  version = "v0.3.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/asaskevich/govalidator"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "4918b99a7cb949bb295f3c7bbaf24b577d806e35"
 | 
			
		||||
  version = "v6"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/aws/aws-sdk-go"
 | 
			
		||||
  packages = ["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/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/s3","service/sts"]
 | 
			
		||||
  revision = "e4f7e38b704e3ed0acc4a7f8196b777696f6f1f3"
 | 
			
		||||
  version = "v1.12.30"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/boltdb/bolt"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "2f1ce7a837dcb8da3ec595b1dac9d0632f0f99e8"
 | 
			
		||||
  version = "v1.3.1"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/cenkalti/backoff"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "61153c768f31ee5f130071d08fc82b85208528de"
 | 
			
		||||
  version = "v1.1.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/cheggaaa/pb"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "657164d0228d6bebe316fdf725c69f131a50fb10"
 | 
			
		||||
  version = "v1.0.18"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/dgrijalva/jwt-go"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "dbeaa9332f19a944acb5736b4456cfcc02140e29"
 | 
			
		||||
  version = "v3.1.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/go-ini/ini"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a"
 | 
			
		||||
  version = "v1.32.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/go-redis/redis"
 | 
			
		||||
  packages = [".","internal","internal/consistenthash","internal/hashtag","internal/pool","internal/proto"]
 | 
			
		||||
  revision = "e5e021257bfc6d5fbf0beac33e30194311fb189f"
 | 
			
		||||
  version = "v6.7.4"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/go-sql-driver/mysql"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "a0583e0143b1624142adab07e0e97fe106d99561"
 | 
			
		||||
  version = "v1.3"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/google/subcommands"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "ce3d4cfc062faac7115d44e5befec8b5a08c3faa"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/gosuri/uitable"
 | 
			
		||||
  packages = [".","util/strutil","util/wordwrap"]
 | 
			
		||||
  revision = "36ee7e946282a3fb1cfecd476ddc9b35d8847e42"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/howeyc/gopass"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/jinzhu/gorm"
 | 
			
		||||
  packages = [".","dialects/mysql","dialects/postgres","dialects/sqlite"]
 | 
			
		||||
  revision = "5174cc5c242a728b435ea2be8a2f7f998e15429b"
 | 
			
		||||
  version = "v1.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/jinzhu/inflection"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "1c35d901db3da928c72a72d8458480cc9ade058f"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/jmespath/go-jmespath"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "0b12d6b5"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/jroimartin/gocui"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "4e9ce9a8e26f2ef33dfe297dbdfca148733b6b9b"
 | 
			
		||||
  version = "v0.3.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/k0kubun/pp"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "e057ee7a28277be4d2af303443b6da377768181f"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/knqyf263/go-deb-version"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "9865fe14d09b1c729188ac810466dde90f897ee3"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/knqyf263/go-rpm-version"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "74609b86c936dff800c69ec89fcf4bc52d5f13a4"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/go-cve-dictionary"
 | 
			
		||||
  packages = ["config","db","jvn","log","models","nvd","util"]
 | 
			
		||||
  revision = "a64c5fc25cd9669b213986e1a02831c71a5c601d"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/kotakanbe/go-pingscanner"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "641dc2cc2d3cbf295dad356667b74c69bcbd6f70"
 | 
			
		||||
  version = "v0.1.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/goval-dictionary"
 | 
			
		||||
  packages = ["config","db","db/rdb","log","models"]
 | 
			
		||||
  revision = "dca4f21940cbf2245a50817177fed7eb6793e72c"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "75edb2e85a38873f0318be05a458446681d1022f"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/lib/pq"
 | 
			
		||||
  packages = [".","hstore","oid"]
 | 
			
		||||
  revision = "8c6ee72f3e6bcb1542298dd5f76cb74af9742cec"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/mattn/go-colorable"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
 | 
			
		||||
  version = "v0.0.9"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/mattn/go-isatty"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
 | 
			
		||||
  version = "v0.0.3"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/mattn/go-runewidth"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
 | 
			
		||||
  version = "v0.0.2"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/mattn/go-sqlite3"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "ed69081a91fd053f17672236b0dd52ba7485e1a3"
 | 
			
		||||
  version = "v1.4.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/mgutz/ansi"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "9520e82c474b0a04dd04f8a40959027271bab992"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/moul/http2curl"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "9ac6cf4d929b2fa8fd2d2e6dec5bb0feb4f4911d"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/nlopes/slack"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "c86337c0ef2486a15edd804355d9c73d2f2caed1"
 | 
			
		||||
  version = "v0.1.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/nsf/termbox-go"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "aa4a75b1c20a2b03751b1a9f7e41d58bd6f71c43"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/parnurzeal/gorequest"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "a578a48e8d6ca8b01a3b18314c43c6716bb5f5a3"
 | 
			
		||||
  version = "v0.2.15"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/pkg/errors"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
 | 
			
		||||
  version = "v0.8.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/rifflock/lfshook"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "6844c808343cb8fa357d7f141b1b990e05d24e41"
 | 
			
		||||
  version = "1.7"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/satori/uuid"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "879c5887cd475cd7864858769793b2ceb0d44feb"
 | 
			
		||||
  version = "v1.1.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/sirupsen/logrus"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "95cd2b9c79aa5e72ab0bc69b7ccc2be15bf850f6"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/ymomoi/goval-parser"
 | 
			
		||||
  packages = ["oval"]
 | 
			
		||||
  revision = "0a0be1dd9d0855b50be0be5a10ad3085382b6d59"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "golang.org/x/crypto"
 | 
			
		||||
  packages = ["curve25519","ed25519","ed25519/internal/edwards25519","ssh","ssh/agent","ssh/terminal"]
 | 
			
		||||
  revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "golang.org/x/net"
 | 
			
		||||
  packages = ["context","idna","publicsuffix","websocket"]
 | 
			
		||||
  revision = "9dfe39835686865bff950a07b394c12a98ddc811"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "golang.org/x/sys"
 | 
			
		||||
  packages = ["unix","windows"]
 | 
			
		||||
  revision = "0ac51a24ef1c37380f98ba8b98f56e3bffd59850"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "golang.org/x/text"
 | 
			
		||||
  packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
 | 
			
		||||
  revision = "88f656faf3f37f690df1a32515b479415e1a6769"
 | 
			
		||||
 | 
			
		||||
[solve-meta]
 | 
			
		||||
  analyzer-name = "dep"
 | 
			
		||||
  analyzer-version = 1
 | 
			
		||||
  inputs-digest = "58ae46498625e705c582d70591148e07dbac30bc4f75cadef3d2fda514cd5099"
 | 
			
		||||
  solver-name = "gps-cdcl"
 | 
			
		||||
  solver-version = 1
 | 
			
		||||
							
								
								
									
										94
									
								
								Gopkg.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,94 @@
 | 
			
		||||
 | 
			
		||||
# Gopkg.toml example
 | 
			
		||||
#
 | 
			
		||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
 | 
			
		||||
# for detailed Gopkg.toml documentation.
 | 
			
		||||
#
 | 
			
		||||
# required = ["github.com/user/thing/cmd/thing"]
 | 
			
		||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
 | 
			
		||||
#
 | 
			
		||||
# [[constraint]]
 | 
			
		||||
#   name = "github.com/user/project"
 | 
			
		||||
#   version = "1.0.0"
 | 
			
		||||
#
 | 
			
		||||
# [[constraint]]
 | 
			
		||||
#   name = "github.com/user/project2"
 | 
			
		||||
#   branch = "dev"
 | 
			
		||||
#   source = "github.com/myfork/project2"
 | 
			
		||||
#
 | 
			
		||||
# [[override]]
 | 
			
		||||
#  name = "github.com/x/y"
 | 
			
		||||
#  version = "2.4.0"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/BurntSushi/toml"
 | 
			
		||||
  version = "0.3.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/asaskevich/govalidator"
 | 
			
		||||
  version = "6.0.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/boltdb/bolt"
 | 
			
		||||
  version = "1.3.1"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/cenkalti/backoff"
 | 
			
		||||
  version = "1.0.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/google/subcommands"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/gosuri/uitable"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/howeyc/gopass"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/jroimartin/gocui"
 | 
			
		||||
  version = "0.3.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/k0kubun/pp"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/knqyf263/go-deb-version"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/knqyf263/go-rpm-version"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/kotakanbe/go-pingscanner"
 | 
			
		||||
  version = "0.1.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/parnurzeal/gorequest"
 | 
			
		||||
  version = "0.2.15"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/rifflock/lfshook"
 | 
			
		||||
  version = "1.7.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/sirupsen/logrus"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/go-cve-dictionary"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/goval-dictionary"
 | 
			
		||||
							
								
								
									
										51
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						@@ -1,51 +0,0 @@
 | 
			
		||||
.PHONY: \
 | 
			
		||||
	all \
 | 
			
		||||
	vendor \
 | 
			
		||||
	lint \
 | 
			
		||||
	vet \
 | 
			
		||||
	fmt \
 | 
			
		||||
	fmtcheck \
 | 
			
		||||
	pretest \
 | 
			
		||||
	test \
 | 
			
		||||
	cov \
 | 
			
		||||
	clean
 | 
			
		||||
 | 
			
		||||
SRCS = $(shell git ls-files '*.go')
 | 
			
		||||
PKGS = ./. ./db ./config ./models ./report ./cveapi ./scan ./util ./commands
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	$(foreach pkg,$(PKGS),go vet $(pkg);)
 | 
			
		||||
 | 
			
		||||
fmt:
 | 
			
		||||
	gofmt -w $(SRCS)
 | 
			
		||||
 | 
			
		||||
fmtcheck:
 | 
			
		||||
	$(foreach file,$(SRCS),gofmt -d $(file);)
 | 
			
		||||
 | 
			
		||||
pretest: lint vet fmtcheck
 | 
			
		||||
 | 
			
		||||
test: pretest
 | 
			
		||||
	$(foreach pkg,$(PKGS),go test -v $(pkg) || exit;)
 | 
			
		||||
 | 
			
		||||
unused :
 | 
			
		||||
	$(foreach pkg,$(PKGS),unused $(pkg);)
 | 
			
		||||
 | 
			
		||||
cov:
 | 
			
		||||
	@ go get -v github.com/axw/gocov/gocov
 | 
			
		||||
	@ go get golang.org/x/tools/cmd/cover
 | 
			
		||||
	gocov test | gocov report
 | 
			
		||||
 | 
			
		||||
clean:
 | 
			
		||||
	$(foreach pkg,$(PKGS),go clean $(pkg) || exit;)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										224
									
								
								README.fr.md
									
									
									
									
									
								
							
							
						
						@@ -1,224 +0,0 @@
 | 
			
		||||
 | 
			
		||||
# Vuls: VULnerability Scanner
 | 
			
		||||
 | 
			
		||||
[](http://goo.gl/forms/xm5KFo35tu)
 | 
			
		||||
 | 
			
		||||
Scanneur de vulnérabilité Linux, sans agent, écrit en golang
 | 
			
		||||
 | 
			
		||||
Nous avons une équipe Slack. [Rejoignez notre Slack Team](http://goo.gl/forms/xm5KFo35tu)  
 | 
			
		||||
 | 
			
		||||
[README en English](https://github.com/future-architect/vuls/blob/master/README.md)  
 | 
			
		||||
[README en Japonais](https://github.com/future-architect/vuls/blob/master/README.ja.md)  
 | 
			
		||||
 | 
			
		||||
[](https://asciinema.org/a/3y9zrf950agiko7klg8abvyck)
 | 
			
		||||
 | 
			
		||||

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

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

 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
For more information see [README in English](https://github.com/future-architect/vuls/blob/master/README.md)  
 | 
			
		||||
							
								
								
									
										1649
									
								
								README.ja.md
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										191
									
								
								cache/bolt.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,191 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package cache
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/boltdb/bolt"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Bolt holds a pointer of bolt.DB
 | 
			
		||||
// boltdb is used to store a cache of Changelogs of Ubuntu/Debian
 | 
			
		||||
type Bolt struct {
 | 
			
		||||
	Path string
 | 
			
		||||
	Log  *logrus.Entry
 | 
			
		||||
	db   *bolt.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetupBolt opens a boltdb and creates a meta bucket if not exists.
 | 
			
		||||
func SetupBolt(path string, l *logrus.Entry) error {
 | 
			
		||||
	l.Infof("Open boltDB: %s", path)
 | 
			
		||||
	db, err := bolt.Open(path, 0600, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b := Bolt{
 | 
			
		||||
		Path: path,
 | 
			
		||||
		Log:  l,
 | 
			
		||||
		db:   db,
 | 
			
		||||
	}
 | 
			
		||||
	if err = b.createBucketIfNotExists(metabucket); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	DB = b
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close a db.
 | 
			
		||||
func (b Bolt) Close() error {
 | 
			
		||||
	if b.db == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return b.db.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  CreateBucketIfNotExists creates a buket that is specified by arg.
 | 
			
		||||
func (b *Bolt) createBucketIfNotExists(name string) error {
 | 
			
		||||
	return b.db.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		_, err := tx.CreateBucketIfNotExists([]byte(name))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to create bucket: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMeta gets a Meta Information os the servername to boltdb.
 | 
			
		||||
func (b Bolt) GetMeta(serverName string) (meta Meta, found bool, err error) {
 | 
			
		||||
	err = b.db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		v := bkt.Get([]byte(serverName))
 | 
			
		||||
		if len(v) == 0 {
 | 
			
		||||
			found = false
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if e := json.Unmarshal(v, &meta); e != nil {
 | 
			
		||||
			return e
 | 
			
		||||
		}
 | 
			
		||||
		found = true
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RefreshMeta gets a Meta Information os the servername to boltdb.
 | 
			
		||||
func (b Bolt) RefreshMeta(meta Meta) error {
 | 
			
		||||
	meta.CreatedAt = time.Now()
 | 
			
		||||
	jsonBytes, err := json.Marshal(meta)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to marshal to JSON: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return b.db.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		if err := bkt.Put([]byte(meta.Name), jsonBytes); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		b.Log.Debugf("Refreshed Meta: %s", meta.Name)
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnsureBuckets puts a Meta information and create a buket that holds changelogs.
 | 
			
		||||
func (b Bolt) EnsureBuckets(meta Meta) error {
 | 
			
		||||
	jsonBytes, err := json.Marshal(meta)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to marshal to JSON: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return b.db.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		b.Log.Debugf("Put to meta: %s", meta.Name)
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		if err := bkt.Put([]byte(meta.Name), jsonBytes); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// re-create a bucket (bucket name: servername)
 | 
			
		||||
		bkt = tx.Bucket([]byte(meta.Name))
 | 
			
		||||
		if bkt != nil {
 | 
			
		||||
			b.Log.Debugf("Delete bucket: %s", meta.Name)
 | 
			
		||||
			if err := tx.DeleteBucket([]byte(meta.Name)); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			b.Log.Debugf("Bucket deleted: %s", meta.Name)
 | 
			
		||||
		}
 | 
			
		||||
		b.Log.Debugf("Create bucket: %s", meta.Name)
 | 
			
		||||
		if _, err := tx.CreateBucket([]byte(meta.Name)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		b.Log.Debugf("Bucket created: %s", meta.Name)
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrettyPrint is for debug
 | 
			
		||||
func (b Bolt) PrettyPrint(meta Meta) error {
 | 
			
		||||
	return b.db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		v := bkt.Get([]byte(meta.Name))
 | 
			
		||||
		b.Log.Debugf("Meta: key:%s, value:%s", meta.Name, v)
 | 
			
		||||
 | 
			
		||||
		bkt = tx.Bucket([]byte(meta.Name))
 | 
			
		||||
		c := bkt.Cursor()
 | 
			
		||||
		for k, v := c.First(); k != nil; k, v = c.Next() {
 | 
			
		||||
			b.Log.Debugf("key:%s, len: %d, %s...",
 | 
			
		||||
				k, len(v), util.Truncate(string(v), 30))
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetChangelog get the changelgo of specified packName from the Bucket
 | 
			
		||||
func (b Bolt) GetChangelog(servername, packName string) (changelog string, err error) {
 | 
			
		||||
	err = b.db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(servername))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			return fmt.Errorf("Failed 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("Failed to get Bucket: %s", servername)
 | 
			
		||||
		}
 | 
			
		||||
		if err := bkt.Put([]byte(packName), []byte(changelog)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										137
									
								
								cache/bolt_test.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,137 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package cache
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/boltdb/bolt"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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.Packages{
 | 
			
		||||
		"apt": {
 | 
			
		||||
			Name:    "apt",
 | 
			
		||||
			Version: "1",
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetupBolt(t *testing.T) {
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	err := SetupBolt(path, log)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(path)
 | 
			
		||||
 | 
			
		||||
	if err := DB.Close(); err != nil {
 | 
			
		||||
		t.Errorf("Failed to close bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check if meta bucket exists
 | 
			
		||||
	db, err := bolt.Open(path, 0600, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to open bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			t.Errorf("Meta bucket nof found")
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEnsureBuckets(t *testing.T) {
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	if err := SetupBolt(path, log); err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := DB.EnsureBuckets(meta); err != nil {
 | 
			
		||||
		t.Errorf("Failed to ensure buckets: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(path)
 | 
			
		||||
 | 
			
		||||
	m, found, err := DB.GetMeta(servername)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to get meta: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		t.Errorf("Not Found in meta")
 | 
			
		||||
	}
 | 
			
		||||
	if meta.Name != m.Name || meta.Distro != m.Distro {
 | 
			
		||||
		t.Errorf("expected %v, actual %v", meta, m)
 | 
			
		||||
	}
 | 
			
		||||
	if !reflect.DeepEqual(meta.Packs, m.Packs) {
 | 
			
		||||
		t.Errorf("expected %v, actual %v", meta.Packs, m.Packs)
 | 
			
		||||
	}
 | 
			
		||||
	if err := DB.Close(); err != nil {
 | 
			
		||||
		t.Errorf("Failed to close bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db, err := bolt.Open(path, 0600, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to open bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(servername))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			t.Errorf("Meta bucket nof found")
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPutGetChangelog(t *testing.T) {
 | 
			
		||||
	clog := "changelog-text"
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	if err := SetupBolt(path, log); err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(path)
 | 
			
		||||
 | 
			
		||||
	if err := DB.EnsureBuckets(meta); err != nil {
 | 
			
		||||
		t.Errorf("Failed to ensure buckets: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := DB.PutChangelog(servername, "apt", clog); err != nil {
 | 
			
		||||
		t.Errorf("Failed to put changelog: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if actual, err := DB.GetChangelog(servername, "apt"); err != nil {
 | 
			
		||||
		t.Errorf("Failed to get changelog: %s", err)
 | 
			
		||||
	} else {
 | 
			
		||||
		if actual != clog {
 | 
			
		||||
			t.Errorf("changelog is not same. e: %s, a: %s", clog, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								report/logrus.go → cache/db.go
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -15,42 +15,36 @@ 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
 | 
			
		||||
package cache
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	formatter "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LogrusWriter write to logfile
 | 
			
		||||
type LogrusWriter struct {
 | 
			
		||||
// DB has a cache instance
 | 
			
		||||
var DB Cache
 | 
			
		||||
 | 
			
		||||
const metabucket = "changelog-meta"
 | 
			
		||||
 | 
			
		||||
// Cache is a interface of cache
 | 
			
		||||
type Cache interface {
 | 
			
		||||
	Close() error
 | 
			
		||||
	GetMeta(string) (Meta, bool, error)
 | 
			
		||||
	RefreshMeta(Meta) error
 | 
			
		||||
	EnsureBuckets(Meta) error
 | 
			
		||||
	PrettyPrint(Meta) error
 | 
			
		||||
	GetChangelog(string, string) (string, error)
 | 
			
		||||
	PutChangelog(string, string, string) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w LogrusWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
	path := "/var/log/vuls/report.log"
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		path = filepath.Join(os.Getenv("APPDATA"), "vuls", "report.log")
 | 
			
		||||
	}
 | 
			
		||||
	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	log := logrus.New()
 | 
			
		||||
	log.Formatter = &formatter.TextFormatter{}
 | 
			
		||||
	log.Out = f
 | 
			
		||||
	log.Level = logrus.InfoLevel
 | 
			
		||||
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		text, err := toPlainText(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		log.Infof(text)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
// 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.Packages
 | 
			
		||||
	CreatedAt time.Time
 | 
			
		||||
}
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
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
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -18,15 +18,12 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/scan"
 | 
			
		||||
@@ -36,8 +33,13 @@ import (
 | 
			
		||||
// ConfigtestCmd is Subcommand
 | 
			
		||||
type ConfigtestCmd struct {
 | 
			
		||||
	configPath     string
 | 
			
		||||
	logDir         string
 | 
			
		||||
	askKeyPassword bool
 | 
			
		||||
	sshExternal    bool
 | 
			
		||||
	containersOnly bool
 | 
			
		||||
	deep           bool
 | 
			
		||||
	sshNative      bool
 | 
			
		||||
	httpProxy      string
 | 
			
		||||
	timeoutSec     int
 | 
			
		||||
 | 
			
		||||
	debug bool
 | 
			
		||||
}
 | 
			
		||||
@@ -52,12 +54,17 @@ func (*ConfigtestCmd) Synopsis() string { return "Test configuration" }
 | 
			
		||||
func (*ConfigtestCmd) Usage() string {
 | 
			
		||||
	return `configtest:
 | 
			
		||||
	configtest
 | 
			
		||||
		        [-config=/path/to/config.toml]
 | 
			
		||||
	        	[-ask-key-password]
 | 
			
		||||
	        	[-ssh-external]
 | 
			
		||||
		        [-debug]
 | 
			
		||||
			[-deep]
 | 
			
		||||
			[-config=/path/to/config.toml]
 | 
			
		||||
			[-log-dir=/path/to/log]
 | 
			
		||||
			[-ask-key-password]
 | 
			
		||||
			[-timeout=300]
 | 
			
		||||
			[-ssh-external]
 | 
			
		||||
			[-containers-only]
 | 
			
		||||
			[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
			[-debug]
 | 
			
		||||
 | 
			
		||||
		        [SERVER]...
 | 
			
		||||
			[SERVER]...
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -67,8 +74,13 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	f.IntVar(&p.timeoutSec, "timeout", 5*60, "Timeout(Sec)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
@@ -76,50 +88,60 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.deep, "deep", false, "Config test for deep scan mode")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.httpProxy,
 | 
			
		||||
		"http-proxy",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://proxy-url:port (default: empty)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.sshExternal,
 | 
			
		||||
		"ssh-external",
 | 
			
		||||
		&p.sshNative,
 | 
			
		||||
		"ssh-native-insecure",
 | 
			
		||||
		false,
 | 
			
		||||
		"Use external ssh command. Default: Use the Go native implementation")
 | 
			
		||||
		"Use Native Go implementation of SSH. Default: Use the external command")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.containersOnly,
 | 
			
		||||
		"containers-only",
 | 
			
		||||
		false,
 | 
			
		||||
		"Test containers only. Default: Test both of hosts and containers")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	// Setup Logger
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.LogDir = p.logDir
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			util.Log.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		util.Log.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		util.Log.Errorf("If you update Vuls and get this error, there may be incompatible changes in config.toml")
 | 
			
		||||
		util.Log.Errorf("Please check README: https://github.com/future-architect/vuls#configuration")
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
	c.Conf.SSHNative = p.sshNative
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
	c.Conf.ContainersOnly = p.containersOnly
 | 
			
		||||
	c.Conf.Deep = p.deep
 | 
			
		||||
 | 
			
		||||
	var servernames []string
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		servernames = f.Args()
 | 
			
		||||
	} else {
 | 
			
		||||
		stat, _ := os.Stdin.Stat()
 | 
			
		||||
		if (stat.Mode() & os.ModeCharDevice) == 0 {
 | 
			
		||||
			bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			fields := strings.Fields(string(bytes))
 | 
			
		||||
			if 0 < len(fields) {
 | 
			
		||||
				servernames = fields
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
@@ -133,7 +155,7 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			logrus.Errorf("%s is not in config", arg)
 | 
			
		||||
			util.Log.Errorf("%s is not in config", arg)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -141,22 +163,23 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// logger
 | 
			
		||||
	Log := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	Log.Info("Validating Config...")
 | 
			
		||||
	if !c.Conf.Validate() {
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnConfigtest() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting Server/Contianer OS... ")
 | 
			
		||||
	scan.InitServers(Log)
 | 
			
		||||
 | 
			
		||||
	Log.Info("Checking sudo configuration... ")
 | 
			
		||||
	if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers. err: %s", err)
 | 
			
		||||
	util.Log.Info("Detecting Server/Container OS... ")
 | 
			
		||||
	if err := scan.InitServers(p.timeoutSec); err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to init servers: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking dependencies...")
 | 
			
		||||
	scan.CheckDependencies(p.timeoutSec)
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking sudo settings...")
 | 
			
		||||
	scan.CheckIfSudoNoPasswd(p.timeoutSec)
 | 
			
		||||
 | 
			
		||||
	scan.PrintSSHableServerNames()
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
@@ -25,10 +26,9 @@ import (
 | 
			
		||||
	"text/template"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	ps "github.com/kotakanbe/go-pingscanner"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DiscoverCmd is Subcommand of host discovery mode
 | 
			
		||||
@@ -57,6 +57,7 @@ func (p *DiscoverCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	// validate
 | 
			
		||||
	if len(f.Args()) == 0 {
 | 
			
		||||
		logrus.Errorf("Usage: " + p.Usage())
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -65,7 +66,6 @@ func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface
 | 
			
		||||
			CIDR: cidr,
 | 
			
		||||
			PingOptions: []string{
 | 
			
		||||
				"-c1",
 | 
			
		||||
				"-t1",
 | 
			
		||||
			},
 | 
			
		||||
			NumOfConcurrency: 100,
 | 
			
		||||
		}
 | 
			
		||||
@@ -87,25 +87,26 @@ func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Output the tmeplate of config.toml
 | 
			
		||||
// Output the template of config.toml
 | 
			
		||||
func printConfigToml(ips []string) (err error) {
 | 
			
		||||
	const tomlTempale = `
 | 
			
		||||
	const tomlTemplate = `
 | 
			
		||||
[slack]
 | 
			
		||||
hookURL      = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
 | 
			
		||||
#legacyToken  = "xoxp-11111111111-222222222222-3333333333"
 | 
			
		||||
channel      = "#channel-name"
 | 
			
		||||
#channel      = "${servername}"
 | 
			
		||||
iconEmoji    = ":ghost:"
 | 
			
		||||
authUser     = "username"
 | 
			
		||||
notifyUsers  = ["@username"]
 | 
			
		||||
 | 
			
		||||
[mail]
 | 
			
		||||
smtpAddr      = "smtp.gmail.com"
 | 
			
		||||
smtpPort      = "465"
 | 
			
		||||
[email]
 | 
			
		||||
smtpAddr      = "smtp.example.com"
 | 
			
		||||
smtpPort      = "587"
 | 
			
		||||
user          = "username"
 | 
			
		||||
password      = "password"
 | 
			
		||||
from          = "from@address.com"
 | 
			
		||||
to            = ["to@address.com"]
 | 
			
		||||
cc            = ["cc@address.com"]
 | 
			
		||||
from          = "from@example.com"
 | 
			
		||||
to            = ["to@example.com"]
 | 
			
		||||
cc            = ["cc@example.com"]
 | 
			
		||||
subjectPrefix = "[vuls]"
 | 
			
		||||
 | 
			
		||||
[default]
 | 
			
		||||
@@ -115,10 +116,13 @@ subjectPrefix = "[vuls]"
 | 
			
		||||
#cpeNames = [
 | 
			
		||||
#  "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
#]
 | 
			
		||||
#containers = ["${running}"]
 | 
			
		||||
#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml"
 | 
			
		||||
#ignoreCves = ["CVE-2014-6271"]
 | 
			
		||||
#optional = [
 | 
			
		||||
#    ["key", "value"],
 | 
			
		||||
#]
 | 
			
		||||
#containers = ["${running}"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[servers]
 | 
			
		||||
{{- $names:=  .Names}}
 | 
			
		||||
@@ -128,18 +132,26 @@ host         = "{{$ip}}"
 | 
			
		||||
#port        = "22"
 | 
			
		||||
#user        = "root"
 | 
			
		||||
#keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
#type 		 = "pseudo"
 | 
			
		||||
#cpeNames = [
 | 
			
		||||
#  "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
#]
 | 
			
		||||
#containers = ["${running}"]
 | 
			
		||||
#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml"
 | 
			
		||||
#ignoreCves = ["CVE-2014-0160"]
 | 
			
		||||
#optional = [
 | 
			
		||||
#    ["key", "value"],
 | 
			
		||||
#]
 | 
			
		||||
#[servers.{{index $names $i}}.containers]
 | 
			
		||||
#type = "docker" #or "lxd" default: docker
 | 
			
		||||
#includes = ["${running}"]
 | 
			
		||||
#excludes = ["container_name_a", "4aa37a8b63b9"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
	var tpl *template.Template
 | 
			
		||||
	if tpl, err = template.New("tempalte").Parse(tomlTempale); err != nil {
 | 
			
		||||
	if tpl, err = template.New("template").Parse(tomlTemplate); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -157,7 +169,7 @@ host         = "{{$ip}}"
 | 
			
		||||
	}
 | 
			
		||||
	a.Names = names
 | 
			
		||||
 | 
			
		||||
	fmt.Println("# Create config.toml using below and then ./vuls --config=/path/to/config.toml")
 | 
			
		||||
	fmt.Println("# Create config.toml using below and then ./vuls -config=/path/to/config.toml")
 | 
			
		||||
	if err = tpl.Execute(os.Stdout, a); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,27 +18,24 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/db"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HistoryCmd is Subcommand of list scanned results
 | 
			
		||||
type HistoryCmd struct {
 | 
			
		||||
	debug    bool
 | 
			
		||||
	debugSQL bool
 | 
			
		||||
 | 
			
		||||
	dbpath string
 | 
			
		||||
	debug      bool
 | 
			
		||||
	debugSQL   bool
 | 
			
		||||
	resultsDir string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
@@ -53,7 +50,7 @@ func (*HistoryCmd) Synopsis() string {
 | 
			
		||||
func (*HistoryCmd) Usage() string {
 | 
			
		||||
	return `history:
 | 
			
		||||
	history
 | 
			
		||||
		[-dbpath=/path/to/vuls.sqlite3]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
	`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -62,47 +59,40 @@ func (p *HistoryCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultDBPath := filepath.Join(wd, "vuls.sqlite3")
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3")
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.DBPath = p.dbpath
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
 | 
			
		||||
	//  _, err := scanHistories()
 | 
			
		||||
	histories, err := scanHistories()
 | 
			
		||||
	dirs, err := report.ListValidJSONDirs()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Error("Failed to select scan histories: ", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	const timeLayout = "2006-01-02 15:04"
 | 
			
		||||
	for _, history := range histories {
 | 
			
		||||
		names := []string{}
 | 
			
		||||
		for _, result := range history.ScanResults {
 | 
			
		||||
			if 0 < len(result.Container.ContainerID) {
 | 
			
		||||
				names = append(names, result.Container.Name)
 | 
			
		||||
			} else {
 | 
			
		||||
				names = append(names, result.ServerName)
 | 
			
		||||
			}
 | 
			
		||||
	for _, d := range dirs {
 | 
			
		||||
		var files []os.FileInfo
 | 
			
		||||
		if files, err = ioutil.ReadDir(d); err != nil {
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Printf("%-3d %s scanned %d servers: %s\n",
 | 
			
		||||
			history.ID,
 | 
			
		||||
			history.ScannedAt.Format(timeLayout),
 | 
			
		||||
			len(history.ScanResults),
 | 
			
		||||
			strings.Join(names, ", "),
 | 
			
		||||
		var hosts []string
 | 
			
		||||
		for _, f := range files {
 | 
			
		||||
			if filepath.Ext(f.Name()) != ".json" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			fileBase := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
 | 
			
		||||
			hosts = append(hosts, fileBase)
 | 
			
		||||
		}
 | 
			
		||||
		splitPath := strings.Split(d, string(os.PathSeparator))
 | 
			
		||||
		timeStr := splitPath[len(splitPath)-1]
 | 
			
		||||
		fmt.Printf("%s %d servers: %s\n",
 | 
			
		||||
			timeStr,
 | 
			
		||||
			len(hosts),
 | 
			
		||||
			strings.Join(hosts, ", "),
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanHistories() (histories []models.ScanHistory, err error) {
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		return histories, fmt.Errorf(
 | 
			
		||||
			"Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	histories, err = db.SelectScanHistories()
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,164 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/scan"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// PrepareCmd is Subcommand of host discovery mode
 | 
			
		||||
type PrepareCmd struct {
 | 
			
		||||
	debug      bool
 | 
			
		||||
	configPath string
 | 
			
		||||
 | 
			
		||||
	askSudoPassword bool
 | 
			
		||||
	askKeyPassword  bool
 | 
			
		||||
 | 
			
		||||
	useUnattendedUpgrades bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*PrepareCmd) Name() string { return "prepare" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*PrepareCmd) Synopsis() string {
 | 
			
		||||
	//  return "Install packages Ubuntu: unattended-upgrade, CentOS: yum-plugin-security)"
 | 
			
		||||
	return `Install required packages to scan.
 | 
			
		||||
				CentOS: yum-plugin-security, yum-plugin-changelog
 | 
			
		||||
				Amazon: None
 | 
			
		||||
				RHEL:   TODO
 | 
			
		||||
				Ubuntu: None
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*PrepareCmd) Usage() string {
 | 
			
		||||
	return `prepare:
 | 
			
		||||
	prepare
 | 
			
		||||
			[-config=/path/to/config.toml]
 | 
			
		||||
			[-ask-key-password]
 | 
			
		||||
			[-debug]
 | 
			
		||||
 | 
			
		||||
		    [SERVER]...
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *PrepareCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askSudoPassword,
 | 
			
		||||
		"ask-sudo-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASON. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useUnattendedUpgrades,
 | 
			
		||||
		"use-unattended-upgrades",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] For Ubuntu, install unattended-upgrades",
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if p.askSudoPassword {
 | 
			
		||||
		logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Infof("Start Preparing (config: %s)", p.configPath)
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
	for _, arg := range f.Args() {
 | 
			
		||||
		found := false
 | 
			
		||||
		for servername, info := range c.Conf.Servers {
 | 
			
		||||
			if servername == arg {
 | 
			
		||||
				target[servername] = info
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			logrus.Errorf("%s is not in config", arg)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
 | 
			
		||||
 | 
			
		||||
	// Set up custom logger
 | 
			
		||||
	logger := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	logger.Info("Detecting OS... ")
 | 
			
		||||
	scan.InitServers(logger)
 | 
			
		||||
 | 
			
		||||
	logger.Info("Installing...")
 | 
			
		||||
	if errs := scan.Prepare(); 0 < len(errs) {
 | 
			
		||||
		for _, e := range errs {
 | 
			
		||||
			logger.Errorf("Failed: %s", e)
 | 
			
		||||
		}
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Info("Success")
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										454
									
								
								commands/report.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,454 @@
 | 
			
		||||
/* 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/models"
 | 
			
		||||
	"github.com/future-architect/vuls/oval"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ReportCmd is subcommand for reporting
 | 
			
		||||
type ReportCmd struct {
 | 
			
		||||
	lang       string
 | 
			
		||||
	debug      bool
 | 
			
		||||
	debugSQL   bool
 | 
			
		||||
	configPath string
 | 
			
		||||
	resultsDir string
 | 
			
		||||
	logDir     string
 | 
			
		||||
	refreshCve bool
 | 
			
		||||
 | 
			
		||||
	cvssScoreOver      float64
 | 
			
		||||
	ignoreUnscoredCves bool
 | 
			
		||||
	ignoreUnfixed      bool
 | 
			
		||||
 | 
			
		||||
	httpProxy string
 | 
			
		||||
 | 
			
		||||
	cveDBType string
 | 
			
		||||
	cveDBPath string
 | 
			
		||||
	cveDBURL  string
 | 
			
		||||
 | 
			
		||||
	ovalDBType string
 | 
			
		||||
	ovalDBPath string
 | 
			
		||||
	ovalDBURL  string
 | 
			
		||||
 | 
			
		||||
	toSlack     bool
 | 
			
		||||
	toEMail     bool
 | 
			
		||||
	toLocalFile bool
 | 
			
		||||
	toS3        bool
 | 
			
		||||
	toAzureBlob bool
 | 
			
		||||
 | 
			
		||||
	formatJSON        bool
 | 
			
		||||
	formatXML         bool
 | 
			
		||||
	formatOneEMail    bool
 | 
			
		||||
	formatOneLineText bool
 | 
			
		||||
	formatShortText   bool
 | 
			
		||||
	formatFullText    bool
 | 
			
		||||
 | 
			
		||||
	gzip bool
 | 
			
		||||
 | 
			
		||||
	awsProfile      string
 | 
			
		||||
	awsS3Bucket     string
 | 
			
		||||
	awsS3ResultsDir string
 | 
			
		||||
	awsRegion       string
 | 
			
		||||
 | 
			
		||||
	azureAccount   string
 | 
			
		||||
	azureKey       string
 | 
			
		||||
	azureContainer string
 | 
			
		||||
 | 
			
		||||
	pipe bool
 | 
			
		||||
 | 
			
		||||
	diff bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*ReportCmd) Name() string { return "report" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*ReportCmd) Synopsis() string { return "Reporting" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*ReportCmd) Usage() string {
 | 
			
		||||
	return `report:
 | 
			
		||||
	report
 | 
			
		||||
		[-lang=en|ja]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-refresh-cve]
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql|postgres]
 | 
			
		||||
		[-cvedb-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
 | 
			
		||||
		[-ovaldb-type=sqlite3|mysql]
 | 
			
		||||
		[-ovaldb-path=/path/to/oval.sqlite3]
 | 
			
		||||
		[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-diff]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ignore-unfixed]
 | 
			
		||||
		[-to-email]
 | 
			
		||||
		[-to-slack]
 | 
			
		||||
		[-to-localfile]
 | 
			
		||||
		[-to-s3]
 | 
			
		||||
		[-to-azure-blob]
 | 
			
		||||
		[-format-json]
 | 
			
		||||
		[-format-xml]
 | 
			
		||||
		[-format-one-email]
 | 
			
		||||
		[-format-one-line-text]
 | 
			
		||||
		[-format-short-text]
 | 
			
		||||
		[-format-full-text]
 | 
			
		||||
		[-gzip]
 | 
			
		||||
		[-aws-profile=default]
 | 
			
		||||
		[-aws-region=us-west-2]
 | 
			
		||||
		[-aws-s3-bucket=bucket_name]
 | 
			
		||||
		[-aws-s3-results-dir=/bucket/path/to/results]
 | 
			
		||||
		[-azure-account=account]
 | 
			
		||||
		[-azure-key=key]
 | 
			
		||||
		[-azure-container=container]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-pipe]
 | 
			
		||||
 | 
			
		||||
		[RFC3339 datetime format under results dir]
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.refreshCve,
 | 
			
		||||
		"refresh-cve",
 | 
			
		||||
		false,
 | 
			
		||||
		"Refresh CVE information in JSON file under results dir")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDBType,
 | 
			
		||||
		"cvedb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching CVE dictionary (sqlite3, mysql or postgres)")
 | 
			
		||||
 | 
			
		||||
	defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDBPath,
 | 
			
		||||
		"cvedb-path",
 | 
			
		||||
		defaultCveDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDBURL,
 | 
			
		||||
		"cvedb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://cve-dictionary.com:1323 or mysql connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBType,
 | 
			
		||||
		"ovaldb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching OVAL dictionary (sqlite3 or mysql)")
 | 
			
		||||
 | 
			
		||||
	defaultOvalDBPath := filepath.Join(wd, "oval.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBPath,
 | 
			
		||||
		"ovaldb-path",
 | 
			
		||||
		defaultOvalDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get oval detail from oval.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBURL,
 | 
			
		||||
		"ovaldb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://goval-dictionary.com:1324 or mysql connection string")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(
 | 
			
		||||
		&p.cvssScoreOver,
 | 
			
		||||
		"cvss-over",
 | 
			
		||||
		0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.diff,
 | 
			
		||||
		"diff",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Difference between previous result and current result "))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.ignoreUnscoredCves,
 | 
			
		||||
		"ignore-unscored-cves",
 | 
			
		||||
		false,
 | 
			
		||||
		"Don't report the unscored CVEs")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.ignoreUnfixed,
 | 
			
		||||
		"ignore-unfixed",
 | 
			
		||||
		false,
 | 
			
		||||
		"Don't report the unfixed CVEs")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.httpProxy,
 | 
			
		||||
		"http-proxy",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://proxy-url:port (default: empty)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatJSON,
 | 
			
		||||
		"format-json",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("JSON format"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatXML,
 | 
			
		||||
		"format-xml",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("XML format"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatOneEMail,
 | 
			
		||||
		"format-one-email",
 | 
			
		||||
		false,
 | 
			
		||||
		"Send all the host report via only one EMail (Specify with -to-email)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatOneLineText,
 | 
			
		||||
		"format-one-line-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("One line summary in plain text"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatShortText,
 | 
			
		||||
		"format-short-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Summary in plain text"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatFullText,
 | 
			
		||||
		"format-full-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Detail report in plain text"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.gzip, "gzip", false, "gzip compression")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toSlack, "to-slack", false, "Send report via Slack")
 | 
			
		||||
	f.BoolVar(&p.toEMail, "to-email", false, "Send report via Email")
 | 
			
		||||
	f.BoolVar(&p.toLocalFile,
 | 
			
		||||
		"to-localfile",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Write report to localfile"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toS3,
 | 
			
		||||
		"to-s3",
 | 
			
		||||
		false,
 | 
			
		||||
		"Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/xml/txt)")
 | 
			
		||||
	f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
 | 
			
		||||
	f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
 | 
			
		||||
	f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
 | 
			
		||||
	f.StringVar(&p.awsS3ResultsDir, "aws-s3-results-dir", "", "/bucket/path/to/results")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toAzureBlob,
 | 
			
		||||
		"to-azure-blob",
 | 
			
		||||
		false,
 | 
			
		||||
		"Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/xml/txt)")
 | 
			
		||||
	f.StringVar(&p.azureAccount,
 | 
			
		||||
		"azure-account",
 | 
			
		||||
		"",
 | 
			
		||||
		"Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureKey,
 | 
			
		||||
		"azure-key",
 | 
			
		||||
		"",
 | 
			
		||||
		"Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureContainer, "azure-container", "", "Azure storage container name")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.pipe,
 | 
			
		||||
		"pipe",
 | 
			
		||||
		false,
 | 
			
		||||
		"Use args passed via PIPE")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.LogDir = p.logDir
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	if err := c.Load(p.configPath, ""); err != nil {
 | 
			
		||||
		util.Log.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Lang = p.lang
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
	c.Conf.RefreshCve = p.refreshCve
 | 
			
		||||
	c.Conf.Diff = p.diff
 | 
			
		||||
	c.Conf.CveDBType = p.cveDBType
 | 
			
		||||
	c.Conf.CveDBPath = p.cveDBPath
 | 
			
		||||
	c.Conf.CveDBURL = p.cveDBURL
 | 
			
		||||
	c.Conf.OvalDBType = p.ovalDBType
 | 
			
		||||
	c.Conf.OvalDBPath = p.ovalDBPath
 | 
			
		||||
	c.Conf.OvalDBURL = p.ovalDBURL
 | 
			
		||||
	c.Conf.CvssScoreOver = p.cvssScoreOver
 | 
			
		||||
	c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
 | 
			
		||||
	c.Conf.IgnoreUnfixed = p.ignoreUnfixed
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
 | 
			
		||||
	c.Conf.FormatXML = p.formatXML
 | 
			
		||||
	c.Conf.FormatJSON = p.formatJSON
 | 
			
		||||
	c.Conf.FormatOneEMail = p.formatOneEMail
 | 
			
		||||
	c.Conf.FormatOneLineText = p.formatOneLineText
 | 
			
		||||
	c.Conf.FormatShortText = p.formatShortText
 | 
			
		||||
	c.Conf.FormatFullText = p.formatFullText
 | 
			
		||||
 | 
			
		||||
	c.Conf.GZIP = p.gzip
 | 
			
		||||
	c.Conf.Diff = p.diff
 | 
			
		||||
	c.Conf.Pipe = p.pipe
 | 
			
		||||
 | 
			
		||||
	var dir string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.diff {
 | 
			
		||||
		dir, err = report.JSONDir([]string{})
 | 
			
		||||
	} else {
 | 
			
		||||
		dir, err = report.JSONDir(f.Args())
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to read from JSON: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// report
 | 
			
		||||
	reports := []report.ResultWriter{
 | 
			
		||||
		report.StdoutWriter{},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toSlack {
 | 
			
		||||
		reports = append(reports, report.SlackWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toEMail {
 | 
			
		||||
		reports = append(reports, report.EMailWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toLocalFile {
 | 
			
		||||
		reports = append(reports, report.LocalFileWriter{
 | 
			
		||||
			CurrentDir: dir,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toS3 {
 | 
			
		||||
		c.Conf.AwsRegion = p.awsRegion
 | 
			
		||||
		c.Conf.AwsProfile = p.awsProfile
 | 
			
		||||
		c.Conf.S3Bucket = p.awsS3Bucket
 | 
			
		||||
		c.Conf.S3ResultsDir = p.awsS3ResultsDir
 | 
			
		||||
		if err := report.CheckIfBucketExists(); err != nil {
 | 
			
		||||
			util.Log.Errorf("Check if there is a bucket beforehand: %s, err: %s", c.Conf.S3Bucket, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.S3Writer{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toAzureBlob {
 | 
			
		||||
		c.Conf.AzureAccount = p.azureAccount
 | 
			
		||||
		if len(c.Conf.AzureAccount) == 0 {
 | 
			
		||||
			c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureKey = p.azureKey
 | 
			
		||||
		if len(c.Conf.AzureKey) == 0 {
 | 
			
		||||
			c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureContainer = p.azureContainer
 | 
			
		||||
		if len(c.Conf.AzureContainer) == 0 {
 | 
			
		||||
			util.Log.Error("Azure storage container name is required with -azure-container option")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		if err := report.CheckIfAzureContainerExists(); err != nil {
 | 
			
		||||
			util.Log.Errorf("Check if there is a container beforehand: %s, err: %s", c.Conf.AzureContainer, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.AzureBlobWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !(p.formatJSON || p.formatOneLineText ||
 | 
			
		||||
		p.formatShortText || p.formatFullText || p.formatXML) {
 | 
			
		||||
		c.Conf.FormatShortText = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnReport() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
	if err := report.CveClient.CheckHealth(); err != nil {
 | 
			
		||||
		util.Log.Errorf("CVE HTTP server is not running. err: %s", err)
 | 
			
		||||
		util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with -cvedb-path option")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	if c.Conf.CveDBURL != "" {
 | 
			
		||||
		util.Log.Infof("cve-dictionary: %s", c.Conf.CveDBURL)
 | 
			
		||||
	} else {
 | 
			
		||||
		if c.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
			util.Log.Infof("cve-dictionary: %s", c.Conf.CveDBPath)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.OvalDBURL != "" {
 | 
			
		||||
		err := oval.Base{}.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("OVAL HTTP server is not running. err: %s", err)
 | 
			
		||||
			util.Log.Errorf("Run goval-dictionary as server mode before reporting or run with -ovaldb-path option")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res models.ScanResults
 | 
			
		||||
	if res, err = report.LoadScanResults(dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("Loaded: %s", dir)
 | 
			
		||||
 | 
			
		||||
	if res, err = report.FillCveInfos(res, dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, w := range reports {
 | 
			
		||||
		if err := w.Write(res...); err != nil {
 | 
			
		||||
			util.Log.Errorf("Failed to report: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										385
									
								
								commands/scan.go
									
									
									
									
									
								
							
							
						
						@@ -18,6 +18,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
@@ -25,56 +26,29 @@ import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/db"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/scan"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanCmd is Subcommand of host discovery mode
 | 
			
		||||
type ScanCmd struct {
 | 
			
		||||
	lang     string
 | 
			
		||||
	debug    bool
 | 
			
		||||
	debugSQL bool
 | 
			
		||||
 | 
			
		||||
	configPath string
 | 
			
		||||
 | 
			
		||||
	dbpath           string
 | 
			
		||||
	cvedbpath        string
 | 
			
		||||
	cveDictionaryURL string
 | 
			
		||||
 | 
			
		||||
	cvssScoreOver      float64
 | 
			
		||||
	ignoreUnscoredCves bool
 | 
			
		||||
 | 
			
		||||
	httpProxy       string
 | 
			
		||||
	askSudoPassword bool
 | 
			
		||||
	askKeyPassword  bool
 | 
			
		||||
 | 
			
		||||
	// reporting
 | 
			
		||||
	reportSlack     bool
 | 
			
		||||
	reportMail      bool
 | 
			
		||||
	reportJSON      bool
 | 
			
		||||
	reportText      bool
 | 
			
		||||
	reportS3        bool
 | 
			
		||||
	reportAzureBlob bool
 | 
			
		||||
 | 
			
		||||
	awsProfile  string
 | 
			
		||||
	awsS3Bucket string
 | 
			
		||||
	awsRegion   string
 | 
			
		||||
 | 
			
		||||
	azureAccount   string
 | 
			
		||||
	azureKey       string
 | 
			
		||||
	azureContainer string
 | 
			
		||||
 | 
			
		||||
	useYumPluginSecurity  bool
 | 
			
		||||
	useUnattendedUpgrades bool
 | 
			
		||||
 | 
			
		||||
	sshExternal bool
 | 
			
		||||
	debug          bool
 | 
			
		||||
	configPath     string
 | 
			
		||||
	resultsDir     string
 | 
			
		||||
	logDir         string
 | 
			
		||||
	cacheDBPath    string
 | 
			
		||||
	httpProxy      string
 | 
			
		||||
	askKeyPassword bool
 | 
			
		||||
	containersOnly bool
 | 
			
		||||
	deep           bool
 | 
			
		||||
	skipBroken     bool
 | 
			
		||||
	sshNative      bool
 | 
			
		||||
	pipe           bool
 | 
			
		||||
	timeoutSec     int
 | 
			
		||||
	scanTimeoutSec int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
@@ -87,30 +61,20 @@ func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities" }
 | 
			
		||||
func (*ScanCmd) Usage() string {
 | 
			
		||||
	return `scan:
 | 
			
		||||
	scan
 | 
			
		||||
		[-lang=en|ja]
 | 
			
		||||
		[-deep]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-dbpath=/path/to/vuls.sqlite3]
 | 
			
		||||
		[-cve-dictionary-dbpath=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cve-dictionary-url=http://127.0.0.1:1323]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ssh-external]
 | 
			
		||||
		[-report-azure-blob]
 | 
			
		||||
		[-report-json]
 | 
			
		||||
		[-report-mail]
 | 
			
		||||
		[-report-s3]
 | 
			
		||||
		[-report-slack]
 | 
			
		||||
		[-report-text]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-cachedb-path=/path/to/cache.db]
 | 
			
		||||
		[-ssh-native-insecure]
 | 
			
		||||
		[-containers-only]
 | 
			
		||||
		[-skip-broken]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-ask-key-password]
 | 
			
		||||
		[-timeout=300]
 | 
			
		||||
		[-timeout-scan=7200]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-aws-profile=default]
 | 
			
		||||
		[-aws-region=us-west-2]
 | 
			
		||||
		[-aws-s3-bucket=bucket_name]
 | 
			
		||||
		[-azure-account=accout]
 | 
			
		||||
		[-azure-key=key]
 | 
			
		||||
		[-azure-container=container]
 | 
			
		||||
		[-pipe]
 | 
			
		||||
 | 
			
		||||
		[SERVER]...
 | 
			
		||||
`
 | 
			
		||||
@@ -118,48 +82,43 @@ func (*ScanCmd) Usage() string {
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultDBPath := filepath.Join(wd, "vuls.sqlite3")
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3")
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	defaultCacheDBPath := filepath.Join(wd, "cache.db")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbpath,
 | 
			
		||||
		"cve-dictionary-dbpath",
 | 
			
		||||
		"",
 | 
			
		||||
		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	defaultURL := "http://127.0.0.1:1323"
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDictionaryURL,
 | 
			
		||||
		"cve-dictionary-url",
 | 
			
		||||
		defaultURL,
 | 
			
		||||
		"http://CVE.Dictionary")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(
 | 
			
		||||
		&p.cvssScoreOver,
 | 
			
		||||
		"cvss-over",
 | 
			
		||||
		0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
		&p.cacheDBPath,
 | 
			
		||||
		"cachedb-path",
 | 
			
		||||
		defaultCacheDBPath,
 | 
			
		||||
		"/path/to/cache.db (local cache of changelog for Ubuntu/Debian)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.ignoreUnscoredCves,
 | 
			
		||||
		"ignore-unscored-cves",
 | 
			
		||||
		&p.sshNative,
 | 
			
		||||
		"ssh-native-insecure",
 | 
			
		||||
		false,
 | 
			
		||||
		"Don't report the unscored CVEs")
 | 
			
		||||
		"Use Native Go implementation of SSH. Default: Use the external command")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.sshExternal,
 | 
			
		||||
		"ssh-external",
 | 
			
		||||
		&p.containersOnly,
 | 
			
		||||
		"containers-only",
 | 
			
		||||
		false,
 | 
			
		||||
		"Use external ssh command. Default: Use the Go native implementation")
 | 
			
		||||
		"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,
 | 
			
		||||
@@ -168,37 +127,6 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		"http://proxy-url:port (default: empty)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.reportSlack, "report-slack", false, "Send report via Slack")
 | 
			
		||||
	f.BoolVar(&p.reportMail, "report-mail", false, "Send report via Email")
 | 
			
		||||
	f.BoolVar(&p.reportJSON,
 | 
			
		||||
		"report-json",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Write report to JSON files (%s/results/current)", wd),
 | 
			
		||||
	)
 | 
			
		||||
	f.BoolVar(&p.reportText,
 | 
			
		||||
		"report-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Write report to text files (%s/results/current)", wd),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.reportS3,
 | 
			
		||||
		"report-s3",
 | 
			
		||||
		false,
 | 
			
		||||
		"Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json)",
 | 
			
		||||
	)
 | 
			
		||||
	f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
 | 
			
		||||
	f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
 | 
			
		||||
	f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.reportAzureBlob,
 | 
			
		||||
		"report-azure-blob",
 | 
			
		||||
		false,
 | 
			
		||||
		"Write report to S3 (container/yyyyMMdd_HHmm/servername.json)",
 | 
			
		||||
	)
 | 
			
		||||
	f.StringVar(&p.azureAccount, "azure-account", "", "Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureKey, "azure-key", "", "Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureContainer, "azure-container", "", "Azure storage container name")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
@@ -207,73 +135,74 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askSudoPassword,
 | 
			
		||||
		"ask-sudo-password",
 | 
			
		||||
		&p.deep,
 | 
			
		||||
		"deep",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication",
 | 
			
		||||
	)
 | 
			
		||||
		"Deep scan mode. Scan accuracy improves and scanned information becomes richer. Since analysis of changelog, issue commands requiring sudo, but it may be slower and high load on the target server")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useYumPluginSecurity,
 | 
			
		||||
		"use-yum-plugin-security",
 | 
			
		||||
		&p.pipe,
 | 
			
		||||
		"pipe",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)",
 | 
			
		||||
		"Use stdin via PIPE")
 | 
			
		||||
 | 
			
		||||
	f.IntVar(
 | 
			
		||||
		&p.timeoutSec,
 | 
			
		||||
		"timeout",
 | 
			
		||||
		5*60,
 | 
			
		||||
		"Number of seconds for processing other than scan",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useUnattendedUpgrades,
 | 
			
		||||
		"use-unattended-upgrades",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)",
 | 
			
		||||
	f.IntVar(
 | 
			
		||||
		&p.scanTimeoutSec,
 | 
			
		||||
		"timeout-scan",
 | 
			
		||||
		120*60,
 | 
			
		||||
		"Number of seconds for scanning vulnerabilities for all servers",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
 | 
			
		||||
	// Setup Logger
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.LogDir = p.logDir
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			util.Log.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if p.askSudoPassword {
 | 
			
		||||
		logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		util.Log.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		util.Log.Errorf("If you update Vuls and get this error, there may be incompatible changes in config.toml")
 | 
			
		||||
		util.Log.Errorf("Please check README: https://github.com/future-architect/vuls#configuration")
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Info("Start scanning")
 | 
			
		||||
	logrus.Infof("config: %s", p.configPath)
 | 
			
		||||
	if p.cvedbpath != "" {
 | 
			
		||||
		logrus.Infof("cve-dictionary: %s", p.cvedbpath)
 | 
			
		||||
	} else {
 | 
			
		||||
		logrus.Infof("cve-dictionary: %s", p.cveDictionaryURL)
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Info("Start scanning")
 | 
			
		||||
	util.Log.Infof("config: %s", p.configPath)
 | 
			
		||||
 | 
			
		||||
	c.Conf.Pipe = p.pipe
 | 
			
		||||
	var servernames []string
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		servernames = f.Args()
 | 
			
		||||
	} else {
 | 
			
		||||
		stat, _ := os.Stdin.Stat()
 | 
			
		||||
		if (stat.Mode() & os.ModeCharDevice) == 0 {
 | 
			
		||||
			bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			fields := strings.Fields(string(bytes))
 | 
			
		||||
			if 0 < len(fields) {
 | 
			
		||||
				servernames = fields
 | 
			
		||||
			}
 | 
			
		||||
	} else if c.Conf.Pipe {
 | 
			
		||||
		bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		fields := strings.Fields(string(bytes))
 | 
			
		||||
		if 0 < len(fields) {
 | 
			
		||||
			servernames = fields
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -288,143 +217,45 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			logrus.Errorf("%s is not in config", arg)
 | 
			
		||||
			util.Log.Errorf("%s is not in config", arg)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(servernames) {
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("%s", pp.Sprintf("%v", target))
 | 
			
		||||
 | 
			
		||||
	c.Conf.Lang = p.lang
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
 | 
			
		||||
	// logger
 | 
			
		||||
	Log := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	// report
 | 
			
		||||
	reports := []report.ResultWriter{
 | 
			
		||||
		report.StdoutWriter{},
 | 
			
		||||
		report.LogrusWriter{},
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportSlack {
 | 
			
		||||
		reports = append(reports, report.SlackWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportMail {
 | 
			
		||||
		reports = append(reports, report.MailWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportJSON {
 | 
			
		||||
		reports = append(reports, report.JSONWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportText {
 | 
			
		||||
		reports = append(reports, report.TextFileWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportS3 {
 | 
			
		||||
		c.Conf.AwsRegion = p.awsRegion
 | 
			
		||||
		c.Conf.AwsProfile = p.awsProfile
 | 
			
		||||
		c.Conf.S3Bucket = p.awsS3Bucket
 | 
			
		||||
		if err := report.CheckIfBucketExists(); err != nil {
 | 
			
		||||
			Log.Errorf("Failed to access to the S3 bucket. err: %s", err)
 | 
			
		||||
			Log.Error("Ensure the bucket or check AWS config before scanning")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.S3Writer{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportAzureBlob {
 | 
			
		||||
		c.Conf.AzureAccount = p.azureAccount
 | 
			
		||||
		if c.Conf.AzureAccount == "" {
 | 
			
		||||
			c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureKey = p.azureKey
 | 
			
		||||
		if c.Conf.AzureKey == "" {
 | 
			
		||||
			c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureContainer = p.azureContainer
 | 
			
		||||
		if c.Conf.AzureContainer == "" {
 | 
			
		||||
			Log.Error("Azure storage container name is requied with --azure-container option")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		if err := report.CheckIfAzureContainerExists(); err != nil {
 | 
			
		||||
			Log.Errorf("Failed to access to the Azure Blob container. err: %s", err)
 | 
			
		||||
			Log.Error("Ensure the container or check Azure config before scanning")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.AzureBlobWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.DBPath = p.dbpath
 | 
			
		||||
	c.Conf.CveDBPath = p.cvedbpath
 | 
			
		||||
	c.Conf.CveDictionaryURL = p.cveDictionaryURL
 | 
			
		||||
	c.Conf.CvssScoreOver = p.cvssScoreOver
 | 
			
		||||
	c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
 | 
			
		||||
	c.Conf.SSHExternal = p.sshExternal
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
	c.Conf.CacheDBPath = p.cacheDBPath
 | 
			
		||||
	c.Conf.SSHNative = p.sshNative
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
	c.Conf.UseYumPluginSecurity = p.useYumPluginSecurity
 | 
			
		||||
	c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
 | 
			
		||||
	c.Conf.ContainersOnly = p.containersOnly
 | 
			
		||||
	c.Conf.Deep = p.deep
 | 
			
		||||
	c.Conf.SkipBroken = p.skipBroken
 | 
			
		||||
 | 
			
		||||
	Log.Info("Validating Config...")
 | 
			
		||||
	if !c.Conf.Validate() {
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnScan() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ok, err := cveapi.CveClient.CheckHealth(); !ok {
 | 
			
		||||
		Log.Errorf("CVE HTTP server is not running. err: %s", err)
 | 
			
		||||
		Log.Errorf("Run go-cve-dictionary as server mode or specify -cve-dictionary-dbpath option")
 | 
			
		||||
	util.Log.Info("Detecting Server/Container OS... ")
 | 
			
		||||
	if err := scan.InitServers(p.timeoutSec); err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to init servers: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting Server/Contianer OS... ")
 | 
			
		||||
	scan.InitServers(Log)
 | 
			
		||||
	util.Log.Info("Detecting Platforms... ")
 | 
			
		||||
	scan.DetectPlatforms(p.timeoutSec)
 | 
			
		||||
 | 
			
		||||
	Log.Info("Checking sudo configuration... ")
 | 
			
		||||
	if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers")
 | 
			
		||||
	util.Log.Info("Scanning vulnerabilities... ")
 | 
			
		||||
	if err := scan.Scan(p.scanTimeoutSec); err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to scan. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting Platforms... ")
 | 
			
		||||
	scan.DetectPlatforms(Log)
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerabilities... ")
 | 
			
		||||
	if errs := scan.Scan(); 0 < len(errs) {
 | 
			
		||||
		for _, e := range errs {
 | 
			
		||||
			Log.Errorf("Failed to scan. err: %s", e)
 | 
			
		||||
		}
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scanResults, err := scan.GetScanResults()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		Log.Fatal(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Insert to DB...")
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.MigrateDB(); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to migrate. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := db.Insert(scanResults); err != nil {
 | 
			
		||||
		Log.Fatalf("Failed to insert. dbpath: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Reporting...")
 | 
			
		||||
	filtered := scanResults.FilterByCvssOver()
 | 
			
		||||
	for _, w := range reports {
 | 
			
		||||
		if err := w.Write(filtered); err != nil {
 | 
			
		||||
			Log.Fatalf("Failed to report, err: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Printf("\n\n\n")
 | 
			
		||||
	fmt.Println("To view the detail, vuls tui is useful.")
 | 
			
		||||
	fmt.Println("To send a report, run vuls report -h.")
 | 
			
		||||
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										203
									
								
								commands/tui.go
									
									
									
									
									
								
							
							
						
						@@ -18,38 +18,70 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TuiCmd is Subcommand of host discovery mode
 | 
			
		||||
type TuiCmd struct {
 | 
			
		||||
	lang     string
 | 
			
		||||
	debugSQL bool
 | 
			
		||||
	dbpath   string
 | 
			
		||||
	lang       string
 | 
			
		||||
	debugSQL   bool
 | 
			
		||||
	debug      bool
 | 
			
		||||
	configPath string
 | 
			
		||||
	logDir     string
 | 
			
		||||
 | 
			
		||||
	resultsDir string
 | 
			
		||||
	refreshCve bool
 | 
			
		||||
 | 
			
		||||
	cvedbtype        string
 | 
			
		||||
	cvedbpath        string
 | 
			
		||||
	cveDictionaryURL string
 | 
			
		||||
 | 
			
		||||
	ovalDBType string
 | 
			
		||||
	ovalDBPath string
 | 
			
		||||
	ovalDBURL  string
 | 
			
		||||
 | 
			
		||||
	cvssScoreOver      float64
 | 
			
		||||
	ignoreUnscoredCves bool
 | 
			
		||||
	ignoreUnfixed      bool
 | 
			
		||||
 | 
			
		||||
	pipe bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 analyze vulnerabilities" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*TuiCmd) Usage() string {
 | 
			
		||||
	return `tui:
 | 
			
		||||
	tui [-dbpath=/path/to/vuls.sqlite3]
 | 
			
		||||
	tui
 | 
			
		||||
		[-refresh-cve]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql|postgres]
 | 
			
		||||
		[-cvedb-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
 | 
			
		||||
		[-ovaldb-type=sqlite3|mysql]
 | 
			
		||||
		[-ovaldb-path=/path/to/oval.sqlite3]
 | 
			
		||||
		[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ignore-unfixed]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-pipe]
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
@@ -58,40 +90,137 @@ func (*TuiCmd) Usage() string {
 | 
			
		||||
func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	//  f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "debug SQL")
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultDBPath := filepath.Join(wd, "vuls.sqlite3")
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath,
 | 
			
		||||
		fmt.Sprintf("/path/to/sqlite3 (default: %s)", defaultDBPath))
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	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, mysql or postgres)")
 | 
			
		||||
 | 
			
		||||
	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.example.com:1323 or mysql connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBType,
 | 
			
		||||
		"ovaldb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching OVAL dictionary (sqlite3 or mysql)")
 | 
			
		||||
 | 
			
		||||
	defaultOvalDBPath := filepath.Join(wd, "oval.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBPath,
 | 
			
		||||
		"ovaldb-path",
 | 
			
		||||
		defaultOvalDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get oval detail from oval.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBURL,
 | 
			
		||||
		"ovaldb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://goval-dictionary.example.com:1324 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.BoolVar(
 | 
			
		||||
		&p.ignoreUnfixed,
 | 
			
		||||
		"ignore-unfixed",
 | 
			
		||||
		false,
 | 
			
		||||
		"Don't report the unfixed CVEs")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.pipe,
 | 
			
		||||
		"pipe",
 | 
			
		||||
		false,
 | 
			
		||||
		"Use stdin via PIPE")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	c.Conf.Lang = "en"
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.DBPath = p.dbpath
 | 
			
		||||
 | 
			
		||||
	historyID := ""
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		if _, err := strconv.Atoi(f.Args()[0]); err != nil {
 | 
			
		||||
			log.Errorf("First Argument have to be scan_histores record ID: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		historyID = f.Args()[0]
 | 
			
		||||
	} else {
 | 
			
		||||
		stat, _ := os.Stdin.Stat()
 | 
			
		||||
		if (stat.Mode() & os.ModeCharDevice) == 0 {
 | 
			
		||||
			bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			fields := strings.Fields(string(bytes))
 | 
			
		||||
			if 0 < len(fields) {
 | 
			
		||||
				historyID = fields[0]
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	// Setup Logger
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.LogDir = p.logDir
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
	log := util.Log
 | 
			
		||||
 | 
			
		||||
	if err := c.Load(p.configPath, ""); err != nil {
 | 
			
		||||
		util.Log.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
	return report.RunTui(historyID)
 | 
			
		||||
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
	c.Conf.CveDBType = p.cvedbtype
 | 
			
		||||
	c.Conf.CveDBPath = p.cvedbpath
 | 
			
		||||
	c.Conf.CveDBURL = p.cveDictionaryURL
 | 
			
		||||
	c.Conf.OvalDBType = p.ovalDBType
 | 
			
		||||
	c.Conf.OvalDBPath = p.ovalDBPath
 | 
			
		||||
	c.Conf.OvalDBURL = p.ovalDBURL
 | 
			
		||||
	c.Conf.CvssScoreOver = p.cvssScoreOver
 | 
			
		||||
	c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
 | 
			
		||||
	c.Conf.IgnoreUnfixed = p.ignoreUnfixed
 | 
			
		||||
	c.Conf.RefreshCve = p.refreshCve
 | 
			
		||||
 | 
			
		||||
	log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnTui() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Pipe = p.pipe
 | 
			
		||||
 | 
			
		||||
	dir, err := report.JSONDir(f.Args())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to read from JSON: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	var res models.ScanResults
 | 
			
		||||
	if res, err = report.LoadScanResults(dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("Loaded: %s", dir)
 | 
			
		||||
 | 
			
		||||
	if res, err = report.FillCveInfos(res, dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	return report.RunTui(res)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								commands/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,38 @@
 | 
			
		||||
/* 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 (
 | 
			
		||||
	"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
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -15,10 +15,4 @@ You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package version
 | 
			
		||||
 | 
			
		||||
// Name is Vuls
 | 
			
		||||
const Name string = "vuls"
 | 
			
		||||
 | 
			
		||||
// Version of Vuls
 | 
			
		||||
const Version string = "0.1.5"
 | 
			
		||||
package commands
 | 
			
		||||
							
								
								
									
										340
									
								
								config/config.go
									
									
									
									
									
								
							
							
						
						@@ -19,66 +19,182 @@ package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	valid "github.com/asaskevich/govalidator"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Conf has Configuration
 | 
			
		||||
var Conf Config
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// RedHat is
 | 
			
		||||
	RedHat = "redhat"
 | 
			
		||||
 | 
			
		||||
	// Debian is
 | 
			
		||||
	Debian = "debian"
 | 
			
		||||
 | 
			
		||||
	// Ubuntu is
 | 
			
		||||
	Ubuntu = "ubuntu"
 | 
			
		||||
 | 
			
		||||
	// CentOS is
 | 
			
		||||
	CentOS = "centos"
 | 
			
		||||
 | 
			
		||||
	// Fedora is
 | 
			
		||||
	Fedora = "fedora"
 | 
			
		||||
 | 
			
		||||
	// Amazon is
 | 
			
		||||
	Amazon = "amazon"
 | 
			
		||||
 | 
			
		||||
	// Oracle is
 | 
			
		||||
	Oracle = "oracle"
 | 
			
		||||
 | 
			
		||||
	// FreeBSD is
 | 
			
		||||
	FreeBSD = "freebsd"
 | 
			
		||||
 | 
			
		||||
	// Raspbian is
 | 
			
		||||
	Raspbian = "raspbian"
 | 
			
		||||
 | 
			
		||||
	// Windows is
 | 
			
		||||
	Windows = "windows"
 | 
			
		||||
 | 
			
		||||
	// OpenSUSE is
 | 
			
		||||
	OpenSUSE = "opensuse"
 | 
			
		||||
 | 
			
		||||
	// OpenSUSELeap is
 | 
			
		||||
	OpenSUSELeap = "opensuse.leap"
 | 
			
		||||
 | 
			
		||||
	// SUSEEnterpriseServer is
 | 
			
		||||
	SUSEEnterpriseServer = "suse.linux.enterprise.server"
 | 
			
		||||
 | 
			
		||||
	// SUSEEnterpriseDesktop is
 | 
			
		||||
	SUSEEnterpriseDesktop = "suse.linux.enterprise.desktop"
 | 
			
		||||
 | 
			
		||||
	// SUSEOpenstackCloud is
 | 
			
		||||
	SUSEOpenstackCloud = "suse.openstack.cloud"
 | 
			
		||||
 | 
			
		||||
	// Alpine is
 | 
			
		||||
	Alpine = "alpine"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// ServerTypePseudo is used for ServerInfo.Type
 | 
			
		||||
	ServerTypePseudo = "pseudo"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//Config is struct of Configuration
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Debug    bool
 | 
			
		||||
	DebugSQL bool
 | 
			
		||||
	Lang     string
 | 
			
		||||
 | 
			
		||||
	Mail    smtpConf
 | 
			
		||||
	EMail   SMTPConf
 | 
			
		||||
	Slack   SlackConf
 | 
			
		||||
	Default ServerInfo
 | 
			
		||||
	Servers map[string]ServerInfo
 | 
			
		||||
 | 
			
		||||
	CveDictionaryURL string `valid:"url"`
 | 
			
		||||
 | 
			
		||||
	CvssScoreOver      float64
 | 
			
		||||
	IgnoreUnscoredCves bool
 | 
			
		||||
	IgnoreUnfixed      bool
 | 
			
		||||
 | 
			
		||||
	SSHExternal bool
 | 
			
		||||
	SSHNative      bool
 | 
			
		||||
	ContainersOnly bool
 | 
			
		||||
	Deep           bool
 | 
			
		||||
	SkipBroken     bool
 | 
			
		||||
 | 
			
		||||
	HTTPProxy string `valid:"url"`
 | 
			
		||||
	DBPath    string
 | 
			
		||||
	HTTPProxy  string `valid:"url"`
 | 
			
		||||
	LogDir     string
 | 
			
		||||
	ResultsDir string
 | 
			
		||||
 | 
			
		||||
	CveDBType string
 | 
			
		||||
	CveDBPath string
 | 
			
		||||
	CveDBURL  string
 | 
			
		||||
 | 
			
		||||
	AwsProfile string
 | 
			
		||||
	AwsRegion  string
 | 
			
		||||
	S3Bucket   string
 | 
			
		||||
	OvalDBType string
 | 
			
		||||
	OvalDBPath string
 | 
			
		||||
	OvalDBURL  string
 | 
			
		||||
 | 
			
		||||
	CacheDBPath string
 | 
			
		||||
 | 
			
		||||
	RefreshCve bool
 | 
			
		||||
 | 
			
		||||
	FormatXML         bool
 | 
			
		||||
	FormatJSON        bool
 | 
			
		||||
	FormatOneEMail    bool
 | 
			
		||||
	FormatOneLineText bool
 | 
			
		||||
	FormatShortText   bool
 | 
			
		||||
	FormatFullText    bool
 | 
			
		||||
 | 
			
		||||
	GZIP bool
 | 
			
		||||
 | 
			
		||||
	AwsProfile   string
 | 
			
		||||
	AwsRegion    string
 | 
			
		||||
	S3Bucket     string
 | 
			
		||||
	S3ResultsDir string
 | 
			
		||||
 | 
			
		||||
	AzureAccount   string
 | 
			
		||||
	AzureKey       string
 | 
			
		||||
	AzureKey       string `json:"-"`
 | 
			
		||||
	AzureContainer string
 | 
			
		||||
 | 
			
		||||
	//  CpeNames      []string
 | 
			
		||||
	//  SummaryMode          bool
 | 
			
		||||
	UseYumPluginSecurity  bool
 | 
			
		||||
	UseUnattendedUpgrades bool
 | 
			
		||||
	Pipe bool
 | 
			
		||||
	Diff bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate configuration
 | 
			
		||||
func (c Config) Validate() bool {
 | 
			
		||||
// ValidateOnConfigtest validates
 | 
			
		||||
func (c Config) ValidateOnConfigtest() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.DBPath) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.DBPath); !ok {
 | 
			
		||||
	if runtime.GOOS == "windows" && !c.SSHNative {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("-ssh-native-insecure is needed on windows"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := valid.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnPrepare validates configuration
 | 
			
		||||
func (c Config) ValidateOnPrepare() bool {
 | 
			
		||||
	return c.ValidateOnConfigtest()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnScan validates configuration
 | 
			
		||||
func (c Config) ValidateOnScan() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB path must be a *Absolute* file path. dbpath: %s", c.DBPath))
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.CveDBPath) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
 | 
			
		||||
	if runtime.GOOS == "windows" && !c.SSHNative {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("-ssh-native-insecure is needed on windows"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB(Cve Dictionary) path must be a *Absolute* file path. dbpath: %s", c.CveDBPath))
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.CacheDBPath) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.CacheDBPath); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"Cache DB path must be a *Absolute* file path. -cache-dbpath: %s", c.CacheDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -87,7 +203,43 @@ 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 err := validateDB("cvedb", c.CveDBType, c.CveDBPath, c.CveDBURL); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
	if c.CveDBType == "sqlite3" {
 | 
			
		||||
		if _, err := os.Stat(c.CveDBPath); os.IsNotExist(err) {
 | 
			
		||||
			errs = append(errs, fmt.Errorf("SQLite3 DB path (%s) is not exist: %s", "cvedb", c.CveDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := validateDB("ovaldb", c.OvalDBType, c.OvalDBPath, c.OvalDBURL); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := valid.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mailerrs := c.EMail.Validate(); 0 < len(mailerrs) {
 | 
			
		||||
		errs = append(errs, mailerrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -102,13 +254,80 @@ func (c Config) Validate() bool {
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// smtpConf is smtp config
 | 
			
		||||
type smtpConf struct {
 | 
			
		||||
// ValidateOnTui validates configuration
 | 
			
		||||
func (c Config) ValidateOnTui() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := validateDB("cvedb", c.CveDBType, c.CveDBPath, c.CveDBURL); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
	if c.CveDBType == "sqlite3" {
 | 
			
		||||
		if _, err := os.Stat(c.CveDBPath); os.IsNotExist(err) {
 | 
			
		||||
			errs = append(errs, fmt.Errorf("SQLite3 DB path (%s) is not exist: %s", "cvedb", c.CveDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// validateDB validates configuration
 | 
			
		||||
//  dictionaryDB name is 'cvedb' or 'ovaldb'
 | 
			
		||||
func validateDB(dictionaryDBName, dbType, dbPath, dbURL string) error {
 | 
			
		||||
	switch dbType {
 | 
			
		||||
	case "sqlite3":
 | 
			
		||||
		if ok, _ := valid.IsFilePath(dbPath); !ok {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB path (%s) must be a *Absolute* file path. -%s-path: %s",
 | 
			
		||||
				dictionaryDBName,
 | 
			
		||||
				dictionaryDBName,
 | 
			
		||||
				dbPath)
 | 
			
		||||
		}
 | 
			
		||||
	case "mysql":
 | 
			
		||||
		if dbURL == "" {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				`MySQL connection string is needed. -%s-url="user:pass@tcp(localhost:3306)/dbname"`,
 | 
			
		||||
				dictionaryDBName)
 | 
			
		||||
		}
 | 
			
		||||
	case "postgres":
 | 
			
		||||
		if dbURL == "" {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				`PostgreSQL connection string is needed. -%s-url="host=myhost user=user dbname=dbname sslmode=disable password=password"`,
 | 
			
		||||
				dictionaryDBName)
 | 
			
		||||
		}
 | 
			
		||||
	case "redis":
 | 
			
		||||
		if dbURL == "" {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				`Redis connection string is needed. -%s-url="redis://localhost/0"`,
 | 
			
		||||
				dictionaryDBName)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"%s type must be either 'sqlite3', 'mysql', 'postgres' or 'redis'.  -%s-type: %s",
 | 
			
		||||
			dictionaryDBName,
 | 
			
		||||
			dictionaryDBName,
 | 
			
		||||
			dbType)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SMTPConf is smtp config
 | 
			
		||||
type SMTPConf struct {
 | 
			
		||||
	SMTPAddr string
 | 
			
		||||
	SMTPPort string `valid:"port"`
 | 
			
		||||
 | 
			
		||||
	User          string
 | 
			
		||||
	Password      string
 | 
			
		||||
	Password      string `json:"-"`
 | 
			
		||||
	From          string
 | 
			
		||||
	To            []string
 | 
			
		||||
	Cc            []string
 | 
			
		||||
@@ -130,7 +349,7 @@ func checkEmails(emails []string) (errs []error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate SMTP configuration
 | 
			
		||||
func (c *smtpConf) Validate() (errs []error) {
 | 
			
		||||
func (c *SMTPConf) Validate() (errs []error) {
 | 
			
		||||
 | 
			
		||||
	if !c.UseThisTime {
 | 
			
		||||
		return
 | 
			
		||||
@@ -168,10 +387,11 @@ func (c *smtpConf) Validate() (errs []error) {
 | 
			
		||||
 | 
			
		||||
// SlackConf is slack config
 | 
			
		||||
type SlackConf struct {
 | 
			
		||||
	HookURL   string `valid:"url"`
 | 
			
		||||
	Channel   string `json:"channel"`
 | 
			
		||||
	IconEmoji string `json:"icon_emoji"`
 | 
			
		||||
	AuthUser  string `json:"username"`
 | 
			
		||||
	HookURL     string `valid:"url" json:"-"`
 | 
			
		||||
	LegacyToken string `json:"token" toml:"legacyToken,omitempty"`
 | 
			
		||||
	Channel     string `json:"channel"`
 | 
			
		||||
	IconEmoji   string `json:"icon_emoji"`
 | 
			
		||||
	AuthUser    string `json:"username"`
 | 
			
		||||
 | 
			
		||||
	NotifyUsers []string
 | 
			
		||||
	Text        string `json:"text"`
 | 
			
		||||
@@ -181,7 +401,6 @@ type SlackConf struct {
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *SlackConf) Validate() (errs []error) {
 | 
			
		||||
 | 
			
		||||
	if !c.UseThisTime {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -219,20 +438,58 @@ type ServerInfo struct {
 | 
			
		||||
	Host        string
 | 
			
		||||
	Port        string
 | 
			
		||||
	KeyPath     string
 | 
			
		||||
	KeyPassword string
 | 
			
		||||
	KeyPassword string `json:"-"`
 | 
			
		||||
 | 
			
		||||
	CpeNames []string
 | 
			
		||||
	CpeNames               []string
 | 
			
		||||
	DependencyCheckXMLPath string
 | 
			
		||||
 | 
			
		||||
	// Container Names or IDs
 | 
			
		||||
	Containers []string
 | 
			
		||||
	Containers Containers
 | 
			
		||||
 | 
			
		||||
	IgnoreCves []string
 | 
			
		||||
 | 
			
		||||
	// Optional key-value set that will be outputted to JSON
 | 
			
		||||
	Optional [][]interface{}
 | 
			
		||||
 | 
			
		||||
	// For CentOS, RHEL, Amazon
 | 
			
		||||
	Enablerepo []string
 | 
			
		||||
 | 
			
		||||
	// "pseudo" or ""
 | 
			
		||||
	Type string
 | 
			
		||||
 | 
			
		||||
	// used internal
 | 
			
		||||
	LogMsgAnsiColor string // DebugLog Color
 | 
			
		||||
	Container       Container
 | 
			
		||||
	Family          string
 | 
			
		||||
	Distro          Distro
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetServerName returns ServerName if this serverInfo is about host.
 | 
			
		||||
// If this serverInfo is abount a container, returns containerID@ServerName
 | 
			
		||||
func (s ServerInfo) GetServerName() string {
 | 
			
		||||
	if len(s.Container.ContainerID) == 0 {
 | 
			
		||||
		return s.ServerName
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s@%s", s.Container.Name, s.ServerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Distro has distribution info
 | 
			
		||||
type Distro struct {
 | 
			
		||||
	Family  string
 | 
			
		||||
	Release string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l Distro) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s %s", l.Family, l.Release)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MajorVersion returns Major version
 | 
			
		||||
func (l Distro) MajorVersion() (ver int, err error) {
 | 
			
		||||
	if 0 < len(l.Release) {
 | 
			
		||||
		ver, err = strconv.Atoi(strings.Split(l.Release, ".")[0])
 | 
			
		||||
	} else {
 | 
			
		||||
		err = fmt.Errorf("Release is empty")
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsContainer returns whether this ServerInfo is about container
 | 
			
		||||
@@ -245,9 +502,16 @@ func (s *ServerInfo) SetContainer(d Container) {
 | 
			
		||||
	s.Container = d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Containers has Containers information.
 | 
			
		||||
type Containers struct {
 | 
			
		||||
	Type     string
 | 
			
		||||
	Includes []string
 | 
			
		||||
	Excludes []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Container has Container information.
 | 
			
		||||
type Container struct {
 | 
			
		||||
	ContainerID string
 | 
			
		||||
	Name        string
 | 
			
		||||
	Type        string
 | 
			
		||||
	Image       string
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import "fmt"
 | 
			
		||||
type JSONLoader struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load load the configuraiton JSON file specified by path arg.
 | 
			
		||||
// Load load the configuration JSON file specified by path arg.
 | 
			
		||||
func (c JSONLoader) Load(path, sudoPass, keyPass string) (err error) {
 | 
			
		||||
	return fmt.Errorf("Not implement yet")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,8 +22,8 @@ import (
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/BurntSushi/toml"
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
	"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TOMLLoader loads config
 | 
			
		||||
@@ -31,14 +31,18 @@ type TOMLLoader struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load load the configuraiton TOML file specified by path arg.
 | 
			
		||||
func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
 | 
			
		||||
func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 | 
			
		||||
	if Conf.Debug {
 | 
			
		||||
		log.SetLevel(log.DebugLevel)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var conf Config
 | 
			
		||||
	if _, err := toml.DecodeFile(pathToToml, &conf); err != nil {
 | 
			
		||||
		log.Error("Load config failed", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Conf.Mail = conf.Mail
 | 
			
		||||
	Conf.EMail = conf.EMail
 | 
			
		||||
	Conf.Slack = conf.Slack
 | 
			
		||||
 | 
			
		||||
	d := conf.Default
 | 
			
		||||
@@ -51,51 +55,54 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
 | 
			
		||||
 | 
			
		||||
	i := 0
 | 
			
		||||
	for name, v := range conf.Servers {
 | 
			
		||||
 | 
			
		||||
		if 0 < len(v.KeyPassword) {
 | 
			
		||||
			log.Warn("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE.")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s := ServerInfo{ServerName: name}
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case v.User != "":
 | 
			
		||||
			s.User = v.User
 | 
			
		||||
		case d.User != "":
 | 
			
		||||
			s.User = d.User
 | 
			
		||||
		default:
 | 
			
		||||
			return fmt.Errorf("%s is invalid. User is empty", name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Host = v.Host
 | 
			
		||||
		if s.Host == "" {
 | 
			
		||||
			return fmt.Errorf("%s is invalid. host is empty", name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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 == "" {
 | 
			
		||||
			s.KeyPath = d.KeyPath
 | 
			
		||||
		}
 | 
			
		||||
		if s.KeyPath != "" {
 | 
			
		||||
			if _, err := os.Stat(s.KeyPath); err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"%s is invalid. keypath: %s not exists", name, s.KeyPath)
 | 
			
		||||
		if v.Type != ServerTypePseudo {
 | 
			
		||||
			s.Host = v.Host
 | 
			
		||||
			if len(s.Host) == 0 {
 | 
			
		||||
				return fmt.Errorf("%s is invalid. host is empty", name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//  s.KeyPassword = keyPass
 | 
			
		||||
		s.KeyPassword = v.KeyPassword
 | 
			
		||||
		if s.KeyPassword == "" {
 | 
			
		||||
			s.KeyPassword = d.KeyPassword
 | 
			
		||||
			switch {
 | 
			
		||||
			case v.Port != "":
 | 
			
		||||
				s.Port = v.Port
 | 
			
		||||
			case d.Port != "":
 | 
			
		||||
				s.Port = d.Port
 | 
			
		||||
			default:
 | 
			
		||||
				s.Port = "22"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			switch {
 | 
			
		||||
			case v.User != "":
 | 
			
		||||
				s.User = v.User
 | 
			
		||||
			case d.User != "":
 | 
			
		||||
				s.User = d.User
 | 
			
		||||
			default:
 | 
			
		||||
				if s.Port != "local" {
 | 
			
		||||
					return fmt.Errorf("%s is invalid. User is empty", name)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			s.KeyPath = v.KeyPath
 | 
			
		||||
			if len(s.KeyPath) == 0 {
 | 
			
		||||
				s.KeyPath = d.KeyPath
 | 
			
		||||
			}
 | 
			
		||||
			if s.KeyPath != "" {
 | 
			
		||||
				if _, err := os.Stat(s.KeyPath); err != nil {
 | 
			
		||||
					return fmt.Errorf(
 | 
			
		||||
						"%s is invalid. keypath: %s not exists", name, s.KeyPath)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//  s.KeyPassword = keyPass
 | 
			
		||||
			s.KeyPassword = v.KeyPassword
 | 
			
		||||
			if len(s.KeyPassword) == 0 {
 | 
			
		||||
				s.KeyPassword = d.KeyPassword
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.CpeNames = v.CpeNames
 | 
			
		||||
@@ -103,11 +110,42 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
 | 
			
		||||
			s.CpeNames = d.CpeNames
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.DependencyCheckXMLPath = v.DependencyCheckXMLPath
 | 
			
		||||
		if len(s.DependencyCheckXMLPath) == 0 {
 | 
			
		||||
			s.DependencyCheckXMLPath = d.DependencyCheckXMLPath
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Load CPEs from OWASP Dependency Check XML
 | 
			
		||||
		if len(s.DependencyCheckXMLPath) != 0 {
 | 
			
		||||
			cpes, err := parser.Parse(s.DependencyCheckXMLPath)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"Failed to read OWASP Dependency Check XML: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			log.Debugf("Loaded from OWASP Dependency Check XML: %s",
 | 
			
		||||
				s.ServerName)
 | 
			
		||||
			s.CpeNames = append(s.CpeNames, cpes...)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Containers = v.Containers
 | 
			
		||||
		if len(s.Containers) == 0 {
 | 
			
		||||
		if len(s.Containers.Includes) == 0 {
 | 
			
		||||
			s.Containers = d.Containers
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.IgnoreCves = v.IgnoreCves
 | 
			
		||||
		for _, cve := range d.IgnoreCves {
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, c := range s.IgnoreCves {
 | 
			
		||||
				if cve == c {
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !found {
 | 
			
		||||
				s.IgnoreCves = append(s.IgnoreCves, cve)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Optional = v.Optional
 | 
			
		||||
		for _, dkv := range d.Optional {
 | 
			
		||||
			found := false
 | 
			
		||||
@@ -122,13 +160,30 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Enablerepo = v.Enablerepo
 | 
			
		||||
		if len(s.Enablerepo) == 0 {
 | 
			
		||||
			s.Enablerepo = d.Enablerepo
 | 
			
		||||
		}
 | 
			
		||||
		if len(s.Enablerepo) != 0 {
 | 
			
		||||
			for _, repo := range 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.Type = v.Type
 | 
			
		||||
 | 
			
		||||
		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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										62
									
								
								contrib/owasp-dependency-check/parser/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,62 @@
 | 
			
		||||
package parser
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"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 nil, fmt.Errorf("Failed to open: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	b, err := ioutil.ReadAll(file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to read: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var anal analysis
 | 
			
		||||
	if err := xml.Unmarshal(b, &anal); err != nil {
 | 
			
		||||
		return 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)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return cpes, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										324
									
								
								db/db.go
									
									
									
									
									
								
							
							
						
						@@ -1,324 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	m "github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/jinzhu/gorm"
 | 
			
		||||
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var db *gorm.DB
 | 
			
		||||
 | 
			
		||||
// OpenDB opens Database
 | 
			
		||||
func OpenDB() (err error) {
 | 
			
		||||
	db, err = gorm.Open("sqlite3", config.Conf.DBPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
 | 
			
		||||
		return
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	db.LogMode(config.Conf.DebugSQL)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MigrateDB migrates Database
 | 
			
		||||
func MigrateDB() error {
 | 
			
		||||
	if err := db.AutoMigrate(
 | 
			
		||||
		&m.ScanHistory{},
 | 
			
		||||
		&m.ScanResult{},
 | 
			
		||||
		//  &m.NWLink{},
 | 
			
		||||
		&m.Container{},
 | 
			
		||||
		&m.CveInfo{},
 | 
			
		||||
		&m.CpeName{},
 | 
			
		||||
		&m.PackageInfo{},
 | 
			
		||||
		&m.DistroAdvisory{},
 | 
			
		||||
		&cve.CveDetail{},
 | 
			
		||||
		&cve.Jvn{},
 | 
			
		||||
		&cve.Nvd{},
 | 
			
		||||
		&cve.Reference{},
 | 
			
		||||
		&cve.Cpe{},
 | 
			
		||||
	).Error; err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to migrate. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errMsg := "Failed to create index. err: %s"
 | 
			
		||||
	//  if err := db.Model(&m.NWLink{}).
 | 
			
		||||
	//      AddIndex("idx_n_w_links_scan_result_id", "scan_result_id").Error; err != nil {
 | 
			
		||||
	//      return fmt.Errorf(errMsg, err)
 | 
			
		||||
	//  }
 | 
			
		||||
	if err := db.Model(&m.Container{}).
 | 
			
		||||
		AddIndex("idx_containers_scan_result_id", "scan_result_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.CveInfo{}).
 | 
			
		||||
		AddIndex("idx_cve_infos_scan_result_id", "scan_result_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.CpeName{}).
 | 
			
		||||
		AddIndex("idx_cpe_names_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.PackageInfo{}).
 | 
			
		||||
		AddIndex("idx_package_infos_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.DistroAdvisory{}).
 | 
			
		||||
		//TODO check table name
 | 
			
		||||
		AddIndex("idx_distro_advisories_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.CveDetail{}).
 | 
			
		||||
		AddIndex("idx_cve_details_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.CveDetail{}).
 | 
			
		||||
		AddIndex("idx_cve_details_cveid", "cve_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Nvd{}).
 | 
			
		||||
		AddIndex("idx_nvds_cve_detail_id", "cve_detail_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Jvn{}).
 | 
			
		||||
		AddIndex("idx_jvns_cve_detail_id", "cve_detail_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Cpe{}).
 | 
			
		||||
		AddIndex("idx_cpes_jvn_id", "jvn_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Reference{}).
 | 
			
		||||
		AddIndex("idx_references_jvn_id", "jvn_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Cpe{}).
 | 
			
		||||
		AddIndex("idx_cpes_nvd_id", "nvd_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Reference{}).
 | 
			
		||||
		AddIndex("idx_references_nvd_id", "nvd_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Insert inserts scan results into DB
 | 
			
		||||
func Insert(results []m.ScanResult) error {
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		r.KnownCves = resetGormIDs(r.KnownCves)
 | 
			
		||||
		r.UnknownCves = resetGormIDs(r.UnknownCves)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	history := m.ScanHistory{
 | 
			
		||||
		ScanResults: results,
 | 
			
		||||
		ScannedAt:   time.Now(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db = db.Set("gorm:save_associations", false)
 | 
			
		||||
	if err := db.Create(&history).Error; err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, scanResult := range history.ScanResults {
 | 
			
		||||
		scanResult.ScanHistoryID = history.ID
 | 
			
		||||
		if err := db.Create(&scanResult).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		scanResult.Container.ScanResultID = scanResult.ID
 | 
			
		||||
		if err := db.Create(&scanResult.Container).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := insertCveInfos(scanResult.ID, scanResult.KnownCves); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := insertCveInfos(scanResult.ID, scanResult.UnknownCves); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func insertCveInfos(scanResultID uint, infos []m.CveInfo) error {
 | 
			
		||||
	for _, cveInfo := range infos {
 | 
			
		||||
		cveInfo.ScanResultID = scanResultID
 | 
			
		||||
		if err := db.Create(&cveInfo).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, pack := range cveInfo.Packages {
 | 
			
		||||
			pack.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&pack).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, distroAdvisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			distroAdvisory.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&distroAdvisory).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, cpeName := range cveInfo.CpeNames {
 | 
			
		||||
			cpeName.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&cpeName).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		db = db.Set("gorm:save_associations", true)
 | 
			
		||||
		cveDetail := cveInfo.CveDetail
 | 
			
		||||
		cveDetail.CveInfoID = cveInfo.ID
 | 
			
		||||
		if err := db.Create(&cveDetail).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		db = db.Set("gorm:save_associations", false)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func resetGormIDs(infos []m.CveInfo) []m.CveInfo {
 | 
			
		||||
	for i := range infos {
 | 
			
		||||
		infos[i].CveDetail.ID = 0
 | 
			
		||||
		// NVD
 | 
			
		||||
		infos[i].CveDetail.Nvd.ID = 0
 | 
			
		||||
		for j := range infos[i].CveDetail.Nvd.Cpes {
 | 
			
		||||
			infos[i].CveDetail.Nvd.Cpes[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
		for j := range infos[i].CveDetail.Nvd.References {
 | 
			
		||||
			infos[i].CveDetail.Nvd.References[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// JVN
 | 
			
		||||
		infos[i].CveDetail.Jvn.ID = 0
 | 
			
		||||
		for j := range infos[i].CveDetail.Jvn.Cpes {
 | 
			
		||||
			infos[i].CveDetail.Jvn.Cpes[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
		for j := range infos[i].CveDetail.Jvn.References {
 | 
			
		||||
			infos[i].CveDetail.Jvn.References[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//Packages
 | 
			
		||||
		for j := range infos[i].Packages {
 | 
			
		||||
			infos[i].Packages[j].ID = 0
 | 
			
		||||
			infos[i].Packages[j].CveInfoID = 0
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return infos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SelectScanHistory select scan history from DB
 | 
			
		||||
func SelectScanHistory(historyID string) (m.ScanHistory, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	scanHistory := m.ScanHistory{}
 | 
			
		||||
	if historyID == "" {
 | 
			
		||||
		// select latest
 | 
			
		||||
		db.Order("scanned_at desc").First(&scanHistory)
 | 
			
		||||
	} else {
 | 
			
		||||
		var id int
 | 
			
		||||
		if id, err = strconv.Atoi(historyID); err != nil {
 | 
			
		||||
			return m.ScanHistory{},
 | 
			
		||||
				fmt.Errorf("historyID have to be numeric number: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		db.First(&scanHistory, id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if scanHistory.ID == 0 {
 | 
			
		||||
		return m.ScanHistory{}, fmt.Errorf("No scanHistory records")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//  results := []m.ScanResult{}
 | 
			
		||||
	results := m.ScanResults{}
 | 
			
		||||
	db.Model(&scanHistory).Related(&results, "ScanResults")
 | 
			
		||||
	scanHistory.ScanResults = results
 | 
			
		||||
 | 
			
		||||
	for i, r := range results {
 | 
			
		||||
		//  nw := []m.NWLink{}
 | 
			
		||||
		//  db.Model(&r).Related(&nw, "NWLinks")
 | 
			
		||||
		//  scanHistory.ScanResults[i].NWLinks = nw
 | 
			
		||||
 | 
			
		||||
		di := m.Container{}
 | 
			
		||||
		db.Model(&r).Related(&di, "Container")
 | 
			
		||||
		scanHistory.ScanResults[i].Container = di
 | 
			
		||||
 | 
			
		||||
		knownCves := selectCveInfos(&r, "KnownCves")
 | 
			
		||||
		sort.Sort(m.CveInfos(knownCves))
 | 
			
		||||
		scanHistory.ScanResults[i].KnownCves = knownCves
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Sort(scanHistory.ScanResults)
 | 
			
		||||
	return scanHistory, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func selectCveInfos(result *m.ScanResult, fieldName string) []m.CveInfo {
 | 
			
		||||
	cveInfos := []m.CveInfo{}
 | 
			
		||||
	db.Model(&result).Related(&cveInfos, fieldName)
 | 
			
		||||
 | 
			
		||||
	for i, cveInfo := range cveInfos {
 | 
			
		||||
		cveDetail := cve.CveDetail{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&cveDetail, "CveDetail")
 | 
			
		||||
		id := cveDetail.CveID
 | 
			
		||||
		filledCveDetail := cvedb.Get(id, db)
 | 
			
		||||
		cveInfos[i].CveDetail = filledCveDetail
 | 
			
		||||
 | 
			
		||||
		packs := []m.PackageInfo{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&packs, "Packages")
 | 
			
		||||
		cveInfos[i].Packages = packs
 | 
			
		||||
 | 
			
		||||
		advisories := []m.DistroAdvisory{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&advisories, "DistroAdvisories")
 | 
			
		||||
		cveInfos[i].DistroAdvisories = advisories
 | 
			
		||||
 | 
			
		||||
		names := []m.CpeName{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&names, "CpeNames")
 | 
			
		||||
		cveInfos[i].CpeNames = names
 | 
			
		||||
	}
 | 
			
		||||
	return cveInfos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SelectScanHistories select latest scan history from DB
 | 
			
		||||
func SelectScanHistories() ([]m.ScanHistory, error) {
 | 
			
		||||
	scanHistories := []m.ScanHistory{}
 | 
			
		||||
	db.Order("scanned_at desc").Find(&scanHistories)
 | 
			
		||||
 | 
			
		||||
	if len(scanHistories) == 0 {
 | 
			
		||||
		return []m.ScanHistory{}, fmt.Errorf("No scanHistory records")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, history := range scanHistories {
 | 
			
		||||
		results := m.ScanResults{}
 | 
			
		||||
		db.Model(&history).Related(&results, "ScanResults")
 | 
			
		||||
		scanHistories[i].ScanResults = results
 | 
			
		||||
 | 
			
		||||
		for j, r := range results {
 | 
			
		||||
			di := m.Container{}
 | 
			
		||||
			db.Model(&r).Related(&di, "Container")
 | 
			
		||||
			scanHistories[i].ScanResults[j].Container = di
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return scanHistories, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								glide.lock
									
									
									
										generated
									
									
									
								
							
							
						
						@@ -1,117 +0,0 @@
 | 
			
		||||
hash: 9683c87b3cf998e7fac1b12c4a94bf2bd18cb5422e9108539811546e703a439a
 | 
			
		||||
updated: 2016-07-12T16:20:45.462913061+09:00
 | 
			
		||||
imports:
 | 
			
		||||
- name: github.com/asaskevich/govalidator
 | 
			
		||||
  version: df81827fdd59d8b4fb93d8910b286ab7a3919520
 | 
			
		||||
- name: github.com/aws/aws-sdk-go
 | 
			
		||||
  version: 90dec2183a5f5458ee79cbaf4b8e9ab910bc81a6
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - aws
 | 
			
		||||
  - aws/credentials
 | 
			
		||||
  - aws/session
 | 
			
		||||
  - service/s3
 | 
			
		||||
  - aws/awserr
 | 
			
		||||
  - aws/client
 | 
			
		||||
  - aws/corehandlers
 | 
			
		||||
  - aws/defaults
 | 
			
		||||
  - aws/request
 | 
			
		||||
  - private/endpoints
 | 
			
		||||
  - aws/awsutil
 | 
			
		||||
  - aws/client/metadata
 | 
			
		||||
  - aws/signer/v4
 | 
			
		||||
  - private/protocol
 | 
			
		||||
  - private/protocol/restxml
 | 
			
		||||
  - private/waiter
 | 
			
		||||
  - aws/credentials/ec2rolecreds
 | 
			
		||||
  - aws/ec2metadata
 | 
			
		||||
  - private/protocol/rest
 | 
			
		||||
  - private/protocol/query
 | 
			
		||||
  - private/protocol/xml/xmlutil
 | 
			
		||||
  - private/protocol/query/queryutil
 | 
			
		||||
- name: github.com/Azure/azure-sdk-for-go
 | 
			
		||||
  version: 58a13e378daf3b06e65925397185684b16321111
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - storage
 | 
			
		||||
- name: github.com/BurntSushi/toml
 | 
			
		||||
  version: ffaa107fbd880f6d18cd6fec9b511668dcad8639
 | 
			
		||||
- name: github.com/cenkalti/backoff
 | 
			
		||||
  version: cdf48bbc1eb78d1349cbda326a4a037f7ba565c6
 | 
			
		||||
- name: github.com/cheggaaa/pb
 | 
			
		||||
  version: 04b234c80d661c663dbcebd52fc7218fdacc6d0c
 | 
			
		||||
- name: github.com/go-ini/ini
 | 
			
		||||
  version: cf53f9204df4fbdd7ec4164b57fa6184ba168292
 | 
			
		||||
- name: github.com/google/subcommands
 | 
			
		||||
  version: 1c7173745a6001f67d8d96ab4e178284c77f7759
 | 
			
		||||
- name: github.com/gosuri/uitable
 | 
			
		||||
  version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - util/strutil
 | 
			
		||||
  - util/wordwrap
 | 
			
		||||
- name: github.com/howeyc/gopass
 | 
			
		||||
  version: 66487b23f2880ba32e185121d2cd51a338ea069a
 | 
			
		||||
- name: github.com/jinzhu/gorm
 | 
			
		||||
  version: 613c0655691abb7691b70c5fda80a716d9e20b1b
 | 
			
		||||
- name: github.com/jinzhu/inflection
 | 
			
		||||
  version: 8f4d3a0d04ce0b7c0cf3126fb98524246d00d102
 | 
			
		||||
- name: github.com/jmespath/go-jmespath
 | 
			
		||||
  version: 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74
 | 
			
		||||
- name: github.com/jroimartin/gocui
 | 
			
		||||
  version: 2dcda558bf18ec07c7065bf1eaf071b5305f7c0c
 | 
			
		||||
- name: github.com/k0kubun/pp
 | 
			
		||||
  version: f5dce6ed0ccf6c350f1679964ff6b61f3d6d2033
 | 
			
		||||
- name: github.com/kotakanbe/go-cve-dictionary
 | 
			
		||||
  version: 1a336b8ac785badfe89a175ee926d39574901232
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - config
 | 
			
		||||
  - db
 | 
			
		||||
  - models
 | 
			
		||||
  - log
 | 
			
		||||
  - jvn
 | 
			
		||||
  - nvd
 | 
			
		||||
- name: github.com/kotakanbe/go-pingscanner
 | 
			
		||||
  version: 58e188a3e4f6ab1a6371e33421e4502e26fa1e80
 | 
			
		||||
- name: github.com/kotakanbe/logrus-prefixed-formatter
 | 
			
		||||
  version: f4f7d41649cf1e75e736884da8d05324aa76ea25
 | 
			
		||||
- name: github.com/mattn/go-colorable
 | 
			
		||||
  version: 9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59
 | 
			
		||||
- name: github.com/mattn/go-isatty
 | 
			
		||||
  version: 56b76bdf51f7708750eac80fa38b952bb9f32639
 | 
			
		||||
- name: github.com/mattn/go-runewidth
 | 
			
		||||
  version: d6bea18f789704b5f83375793155289da36a3c7f
 | 
			
		||||
- name: github.com/mattn/go-sqlite3
 | 
			
		||||
  version: 38ee283dabf11c9cbdb968eebd79b1fa7acbabe6
 | 
			
		||||
- name: github.com/mgutz/ansi
 | 
			
		||||
  version: c286dcecd19ff979eeb73ea444e479b903f2cfcb
 | 
			
		||||
- name: github.com/moul/http2curl
 | 
			
		||||
  version: b1479103caacaa39319f75e7f57fc545287fca0d
 | 
			
		||||
- name: github.com/nsf/termbox-go
 | 
			
		||||
  version: c45773466a30b680355d6494cc8826113c93cd0f
 | 
			
		||||
- name: github.com/parnurzeal/gorequest
 | 
			
		||||
  version: 6e8ad4ebdee4bec2934ed5afaaa1c7b877832a17
 | 
			
		||||
- name: github.com/rifflock/lfshook
 | 
			
		||||
  version: 05a24e24fa8d3a2eca8c2baf23aa2d5a2c51490c
 | 
			
		||||
- name: github.com/Sirupsen/logrus
 | 
			
		||||
  version: f3cfb454f4c209e6668c95216c4744b8fddb2356
 | 
			
		||||
- name: golang.org/x/crypto
 | 
			
		||||
  version: c2f4947f41766b144bb09066e919466da5eddeae
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - ssh
 | 
			
		||||
  - ssh/agent
 | 
			
		||||
  - ssh/terminal
 | 
			
		||||
  - curve25519
 | 
			
		||||
  - ed25519
 | 
			
		||||
  - ed25519/internal/edwards25519
 | 
			
		||||
- name: golang.org/x/net
 | 
			
		||||
  version: f841c39de738b1d0df95b5a7187744f0e03d8112
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - context
 | 
			
		||||
  - publicsuffix
 | 
			
		||||
- name: golang.org/x/sys
 | 
			
		||||
  version: a408501be4d17ee978c04a618e7a1b22af058c0e
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - unix
 | 
			
		||||
- name: gopkg.in/alexcesaro/quotedprintable.v3
 | 
			
		||||
  version: 2caba252f4dc53eaf6b553000885530023f54623
 | 
			
		||||
- name: gopkg.in/gomail.v2
 | 
			
		||||
  version: 81ebce5c23dfd25c6c67194b37d3dd3f338c98b1
 | 
			
		||||
devImports: []
 | 
			
		||||
							
								
								
									
										39
									
								
								glide.yaml
									
									
									
									
									
								
							
							
						
						@@ -1,39 +0,0 @@
 | 
			
		||||
package: github.com/future-architect/vuls
 | 
			
		||||
import:
 | 
			
		||||
- package: github.com/Azure/azure-sdk-for-go
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - storage
 | 
			
		||||
- package: github.com/BurntSushi/toml
 | 
			
		||||
- package: github.com/Sirupsen/logrus
 | 
			
		||||
- package: github.com/asaskevich/govalidator
 | 
			
		||||
- package: github.com/aws/aws-sdk-go
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - aws
 | 
			
		||||
  - aws/credentials
 | 
			
		||||
  - aws/session
 | 
			
		||||
  - service/s3
 | 
			
		||||
- package: github.com/cenkalti/backoff
 | 
			
		||||
- package: github.com/google/subcommands
 | 
			
		||||
- package: github.com/gosuri/uitable
 | 
			
		||||
- package: github.com/howeyc/gopass
 | 
			
		||||
- package: github.com/jinzhu/gorm
 | 
			
		||||
- package: github.com/jroimartin/gocui
 | 
			
		||||
- package: github.com/k0kubun/pp
 | 
			
		||||
- package: github.com/kotakanbe/go-cve-dictionary
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - config
 | 
			
		||||
  - db
 | 
			
		||||
  - models
 | 
			
		||||
- package: github.com/kotakanbe/go-pingscanner
 | 
			
		||||
- package: github.com/kotakanbe/logrus-prefixed-formatter
 | 
			
		||||
- package: github.com/mattn/go-sqlite3
 | 
			
		||||
- package: github.com/parnurzeal/gorequest
 | 
			
		||||
- package: github.com/rifflock/lfshook
 | 
			
		||||
- package: golang.org/x/crypto
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - ssh
 | 
			
		||||
  - ssh/agent
 | 
			
		||||
- package: golang.org/x/net
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - context
 | 
			
		||||
- package: gopkg.in/gomail.v2
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								img/vuls-abstract.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 123 KiB  | 
							
								
								
									
										1533
									
								
								img/vuls-architecture-localscan.graphml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								img/vuls-architecture-localscan.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 97 KiB  | 
| 
		 Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 91 KiB  | 
							
								
								
									
										414
									
								
								img/vuls-scan-flow-fast.graphml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,414 @@
 | 
			
		||||
<?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.17-->
 | 
			
		||||
  <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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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="right" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="88.796875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="0.8228014380530908">Get installed packages
 | 
			
		||||
Alpine: apk
 | 
			
		||||
Debian/Ubuntu: dpkg-query
 | 
			
		||||
Amazon/RHEL/CentOS: rpm
 | 
			
		||||
SUSE: zypper
 | 
			
		||||
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="609.3698412698412" y="630.0546766682629"/>
 | 
			
		||||
          <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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="152.634765625" x="57.6826171875" y="18.93359375">Write results to JSON files<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="n4">
 | 
			
		||||
      <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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="232.744140625" x="17.6279296875" y="4.80078125">Get CVE IDs by using package manager
 | 
			
		||||
Amazon: 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="n5">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="750.4705298628534"/>
 | 
			
		||||
          <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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="42.595703125" x="112.7021484375" y="18.93359375">Report<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" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="116.89483989807195" width="333.6788874841973" x="234.29467728596296" y="709.1901021013174"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="333.6788874841973" x="0.0" y="0.0">Vulnerability Database</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 1</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
          </y:Realizers>
 | 
			
		||||
        </y:ProxyAutoBoundsNode>
 | 
			
		||||
      </data>
 | 
			
		||||
      <graph edgedefault="directed" id="n6:">
 | 
			
		||||
        <node id="n6::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="65.22882427307195" width="136.83944374209864" x="416.1341210280616" y="745.8561177263174"/>
 | 
			
		||||
              <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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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="n6::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="65.22882427307195" width="136.83944374209864" x="249.29467728596296" y="745.8561177263174"/>
 | 
			
		||||
              <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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.533203125" x="40.653120308549205" y="23.548005886535975">OVAL DB<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>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n7">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="27.144753476611868" 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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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="n8">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.loopLimit">
 | 
			
		||||
          <y:Geometry height="51.10998735777497" width="137.19216182048035" x="92.54867256637169" 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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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="n9">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="27.144753476611868" 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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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="n10">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.loopLimitEnd">
 | 
			
		||||
          <y:Geometry height="50.0" width="137.0" x="92.64475347661187" 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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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>
 | 
			
		||||
    <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="n4">
 | 
			
		||||
      <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="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="183.35883739927397" y="2.000003510871693">Amazon
 | 
			
		||||
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="right" ratio="0.7796030035582084" 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="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="e3" source="n5" target="n6">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="10.8330078125"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e4" source="n1" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="-123.36984126984123" ty="0.0">
 | 
			
		||||
            <y:Point x="443.6849206349206" y="658.0546766682629"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="102.9296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="77.078125" x="-97.68364242524859" y="5.005267793098369">Alpine Linux
 | 
			
		||||
CentOS
 | 
			
		||||
RHEL
 | 
			
		||||
Ubuntu
 | 
			
		||||
Debian
 | 
			
		||||
Oracle Linux
 | 
			
		||||
Suse<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="59.14459455430983" distanceToCenter="true" position="right" ratio="0.0" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e5" source="n4" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e6" source="n7" target="n8">
 | 
			
		||||
      <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="e7" source="n8" target="n9">
 | 
			
		||||
      <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="e8" source="n9" target="n10">
 | 
			
		||||
      <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="e9" source="n3" target="n5">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e10" source="n1" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
 | 
			
		||||
            <y:Point x="161.14475347661187" 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="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.98046875" x="-196.80057112212188" y="20.933597260871807">Raspbian<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="left" ratio="0.6447921222409765" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e11" source="n10" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="-125.78842258255952" ty="0.0">
 | 
			
		||||
            <y:Point x="161.14475347661187" y="658.0546766682629"/>
 | 
			
		||||
          </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-fast.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 78 KiB  | 
@@ -1,6 +1,6 @@
 | 
			
		||||
<?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-->
 | 
			
		||||
  <!--Created by yEd 3.17-->
 | 
			
		||||
  <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"/>
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
          <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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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>
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
          <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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="38.0" y="18.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -53,10 +53,12 @@
 | 
			
		||||
          <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
 | 
			
		||||
          <y:NodeLabel alignment="right" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="88.796875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="0.8228014380530908">Get installed packages
 | 
			
		||||
Alpine Linux: apk
 | 
			
		||||
Debian/Ubuntu: dpkg-query
 | 
			
		||||
Amazon/RHEL/CentOS: rpm
 | 
			
		||||
FreeBSD: pkg<y:LabelModel>
 | 
			
		||||
FreeBSD: pkg
 | 
			
		||||
SUSE: zypper<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -72,7 +74,7 @@ FreeBSD: pkg<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="10.0" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="260.83984375" x="3.580078125" y="11.8671875">Get upgradable packages
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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>
 | 
			
		||||
@@ -89,7 +91,7 @@ Debian/Ubuntu: apt-get upgrade --dry-run<y:LabelModel>
 | 
			
		||||
          <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 
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="131.751953125" x="2.7201043477401754" y="9.422181178887513">foreach 
 | 
			
		||||
upgradable  packages<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -106,7 +108,7 @@ upgradable  packages<y:LabelModel>
 | 
			
		||||
          <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
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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>
 | 
			
		||||
@@ -123,7 +125,7 @@ Debian/Ubuntu: aptitude changelog<y:LabelModel>
 | 
			
		||||
          <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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.24609375" x="40.876953125" y="15.93359375">end loop<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -139,7 +141,7 @@ Debian/Ubuntu: aptitude changelog<y:LabelModel>
 | 
			
		||||
          <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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="152.634765625" x="57.6826171875" y="18.93359375">Write results to JSON files<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -155,7 +157,7 @@ Debian/Ubuntu: aptitude changelog<y:LabelModel>
 | 
			
		||||
          <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
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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"/>
 | 
			
		||||
@@ -168,45 +170,12 @@ FreeBSD: pkg audit<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n9">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
          <y:Geometry height="64.1719342604298" width="111.96965865992411" x="687.3850119398792" y="807.0697396491782"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="48.56640625" x="31.701626204962054" y="23.019560880214726">Vuls DB<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="-8.881784197001252E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n10">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
          <y:Geometry height="65.22882427307195" width="136.83944374209864" x="411.5802781289507" y="687.385587863464"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="117.970703125" x="9.434370308549205" y="23.548005886535975">CVE DB (NVD / JVN)<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-8.326672684688674E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n11">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="716.4553275126422"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="126.396484375" x="70.8017578125" y="11.8671875">Insert results into DB
 | 
			
		||||
Reporting<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="42.595703125" x="112.7021484375" y="18.93359375">Report<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -216,14 +185,14 @@ Reporting<y:LabelModel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n12">
 | 
			
		||||
    <node id="n10">
 | 
			
		||||
      <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:Geometry height="56.0" width="268.0" x="309.6849206349206" y="371.39590905499364"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="271.369140625" x="-1.6845703124999432" y="11.8671875">Get all changelogs by using package manager
 | 
			
		||||
CentOS: yum update --changelog<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="293.06640625" x="-12.533203124999943" y="11.8671875">Get all changelogs of updatable packages at once
 | 
			
		||||
yum changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -233,13 +202,13 @@ CentOS: yum update --changelog<y:LabelModel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n13">
 | 
			
		||||
    <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="373.8409153761062"/>
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.68492063492056" 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="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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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>
 | 
			
		||||
@@ -249,6 +218,86 @@ CentOS: yum update --changelog<y:LabelModel>
 | 
			
		||||
        </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="609.3698412698412" 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="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="293.06640625" x="-12.533203124999886" y="11.8671875">Get all changelogs of updatable packages at once
 | 
			
		||||
Amazon / RHEL: yum changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n13" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="116.89483989807195" width="333.6788874841973" x="229.74083438685204" y="675.1748997511062"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="333.6788874841973" x="0.0" y="0.0">Vulnerability Database</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 1</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
          </y:Realizers>
 | 
			
		||||
        </y:ProxyAutoBoundsNode>
 | 
			
		||||
      </data>
 | 
			
		||||
      <graph edgedefault="directed" id="n13:">
 | 
			
		||||
        <node id="n13::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="65.22882427307195" width="136.83944374209864" x="411.5802781289507" y="711.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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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="n13::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="65.22882427307195" width="136.83944374209864" x="244.74083438685204" y="711.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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.533203125" x="40.653120308549205" y="23.548005886535975">OVAL DB<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>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n2" target="n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
@@ -267,12 +316,13 @@ CentOS: yum update --changelog<y:LabelModel>
 | 
			
		||||
          </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:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.98046875" x="-257.65322875976574" y="2.0000035108718635">Debian
 | 
			
		||||
Ubuntu
 | 
			
		||||
Raspbian<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:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="left" ratio="0.8652035780364729" 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>
 | 
			
		||||
@@ -330,13 +380,13 @@ Ubuntu<y:LabelModel>
 | 
			
		||||
          </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
 | 
			
		||||
          <y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="200.87829463898197" y="4.000003510871693">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:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="6.999999999999886" distanceToCenter="false" position="right" ratio="0.8192728556300707" segment="-1"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
@@ -344,17 +394,7 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </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">
 | 
			
		||||
    <edge id="e7" source="n0" target="n2">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-45.22123893805309"/>
 | 
			
		||||
@@ -364,7 +404,7 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e9" source="n7" target="n11">
 | 
			
		||||
    <edge id="e8" source="n7" target="n9">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
@@ -374,40 +414,17 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e10" source="n7" target="n10">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="-134.01566143419018" sy="6.159084623893818" tx="0.0" ty="-29.333162136535975">
 | 
			
		||||
            <y:Point x="480.0" y="660.0"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e11" source="n11" target="n9">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.86721713021484"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e12" source="n1" target="n12">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
    <edge id="e9" source="n1" target="n10">
 | 
			
		||||
      <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:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="46.708984375" x="-53.35447755843876" y="5.000003510871807">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:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.0" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
@@ -415,8 +432,7 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e13" source="n12" target="n13">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
    <edge id="e10" source="n10" target="n11">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
@@ -426,12 +442,11 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e14" source="n13" target="n7">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
    <edge id="e11" source="n11" 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 sx="0.0" sy="0.0" tx="0.0" ty="-24.34091537610618">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="487.8409153761062"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
@@ -439,6 +454,60 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e12" source="n8" target="n12">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e13" source="n12" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e14" source="n9" target="n13">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="10.8330078125"/>
 | 
			
		||||
          <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="e15" source="n1" target="n7">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
 | 
			
		||||
            <y:Point x="999.0" y="226.44247787610618"/>
 | 
			
		||||
            <y:Point x="999.0" y="570.8409153761062"/>
 | 
			
		||||
            <y:Point x="743.3698412698412" y="570.8409153761062"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="76.8203125" x="422.923942251054" y="13.867191010871807">Alpine Linux
 | 
			
		||||
SUSE<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.8856709076027529" 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>
 | 
			
		||||
  </graph>
 | 
			
		||||
  <data key="d7">
 | 
			
		||||
    <y:Resources/>
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 90 KiB  | 
							
								
								
									
										15
									
								
								main.go
									
									
									
									
									
								
							
							
						
						@@ -22,15 +22,18 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/commands"
 | 
			
		||||
	"github.com/future-architect/vuls/version"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
 | 
			
		||||
	_ "github.com/mattn/go-sqlite3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Version of Vuls
 | 
			
		||||
var version = "0.4.2"
 | 
			
		||||
 | 
			
		||||
// Revision of Git
 | 
			
		||||
var revision string
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	subcommands.Register(subcommands.HelpCommand(), "")
 | 
			
		||||
	subcommands.Register(subcommands.FlagsCommand(), "")
 | 
			
		||||
@@ -38,8 +41,8 @@ func main() {
 | 
			
		||||
	subcommands.Register(&commands.DiscoverCmd{}, "discover")
 | 
			
		||||
	subcommands.Register(&commands.TuiCmd{}, "tui")
 | 
			
		||||
	subcommands.Register(&commands.ScanCmd{}, "scan")
 | 
			
		||||
	subcommands.Register(&commands.PrepareCmd{}, "prepare")
 | 
			
		||||
	subcommands.Register(&commands.HistoryCmd{}, "history")
 | 
			
		||||
	subcommands.Register(&commands.ReportCmd{}, "report")
 | 
			
		||||
	subcommands.Register(&commands.ConfigtestCmd{}, "configtest")
 | 
			
		||||
 | 
			
		||||
	var v = flag.Bool("v", false, "Show version")
 | 
			
		||||
@@ -47,7 +50,7 @@ func main() {
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	if *v {
 | 
			
		||||
		fmt.Printf("%s %s\n", version.Name, version.Version)
 | 
			
		||||
		fmt.Printf("vuls %s %s\n", version, revision)
 | 
			
		||||
		os.Exit(int(subcommands.ExitSuccess))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										275
									
								
								models/cvecontents.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,275 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CveContents has CveContent
 | 
			
		||||
type CveContents map[CveContentType]CveContent
 | 
			
		||||
 | 
			
		||||
// NewCveContents create CveContents
 | 
			
		||||
func NewCveContents(conts ...CveContent) CveContents {
 | 
			
		||||
	m := CveContents{}
 | 
			
		||||
	for _, cont := range conts {
 | 
			
		||||
		m[cont.Type] = cont
 | 
			
		||||
	}
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContentStr has CveContentType and Value
 | 
			
		||||
type CveContentStr struct {
 | 
			
		||||
	Type  CveContentType
 | 
			
		||||
	Value string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Except returns CveContents except given keys for enumeration
 | 
			
		||||
func (v CveContents) Except(exceptCtypes ...CveContentType) (values CveContents) {
 | 
			
		||||
	values = CveContents{}
 | 
			
		||||
	for ctype, content := range v {
 | 
			
		||||
		found := false
 | 
			
		||||
		for _, exceptCtype := range exceptCtypes {
 | 
			
		||||
			if ctype == exceptCtype {
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			values[ctype] = content
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SourceLinks returns link of source
 | 
			
		||||
func (v CveContents) SourceLinks(lang, myFamily, cveID string) (values []CveContentStr) {
 | 
			
		||||
	if lang == "ja" {
 | 
			
		||||
		if cont, found := v[JVN]; found && 0 < len(cont.SourceLink) {
 | 
			
		||||
			values = append(values, CveContentStr{JVN, cont.SourceLink})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	order := CveContentTypes{NVD, NewCveContentType(myFamily)}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v[ctype]; found {
 | 
			
		||||
			values = append(values, CveContentStr{ctype, cont.SourceLink})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(values) == 0 {
 | 
			
		||||
		return []CveContentStr{{
 | 
			
		||||
			Type:  NVD,
 | 
			
		||||
			Value: "https://nvd.nist.gov/vuln/detail/" + cveID,
 | 
			
		||||
		}}
 | 
			
		||||
	}
 | 
			
		||||
	return values
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
// Severities returns Severities
 | 
			
		||||
func (v CveContents) Severities(myFamily string) (values []CveContentStr) {
 | 
			
		||||
	order := CveContentTypes{NVD, NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order)...)...)
 | 
			
		||||
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v[ctype]; found && 0 < len(cont.Severity) {
 | 
			
		||||
			values = append(values, CveContentStr{
 | 
			
		||||
				Type:  ctype,
 | 
			
		||||
				Value: cont.Severity,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// CveContentCpes has CveContentType and Value
 | 
			
		||||
type CveContentCpes struct {
 | 
			
		||||
	Type  CveContentType
 | 
			
		||||
	Value []Cpe
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cpes returns affected CPEs of this Vulnerability
 | 
			
		||||
func (v CveContents) Cpes(myFamily string) (values []CveContentCpes) {
 | 
			
		||||
	order := CveContentTypes{NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order)...)...)
 | 
			
		||||
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v[ctype]; found && 0 < len(cont.Cpes) {
 | 
			
		||||
			values = append(values, CveContentCpes{
 | 
			
		||||
				Type:  ctype,
 | 
			
		||||
				Value: cont.Cpes,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContentRefs has CveContentType and Cpes
 | 
			
		||||
type CveContentRefs struct {
 | 
			
		||||
	Type  CveContentType
 | 
			
		||||
	Value []Reference
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// References returns References
 | 
			
		||||
func (v CveContents) References(myFamily string) (values []CveContentRefs) {
 | 
			
		||||
	order := CveContentTypes{NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order)...)...)
 | 
			
		||||
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v[ctype]; found && 0 < len(cont.References) {
 | 
			
		||||
			values = append(values, CveContentRefs{
 | 
			
		||||
				Type:  ctype,
 | 
			
		||||
				Value: cont.References,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CweIDs returns related CweIDs of the vulnerability
 | 
			
		||||
func (v CveContents) CweIDs(myFamily string) (values []CveContentStr) {
 | 
			
		||||
	order := CveContentTypes{NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order)...)...)
 | 
			
		||||
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v[ctype]; found && 0 < len(cont.CweID) {
 | 
			
		||||
			// RedHat's OVAL sometimes contains multiple CWE-IDs separated by spaces
 | 
			
		||||
			for _, cweID := range strings.Fields(cont.CweID) {
 | 
			
		||||
				values = append(values, CveContentStr{
 | 
			
		||||
					Type:  ctype,
 | 
			
		||||
					Value: cweID,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContent has abstraction of various vulnerability information
 | 
			
		||||
type CveContent struct {
 | 
			
		||||
	Type         CveContentType
 | 
			
		||||
	CveID        string
 | 
			
		||||
	Title        string
 | 
			
		||||
	Summary      string
 | 
			
		||||
	Severity     string
 | 
			
		||||
	Cvss2Score   float64
 | 
			
		||||
	Cvss2Vector  string
 | 
			
		||||
	Cvss3Score   float64
 | 
			
		||||
	Cvss3Vector  string
 | 
			
		||||
	SourceLink   string
 | 
			
		||||
	Cpes         []Cpe
 | 
			
		||||
	References   References
 | 
			
		||||
	CweID        string
 | 
			
		||||
	Published    time.Time
 | 
			
		||||
	LastModified time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Empty checks the content is empty
 | 
			
		||||
func (c CveContent) Empty() bool {
 | 
			
		||||
	return c.Summary == ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContentType is a source of CVE information
 | 
			
		||||
type CveContentType string
 | 
			
		||||
 | 
			
		||||
// NewCveContentType create CveContentType
 | 
			
		||||
func NewCveContentType(name string) CveContentType {
 | 
			
		||||
	switch name {
 | 
			
		||||
	case "nvd":
 | 
			
		||||
		return NVD
 | 
			
		||||
	case "jvn":
 | 
			
		||||
		return JVN
 | 
			
		||||
	case "redhat", "centos":
 | 
			
		||||
		return RedHat
 | 
			
		||||
	case "oracle":
 | 
			
		||||
		return Oracle
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		return Ubuntu
 | 
			
		||||
	case "debian":
 | 
			
		||||
		return Debian
 | 
			
		||||
	default:
 | 
			
		||||
		return Unknown
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// NVD is NVD
 | 
			
		||||
	NVD CveContentType = "nvd"
 | 
			
		||||
 | 
			
		||||
	// JVN is JVN
 | 
			
		||||
	JVN CveContentType = "jvn"
 | 
			
		||||
 | 
			
		||||
	// RedHat is RedHat
 | 
			
		||||
	RedHat CveContentType = "redhat"
 | 
			
		||||
 | 
			
		||||
	// Debian is Debian
 | 
			
		||||
	Debian CveContentType = "debian"
 | 
			
		||||
 | 
			
		||||
	// Ubuntu is Ubuntu
 | 
			
		||||
	Ubuntu CveContentType = "ubuntu"
 | 
			
		||||
 | 
			
		||||
	// Oracle is Oracle Linux
 | 
			
		||||
	Oracle CveContentType = "oracle"
 | 
			
		||||
 | 
			
		||||
	// SUSE is SUSE Linux
 | 
			
		||||
	SUSE CveContentType = "suse"
 | 
			
		||||
 | 
			
		||||
	// Unknown is Unknown
 | 
			
		||||
	Unknown CveContentType = "unknown"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CveContentTypes has slide of CveContentType
 | 
			
		||||
type CveContentTypes []CveContentType
 | 
			
		||||
 | 
			
		||||
// AllCveContetTypes has all of CveContentTypes
 | 
			
		||||
var AllCveContetTypes = CveContentTypes{NVD, JVN, RedHat, Debian, Ubuntu}
 | 
			
		||||
 | 
			
		||||
// Except returns CveContentTypes except for given args
 | 
			
		||||
func (c CveContentTypes) Except(excepts ...CveContentType) (excepted CveContentTypes) {
 | 
			
		||||
	for _, ctype := range c {
 | 
			
		||||
		found := false
 | 
			
		||||
		for _, except := range excepts {
 | 
			
		||||
			if ctype == except {
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			excepted = append(excepted, ctype)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cpe is Common Platform Enumeration
 | 
			
		||||
type Cpe struct {
 | 
			
		||||
	CpeName string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// References is a slice of Reference
 | 
			
		||||
type References []Reference
 | 
			
		||||
 | 
			
		||||
// Reference has a related link of the CVE
 | 
			
		||||
type Reference struct {
 | 
			
		||||
	Source string
 | 
			
		||||
	Link   string
 | 
			
		||||
	RefID  string
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										206
									
								
								models/cvecontents_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,206 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestExcept(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  CveContents
 | 
			
		||||
		out CveContents
 | 
			
		||||
	}{{
 | 
			
		||||
		in: CveContents{
 | 
			
		||||
			RedHat: {Type: RedHat},
 | 
			
		||||
			Ubuntu: {Type: Ubuntu},
 | 
			
		||||
			Debian: {Type: Debian},
 | 
			
		||||
		},
 | 
			
		||||
		out: CveContents{
 | 
			
		||||
			RedHat: {Type: RedHat},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.Except(Ubuntu, Debian)
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSourceLinks(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		lang  string
 | 
			
		||||
		cveID string
 | 
			
		||||
		cont  CveContents
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out []CveContentStr
 | 
			
		||||
	}{
 | 
			
		||||
		// lang: ja
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang:  "ja",
 | 
			
		||||
				cveID: "CVE-2017-6074",
 | 
			
		||||
				cont: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:       JVN,
 | 
			
		||||
						SourceLink: "https://jvn.jp/vu/JVNVU93610402/",
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						SourceLink: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  JVN,
 | 
			
		||||
					Value: "https://jvn.jp/vu/JVNVU93610402/",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  RedHat,
 | 
			
		||||
					Value: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// lang: en
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang:  "en",
 | 
			
		||||
				cveID: "CVE-2017-6074",
 | 
			
		||||
				cont: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:       JVN,
 | 
			
		||||
						SourceLink: "https://jvn.jp/vu/JVNVU93610402/",
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						SourceLink: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  RedHat,
 | 
			
		||||
					Value: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// lang: empty
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang:  "en",
 | 
			
		||||
				cveID: "CVE-2017-6074",
 | 
			
		||||
				cont:  CveContents{},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.cont.SourceLinks(tt.in.lang, "redhat", tt.in.cveID)
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVendorLink(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		family string
 | 
			
		||||
		vinfo  VulnInfo
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out map[string]string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				family: "redhat",
 | 
			
		||||
				vinfo: VulnInfo{
 | 
			
		||||
					CveID: "CVE-2017-6074",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:       JVN,
 | 
			
		||||
							SourceLink: "https://jvn.jp/vu/JVNVU93610402/",
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							SourceLink: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: map[string]string{
 | 
			
		||||
				"RHEL-CVE": "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				family: "ubuntu",
 | 
			
		||||
				vinfo: VulnInfo{
 | 
			
		||||
					CveID: "CVE-2017-6074",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       Ubuntu,
 | 
			
		||||
							SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: map[string]string{
 | 
			
		||||
				"Ubuntu-CVE": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2017-6074",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.vinfo.VendorLinks(tt.in.family)
 | 
			
		||||
		for k := range tt.out {
 | 
			
		||||
			if tt.out[k] != actual[k] {
 | 
			
		||||
				t.Errorf("\nexpected: %s\n  actual: %s\n", tt.out[k], actual[k])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										320
									
								
								models/models.go
									
									
									
									
									
								
							
							
						
						@@ -17,321 +17,5 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/jinzhu/gorm"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanHistory is the history of Scanning.
 | 
			
		||||
type ScanHistory struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	ScanResults ScanResults
 | 
			
		||||
	ScannedAt   time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResults is slice of ScanResult.
 | 
			
		||||
type ScanResults []ScanResult
 | 
			
		||||
 | 
			
		||||
// Len implement Sort Interface
 | 
			
		||||
func (s ScanResults) Len() int {
 | 
			
		||||
	return len(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Swap implement Sort Interface
 | 
			
		||||
func (s ScanResults) Swap(i, j int) {
 | 
			
		||||
	s[i], s[j] = s[j], s[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Less implement Sort Interface
 | 
			
		||||
func (s ScanResults) Less(i, j int) bool {
 | 
			
		||||
	if s[i].ServerName == s[j].ServerName {
 | 
			
		||||
		return s[i].Container.ContainerID < s[i].Container.ContainerID
 | 
			
		||||
	}
 | 
			
		||||
	return s[i].ServerName < s[j].ServerName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver is filter function.
 | 
			
		||||
func (s ScanResults) FilterByCvssOver() (filtered ScanResults) {
 | 
			
		||||
	for _, result := range s {
 | 
			
		||||
		cveInfos := []CveInfo{}
 | 
			
		||||
		for _, cveInfo := range result.KnownCves {
 | 
			
		||||
			if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
 | 
			
		||||
				cveInfos = append(cveInfos, cveInfo)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		result.KnownCves = cveInfos
 | 
			
		||||
		filtered = append(filtered, result)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResult has the result of scanned CVE information.
 | 
			
		||||
type ScanResult struct {
 | 
			
		||||
	gorm.Model    `json:"-"`
 | 
			
		||||
	ScanHistoryID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	ServerName string // TOML Section key
 | 
			
		||||
	//  Hostname    string
 | 
			
		||||
	Family  string
 | 
			
		||||
	Release string
 | 
			
		||||
 | 
			
		||||
	Container Container
 | 
			
		||||
 | 
			
		||||
	Platform Platform
 | 
			
		||||
 | 
			
		||||
	//  Fqdn        string
 | 
			
		||||
	//  NWLinks     []NWLink
 | 
			
		||||
	KnownCves   []CveInfo
 | 
			
		||||
	UnknownCves []CveInfo
 | 
			
		||||
 | 
			
		||||
	Optional [][]interface{} `gorm:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfo returns server name one line
 | 
			
		||||
func (r ScanResult) ServerInfo() string {
 | 
			
		||||
	hostinfo := ""
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"%s (%s%s)",
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
		)
 | 
			
		||||
	} else {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"%s / %s (%s%s) on %s",
 | 
			
		||||
			r.Container.Name,
 | 
			
		||||
			r.Container.ContainerID,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	return hostinfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfoTui returns server infromation for TUI sidebar
 | 
			
		||||
func (r ScanResult) ServerInfoTui() string {
 | 
			
		||||
	hostinfo := ""
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"%s (%s%s)",
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
		)
 | 
			
		||||
	} else {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"|-- %s (%s%s)",
 | 
			
		||||
			r.Container.Name,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
			//  r.Container.ContainerID,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	return hostinfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveSummary summarize the number of CVEs group by CVSSv2 Severity
 | 
			
		||||
func (r ScanResult) CveSummary() string {
 | 
			
		||||
	var high, middle, 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:
 | 
			
		||||
			high++
 | 
			
		||||
		case 4.0 < score:
 | 
			
		||||
			middle++
 | 
			
		||||
		case 0 < score:
 | 
			
		||||
			low++
 | 
			
		||||
		default:
 | 
			
		||||
			unknown++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d)",
 | 
			
		||||
			high+middle+low, high, middle, low)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d ?:%d)",
 | 
			
		||||
		high+middle+low+unknown, high, middle, low, unknown)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NWLink has network link information.
 | 
			
		||||
type NWLink struct {
 | 
			
		||||
	gorm.Model   `json:"-"`
 | 
			
		||||
	ScanResultID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	IPAddress string
 | 
			
		||||
	Netmask   string
 | 
			
		||||
	DevName   string
 | 
			
		||||
	LinkState string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveInfos is for sorting
 | 
			
		||||
type CveInfos []CveInfo
 | 
			
		||||
 | 
			
		||||
func (c CveInfos) Len() int {
 | 
			
		||||
	return len(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c CveInfos) Swap(i, j int) {
 | 
			
		||||
	c[i], c[j] = c[j], c[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c CveInfos) Less(i, j int) bool {
 | 
			
		||||
	lang := config.Conf.Lang
 | 
			
		||||
	if c[i].CveDetail.CvssScore(lang) == c[j].CveDetail.CvssScore(lang) {
 | 
			
		||||
		return c[i].CveDetail.CveID < c[j].CveDetail.CveID
 | 
			
		||||
	}
 | 
			
		||||
	return c[i].CveDetail.CvssScore(lang) > c[j].CveDetail.CvssScore(lang)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveInfo has Cve Information.
 | 
			
		||||
type CveInfo struct {
 | 
			
		||||
	gorm.Model   `json:"-"`
 | 
			
		||||
	ScanResultID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	CveDetail        cve.CveDetail
 | 
			
		||||
	Packages         []PackageInfo
 | 
			
		||||
	DistroAdvisories []DistroAdvisory
 | 
			
		||||
	CpeNames         []CpeName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CpeName has CPE name
 | 
			
		||||
type CpeName struct {
 | 
			
		||||
	gorm.Model `json:"-"`
 | 
			
		||||
	CveInfoID  uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	Name string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageInfoList is slice of PackageInfo
 | 
			
		||||
type PackageInfoList []PackageInfo
 | 
			
		||||
 | 
			
		||||
// Exists returns true if exists the name
 | 
			
		||||
func (ps PackageInfoList) Exists(name string) bool {
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if p.Name == name {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UniqByName be uniq by name.
 | 
			
		||||
func (ps PackageInfoList) UniqByName() (distincted PackageInfoList) {
 | 
			
		||||
	set := make(map[string]PackageInfo)
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		set[p.Name] = p
 | 
			
		||||
	}
 | 
			
		||||
	//sort by key
 | 
			
		||||
	keys := []string{}
 | 
			
		||||
	for key := range set {
 | 
			
		||||
		keys = append(keys, key)
 | 
			
		||||
	}
 | 
			
		||||
	sort.Strings(keys)
 | 
			
		||||
	for _, key := range keys {
 | 
			
		||||
		distincted = append(distincted, set[key])
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindByName search PackageInfo by name
 | 
			
		||||
func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found bool) {
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if p.Name == name {
 | 
			
		||||
			return p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return PackageInfo{}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Find search PackageInfo by name-version-release
 | 
			
		||||
//  func (ps PackageInfoList) find(nameVersionRelease string) (PackageInfo, bool) {
 | 
			
		||||
//      for _, p := range ps {
 | 
			
		||||
//          joined := p.Name
 | 
			
		||||
//          if 0 < len(p.Version) {
 | 
			
		||||
//              joined = fmt.Sprintf("%s-%s", joined, p.Version)
 | 
			
		||||
//          }
 | 
			
		||||
//          if 0 < len(p.Release) {
 | 
			
		||||
//              joined = fmt.Sprintf("%s-%s", joined, p.Release)
 | 
			
		||||
//          }
 | 
			
		||||
//          if joined == nameVersionRelease {
 | 
			
		||||
//              return p, true
 | 
			
		||||
//          }
 | 
			
		||||
//      }
 | 
			
		||||
//      return PackageInfo{}, false
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// PackageInfo has installed packages.
 | 
			
		||||
type PackageInfo struct {
 | 
			
		||||
	gorm.Model `json:"-"`
 | 
			
		||||
	CveInfoID  uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	Name    string
 | 
			
		||||
	Version string
 | 
			
		||||
	Release string
 | 
			
		||||
 | 
			
		||||
	NewVersion string
 | 
			
		||||
	NewRelease string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToStringCurrentVersion returns package name-version-release
 | 
			
		||||
func (p PackageInfo) ToStringCurrentVersion() string {
 | 
			
		||||
	str := p.Name
 | 
			
		||||
	if 0 < len(p.Version) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.Version)
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(p.Release) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.Release)
 | 
			
		||||
	}
 | 
			
		||||
	return str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToStringNewVersion returns package name-version-release
 | 
			
		||||
func (p PackageInfo) ToStringNewVersion() string {
 | 
			
		||||
	str := p.Name
 | 
			
		||||
	if 0 < len(p.NewVersion) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.NewVersion)
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(p.NewRelease) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.NewRelease)
 | 
			
		||||
	}
 | 
			
		||||
	return str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
 | 
			
		||||
type DistroAdvisory struct {
 | 
			
		||||
	gorm.Model `json:"-"`
 | 
			
		||||
	CveInfoID  uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	AdvisoryID string
 | 
			
		||||
	Severity   string
 | 
			
		||||
	Issued     time.Time
 | 
			
		||||
	Updated    time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Container has Container information
 | 
			
		||||
type Container struct {
 | 
			
		||||
	gorm.Model   `json:"-"`
 | 
			
		||||
	ScanResultID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	ContainerID string
 | 
			
		||||
	Name        string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Platform has platform information
 | 
			
		||||
type Platform struct {
 | 
			
		||||
	gorm.Model   `json:"-"`
 | 
			
		||||
	ScanResultID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	Name       string // aws or azure or gcp or other...
 | 
			
		||||
	InstanceID string
 | 
			
		||||
}
 | 
			
		||||
// JSONVersion is JSON Version
 | 
			
		||||
const JSONVersion = 3
 | 
			
		||||
 
 | 
			
		||||
@@ -16,39 +16,3 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestPackageInfosUniqByName(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in  PackageInfoList
 | 
			
		||||
		out PackageInfoList
 | 
			
		||||
	}{
 | 
			
		||||
		PackageInfoList{
 | 
			
		||||
			{
 | 
			
		||||
				Name: "hoge",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name: "fuga",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name: "hoge",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		PackageInfoList{
 | 
			
		||||
			{
 | 
			
		||||
				Name: "hoge",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name: "fuga",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	actual := test.in.UniqByName()
 | 
			
		||||
	for i, ePack := range test.out {
 | 
			
		||||
		if actual[i].Name == ePack.Name {
 | 
			
		||||
			t.Errorf("expected %#v, actual %#v", ePack.Name, actual[i].Name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										183
									
								
								models/packages.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,183 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Packages is Map of Package
 | 
			
		||||
// { "package-name": Package }
 | 
			
		||||
type Packages map[string]Package
 | 
			
		||||
 | 
			
		||||
// NewPackages create Packages
 | 
			
		||||
func NewPackages(packs ...Package) Packages {
 | 
			
		||||
	m := Packages{}
 | 
			
		||||
	for _, pack := range packs {
 | 
			
		||||
		m[pack.Name] = pack
 | 
			
		||||
	}
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MergeNewVersion merges candidate version information to the receiver struct
 | 
			
		||||
func (ps Packages) MergeNewVersion(as Packages) {
 | 
			
		||||
	for _, a := range as {
 | 
			
		||||
		if pack, ok := ps[a.Name]; ok {
 | 
			
		||||
			pack.NewVersion = a.NewVersion
 | 
			
		||||
			pack.NewRelease = a.NewRelease
 | 
			
		||||
			pack.Repository = a.Repository
 | 
			
		||||
			ps[a.Name] = pack
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Merge returns merged map (immutable)
 | 
			
		||||
func (ps Packages) Merge(other Packages) Packages {
 | 
			
		||||
	merged := Packages{}
 | 
			
		||||
	for k, v := range ps {
 | 
			
		||||
		merged[k] = v
 | 
			
		||||
	}
 | 
			
		||||
	for k, v := range other {
 | 
			
		||||
		merged[k] = v
 | 
			
		||||
	}
 | 
			
		||||
	return merged
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatUpdatablePacksSummary returns a summary of updatable packages
 | 
			
		||||
func (ps Packages) FormatUpdatablePacksSummary() string {
 | 
			
		||||
	nUpdatable := 0
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if p.NewVersion != "" {
 | 
			
		||||
			nUpdatable++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%d updatable packages", nUpdatable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindOne search a element by name-newver-newrel-arch
 | 
			
		||||
func (ps Packages) FindOne(f func(Package) bool) (string, Package, bool) {
 | 
			
		||||
	for key, p := range ps {
 | 
			
		||||
		if f(p) {
 | 
			
		||||
			return key, p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return "", Package{}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Package has installed binary packages.
 | 
			
		||||
type Package struct {
 | 
			
		||||
	Name       string
 | 
			
		||||
	Version    string
 | 
			
		||||
	Release    string
 | 
			
		||||
	NewVersion string
 | 
			
		||||
	NewRelease string
 | 
			
		||||
	Arch       string
 | 
			
		||||
	Repository string
 | 
			
		||||
	Changelog  Changelog
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatVer returns package version-release
 | 
			
		||||
func (p Package) FormatVer() string {
 | 
			
		||||
	ver := p.Version
 | 
			
		||||
	if 0 < len(p.Release) {
 | 
			
		||||
		ver = fmt.Sprintf("%s-%s", ver, p.Release)
 | 
			
		||||
	}
 | 
			
		||||
	return ver
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatNewVer returns package version-release
 | 
			
		||||
func (p Package) FormatNewVer() string {
 | 
			
		||||
	ver := p.NewVersion
 | 
			
		||||
	if 0 < len(p.NewRelease) {
 | 
			
		||||
		ver = fmt.Sprintf("%s-%s", ver, p.NewRelease)
 | 
			
		||||
	}
 | 
			
		||||
	return ver
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatVersionFromTo formats installed and new package version
 | 
			
		||||
func (p Package) FormatVersionFromTo(notFixedYet bool) string {
 | 
			
		||||
	to := p.FormatNewVer()
 | 
			
		||||
	if notFixedYet {
 | 
			
		||||
		to = "Not Fixed Yet"
 | 
			
		||||
	} else if p.NewVersion == "" {
 | 
			
		||||
		to = "Unknown"
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s-%s -> %s", p.Name, p.FormatVer(), to)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatChangelog formats the changelog
 | 
			
		||||
func (p Package) FormatChangelog() string {
 | 
			
		||||
	buf := []string{}
 | 
			
		||||
	packVer := fmt.Sprintf("%s-%s -> %s",
 | 
			
		||||
		p.Name, p.FormatVer(), p.FormatNewVer())
 | 
			
		||||
	var delim bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(packVer); i++ {
 | 
			
		||||
		delim.WriteString("-")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clog := p.Changelog.Contents
 | 
			
		||||
	if lines := strings.Split(clog, "\n"); len(lines) != 0 {
 | 
			
		||||
		clog = strings.Join(lines[0:len(lines)-1], "\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch p.Changelog.Method {
 | 
			
		||||
	case FailedToGetChangelog:
 | 
			
		||||
		clog = "No changelogs"
 | 
			
		||||
	case FailedToFindVersionInChangelog:
 | 
			
		||||
		clog = "Failed to parse changelogs. For detials, check yourself"
 | 
			
		||||
	}
 | 
			
		||||
	buf = append(buf, packVer, delim.String(), clog)
 | 
			
		||||
	return strings.Join(buf, "\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Changelog has contents of changelog and how to get it.
 | 
			
		||||
// Method: models.detectionMethodStr
 | 
			
		||||
type Changelog struct {
 | 
			
		||||
	Contents string
 | 
			
		||||
	Method   DetectionMethod
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SrcPackage has installed source package information.
 | 
			
		||||
// Debian based Linux has both of package and source information in dpkg.
 | 
			
		||||
// OVAL database often includes a source version (Not a binary version),
 | 
			
		||||
// so it is also needed to capture source version for OVAL version comparison.
 | 
			
		||||
// https://github.com/future-architect/vuls/issues/504
 | 
			
		||||
type SrcPackage struct {
 | 
			
		||||
	Name        string
 | 
			
		||||
	Version     string
 | 
			
		||||
	BinaryNames []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddBinaryName add the name if not exists
 | 
			
		||||
func (s *SrcPackage) AddBinaryName(name string) {
 | 
			
		||||
	found := false
 | 
			
		||||
	for _, n := range s.BinaryNames {
 | 
			
		||||
		if n == name {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		s.BinaryNames = append(s.BinaryNames, name)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SrcPackages is Map of SrcPackage
 | 
			
		||||
// { "package-name": SrcPackage }
 | 
			
		||||
type SrcPackages map[string]SrcPackage
 | 
			
		||||
							
								
								
									
										135
									
								
								models/packages_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,135 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestMergeNewVersion(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		a        Packages
 | 
			
		||||
		b        Packages
 | 
			
		||||
		expected Packages
 | 
			
		||||
	}{
 | 
			
		||||
		Packages{
 | 
			
		||||
			"hoge": {
 | 
			
		||||
				Name: "hoge",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Packages{
 | 
			
		||||
			"hoge": {
 | 
			
		||||
				Name:       "hoge",
 | 
			
		||||
				NewVersion: "1.0.0",
 | 
			
		||||
				NewRelease: "release1",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Packages{
 | 
			
		||||
			"hoge": {
 | 
			
		||||
				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 TestMerge(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		a        Packages
 | 
			
		||||
		b        Packages
 | 
			
		||||
		expected Packages
 | 
			
		||||
	}{
 | 
			
		||||
		Packages{
 | 
			
		||||
			"hoge": {Name: "hoge"},
 | 
			
		||||
			"fuga": {Name: "fuga"},
 | 
			
		||||
		},
 | 
			
		||||
		Packages{
 | 
			
		||||
			"hega": {Name: "hega"},
 | 
			
		||||
			"hage": {Name: "hage"},
 | 
			
		||||
		},
 | 
			
		||||
		Packages{
 | 
			
		||||
			"hoge": {Name: "hoge"},
 | 
			
		||||
			"fuga": {Name: "fuga"},
 | 
			
		||||
			"hega": {Name: "hega"},
 | 
			
		||||
			"hage": {Name: "hage"},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	actual := test.a.Merge(test.b)
 | 
			
		||||
	if !reflect.DeepEqual(actual, test.expected) {
 | 
			
		||||
		e := pp.Sprintf("%v", test.expected)
 | 
			
		||||
		a := pp.Sprintf("%v", actual)
 | 
			
		||||
		t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddBinaryName(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       SrcPackage
 | 
			
		||||
		name     string
 | 
			
		||||
		expected SrcPackage
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			SrcPackage{Name: "hoge"},
 | 
			
		||||
			"curl",
 | 
			
		||||
			SrcPackage{
 | 
			
		||||
				Name:        "hoge",
 | 
			
		||||
				BinaryNames: []string{"curl"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			SrcPackage{
 | 
			
		||||
				Name:        "hoge",
 | 
			
		||||
				BinaryNames: []string{"curl"},
 | 
			
		||||
			},
 | 
			
		||||
			"curl",
 | 
			
		||||
			SrcPackage{
 | 
			
		||||
				Name:        "hoge",
 | 
			
		||||
				BinaryNames: []string{"curl"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			SrcPackage{
 | 
			
		||||
				Name:        "hoge",
 | 
			
		||||
				BinaryNames: []string{"curl"},
 | 
			
		||||
			},
 | 
			
		||||
			"openssh",
 | 
			
		||||
			SrcPackage{
 | 
			
		||||
				Name:        "hoge",
 | 
			
		||||
				BinaryNames: []string{"curl", "openssh"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		tt.in.AddBinaryName(tt.name)
 | 
			
		||||
		if !reflect.DeepEqual(tt.in, tt.expected) {
 | 
			
		||||
			t.Errorf("expected %#v, actual %#v", tt.in, tt.expected)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										206
									
								
								models/scanresults.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,206 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanResults is a slide of ScanResult
 | 
			
		||||
type ScanResults []ScanResult
 | 
			
		||||
 | 
			
		||||
// ScanResult has the result of scanned CVE information.
 | 
			
		||||
type ScanResult struct {
 | 
			
		||||
	ScannedAt   time.Time
 | 
			
		||||
	ReportedAt  time.Time
 | 
			
		||||
	JSONVersion int
 | 
			
		||||
	Lang        string
 | 
			
		||||
	ServerUUID  string
 | 
			
		||||
	ServerName  string // TOML Section key
 | 
			
		||||
	Family      string
 | 
			
		||||
	Release     string
 | 
			
		||||
	Container   Container
 | 
			
		||||
	Platform    Platform
 | 
			
		||||
 | 
			
		||||
	// Scanned Vulns by SSH scan + CPE + OVAL
 | 
			
		||||
	ScannedCves VulnInfos
 | 
			
		||||
 | 
			
		||||
	RunningKernel Kernel
 | 
			
		||||
	Packages      Packages
 | 
			
		||||
	SrcPackages   SrcPackages
 | 
			
		||||
 | 
			
		||||
	Errors   []string
 | 
			
		||||
	Optional [][]interface{}
 | 
			
		||||
 | 
			
		||||
	Config struct {
 | 
			
		||||
		Scan   config.Config
 | 
			
		||||
		Report config.Config
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Kernel has the Release, version and whether need restart
 | 
			
		||||
type Kernel struct {
 | 
			
		||||
	Release        string
 | 
			
		||||
	Version        string
 | 
			
		||||
	RebootRequired bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver is filter function.
 | 
			
		||||
func (r ScanResult) FilterByCvssOver(over float64) ScanResult {
 | 
			
		||||
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
			
		||||
		v2Max := v.MaxCvss2Score()
 | 
			
		||||
		v3Max := v.MaxCvss3Score()
 | 
			
		||||
		max := v2Max.Value.Score
 | 
			
		||||
		if max < v3Max.Value.Score {
 | 
			
		||||
			max = v3Max.Value.Score
 | 
			
		||||
		}
 | 
			
		||||
		if over <= max {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
	r.ScannedCves = filtered
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterIgnoreCves is filter function.
 | 
			
		||||
func (r ScanResult) FilterIgnoreCves(cveIDs []string) ScanResult {
 | 
			
		||||
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
			
		||||
		for _, c := range cveIDs {
 | 
			
		||||
			if v.CveID == c {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	})
 | 
			
		||||
	r.ScannedCves = filtered
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterUnfixed is filter function.
 | 
			
		||||
func (r ScanResult) FilterUnfixed() ScanResult {
 | 
			
		||||
	if !config.Conf.IgnoreUnfixed {
 | 
			
		||||
		return r
 | 
			
		||||
	}
 | 
			
		||||
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
			
		||||
		NotFixedAll := true
 | 
			
		||||
		for _, p := range v.AffectedPackages {
 | 
			
		||||
			NotFixedAll = NotFixedAll && p.NotFixedYet
 | 
			
		||||
		}
 | 
			
		||||
		return !NotFixedAll
 | 
			
		||||
	})
 | 
			
		||||
	r.ScannedCves = filtered
 | 
			
		||||
	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 {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s (%s%s)",
 | 
			
		||||
			r.FormatServerName(), r.Family, r.Release)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf(
 | 
			
		||||
		"%s (%s%s) on %s",
 | 
			
		||||
		r.FormatServerName(),
 | 
			
		||||
		r.Family,
 | 
			
		||||
		r.Release,
 | 
			
		||||
		r.ServerName,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfoTui returns server infromation for TUI sidebar
 | 
			
		||||
func (r ScanResult) ServerInfoTui() string {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		line := fmt.Sprintf("%s (%s%s)",
 | 
			
		||||
			r.ServerName, r.Family, r.Release)
 | 
			
		||||
		if r.RunningKernel.RebootRequired {
 | 
			
		||||
			return "[Reboot] " + line
 | 
			
		||||
		}
 | 
			
		||||
		return line
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmtstr := "|-- %s (%s%s)"
 | 
			
		||||
	if r.RunningKernel.RebootRequired {
 | 
			
		||||
		fmtstr = "|-- [Reboot] %s (%s%s)"
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf(fmtstr, r.Container.Name, r.Family, r.Release)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatServerName returns server and container name
 | 
			
		||||
func (r ScanResult) FormatServerName() (name string) {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		name = r.ServerName
 | 
			
		||||
	} else {
 | 
			
		||||
		name = fmt.Sprintf("%s@%s",
 | 
			
		||||
			r.Container.Name, r.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	if r.RunningKernel.RebootRequired {
 | 
			
		||||
		name = "[Reboot Required] " + name
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatTextReportHeadedr returns header of text report
 | 
			
		||||
func (r ScanResult) FormatTextReportHeadedr() string {
 | 
			
		||||
	serverInfo := r.ServerInfo()
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(serverInfo); i++ {
 | 
			
		||||
		buf.WriteString("=")
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n%s\t%s\n",
 | 
			
		||||
		r.ServerInfo(),
 | 
			
		||||
		buf.String(),
 | 
			
		||||
		r.ScannedCves.FormatCveSummary(),
 | 
			
		||||
		r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Container has Container information
 | 
			
		||||
type Container struct {
 | 
			
		||||
	ContainerID string
 | 
			
		||||
	Name        string
 | 
			
		||||
	Image       string
 | 
			
		||||
	Type        string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Platform has platform information
 | 
			
		||||
type Platform struct {
 | 
			
		||||
	Name       string // aws or azure or gcp or other...
 | 
			
		||||
	InstanceID string
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										338
									
								
								models/scanresults_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,338 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		over float64
 | 
			
		||||
		rs   ScanResult
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				over: 7.0,
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0001",
 | 
			
		||||
									Cvss2Score:   7.1,
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0002": {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									Cvss2Score:   6.9,
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0003": {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0003",
 | 
			
		||||
									Cvss2Score:   6.9,
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         JVN,
 | 
			
		||||
									CveID:        "CVE-2017-0003",
 | 
			
		||||
									Cvss2Score:   7.2,
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0001": {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         NVD,
 | 
			
		||||
								CveID:        "CVE-2017-0001",
 | 
			
		||||
								Cvss2Score:   7.1,
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
						),
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0003": {
 | 
			
		||||
						CveID: "CVE-2017-0003",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         NVD,
 | 
			
		||||
								CveID:        "CVE-2017-0003",
 | 
			
		||||
								Cvss2Score:   6.9,
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         JVN,
 | 
			
		||||
								CveID:        "CVE-2017-0003",
 | 
			
		||||
								Cvss2Score:   7.2,
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
						),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// OVAL Severity
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				over: 7.0,
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         Ubuntu,
 | 
			
		||||
									CveID:        "CVE-2017-0001",
 | 
			
		||||
									Severity:     "HIGH",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0002": {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         RedHat,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									Severity:     "CRITICAL",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0003": {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         Oracle,
 | 
			
		||||
									CveID:        "CVE-2017-0003",
 | 
			
		||||
									Severity:     "IMPORTANT",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0001": {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         Ubuntu,
 | 
			
		||||
								CveID:        "CVE-2017-0001",
 | 
			
		||||
								Severity:     "HIGH",
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
						),
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0002": {
 | 
			
		||||
						CveID: "CVE-2017-0002",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         RedHat,
 | 
			
		||||
								CveID:        "CVE-2017-0002",
 | 
			
		||||
								Severity:     "CRITICAL",
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
						),
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0003": {
 | 
			
		||||
						CveID: "CVE-2017-0003",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         Oracle,
 | 
			
		||||
								CveID:        "CVE-2017-0003",
 | 
			
		||||
								Severity:     "IMPORTANT",
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
						),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.rs.FilterByCvssOver(tt.in.over)
 | 
			
		||||
		for k := range tt.out.ScannedCves {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
 | 
			
		||||
				o := pp.Sprintf("%v", tt.out.ScannedCves[k])
 | 
			
		||||
				a := pp.Sprintf("%v", actual.ScannedCves[k])
 | 
			
		||||
				t.Errorf("[%s] expected: %v\n  actual: %v\n", k, o, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFilterIgnoreCveIDs(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		cves []string
 | 
			
		||||
		rs   ScanResult
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				cves: []string{"CVE-2017-0002"},
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0002": {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0003": {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0001": {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0003": {
 | 
			
		||||
						CveID: "CVE-2017-0003",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.rs.FilterIgnoreCves(tt.in.cves)
 | 
			
		||||
		for k := range tt.out.ScannedCves {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
 | 
			
		||||
				o := pp.Sprintf("%v", tt.out.ScannedCves[k])
 | 
			
		||||
				a := pp.Sprintf("%v", actual.ScannedCves[k])
 | 
			
		||||
				t.Errorf("[%s] expected: %v\n  actual: %v\n", k, o, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFilterUnfixed(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  ScanResult
 | 
			
		||||
		out ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: ScanResult{
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0001": {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
						AffectedPackages: PackageStatuses{
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "a",
 | 
			
		||||
								NotFixedYet: true,
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0002": {
 | 
			
		||||
						CveID: "CVE-2017-0002",
 | 
			
		||||
						AffectedPackages: PackageStatuses{
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "b",
 | 
			
		||||
								NotFixedYet: false,
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0003": {
 | 
			
		||||
						CveID: "CVE-2017-0003",
 | 
			
		||||
						AffectedPackages: PackageStatuses{
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "c",
 | 
			
		||||
								NotFixedYet: true,
 | 
			
		||||
							},
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "d",
 | 
			
		||||
								NotFixedYet: false,
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0002": {
 | 
			
		||||
						CveID: "CVE-2017-0002",
 | 
			
		||||
						AffectedPackages: PackageStatuses{
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "b",
 | 
			
		||||
								NotFixedYet: false,
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0003": {
 | 
			
		||||
						CveID: "CVE-2017-0003",
 | 
			
		||||
						AffectedPackages: PackageStatuses{
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "c",
 | 
			
		||||
								NotFixedYet: true,
 | 
			
		||||
							},
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "d",
 | 
			
		||||
								NotFixedYet: false,
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		config.Conf.IgnoreUnfixed = true
 | 
			
		||||
		actual := tt.in.FilterUnfixed()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out.ScannedCves, actual.ScannedCves) {
 | 
			
		||||
			o := pp.Sprintf("%v", tt.out.ScannedCves)
 | 
			
		||||
			a := pp.Sprintf("%v", actual.ScannedCves)
 | 
			
		||||
			t.Errorf("[%d] expected: %v\n  actual: %v\n", i, o, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								models/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,114 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	cvedict "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ConvertNvdToModel convert NVD to CveContent
 | 
			
		||||
func ConvertNvdToModel(cveID string, nvd cvedict.Nvd) *CveContent {
 | 
			
		||||
	var cpes []Cpe
 | 
			
		||||
	for _, c := range nvd.Cpes {
 | 
			
		||||
		cpes = append(cpes, Cpe{CpeName: c.CpeName})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var refs []Reference
 | 
			
		||||
	for _, r := range nvd.References {
 | 
			
		||||
		refs = append(refs, Reference{
 | 
			
		||||
			Link:   r.Link,
 | 
			
		||||
			Source: r.Source,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	validVec := true
 | 
			
		||||
	for _, v := range []string{
 | 
			
		||||
		nvd.AccessVector,
 | 
			
		||||
		nvd.AccessComplexity,
 | 
			
		||||
		nvd.Authentication,
 | 
			
		||||
		nvd.ConfidentialityImpact,
 | 
			
		||||
		nvd.IntegrityImpact,
 | 
			
		||||
		nvd.AvailabilityImpact,
 | 
			
		||||
	} {
 | 
			
		||||
		if len(v) == 0 {
 | 
			
		||||
			validVec = false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vector := ""
 | 
			
		||||
	if validVec {
 | 
			
		||||
		vector = fmt.Sprintf("AV:%s/AC:%s/Au:%s/C:%s/I:%s/A:%s",
 | 
			
		||||
			string(nvd.AccessVector[0]),
 | 
			
		||||
			string(nvd.AccessComplexity[0]),
 | 
			
		||||
			string(nvd.Authentication[0]),
 | 
			
		||||
			string(nvd.ConfidentialityImpact[0]),
 | 
			
		||||
			string(nvd.IntegrityImpact[0]),
 | 
			
		||||
			string(nvd.AvailabilityImpact[0]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//TODO CVSSv3
 | 
			
		||||
	return &CveContent{
 | 
			
		||||
		Type:         NVD,
 | 
			
		||||
		CveID:        cveID,
 | 
			
		||||
		Summary:      nvd.Summary,
 | 
			
		||||
		Cvss2Score:   nvd.Score,
 | 
			
		||||
		Cvss2Vector:  vector,
 | 
			
		||||
		Severity:     "", // severity is not contained in NVD
 | 
			
		||||
		SourceLink:   "https://nvd.nist.gov/vuln/detail/" + cveID,
 | 
			
		||||
		Cpes:         cpes,
 | 
			
		||||
		CweID:        nvd.CweID,
 | 
			
		||||
		References:   refs,
 | 
			
		||||
		Published:    nvd.PublishedDate,
 | 
			
		||||
		LastModified: nvd.LastModifiedDate,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertJvnToModel convert JVN to CveContent
 | 
			
		||||
func ConvertJvnToModel(cveID string, jvn cvedict.Jvn) *CveContent {
 | 
			
		||||
	var cpes []Cpe
 | 
			
		||||
	for _, c := range jvn.Cpes {
 | 
			
		||||
		cpes = append(cpes, Cpe{CpeName: c.CpeName})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	refs := []Reference{}
 | 
			
		||||
	for _, r := range jvn.References {
 | 
			
		||||
		refs = append(refs, Reference{
 | 
			
		||||
			Link:   r.Link,
 | 
			
		||||
			Source: r.Source,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vector := strings.TrimSuffix(strings.TrimPrefix(jvn.Vector, "("), ")")
 | 
			
		||||
	return &CveContent{
 | 
			
		||||
		Type:         JVN,
 | 
			
		||||
		CveID:        cveID,
 | 
			
		||||
		Title:        jvn.Title,
 | 
			
		||||
		Summary:      jvn.Summary,
 | 
			
		||||
		Severity:     jvn.Severity,
 | 
			
		||||
		Cvss2Score:   jvn.Score,
 | 
			
		||||
		Cvss2Vector:  vector,
 | 
			
		||||
		SourceLink:   jvn.JvnLink,
 | 
			
		||||
		Cpes:         cpes,
 | 
			
		||||
		References:   refs,
 | 
			
		||||
		Published:    jvn.PublishedDate,
 | 
			
		||||
		LastModified: jvn.LastModifiedDate,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										668
									
								
								models/vulninfos.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,668 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// VulnInfos has a map of VulnInfo
 | 
			
		||||
// Key: CveID
 | 
			
		||||
type VulnInfos map[string]VulnInfo
 | 
			
		||||
 | 
			
		||||
// Find elements that matches the function passed in argument
 | 
			
		||||
func (v VulnInfos) Find(f func(VulnInfo) bool) VulnInfos {
 | 
			
		||||
	filtered := VulnInfos{}
 | 
			
		||||
	for _, vv := range v {
 | 
			
		||||
		if f(vv) {
 | 
			
		||||
			filtered[vv.CveID] = vv
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return filtered
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindScoredVulns return scored vulnerabilities
 | 
			
		||||
func (v VulnInfos) FindScoredVulns() VulnInfos {
 | 
			
		||||
	return v.Find(func(vv VulnInfo) bool {
 | 
			
		||||
		if 0 < vv.MaxCvss2Score().Value.Score ||
 | 
			
		||||
			0 < vv.MaxCvss3Score().Value.Score {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToSortedSlice returns slice of VulnInfos that is sorted by Score, CVE-ID
 | 
			
		||||
func (v VulnInfos) ToSortedSlice() (sorted []VulnInfo) {
 | 
			
		||||
	for k := range v {
 | 
			
		||||
		sorted = append(sorted, v[k])
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(sorted, func(i, j int) bool {
 | 
			
		||||
		maxI := sorted[i].MaxCvssScore()
 | 
			
		||||
		maxJ := sorted[j].MaxCvssScore()
 | 
			
		||||
		if maxI.Value.Score != maxJ.Value.Score {
 | 
			
		||||
			return maxJ.Value.Score < maxI.Value.Score
 | 
			
		||||
		}
 | 
			
		||||
		return sorted[i].CveID < sorted[j].CveID
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CountGroupBySeverity summarize the number of CVEs group by CVSSv2 Severity
 | 
			
		||||
func (v VulnInfos) CountGroupBySeverity() map[string]int {
 | 
			
		||||
	m := map[string]int{}
 | 
			
		||||
	for _, vInfo := range v {
 | 
			
		||||
		score := vInfo.MaxCvss2Score().Value.Score
 | 
			
		||||
		if score < 0.1 {
 | 
			
		||||
			score = vInfo.MaxCvss3Score().Value.Score
 | 
			
		||||
		}
 | 
			
		||||
		switch {
 | 
			
		||||
		case 7.0 <= score:
 | 
			
		||||
			m["High"]++
 | 
			
		||||
		case 4.0 <= score:
 | 
			
		||||
			m["Medium"]++
 | 
			
		||||
		case 0 < score:
 | 
			
		||||
			m["Low"]++
 | 
			
		||||
		default:
 | 
			
		||||
			m["Unknown"]++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatCveSummary summarize the number of CVEs group by CVSSv2 Severity
 | 
			
		||||
func (v VulnInfos) FormatCveSummary() string {
 | 
			
		||||
	m := v.CountGroupBySeverity()
 | 
			
		||||
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)",
 | 
			
		||||
			m["High"]+m["Medium"]+m["Low"], m["High"], m["Medium"], m["Low"])
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)",
 | 
			
		||||
		m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
 | 
			
		||||
		m["High"], m["Medium"], m["Low"], m["Unknown"])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageStatuses is a list of PackageStatus
 | 
			
		||||
type PackageStatuses []PackageStatus
 | 
			
		||||
 | 
			
		||||
// Sort by Name
 | 
			
		||||
func (p PackageStatuses) Sort() {
 | 
			
		||||
	sort.Slice(p, func(i, j int) bool {
 | 
			
		||||
		return p[i].Name < p[j].Name
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageStatus has name and other status abount the package
 | 
			
		||||
type PackageStatus struct {
 | 
			
		||||
	Name        string
 | 
			
		||||
	NotFixedYet bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VulnInfo has a vulnerability information and unsecure packages
 | 
			
		||||
type VulnInfo struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	Confidence       Confidence
 | 
			
		||||
	AffectedPackages PackageStatuses
 | 
			
		||||
	DistroAdvisories []DistroAdvisory // for Aamazon, RHEL, FreeBSD
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
	CveContents      CveContents
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Titles returns tilte (TUI)
 | 
			
		||||
func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) {
 | 
			
		||||
	if lang == "ja" {
 | 
			
		||||
		if cont, found := v.CveContents[JVN]; found && 0 < len(cont.Title) {
 | 
			
		||||
			values = append(values, CveContentStr{JVN, cont.Title})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	order := CveContentTypes{NVD, NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order, JVN)...)...)
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		// Only JVN has meaningful title. so return first 100 char of summary
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Summary) {
 | 
			
		||||
			summary := strings.Replace(cont.Summary, "\n", " ", -1)
 | 
			
		||||
			values = append(values, CveContentStr{
 | 
			
		||||
				Type:  ctype,
 | 
			
		||||
				Value: summary,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, adv := range v.DistroAdvisories {
 | 
			
		||||
		values = append(values, CveContentStr{
 | 
			
		||||
			Type:  "Vendor",
 | 
			
		||||
			Value: strings.Replace(adv.Description, "\n", " ", -1),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(values) == 0 {
 | 
			
		||||
		values = []CveContentStr{{
 | 
			
		||||
			Type:  Unknown,
 | 
			
		||||
			Value: "-",
 | 
			
		||||
		}}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Summaries returns summaries
 | 
			
		||||
func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) {
 | 
			
		||||
	if lang == "ja" {
 | 
			
		||||
		if cont, found := v.CveContents[JVN]; found && 0 < len(cont.Summary) {
 | 
			
		||||
			summary := cont.Title
 | 
			
		||||
			summary += "\n" + strings.Replace(
 | 
			
		||||
				strings.Replace(cont.Summary, "\n", " ", -1), "\r", " ", -1)
 | 
			
		||||
			values = append(values, CveContentStr{JVN, summary})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	order := CveContentTypes{NVD, NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order, JVN)...)...)
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Summary) {
 | 
			
		||||
			summary := strings.Replace(cont.Summary, "\n", " ", -1)
 | 
			
		||||
			values = append(values, CveContentStr{
 | 
			
		||||
				Type:  ctype,
 | 
			
		||||
				Value: summary,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, adv := range v.DistroAdvisories {
 | 
			
		||||
		values = append(values, CveContentStr{
 | 
			
		||||
			Type:  "Vendor",
 | 
			
		||||
			Value: adv.Description,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(values) == 0 {
 | 
			
		||||
		return []CveContentStr{{
 | 
			
		||||
			Type:  Unknown,
 | 
			
		||||
			Value: "-",
 | 
			
		||||
		}}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cvss2Scores returns CVSS V2 Scores
 | 
			
		||||
func (v VulnInfo) Cvss2Scores() (values []CveContentCvss) {
 | 
			
		||||
	order := []CveContentType{NVD, RedHat, JVN}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < cont.Cvss2Score {
 | 
			
		||||
			// https://nvd.nist.gov/vuln-metrics/cvss
 | 
			
		||||
			sev := cont.Severity
 | 
			
		||||
			if ctype == NVD {
 | 
			
		||||
				sev = cvss2ScoreToSeverity(cont.Cvss2Score)
 | 
			
		||||
			}
 | 
			
		||||
			values = append(values, CveContentCvss{
 | 
			
		||||
				Type: ctype,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    cont.Cvss2Score,
 | 
			
		||||
					Vector:   cont.Cvss2Vector,
 | 
			
		||||
					Severity: strings.ToUpper(sev),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, adv := range v.DistroAdvisories {
 | 
			
		||||
		if adv.Severity != "" {
 | 
			
		||||
			values = append(values, CveContentCvss{
 | 
			
		||||
				Type: "Vendor",
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:                 CVSS2,
 | 
			
		||||
					Score:                severityToV2ScoreRoughly(adv.Severity),
 | 
			
		||||
					CalculatedBySeverity: true,
 | 
			
		||||
					Vector:               "-",
 | 
			
		||||
					Severity:             strings.ToUpper(adv.Severity),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cvss3Scores returns CVSS V3 Score
 | 
			
		||||
func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) {
 | 
			
		||||
	// TODO implement NVD
 | 
			
		||||
	order := []CveContentType{RedHat}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < cont.Cvss3Score {
 | 
			
		||||
			// https://nvd.nist.gov/vuln-metrics/cvss
 | 
			
		||||
			sev := cont.Severity
 | 
			
		||||
			values = append(values, CveContentCvss{
 | 
			
		||||
				Type: ctype,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS3,
 | 
			
		||||
					Score:    cont.Cvss3Score,
 | 
			
		||||
					Vector:   cont.Cvss3Vector,
 | 
			
		||||
					Severity: strings.ToUpper(sev),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MaxCvss3Score returns Max CVSS V3 Score
 | 
			
		||||
func (v VulnInfo) MaxCvss3Score() CveContentCvss {
 | 
			
		||||
	// TODO implement NVD
 | 
			
		||||
	order := []CveContentType{RedHat}
 | 
			
		||||
	max := 0.0
 | 
			
		||||
	value := CveContentCvss{
 | 
			
		||||
		Type:  Unknown,
 | 
			
		||||
		Value: Cvss{Type: CVSS3},
 | 
			
		||||
	}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && max < cont.Cvss3Score {
 | 
			
		||||
			// https://nvd.nist.gov/vuln-metrics/cvss
 | 
			
		||||
			sev := cont.Severity
 | 
			
		||||
			value = CveContentCvss{
 | 
			
		||||
				Type: ctype,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS3,
 | 
			
		||||
					Score:    cont.Cvss3Score,
 | 
			
		||||
					Vector:   cont.Cvss3Vector,
 | 
			
		||||
					Severity: sev,
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
			max = cont.Cvss3Score
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MaxCvssScore returns max CVSS Score
 | 
			
		||||
// If there is no CVSS Score, return Severity as a numerical value.
 | 
			
		||||
func (v VulnInfo) MaxCvssScore() CveContentCvss {
 | 
			
		||||
	v3Max := v.MaxCvss3Score()
 | 
			
		||||
	v2Max := v.MaxCvss2Score()
 | 
			
		||||
	max := v3Max
 | 
			
		||||
	if max.Type == Unknown {
 | 
			
		||||
		return v2Max
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if max.Value.Score < v2Max.Value.Score && !v2Max.Value.CalculatedBySeverity {
 | 
			
		||||
		max = v2Max
 | 
			
		||||
	}
 | 
			
		||||
	return max
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MaxCvss2Score returns Max CVSS V2 Score
 | 
			
		||||
func (v VulnInfo) MaxCvss2Score() CveContentCvss {
 | 
			
		||||
	order := []CveContentType{NVD, RedHat, JVN}
 | 
			
		||||
	max := 0.0
 | 
			
		||||
	value := CveContentCvss{
 | 
			
		||||
		Type:  Unknown,
 | 
			
		||||
		Value: Cvss{Type: CVSS2},
 | 
			
		||||
	}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && max < cont.Cvss2Score {
 | 
			
		||||
			// https://nvd.nist.gov/vuln-metrics/cvss
 | 
			
		||||
			sev := cont.Severity
 | 
			
		||||
			if ctype == NVD {
 | 
			
		||||
				sev = cvss2ScoreToSeverity(cont.Cvss2Score)
 | 
			
		||||
			}
 | 
			
		||||
			value = CveContentCvss{
 | 
			
		||||
				Type: ctype,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    cont.Cvss2Score,
 | 
			
		||||
					Vector:   cont.Cvss2Vector,
 | 
			
		||||
					Severity: sev,
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
			max = cont.Cvss2Score
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < max {
 | 
			
		||||
		return value
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If CVSS score isn't on NVD, RedHat and JVN, use OVAL and advisory Severity.
 | 
			
		||||
	// Convert severity to cvss srore roughly, then returns max severity.
 | 
			
		||||
	// Only Ubuntu, RedHat and Oracle have severity data in OVAL.
 | 
			
		||||
	order = []CveContentType{Ubuntu, RedHat, Oracle}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Severity) {
 | 
			
		||||
			score := severityToV2ScoreRoughly(cont.Severity)
 | 
			
		||||
			if max < score {
 | 
			
		||||
				value = CveContentCvss{
 | 
			
		||||
					Type: ctype,
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:                 CVSS2,
 | 
			
		||||
						Score:                score,
 | 
			
		||||
						CalculatedBySeverity: true,
 | 
			
		||||
						Vector:               cont.Cvss2Vector,
 | 
			
		||||
						Severity:             cont.Severity,
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			max = score
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Only RedHat, Oracle and Amazon has severity data in advisory.
 | 
			
		||||
	for _, adv := range v.DistroAdvisories {
 | 
			
		||||
		if adv.Severity != "" {
 | 
			
		||||
			score := severityToV2ScoreRoughly(adv.Severity)
 | 
			
		||||
			if max < score {
 | 
			
		||||
				value = CveContentCvss{
 | 
			
		||||
					Type: "Vendor",
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:                 CVSS2,
 | 
			
		||||
						Score:                score,
 | 
			
		||||
						CalculatedBySeverity: true,
 | 
			
		||||
						Vector:               "-",
 | 
			
		||||
						Severity:             adv.Severity,
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContentCvss has CveContentType and Cvss2
 | 
			
		||||
type CveContentCvss struct {
 | 
			
		||||
	Type  CveContentType
 | 
			
		||||
	Value Cvss
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CvssType Represent the type of CVSS
 | 
			
		||||
type CvssType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// CVSS2 means CVSS vesion2
 | 
			
		||||
	CVSS2 CvssType = "2"
 | 
			
		||||
 | 
			
		||||
	// CVSS3 means CVSS vesion3
 | 
			
		||||
	CVSS3 CvssType = "3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Cvss has CVSS Score
 | 
			
		||||
type Cvss struct {
 | 
			
		||||
	Type                 CvssType
 | 
			
		||||
	Score                float64
 | 
			
		||||
	CalculatedBySeverity bool
 | 
			
		||||
	Vector               string
 | 
			
		||||
	Severity             string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Format CVSS Score and Vector
 | 
			
		||||
func (c Cvss) Format() string {
 | 
			
		||||
	switch c.Type {
 | 
			
		||||
	case CVSS2:
 | 
			
		||||
		return fmt.Sprintf("%3.1f/%s", c.Score, c.Vector)
 | 
			
		||||
	case CVSS3:
 | 
			
		||||
		return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector)
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cvss2ScoreToSeverity(score float64) string {
 | 
			
		||||
	if 7.0 <= score {
 | 
			
		||||
		return "HIGH"
 | 
			
		||||
	} else if 4.0 <= score {
 | 
			
		||||
		return "MEDIUM"
 | 
			
		||||
	}
 | 
			
		||||
	return "LOW"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Amazon Linux Security Advisory
 | 
			
		||||
// Critical, Important, Medium, Low
 | 
			
		||||
// https://alas.aws.amazon.com/
 | 
			
		||||
//
 | 
			
		||||
// RedHat, Oracle OVAL
 | 
			
		||||
// Critical, Important, Moderate, Low
 | 
			
		||||
// https://access.redhat.com/security/updates/classification
 | 
			
		||||
//
 | 
			
		||||
// Ubuntu OVAL
 | 
			
		||||
// Critical, High, Medium, Low
 | 
			
		||||
// https://wiki.ubuntu.com/Bugs/Importance
 | 
			
		||||
// https://people.canonical.com/~ubuntu-security/cve/priority.html
 | 
			
		||||
func severityToV2ScoreRoughly(severity string) float64 {
 | 
			
		||||
	switch strings.ToUpper(severity) {
 | 
			
		||||
	case "CRITICAL":
 | 
			
		||||
		return 10.0
 | 
			
		||||
	case "IMPORTANT", "HIGH":
 | 
			
		||||
		return 8.9
 | 
			
		||||
	case "MODERATE", "MEDIUM":
 | 
			
		||||
		return 6.9
 | 
			
		||||
	case "LOW":
 | 
			
		||||
		return 3.9
 | 
			
		||||
	}
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContentCvss3 has CveContentType and Cvss3
 | 
			
		||||
//  type CveContentCvss3 struct {
 | 
			
		||||
//      Type  CveContentType
 | 
			
		||||
//      Value Cvss3
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// Cvss3 has CVSS v3 Score, Vector and  Severity
 | 
			
		||||
//  type Cvss3 struct {
 | 
			
		||||
//      Score    float64
 | 
			
		||||
//      Vector   string
 | 
			
		||||
//      Severity string
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// Format CVSS Score and Vector
 | 
			
		||||
//  func (c Cvss3) Format() string {
 | 
			
		||||
//      return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector)
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
//  func cvss3ScoreToSeverity(score float64) string {
 | 
			
		||||
//      if 9.0 <= score {
 | 
			
		||||
//          return "CRITICAL"
 | 
			
		||||
//      } else if 7.0 <= score {
 | 
			
		||||
//          return "HIGH"
 | 
			
		||||
//      } else if 4.0 <= score {
 | 
			
		||||
//          return "MEDIUM"
 | 
			
		||||
//      }
 | 
			
		||||
//      return "LOW"
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// FormatMaxCvssScore returns Max CVSS Score
 | 
			
		||||
func (v VulnInfo) FormatMaxCvssScore() string {
 | 
			
		||||
	v2Max := v.MaxCvss2Score()
 | 
			
		||||
	v3Max := v.MaxCvss3Score()
 | 
			
		||||
	if v2Max.Value.Score <= v3Max.Value.Score {
 | 
			
		||||
		return fmt.Sprintf("%3.1f %s (%s)",
 | 
			
		||||
			v3Max.Value.Score,
 | 
			
		||||
			strings.ToUpper(v3Max.Value.Severity),
 | 
			
		||||
			v3Max.Type)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%3.1f %s (%s)",
 | 
			
		||||
		v2Max.Value.Score,
 | 
			
		||||
		strings.ToUpper(v2Max.Value.Severity),
 | 
			
		||||
		v2Max.Type)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cvss2CalcURL returns CVSS v2 caluclator's URL
 | 
			
		||||
func (v VulnInfo) Cvss2CalcURL() string {
 | 
			
		||||
	return "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=" + v.CveID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cvss3CalcURL returns CVSS v3 caluclator's URL
 | 
			
		||||
func (v VulnInfo) Cvss3CalcURL() string {
 | 
			
		||||
	return "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=" + v.CveID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VendorLinks returns links of vendor support's URL
 | 
			
		||||
func (v VulnInfo) VendorLinks(family string) map[string]string {
 | 
			
		||||
	links := map[string]string{}
 | 
			
		||||
	switch family {
 | 
			
		||||
	case config.RedHat, config.CentOS:
 | 
			
		||||
		links["RHEL-CVE"] = "https://access.redhat.com/security/cve/" + v.CveID
 | 
			
		||||
		for _, advisory := range v.DistroAdvisories {
 | 
			
		||||
			aidURL := strings.Replace(advisory.AdvisoryID, ":", "-", -1)
 | 
			
		||||
			links[advisory.AdvisoryID] = fmt.Sprintf("https://rhn.redhat.com/errata/%s.html", aidURL)
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case config.Oracle:
 | 
			
		||||
		links["Oracle-CVE"] = fmt.Sprintf("https://linux.oracle.com/cve/%s.html", v.CveID)
 | 
			
		||||
		for _, advisory := range v.DistroAdvisories {
 | 
			
		||||
			links[advisory.AdvisoryID] =
 | 
			
		||||
				fmt.Sprintf("https://linux.oracle.com/errata/%s.html", advisory.AdvisoryID)
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case config.Amazon:
 | 
			
		||||
		links["RHEL-CVE"] = "https://access.redhat.com/security/cve/" + v.CveID
 | 
			
		||||
		for _, advisory := range v.DistroAdvisories {
 | 
			
		||||
			links[advisory.AdvisoryID] =
 | 
			
		||||
				fmt.Sprintf("https://alas.aws.amazon.com/%s.html", advisory.AdvisoryID)
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case config.Ubuntu:
 | 
			
		||||
		links["Ubuntu-CVE"] = "http://people.ubuntu.com/~ubuntu-security/cve/" + v.CveID
 | 
			
		||||
		return links
 | 
			
		||||
	case config.Debian:
 | 
			
		||||
		links["Debian-CVE"] = "https://security-tracker.debian.org/tracker/" + v.CveID
 | 
			
		||||
	case config.SUSEEnterpriseServer:
 | 
			
		||||
		links["SUSE-CVE"] = "https://www.suse.com/security/cve/" + v.CveID
 | 
			
		||||
	case config.FreeBSD:
 | 
			
		||||
		for _, advisory := range v.DistroAdvisories {
 | 
			
		||||
			links["FreeBSD-VuXML"] = fmt.Sprintf("https://vuxml.freebsd.org/freebsd/%s.html", advisory.AdvisoryID)
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	}
 | 
			
		||||
	return links
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NilToEmpty set nil slice or map fields to empty to avoid null in JSON
 | 
			
		||||
func (v *VulnInfo) NilToEmpty() *VulnInfo {
 | 
			
		||||
	if v.CpeNames == nil {
 | 
			
		||||
		v.CpeNames = []string{}
 | 
			
		||||
	}
 | 
			
		||||
	if v.DistroAdvisories == nil {
 | 
			
		||||
		v.DistroAdvisories = []DistroAdvisory{}
 | 
			
		||||
	}
 | 
			
		||||
	if v.AffectedPackages == nil {
 | 
			
		||||
		v.AffectedPackages = PackageStatuses{}
 | 
			
		||||
	}
 | 
			
		||||
	if v.CveContents == nil {
 | 
			
		||||
		v.CveContents = NewCveContents()
 | 
			
		||||
	}
 | 
			
		||||
	for key := range v.CveContents {
 | 
			
		||||
		if v.CveContents[key].Cpes == nil {
 | 
			
		||||
			cont := v.CveContents[key]
 | 
			
		||||
			cont.Cpes = []Cpe{}
 | 
			
		||||
			v.CveContents[key] = cont
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
 | 
			
		||||
type DistroAdvisory struct {
 | 
			
		||||
	AdvisoryID  string
 | 
			
		||||
	Severity    string
 | 
			
		||||
	Issued      time.Time
 | 
			
		||||
	Updated     time.Time
 | 
			
		||||
	Description string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Format the distro advisory information
 | 
			
		||||
func (p DistroAdvisory) Format() string {
 | 
			
		||||
	if p.AdvisoryID == "" {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var delim bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(p.AdvisoryID); i++ {
 | 
			
		||||
		delim.WriteString("-")
 | 
			
		||||
	}
 | 
			
		||||
	buf := []string{p.AdvisoryID, delim.String(), p.Description}
 | 
			
		||||
	return strings.Join(buf, "\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Confidence is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
// Score: 0 - 100
 | 
			
		||||
type Confidence struct {
 | 
			
		||||
	Score           int
 | 
			
		||||
	DetectionMethod DetectionMethod
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Confidence) String() string {
 | 
			
		||||
	return fmt.Sprintf("%d / %s", c.Score, c.DetectionMethod)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectionMethod indicates
 | 
			
		||||
// - How to detect the CveID
 | 
			
		||||
// - How to get the changelog difference between installed and candidate version
 | 
			
		||||
type DetectionMethod string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// CpeNameMatchStr is a String representation of CpeNameMatch
 | 
			
		||||
	CpeNameMatchStr = "CpeNameMatch"
 | 
			
		||||
 | 
			
		||||
	// YumUpdateSecurityMatchStr is a String representation of YumUpdateSecurityMatch
 | 
			
		||||
	YumUpdateSecurityMatchStr = "YumUpdateSecurityMatch"
 | 
			
		||||
 | 
			
		||||
	// PkgAuditMatchStr is a String representation of PkgAuditMatch
 | 
			
		||||
	PkgAuditMatchStr = "PkgAuditMatch"
 | 
			
		||||
 | 
			
		||||
	// OvalMatchStr is a String representation of OvalMatch
 | 
			
		||||
	OvalMatchStr = "OvalMatch"
 | 
			
		||||
 | 
			
		||||
	// ChangelogExactMatchStr is a String representation of ChangelogExactMatch
 | 
			
		||||
	ChangelogExactMatchStr = "ChangelogExactMatch"
 | 
			
		||||
 | 
			
		||||
	// ChangelogLenientMatchStr is a String representation of ChangelogLenientMatch
 | 
			
		||||
	ChangelogLenientMatchStr = "ChangelogLenientMatch"
 | 
			
		||||
 | 
			
		||||
	// FailedToGetChangelog is a String representation of FailedToGetChangelog
 | 
			
		||||
	FailedToGetChangelog = "FailedToGetChangelog"
 | 
			
		||||
 | 
			
		||||
	// FailedToFindVersionInChangelog is a String representation of FailedToFindVersionInChangelog
 | 
			
		||||
	FailedToFindVersionInChangelog = "FailedToFindVersionInChangelog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// CpeNameMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	CpeNameMatch = Confidence{100, CpeNameMatchStr}
 | 
			
		||||
 | 
			
		||||
	// YumUpdateSecurityMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	YumUpdateSecurityMatch = Confidence{100, YumUpdateSecurityMatchStr}
 | 
			
		||||
 | 
			
		||||
	// PkgAuditMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	PkgAuditMatch = Confidence{100, PkgAuditMatchStr}
 | 
			
		||||
 | 
			
		||||
	// OvalMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	OvalMatch = Confidence{100, OvalMatchStr}
 | 
			
		||||
 | 
			
		||||
	// ChangelogExactMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr}
 | 
			
		||||
 | 
			
		||||
	// ChangelogLenientMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	ChangelogLenientMatch = Confidence{50, ChangelogLenientMatchStr}
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										936
									
								
								models/vulninfos_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,936 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestTitles(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		lang string
 | 
			
		||||
		cont VulnInfo
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out []CveContentStr
 | 
			
		||||
	}{
 | 
			
		||||
		// lang: ja
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang: "ja",
 | 
			
		||||
				cont: VulnInfo{
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:  JVN,
 | 
			
		||||
							Title: "Title1",
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:    RedHat,
 | 
			
		||||
							Summary: "Summary RedHat",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:    NVD,
 | 
			
		||||
							Summary: "Summary NVD",
 | 
			
		||||
							// Severity is NIOT included in NVD
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  JVN,
 | 
			
		||||
					Value: "Title1",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "Summary NVD",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  RedHat,
 | 
			
		||||
					Value: "Summary RedHat",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// lang: en
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang: "en",
 | 
			
		||||
				cont: VulnInfo{
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:  JVN,
 | 
			
		||||
							Title: "Title1",
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:    RedHat,
 | 
			
		||||
							Summary: "Summary RedHat",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:    NVD,
 | 
			
		||||
							Summary: "Summary NVD",
 | 
			
		||||
							// Severity is NIOT included in NVD
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "Summary NVD",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  RedHat,
 | 
			
		||||
					Value: "Summary RedHat",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// lang: empty
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang: "en",
 | 
			
		||||
				cont: VulnInfo{},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  Unknown,
 | 
			
		||||
					Value: "-",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.cont.Titles(tt.in.lang, "redhat")
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSummaries(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		lang string
 | 
			
		||||
		cont VulnInfo
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out []CveContentStr
 | 
			
		||||
	}{
 | 
			
		||||
		// lang: ja
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang: "ja",
 | 
			
		||||
				cont: VulnInfo{
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:    JVN,
 | 
			
		||||
							Title:   "Title JVN",
 | 
			
		||||
							Summary: "Summary JVN",
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:    RedHat,
 | 
			
		||||
							Summary: "Summary RedHat",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:    NVD,
 | 
			
		||||
							Summary: "Summary NVD",
 | 
			
		||||
							// Severity is NIOT included in NVD
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  JVN,
 | 
			
		||||
					Value: "Title JVN\nSummary JVN",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "Summary NVD",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  RedHat,
 | 
			
		||||
					Value: "Summary RedHat",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// lang: en
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang: "en",
 | 
			
		||||
				cont: VulnInfo{
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:    JVN,
 | 
			
		||||
							Title:   "Title JVN",
 | 
			
		||||
							Summary: "Summary JVN",
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:    RedHat,
 | 
			
		||||
							Summary: "Summary RedHat",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:    NVD,
 | 
			
		||||
							Summary: "Summary NVD",
 | 
			
		||||
							// Severity is NIOT included in NVD
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "Summary NVD",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  RedHat,
 | 
			
		||||
					Value: "Summary RedHat",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// lang: empty
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang: "en",
 | 
			
		||||
				cont: VulnInfo{},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  Unknown,
 | 
			
		||||
					Value: "-",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.cont.Summaries(tt.in.lang, "redhat")
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCountGroupBySeverity(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfos
 | 
			
		||||
		out map[string]int
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfos{
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss2Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0003": {
 | 
			
		||||
					CveID: "CVE-2017-0003",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 2.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0004": {
 | 
			
		||||
					CveID: "CVE-2017-0004",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 5.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0005": {
 | 
			
		||||
					CveID: "CVE-2017-0005",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: map[string]int{
 | 
			
		||||
				"High":    1,
 | 
			
		||||
				"Medium":  1,
 | 
			
		||||
				"Low":     1,
 | 
			
		||||
				"Unknown": 1,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.CountGroupBySeverity()
 | 
			
		||||
		for k := range tt.out {
 | 
			
		||||
			if tt.out[k] != actual[k] {
 | 
			
		||||
				t.Errorf("\nexpected %s: %d\n  actual %d\n",
 | 
			
		||||
					k, tt.out[k], actual[k])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestToSortedSlice(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfos
 | 
			
		||||
		out []VulnInfo
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfos{
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss3Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss3Score: 8.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []VulnInfo{
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss3Score: 8.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss3Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// When max scores are the same, sort by CVE-ID
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfos{
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss3Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss2Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []VulnInfo{
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss2Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss3Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// When there are no cvss scores, sort by severity
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfos{
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						Ubuntu: {
 | 
			
		||||
							Type:     Ubuntu,
 | 
			
		||||
							Severity: "High",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						Ubuntu: {
 | 
			
		||||
							Type:     Ubuntu,
 | 
			
		||||
							Severity: "Low",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []VulnInfo{
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						Ubuntu: {
 | 
			
		||||
							Type:     Ubuntu,
 | 
			
		||||
							Severity: "High",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						Ubuntu: {
 | 
			
		||||
							Type:     Ubuntu,
 | 
			
		||||
							Severity: "Low",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.ToSortedSlice()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCvss2Scores(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfo
 | 
			
		||||
		out []CveContentCvss
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:        JVN,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss2Score:  8.2,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:        RedHat,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss2Score:  8.0,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:        NVD,
 | 
			
		||||
						Cvss2Score:  8.1,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						// Severity is NIOT included in NVD
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentCvss{
 | 
			
		||||
				{
 | 
			
		||||
					Type: NVD,
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:     CVSS2,
 | 
			
		||||
						Score:    8.1,
 | 
			
		||||
						Vector:   "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type: RedHat,
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:     CVSS2,
 | 
			
		||||
						Score:    8.0,
 | 
			
		||||
						Vector:   "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type: JVN,
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:     CVSS2,
 | 
			
		||||
						Score:    8.2,
 | 
			
		||||
						Vector:   "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// Empty
 | 
			
		||||
		{
 | 
			
		||||
			in:  VulnInfo{},
 | 
			
		||||
			out: nil,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual := tt.in.Cvss2Scores()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("[%d] expected: %v\n  actual: %v\n", i, tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMaxCvss2Scores(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfo
 | 
			
		||||
		out CveContentCvss
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:        JVN,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss2Score:  8.2,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:        RedHat,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss2Score:  8.0,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:        NVD,
 | 
			
		||||
						Cvss2Score:  8.1,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						// Severity is NIOT included in NVD
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: JVN,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    8.2,
 | 
			
		||||
					Vector:   "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					Severity: "HIGH",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// Severity in OVAL
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					Ubuntu: {
 | 
			
		||||
						Type:     Ubuntu,
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: Ubuntu,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:                 CVSS2,
 | 
			
		||||
					Score:                8.9,
 | 
			
		||||
					CalculatedBySeverity: true,
 | 
			
		||||
					Severity:             "HIGH",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// Empty
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: Unknown,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    0.0,
 | 
			
		||||
					Vector:   "",
 | 
			
		||||
					Severity: "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual := tt.in.MaxCvss2Score()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("[%d] expected: %v\n  actual: %v\n", i, tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCvss3Scores(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfo
 | 
			
		||||
		out []CveContentCvss
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:        RedHat,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss3Score:  8.0,
 | 
			
		||||
						Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:        NVD,
 | 
			
		||||
						Cvss3Score:  8.1,
 | 
			
		||||
						Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
						// Severity is NIOT included in NVD
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentCvss{
 | 
			
		||||
				{
 | 
			
		||||
					Type: RedHat,
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:     CVSS3,
 | 
			
		||||
						Score:    8.0,
 | 
			
		||||
						Vector:   "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// Empty
 | 
			
		||||
		{
 | 
			
		||||
			in:  VulnInfo{},
 | 
			
		||||
			out: nil,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.Cvss3Scores()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMaxCvss3Scores(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfo
 | 
			
		||||
		out CveContentCvss
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:        RedHat,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss3Score:  8.0,
 | 
			
		||||
						Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: RedHat,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS3,
 | 
			
		||||
					Score:    8.0,
 | 
			
		||||
					Vector:   "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
					Severity: "HIGH",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// Empty
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: Unknown,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS3,
 | 
			
		||||
					Score:    0.0,
 | 
			
		||||
					Vector:   "",
 | 
			
		||||
					Severity: "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.MaxCvss3Score()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMaxCvssScores(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfo
 | 
			
		||||
		out CveContentCvss
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						Cvss3Score: 7.0,
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						Cvss2Score: 8.0,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: RedHat,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:  CVSS2,
 | 
			
		||||
					Score: 8.0,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						Cvss3Score: 8.0,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: RedHat,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:  CVSS3,
 | 
			
		||||
					Score: 8.0,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		//2
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					Ubuntu: {
 | 
			
		||||
						Type:     Ubuntu,
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: Ubuntu,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:                 CVSS2,
 | 
			
		||||
					Score:                8.9,
 | 
			
		||||
					CalculatedBySeverity: true,
 | 
			
		||||
					Severity:             "HIGH",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		//3
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					Ubuntu: {
 | 
			
		||||
						Type:     Ubuntu,
 | 
			
		||||
						Severity: "MEDIUM",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						Cvss2Score: 7.0,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: NVD,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    7.0,
 | 
			
		||||
					Severity: "HIGH",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		//4
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				DistroAdvisories: []DistroAdvisory{
 | 
			
		||||
					{
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: "Vendor",
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:                 CVSS2,
 | 
			
		||||
					Score:                8.9,
 | 
			
		||||
					CalculatedBySeverity: true,
 | 
			
		||||
					Vector:               "-",
 | 
			
		||||
					Severity:             "HIGH",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					Ubuntu: {
 | 
			
		||||
						Type:     Ubuntu,
 | 
			
		||||
						Severity: "MEDIUM",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						Cvss2Score: 4.0,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				DistroAdvisories: []DistroAdvisory{
 | 
			
		||||
					{
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: NVD,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    4,
 | 
			
		||||
					Severity: "MEDIUM",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// Empty
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: Unknown,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:  CVSS2,
 | 
			
		||||
					Score: 0,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual := tt.in.MaxCvssScore()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\n[%d] expected: %v\n  actual: %v\n", i, tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFormatMaxCvssScore(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfo
 | 
			
		||||
		out string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:       JVN,
 | 
			
		||||
						Severity:   "HIGH",
 | 
			
		||||
						Cvss2Score: 8.3,
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						Severity:   "HIGH",
 | 
			
		||||
						Cvss3Score: 8.0,
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						Cvss2Score: 8.1,
 | 
			
		||||
						// Severity is NIOT included in NVD
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: "8.3 HIGH (jvn)",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:       JVN,
 | 
			
		||||
						Severity:   "HIGH",
 | 
			
		||||
						Cvss2Score: 8.3,
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						Severity:   "HIGH",
 | 
			
		||||
						Cvss2Score: 8.0,
 | 
			
		||||
						Cvss3Score: 9.9,
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						Cvss2Score: 8.1,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: "9.9 HIGH (redhat)",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.FormatMaxCvssScore()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSortPackageStatues(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  PackageStatuses
 | 
			
		||||
		out PackageStatuses
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: PackageStatuses{
 | 
			
		||||
				{Name: "b"},
 | 
			
		||||
				{Name: "a"},
 | 
			
		||||
			},
 | 
			
		||||
			out: PackageStatuses{
 | 
			
		||||
				{Name: "a"},
 | 
			
		||||
				{Name: "b"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		tt.in.Sort()
 | 
			
		||||
		if !reflect.DeepEqual(tt.in, tt.out) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, tt.in)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										82
									
								
								oval/alpine.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,82 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Alpine is the struct of Alpine Linux
 | 
			
		||||
type Alpine struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAlpine creates OVAL client for SUSE
 | 
			
		||||
func NewAlpine() Alpine {
 | 
			
		||||
	return Alpine{
 | 
			
		||||
		Base{
 | 
			
		||||
			family: config.Alpine,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o Alpine) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if o.isFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
		o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o Alpine) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
	ovalContent := *o.convertToModel(&defPacks.def)
 | 
			
		||||
	cveID := defPacks.def.Advisory.Cves[0].CveID
 | 
			
		||||
	vinfo, ok := r.ScannedCves[cveID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		util.Log.Debugf("%s is newly detected by OVAL", cveID)
 | 
			
		||||
		vinfo = models.VulnInfo{
 | 
			
		||||
			CveID:       cveID,
 | 
			
		||||
			Confidence:  models.OvalMatch,
 | 
			
		||||
			CveContents: models.NewCveContents(ovalContent),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family)
 | 
			
		||||
	vinfo.AffectedPackages.Sort()
 | 
			
		||||
	r.ScannedCves[cveID] = vinfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o Alpine) convertToModel(def *ovalmodels.Definition) *models.CveContent {
 | 
			
		||||
	return &models.CveContent{
 | 
			
		||||
		CveID: def.Advisory.Cves[0].CveID,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										275
									
								
								oval/debian.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,275 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DebianBase is the base struct of Debian and Ubuntu
 | 
			
		||||
type DebianBase struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o DebianBase) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
	ovalContent := *o.convertToModel(&defPacks.def)
 | 
			
		||||
	ovalContent.Type = models.NewCveContentType(o.family)
 | 
			
		||||
	vinfo, ok := r.ScannedCves[defPacks.def.Debian.CveID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		util.Log.Debugf("%s is newly detected by OVAL", defPacks.def.Debian.CveID)
 | 
			
		||||
		vinfo = models.VulnInfo{
 | 
			
		||||
			CveID:       defPacks.def.Debian.CveID,
 | 
			
		||||
			Confidence:  models.OvalMatch,
 | 
			
		||||
			CveContents: models.NewCveContents(ovalContent),
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		cveContents := vinfo.CveContents
 | 
			
		||||
		ctype := models.NewCveContentType(o.family)
 | 
			
		||||
		if _, ok := vinfo.CveContents[ctype]; ok {
 | 
			
		||||
			util.Log.Debugf("%s OVAL will be overwritten",
 | 
			
		||||
				defPacks.def.Debian.CveID)
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Debugf("%s is also detected by OVAL",
 | 
			
		||||
				defPacks.def.Debian.CveID)
 | 
			
		||||
			cveContents = models.CveContents{}
 | 
			
		||||
		}
 | 
			
		||||
		if vinfo.Confidence.Score < models.OvalMatch.Score {
 | 
			
		||||
			vinfo.Confidence = models.OvalMatch
 | 
			
		||||
		}
 | 
			
		||||
		cveContents[ctype] = ovalContent
 | 
			
		||||
		vinfo.CveContents = cveContents
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
 | 
			
		||||
	for _, pack := range vinfo.AffectedPackages {
 | 
			
		||||
		notFixedYet, _ := defPacks.actuallyAffectedPackNames[pack.Name]
 | 
			
		||||
		defPacks.actuallyAffectedPackNames[pack.Name] = notFixedYet
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family)
 | 
			
		||||
	vinfo.AffectedPackages.Sort()
 | 
			
		||||
	r.ScannedCves[defPacks.def.Debian.CveID] = vinfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o DebianBase) convertToModel(def *ovalmodels.Definition) *models.CveContent {
 | 
			
		||||
	var refs []models.Reference
 | 
			
		||||
	for _, r := range def.References {
 | 
			
		||||
		refs = append(refs, models.Reference{
 | 
			
		||||
			Link:   r.RefURL,
 | 
			
		||||
			Source: r.Source,
 | 
			
		||||
			RefID:  r.RefID,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &models.CveContent{
 | 
			
		||||
		CveID:      def.Debian.CveID,
 | 
			
		||||
		Title:      def.Title,
 | 
			
		||||
		Summary:    def.Description,
 | 
			
		||||
		Severity:   def.Advisory.Severity,
 | 
			
		||||
		References: refs,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Debian is the interface for Debian OVAL
 | 
			
		||||
type Debian struct {
 | 
			
		||||
	DebianBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewDebian creates OVAL client for Debian
 | 
			
		||||
func NewDebian() Debian {
 | 
			
		||||
	return Debian{
 | 
			
		||||
		DebianBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.Debian,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o Debian) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
 | 
			
		||||
	//Debian's uname gives both of kernel release(uname -r), version(kernel-image version)
 | 
			
		||||
	linuxImage := "linux-image-" + r.RunningKernel.Release
 | 
			
		||||
 | 
			
		||||
	// Add linux and set the version of running kernel to search OVAL.
 | 
			
		||||
	newVer := ""
 | 
			
		||||
	if p, ok := r.Packages[linuxImage]; ok {
 | 
			
		||||
		newVer = p.NewVersion
 | 
			
		||||
	}
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		r.Packages["linux"] = models.Package{
 | 
			
		||||
			Name:       "linux",
 | 
			
		||||
			Version:    r.RunningKernel.Version,
 | 
			
		||||
			NewVersion: newVer,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if o.isFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	delete(r.Packages, "linux")
 | 
			
		||||
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
		// Remove "linux" added above for oval search
 | 
			
		||||
		// linux is not a real package name (key of affected packages in OVAL)
 | 
			
		||||
		if notFixedYet, ok := defPacks.actuallyAffectedPackNames["linux"]; ok {
 | 
			
		||||
			defPacks.actuallyAffectedPackNames[linuxImage] = notFixedYet
 | 
			
		||||
			delete(defPacks.actuallyAffectedPackNames, "linux")
 | 
			
		||||
			for i, p := range defPacks.def.AffectedPacks {
 | 
			
		||||
				if p.Name == "linux" {
 | 
			
		||||
					p.Name = linuxImage
 | 
			
		||||
					defPacks.def.AffectedPacks[i] = p
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, vuln := range r.ScannedCves {
 | 
			
		||||
		if cont, ok := vuln.CveContents[models.Debian]; ok {
 | 
			
		||||
			cont.SourceLink = "https://security-tracker.debian.org/tracker/" + cont.CveID
 | 
			
		||||
			vuln.CveContents[models.Debian] = cont
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ubuntu is the interface for Debian OVAL
 | 
			
		||||
type Ubuntu struct {
 | 
			
		||||
	DebianBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUbuntu creates OVAL client for Debian
 | 
			
		||||
func NewUbuntu() Ubuntu {
 | 
			
		||||
	return Ubuntu{
 | 
			
		||||
		DebianBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.Ubuntu,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o Ubuntu) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
	ovalKernelImageNames := []string{
 | 
			
		||||
		"linux-aws",
 | 
			
		||||
		"linux-azure",
 | 
			
		||||
		"linux-flo",
 | 
			
		||||
		"linux-gcp",
 | 
			
		||||
		"linux-gke",
 | 
			
		||||
		"linux-goldfish",
 | 
			
		||||
		"linux-hwe",
 | 
			
		||||
		"linux-hwe-edge",
 | 
			
		||||
		"linux-kvm",
 | 
			
		||||
		"linux-mako",
 | 
			
		||||
		"linux-raspi2",
 | 
			
		||||
		"linux-snapdragon",
 | 
			
		||||
	}
 | 
			
		||||
	linuxImage := "linux-image-" + r.RunningKernel.Release
 | 
			
		||||
 | 
			
		||||
	found := false
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		for _, n := range ovalKernelImageNames {
 | 
			
		||||
			if _, ok := r.Packages[n]; ok {
 | 
			
		||||
				v, ok := r.Packages[linuxImage]
 | 
			
		||||
				if ok {
 | 
			
		||||
					// Set running kernel version
 | 
			
		||||
					p := r.Packages[n]
 | 
			
		||||
					p.Version = v.Version
 | 
			
		||||
					p.NewVersion = v.NewVersion
 | 
			
		||||
					r.Packages[n] = p
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Warnf("Running kernel image %s is not found: %s",
 | 
			
		||||
						linuxImage, r.RunningKernel.Version)
 | 
			
		||||
				}
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !found {
 | 
			
		||||
			// linux-generic is described as "linux" in Ubuntu's oval.
 | 
			
		||||
			// Add "linux" and set the version of running kernel to search OVAL.
 | 
			
		||||
			v, ok := r.Packages[linuxImage]
 | 
			
		||||
			if ok {
 | 
			
		||||
				r.Packages["linux"] = models.Package{
 | 
			
		||||
					Name:       "linux",
 | 
			
		||||
					Version:    v.Version,
 | 
			
		||||
					NewVersion: v.NewVersion,
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Warnf("%s is not found. Running: %s",
 | 
			
		||||
					linuxImage, r.RunningKernel.Release)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if o.isFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !found {
 | 
			
		||||
		delete(r.Packages, "linux")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
		// Remove "linux" added above to search for oval
 | 
			
		||||
		// "linux" is not a real package name (key of affected packages in OVAL)
 | 
			
		||||
		if _, ok := defPacks.actuallyAffectedPackNames["linux"]; !found && ok {
 | 
			
		||||
			defPacks.actuallyAffectedPackNames[linuxImage] = true
 | 
			
		||||
			delete(defPacks.actuallyAffectedPackNames, "linux")
 | 
			
		||||
			for i, p := range defPacks.def.AffectedPacks {
 | 
			
		||||
				if p.Name == "linux" {
 | 
			
		||||
					p.Name = linuxImage
 | 
			
		||||
					defPacks.def.AffectedPacks[i] = p
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, vuln := range r.ScannedCves {
 | 
			
		||||
		if cont, ok := vuln.CveContents[models.Ubuntu]; ok {
 | 
			
		||||
			cont.SourceLink = "http://people.ubuntu.com/~ubuntu-security/cve/" + cont.CveID
 | 
			
		||||
			vuln.CveContents[models.Ubuntu] = cont
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										79
									
								
								oval/debian_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,79 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestPackNamesOfUpdateDebian(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       models.ScanResult
 | 
			
		||||
		defPacks defPacks
 | 
			
		||||
		out      models.ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: models.ScanResult{
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2000-1000": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{
 | 
			
		||||
							{Name: "packA"},
 | 
			
		||||
							{Name: "packC"},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			defPacks: defPacks{
 | 
			
		||||
				def: ovalmodels.Definition{
 | 
			
		||||
					Debian: ovalmodels.Debian{
 | 
			
		||||
						CveID: "CVE-2000-1000",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				actuallyAffectedPackNames: map[string]bool{
 | 
			
		||||
					"packB": true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2000-1000": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{
 | 
			
		||||
							{Name: "packA"},
 | 
			
		||||
							{Name: "packB", NotFixedYet: true},
 | 
			
		||||
							{Name: "packC"},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log = util.NewCustomLogger(config.ServerInfo{})
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		Debian{}.update(&tt.in, tt.defPacks)
 | 
			
		||||
		e := tt.out.ScannedCves["CVE-2000-1000"].AffectedPackages
 | 
			
		||||
		a := tt.in.ScannedCves["CVE-2000-1000"].AffectedPackages
 | 
			
		||||
		if !reflect.DeepEqual(a, e) {
 | 
			
		||||
			t.Errorf("[%d] expected: %v\n  actual: %v\n", i, e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										148
									
								
								oval/oval.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,148 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
	ovallog "github.com/kotakanbe/goval-dictionary/log"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Client is the interface of OVAL client.
 | 
			
		||||
type Client interface {
 | 
			
		||||
	CheckHTTPHealth() error
 | 
			
		||||
	FillWithOval(r *models.ScanResult) error
 | 
			
		||||
 | 
			
		||||
	// CheckIfOvalFetched checks if oval entries are in DB by family, release.
 | 
			
		||||
	CheckIfOvalFetched(string, string) (bool, error)
 | 
			
		||||
	CheckIfOvalFresh(string, string) (bool, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Base is a base struct
 | 
			
		||||
type Base struct {
 | 
			
		||||
	family string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckHTTPHealth do health check
 | 
			
		||||
func (b Base) CheckHTTPHealth() error {
 | 
			
		||||
	if !b.isFetchViaHTTP() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", config.Conf.OvalDBURL)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	resp, _, errs = gorequest.New().Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return fmt.Errorf("Failed to request to OVAL server. url: %s, errs: %v",
 | 
			
		||||
			url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfOvalFetched checks if oval entries are in DB by family, release.
 | 
			
		||||
func (b Base) CheckIfOvalFetched(osFamily, release string) (fetched bool, err error) {
 | 
			
		||||
	ovallog.Initialize(config.Conf.LogDir)
 | 
			
		||||
	if !b.isFetchViaHTTP() {
 | 
			
		||||
		var ovaldb db.DB
 | 
			
		||||
		if ovaldb, err = db.NewDB(
 | 
			
		||||
			osFamily,
 | 
			
		||||
			config.Conf.OvalDBType,
 | 
			
		||||
			config.Conf.OvalDBPath,
 | 
			
		||||
			config.Conf.DebugSQL,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		defer ovaldb.CloseDB()
 | 
			
		||||
		count, err := ovaldb.CountDefs(osFamily, release)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, fmt.Errorf("Failed to count OVAL defs: %s, %s, %v",
 | 
			
		||||
				osFamily, release, err)
 | 
			
		||||
		}
 | 
			
		||||
		return 0 < count, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url, _ := util.URLPathJoin(config.Conf.OvalDBURL, "count", osFamily, release)
 | 
			
		||||
	resp, body, errs := gorequest.New().Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return false, fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v",
 | 
			
		||||
			errs, url, resp)
 | 
			
		||||
	}
 | 
			
		||||
	count := 0
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &count); err != nil {
 | 
			
		||||
		return false, fmt.Errorf("Failed to Unmarshall. body: %s, err: %s",
 | 
			
		||||
			body, err)
 | 
			
		||||
	}
 | 
			
		||||
	return 0 < count, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfOvalFresh checks if oval entries are fresh enough
 | 
			
		||||
func (b Base) CheckIfOvalFresh(osFamily, release string) (ok bool, err error) {
 | 
			
		||||
	ovallog.Initialize(config.Conf.LogDir)
 | 
			
		||||
	var lastModified time.Time
 | 
			
		||||
	if !b.isFetchViaHTTP() {
 | 
			
		||||
		var ovaldb db.DB
 | 
			
		||||
		if ovaldb, err = db.NewDB(
 | 
			
		||||
			osFamily,
 | 
			
		||||
			config.Conf.OvalDBType,
 | 
			
		||||
			config.Conf.OvalDBPath,
 | 
			
		||||
			config.Conf.DebugSQL,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		defer ovaldb.CloseDB()
 | 
			
		||||
		lastModified = ovaldb.GetLastModified(osFamily, release)
 | 
			
		||||
	} else {
 | 
			
		||||
		url, _ := util.URLPathJoin(config.Conf.OvalDBURL, "lastmodified", osFamily, release)
 | 
			
		||||
		resp, body, errs := gorequest.New().Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return false, fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v",
 | 
			
		||||
				errs, url, resp)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := json.Unmarshal([]byte(body), &lastModified); err != nil {
 | 
			
		||||
			return false, fmt.Errorf("Failed to Unmarshall. body: %s, err: %s",
 | 
			
		||||
				body, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	since := time.Now()
 | 
			
		||||
	since = since.AddDate(0, 0, -3)
 | 
			
		||||
	if lastModified.Before(since) {
 | 
			
		||||
		util.Log.Warnf("OVAL for %s %s is old, last modified is %s. It's recommended to update OVAL to improve scanning accuracy. How to update OVAL database, see https://github.com/kotakanbe/goval-dictionary#usage",
 | 
			
		||||
			osFamily, release, lastModified)
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("OVAL is fresh: %s %s ", osFamily, release)
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b Base) isFetchViaHTTP() bool {
 | 
			
		||||
	// Default value of OvalDBType is sqlite3
 | 
			
		||||
	return config.Conf.OvalDBURL != "" && config.Conf.OvalDBType == "sqlite3"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										253
									
								
								oval/redhat.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,253 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// RedHatBase is the base struct for RedHat and CentOS
 | 
			
		||||
type RedHatBase struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o RedHatBase) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if o.isFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
		o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, vuln := range r.ScannedCves {
 | 
			
		||||
		switch models.NewCveContentType(o.family) {
 | 
			
		||||
		case models.RedHat:
 | 
			
		||||
			if cont, ok := vuln.CveContents[models.RedHat]; ok {
 | 
			
		||||
				cont.SourceLink = "https://access.redhat.com/security/cve/" + cont.CveID
 | 
			
		||||
				vuln.CveContents[models.RedHat] = cont
 | 
			
		||||
			}
 | 
			
		||||
		case models.Oracle:
 | 
			
		||||
			if cont, ok := vuln.CveContents[models.Oracle]; ok {
 | 
			
		||||
				cont.SourceLink = fmt.Sprintf("https://linux.oracle.com/cve/%s.html", cont.CveID)
 | 
			
		||||
				vuln.CveContents[models.Oracle] = cont
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var kernelRelatedPackNames = map[string]bool{
 | 
			
		||||
	"kernel":                  true,
 | 
			
		||||
	"kernel-aarch64":          true,
 | 
			
		||||
	"kernel-abi-whitelists":   true,
 | 
			
		||||
	"kernel-bootwrapper":      true,
 | 
			
		||||
	"kernel-debug":            true,
 | 
			
		||||
	"kernel-debug-devel":      true,
 | 
			
		||||
	"kernel-devel":            true,
 | 
			
		||||
	"kernel-doc":              true,
 | 
			
		||||
	"kernel-headers":          true,
 | 
			
		||||
	"kernel-kdump":            true,
 | 
			
		||||
	"kernel-kdump-devel":      true,
 | 
			
		||||
	"kernel-rt":               true,
 | 
			
		||||
	"kernel-rt-debug":         true,
 | 
			
		||||
	"kernel-rt-debug-devel":   true,
 | 
			
		||||
	"kernel-rt-debug-kvm":     true,
 | 
			
		||||
	"kernel-rt-devel":         true,
 | 
			
		||||
	"kernel-rt-doc":           true,
 | 
			
		||||
	"kernel-rt-kvm":           true,
 | 
			
		||||
	"kernel-rt-trace":         true,
 | 
			
		||||
	"kernel-rt-trace-devel":   true,
 | 
			
		||||
	"kernel-rt-trace-kvm":     true,
 | 
			
		||||
	"kernel-rt-virt":          true,
 | 
			
		||||
	"kernel-rt-virt-devel":    true,
 | 
			
		||||
	"kernel-tools":            true,
 | 
			
		||||
	"kernel-tools-libs":       true,
 | 
			
		||||
	"kernel-tools-libs-devel": true,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
	ctype := models.NewCveContentType(o.family)
 | 
			
		||||
	for _, cve := range defPacks.def.Advisory.Cves {
 | 
			
		||||
		ovalContent := *o.convertToModel(cve.CveID, &defPacks.def)
 | 
			
		||||
		vinfo, ok := r.ScannedCves[cve.CveID]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			util.Log.Debugf("%s is newly detected by OVAL", cve.CveID)
 | 
			
		||||
			vinfo = models.VulnInfo{
 | 
			
		||||
				CveID:       cve.CveID,
 | 
			
		||||
				Confidence:  models.OvalMatch,
 | 
			
		||||
				CveContents: models.NewCveContents(ovalContent),
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			cveContents := vinfo.CveContents
 | 
			
		||||
			if _, ok := vinfo.CveContents[ctype]; ok {
 | 
			
		||||
				util.Log.Debugf("%s OVAL will be overwritten", cve.CveID)
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Debugf("%s also detected by OVAL", cve.CveID)
 | 
			
		||||
				cveContents = models.CveContents{}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if vinfo.Confidence.Score < models.OvalMatch.Score {
 | 
			
		||||
				vinfo.Confidence = models.OvalMatch
 | 
			
		||||
			}
 | 
			
		||||
			cveContents[ctype] = ovalContent
 | 
			
		||||
			vinfo.CveContents = cveContents
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
 | 
			
		||||
		for _, pack := range vinfo.AffectedPackages {
 | 
			
		||||
			notFixedYet, _ := defPacks.actuallyAffectedPackNames[pack.Name]
 | 
			
		||||
			defPacks.actuallyAffectedPackNames[pack.Name] = notFixedYet
 | 
			
		||||
		}
 | 
			
		||||
		vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family)
 | 
			
		||||
		vinfo.AffectedPackages.Sort()
 | 
			
		||||
		r.ScannedCves[cve.CveID] = vinfo
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o RedHatBase) convertToModel(cveID string, def *ovalmodels.Definition) *models.CveContent {
 | 
			
		||||
	for _, cve := range def.Advisory.Cves {
 | 
			
		||||
		if cve.CveID != cveID {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		var refs []models.Reference
 | 
			
		||||
		for _, r := range def.References {
 | 
			
		||||
			refs = append(refs, models.Reference{
 | 
			
		||||
				Link:   r.RefURL,
 | 
			
		||||
				Source: r.Source,
 | 
			
		||||
				RefID:  r.RefID,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		score2, vec2 := o.parseCvss2(cve.Cvss2)
 | 
			
		||||
		score3, vec3 := o.parseCvss3(cve.Cvss3)
 | 
			
		||||
 | 
			
		||||
		severity := def.Advisory.Severity
 | 
			
		||||
		if cve.Impact != "" {
 | 
			
		||||
			severity = cve.Impact
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return &models.CveContent{
 | 
			
		||||
			Type:         models.NewCveContentType(o.family),
 | 
			
		||||
			CveID:        cve.CveID,
 | 
			
		||||
			Title:        def.Title,
 | 
			
		||||
			Summary:      def.Description,
 | 
			
		||||
			Severity:     severity,
 | 
			
		||||
			Cvss2Score:   score2,
 | 
			
		||||
			Cvss2Vector:  vec2,
 | 
			
		||||
			Cvss3Score:   score3,
 | 
			
		||||
			Cvss3Vector:  vec3,
 | 
			
		||||
			References:   refs,
 | 
			
		||||
			CweID:        cve.Cwe,
 | 
			
		||||
			Published:    def.Advisory.Issued,
 | 
			
		||||
			LastModified: def.Advisory.Updated,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseCvss2 divide CVSSv2 string into score and vector
 | 
			
		||||
// 5/AV:N/AC:L/Au:N/C:N/I:N/A:P
 | 
			
		||||
func (o RedHatBase) parseCvss2(scoreVector string) (score float64, vector string) {
 | 
			
		||||
	var err error
 | 
			
		||||
	ss := strings.Split(scoreVector, "/")
 | 
			
		||||
	if 1 < len(ss) {
 | 
			
		||||
		if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
 | 
			
		||||
			return 0, ""
 | 
			
		||||
		}
 | 
			
		||||
		return score, strings.Join(ss[1:], "/")
 | 
			
		||||
	}
 | 
			
		||||
	return 0, ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseCvss3 divide CVSSv3 string into score and vector
 | 
			
		||||
// 5.6/CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L
 | 
			
		||||
func (o RedHatBase) parseCvss3(scoreVector string) (score float64, vector string) {
 | 
			
		||||
	var err error
 | 
			
		||||
	ss := strings.Split(scoreVector, "/CVSS:3.0/")
 | 
			
		||||
	if 1 < len(ss) {
 | 
			
		||||
		if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
 | 
			
		||||
			return 0, ""
 | 
			
		||||
		}
 | 
			
		||||
		return score, strings.Join(ss[1:], "/")
 | 
			
		||||
	}
 | 
			
		||||
	return 0, ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RedHat is the interface for RedhatBase OVAL
 | 
			
		||||
type RedHat struct {
 | 
			
		||||
	RedHatBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRedhat creates OVAL client for Redhat
 | 
			
		||||
func NewRedhat() RedHat {
 | 
			
		||||
	return RedHat{
 | 
			
		||||
		RedHatBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.RedHat,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CentOS is the interface for CentOS OVAL
 | 
			
		||||
type CentOS struct {
 | 
			
		||||
	RedHatBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCentOS creates OVAL client for CentOS
 | 
			
		||||
func NewCentOS() CentOS {
 | 
			
		||||
	return CentOS{
 | 
			
		||||
		RedHatBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.CentOS,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Oracle is the interface for CentOS OVAL
 | 
			
		||||
type Oracle struct {
 | 
			
		||||
	RedHatBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewOracle creates OVAL client for Oracle
 | 
			
		||||
func NewOracle() Oracle {
 | 
			
		||||
	return Oracle{
 | 
			
		||||
		RedHatBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.Oracle,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										148
									
								
								oval/redhat_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,148 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseCvss2(t *testing.T) {
 | 
			
		||||
	type out struct {
 | 
			
		||||
		score  float64
 | 
			
		||||
		vector string
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out out
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: "5/AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
			out: out{
 | 
			
		||||
				score:  5.0,
 | 
			
		||||
				vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: "",
 | 
			
		||||
			out: out{
 | 
			
		||||
				score:  0,
 | 
			
		||||
				vector: "",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		s, v := RedHatBase{}.parseCvss2(tt.in)
 | 
			
		||||
		if s != tt.out.score || v != tt.out.vector {
 | 
			
		||||
			t.Errorf("\nexpected: %f, %s\n  actual: %f, %s",
 | 
			
		||||
				tt.out.score, tt.out.vector, s, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseCvss3(t *testing.T) {
 | 
			
		||||
	type out struct {
 | 
			
		||||
		score  float64
 | 
			
		||||
		vector string
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out out
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: "5.6/CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
			out: out{
 | 
			
		||||
				score:  5.6,
 | 
			
		||||
				vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: "",
 | 
			
		||||
			out: out{
 | 
			
		||||
				score:  0,
 | 
			
		||||
				vector: "",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		s, v := RedHatBase{}.parseCvss3(tt.in)
 | 
			
		||||
		if s != tt.out.score || v != tt.out.vector {
 | 
			
		||||
			t.Errorf("\nexpected: %f, %s\n  actual: %f, %s",
 | 
			
		||||
				tt.out.score, tt.out.vector, s, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPackNamesOfUpdate(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       models.ScanResult
 | 
			
		||||
		defPacks defPacks
 | 
			
		||||
		out      models.ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: models.ScanResult{
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2000-1000": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{
 | 
			
		||||
							{Name: "packA"},
 | 
			
		||||
							{Name: "packB", NotFixedYet: false},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			defPacks: defPacks{
 | 
			
		||||
				def: ovalmodels.Definition{
 | 
			
		||||
					Advisory: ovalmodels.Advisory{
 | 
			
		||||
						Cves: []ovalmodels.Cve{
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2000-1000",
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				actuallyAffectedPackNames: map[string]bool{
 | 
			
		||||
					"packB": true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2000-1000": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{
 | 
			
		||||
							{Name: "packA"},
 | 
			
		||||
							{Name: "packB", NotFixedYet: true},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log = util.NewCustomLogger(config.ServerInfo{})
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		RedHat{}.update(&tt.in, tt.defPacks)
 | 
			
		||||
		e := tt.out.ScannedCves["CVE-2000-1000"].AffectedPackages
 | 
			
		||||
		a := tt.in.ScannedCves["CVE-2000-1000"].AffectedPackages
 | 
			
		||||
		if !reflect.DeepEqual(a, e) {
 | 
			
		||||
			t.Errorf("[%d] expected: %v\n  actual: %v\n", i, e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										120
									
								
								oval/suse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,120 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SUSE is the struct of SUSE Linux
 | 
			
		||||
type SUSE struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewSUSE creates OVAL client for SUSE
 | 
			
		||||
func NewSUSE() SUSE {
 | 
			
		||||
	// TODO implement other family
 | 
			
		||||
	return SUSE{
 | 
			
		||||
		Base{
 | 
			
		||||
			family: config.SUSEEnterpriseServer,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o SUSE) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if o.isFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
		o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, vuln := range r.ScannedCves {
 | 
			
		||||
		if cont, ok := vuln.CveContents[models.SUSE]; ok {
 | 
			
		||||
			cont.SourceLink = "https://security-tracker.debian.org/tracker/" + cont.CveID
 | 
			
		||||
			vuln.CveContents[models.SUSE] = cont
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o SUSE) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
	ovalContent := *o.convertToModel(&defPacks.def)
 | 
			
		||||
	ovalContent.Type = models.NewCveContentType(o.family)
 | 
			
		||||
	vinfo, ok := r.ScannedCves[defPacks.def.Title]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		util.Log.Debugf("%s is newly detected by OVAL", defPacks.def.Title)
 | 
			
		||||
		vinfo = models.VulnInfo{
 | 
			
		||||
			CveID:       defPacks.def.Title,
 | 
			
		||||
			Confidence:  models.OvalMatch,
 | 
			
		||||
			CveContents: models.NewCveContents(ovalContent),
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		cveContents := vinfo.CveContents
 | 
			
		||||
		ctype := models.NewCveContentType(o.family)
 | 
			
		||||
		if _, ok := vinfo.CveContents[ctype]; ok {
 | 
			
		||||
			util.Log.Debugf("%s OVAL will be overwritten", defPacks.def.Title)
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Debugf("%s is also detected by OVAL", defPacks.def.Title)
 | 
			
		||||
			cveContents = models.CveContents{}
 | 
			
		||||
		}
 | 
			
		||||
		if vinfo.Confidence.Score < models.OvalMatch.Score {
 | 
			
		||||
			vinfo.Confidence = models.OvalMatch
 | 
			
		||||
		}
 | 
			
		||||
		cveContents[ctype] = ovalContent
 | 
			
		||||
		vinfo.CveContents = cveContents
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
 | 
			
		||||
	for _, pack := range vinfo.AffectedPackages {
 | 
			
		||||
		notFixedYet, _ := defPacks.actuallyAffectedPackNames[pack.Name]
 | 
			
		||||
		defPacks.actuallyAffectedPackNames[pack.Name] = notFixedYet
 | 
			
		||||
	}
 | 
			
		||||
	vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family)
 | 
			
		||||
	vinfo.AffectedPackages.Sort()
 | 
			
		||||
	r.ScannedCves[defPacks.def.Title] = vinfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o SUSE) convertToModel(def *ovalmodels.Definition) *models.CveContent {
 | 
			
		||||
	var refs []models.Reference
 | 
			
		||||
	for _, r := range def.References {
 | 
			
		||||
		refs = append(refs, models.Reference{
 | 
			
		||||
			Link:   r.RefURL,
 | 
			
		||||
			Source: r.Source,
 | 
			
		||||
			RefID:  r.RefID,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &models.CveContent{
 | 
			
		||||
		CveID:      def.Title,
 | 
			
		||||
		Title:      def.Title,
 | 
			
		||||
		Summary:    def.Description,
 | 
			
		||||
		References: refs,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										365
									
								
								oval/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,365 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	debver "github.com/knqyf263/go-deb-version"
 | 
			
		||||
	rpmver "github.com/knqyf263/go-rpm-version"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
	ovallog "github.com/kotakanbe/goval-dictionary/log"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ovalResult struct {
 | 
			
		||||
	entries []defPacks
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type defPacks struct {
 | 
			
		||||
	def ovalmodels.Definition
 | 
			
		||||
 | 
			
		||||
	// BinaryPackageName : NotFixedYet
 | 
			
		||||
	actuallyAffectedPackNames map[string]bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e defPacks) toPackStatuses(family string) (ps models.PackageStatuses) {
 | 
			
		||||
	for name, notFixedYet := range e.actuallyAffectedPackNames {
 | 
			
		||||
		ps = append(ps, models.PackageStatus{
 | 
			
		||||
			Name:        name,
 | 
			
		||||
			NotFixedYet: notFixedYet,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *ovalResult) upsert(def ovalmodels.Definition, packName string, notFixedYet bool) (upserted bool) {
 | 
			
		||||
	// alpine's entry is empty since Alpine secdb is not OVAL format
 | 
			
		||||
	if def.DefinitionID != "" {
 | 
			
		||||
		for i, entry := range e.entries {
 | 
			
		||||
			if entry.def.DefinitionID == def.DefinitionID {
 | 
			
		||||
				e.entries[i].actuallyAffectedPackNames[packName] = notFixedYet
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	e.entries = append(e.entries, defPacks{
 | 
			
		||||
		def: def,
 | 
			
		||||
		actuallyAffectedPackNames: map[string]bool{packName: notFixedYet},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type request struct {
 | 
			
		||||
	packName          string
 | 
			
		||||
	versionRelease    string
 | 
			
		||||
	NewVersionRelease string
 | 
			
		||||
	binaryPackNames   []string
 | 
			
		||||
	isSrcPack         bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type response struct {
 | 
			
		||||
	request request
 | 
			
		||||
	defs    []ovalmodels.Definition
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getDefsByPackNameViaHTTP fetches OVAL information via HTTP
 | 
			
		||||
func getDefsByPackNameViaHTTP(r *models.ScanResult) (
 | 
			
		||||
	relatedDefs ovalResult, err error) {
 | 
			
		||||
 | 
			
		||||
	nReq := len(r.Packages) + len(r.SrcPackages)
 | 
			
		||||
	reqChan := make(chan request, nReq)
 | 
			
		||||
	resChan := make(chan response, nReq)
 | 
			
		||||
	errChan := make(chan error, nReq)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, pack := range r.Packages {
 | 
			
		||||
			reqChan <- request{
 | 
			
		||||
				packName:          pack.Name,
 | 
			
		||||
				versionRelease:    pack.FormatVer(),
 | 
			
		||||
				NewVersionRelease: pack.FormatVer(),
 | 
			
		||||
				isSrcPack:         false,
 | 
			
		||||
			}
 | 
			
		||||
			for _, pack := range r.SrcPackages {
 | 
			
		||||
				reqChan <- request{
 | 
			
		||||
					packName:        pack.Name,
 | 
			
		||||
					binaryPackNames: pack.BinaryNames,
 | 
			
		||||
					versionRelease:  pack.Version,
 | 
			
		||||
					isSrcPack:       true,
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for i := 0; i < nReq; i++ {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case req := <-reqChan:
 | 
			
		||||
				url, err := util.URLPathJoin(
 | 
			
		||||
					config.Conf.OvalDBURL,
 | 
			
		||||
					"packs",
 | 
			
		||||
					r.Family,
 | 
			
		||||
					r.Release,
 | 
			
		||||
					req.packName,
 | 
			
		||||
				)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					httpGet(url, req, resChan, errChan)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(2 * 60 * time.Second)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	for i := 0; i < nReq; i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-resChan:
 | 
			
		||||
			for _, def := range res.defs {
 | 
			
		||||
				affected, notFixedYet := isOvalDefAffected(def, res.request, r.Family, r.RunningKernel)
 | 
			
		||||
				if !affected {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if res.request.isSrcPack {
 | 
			
		||||
					for _, n := range res.request.binaryPackNames {
 | 
			
		||||
						relatedDefs.upsert(def, n, false)
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					relatedDefs.upsert(def, res.request.packName, notFixedYet)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return relatedDefs, fmt.Errorf("Timeout Fetching OVAL")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return relatedDefs, fmt.Errorf("Failed to fetch OVAL. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func httpGet(url string, req request, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
	var body string
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	count, retryMax := 0, 3
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		//  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 {
 | 
			
		||||
			count++
 | 
			
		||||
			if count == retryMax {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v",
 | 
			
		||||
				errs, url, resp)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		util.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errChan <- fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if count == retryMax {
 | 
			
		||||
		errChan <- fmt.Errorf("HRetry count exceeded")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defs := []ovalmodels.Definition{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &defs); err != nil {
 | 
			
		||||
		errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s",
 | 
			
		||||
			body, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resChan <- response{
 | 
			
		||||
		request: req,
 | 
			
		||||
		defs:    defs,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getDefsByPackNameFromOvalDB(r *models.ScanResult) (relatedDefs ovalResult, err error) {
 | 
			
		||||
	ovallog.Initialize(config.Conf.LogDir)
 | 
			
		||||
	path := config.Conf.OvalDBURL
 | 
			
		||||
	if config.Conf.OvalDBType == "sqlite3" {
 | 
			
		||||
		path = config.Conf.OvalDBPath
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("Open oval-dictionary db (%s): %s", config.Conf.OvalDBType, path)
 | 
			
		||||
 | 
			
		||||
	var ovaldb db.DB
 | 
			
		||||
	if ovaldb, err = db.NewDB(r.Family, config.Conf.OvalDBType,
 | 
			
		||||
		path, config.Conf.DebugSQL); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer ovaldb.CloseDB()
 | 
			
		||||
 | 
			
		||||
	requests := []request{}
 | 
			
		||||
	for _, pack := range r.Packages {
 | 
			
		||||
		requests = append(requests, request{
 | 
			
		||||
			packName:          pack.Name,
 | 
			
		||||
			versionRelease:    pack.FormatVer(),
 | 
			
		||||
			NewVersionRelease: pack.FormatNewVer(),
 | 
			
		||||
			isSrcPack:         false,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	for _, pack := range r.SrcPackages {
 | 
			
		||||
		requests = append(requests, request{
 | 
			
		||||
			packName:        pack.Name,
 | 
			
		||||
			binaryPackNames: pack.BinaryNames,
 | 
			
		||||
			versionRelease:  pack.Version,
 | 
			
		||||
			isSrcPack:       true,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, req := range requests {
 | 
			
		||||
		definitions, err := ovaldb.GetByPackName(r.Release, req.packName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return relatedDefs, fmt.Errorf("Failed to get %s OVAL info by package name: %v", r.Family, err)
 | 
			
		||||
		}
 | 
			
		||||
		for _, def := range definitions {
 | 
			
		||||
			affected, notFixedYet := isOvalDefAffected(def, req, r.Family, r.RunningKernel)
 | 
			
		||||
			if !affected {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if req.isSrcPack {
 | 
			
		||||
				for _, n := range req.binaryPackNames {
 | 
			
		||||
					relatedDefs.upsert(def, n, false)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				relatedDefs.upsert(def, req.packName, notFixedYet)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func major(version string) string {
 | 
			
		||||
	ss := strings.SplitN(version, ":", 2)
 | 
			
		||||
	ver := ""
 | 
			
		||||
	if len(ss) == 1 {
 | 
			
		||||
		ver = ss[0]
 | 
			
		||||
	} else {
 | 
			
		||||
		ver = ss[1]
 | 
			
		||||
	}
 | 
			
		||||
	return ver[0:strings.Index(ver, ".")]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isOvalDefAffected(def ovalmodels.Definition, req request, family string, running models.Kernel) (affected, notFixedYet bool) {
 | 
			
		||||
	for _, ovalPack := range def.AffectedPacks {
 | 
			
		||||
		if req.packName != ovalPack.Name {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if running.Release != "" {
 | 
			
		||||
			switch family {
 | 
			
		||||
			case config.RedHat, config.CentOS:
 | 
			
		||||
				// For kernel related packages, ignore OVAL information with different major versions
 | 
			
		||||
				if _, ok := kernelRelatedPackNames[ovalPack.Name]; ok {
 | 
			
		||||
					if major(ovalPack.Version) != major(running.Release) {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if ovalPack.NotFixedYet {
 | 
			
		||||
			return true, true
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		less, err := lessThan(family, req.versionRelease, ovalPack)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Debugf("Failed to parse versions: %s, Ver: %#v, OVAL: %#v, DefID: %s",
 | 
			
		||||
				err, req.versionRelease, ovalPack, def.DefinitionID)
 | 
			
		||||
			return false, false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if less {
 | 
			
		||||
			if req.isSrcPack {
 | 
			
		||||
				// Unable to judge whether fixed or not fixed of src package(Ubuntu, Debian)
 | 
			
		||||
				return true, false
 | 
			
		||||
			}
 | 
			
		||||
			if req.NewVersionRelease == "" {
 | 
			
		||||
				return true, true
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// compare version: newVer vs oval
 | 
			
		||||
			less, err := lessThan(family, req.NewVersionRelease, ovalPack)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				util.Log.Debugf("Failed to parse versions: %s, NewVer: %#v, OVAL: %#v, DefID: %s",
 | 
			
		||||
					err, req.NewVersionRelease, ovalPack, def.DefinitionID)
 | 
			
		||||
				return false, false
 | 
			
		||||
			}
 | 
			
		||||
			return true, less
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func lessThan(family, versionRelease string, packB ovalmodels.Package) (bool, error) {
 | 
			
		||||
	switch family {
 | 
			
		||||
	case config.Debian, config.Ubuntu:
 | 
			
		||||
		vera, err := debver.NewVersion(versionRelease)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		verb, err := debver.NewVersion(packB.Version)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		return vera.LessThan(verb), nil
 | 
			
		||||
	case config.Oracle, config.SUSEEnterpriseServer, config.Alpine:
 | 
			
		||||
		vera := rpmver.NewVersion(versionRelease)
 | 
			
		||||
		verb := rpmver.NewVersion(packB.Version)
 | 
			
		||||
		return vera.LessThan(verb), nil
 | 
			
		||||
	case config.RedHat, config.CentOS: // TODO: Suport config.Scientific
 | 
			
		||||
		rea := regexp.MustCompile(`\.[es]l(\d+)(?:_\d+)?(?:\.centos)?`)
 | 
			
		||||
		reb := regexp.MustCompile(`\.el(\d+)(?:_\d+)?`)
 | 
			
		||||
		vera := rpmver.NewVersion(rea.ReplaceAllString(versionRelease, ".el$1"))
 | 
			
		||||
		verb := rpmver.NewVersion(reb.ReplaceAllString(packB.Version, ".el$1"))
 | 
			
		||||
		return vera.LessThan(verb), nil
 | 
			
		||||
	default:
 | 
			
		||||
		util.Log.Errorf("Not implemented yet: %s", family)
 | 
			
		||||
	}
 | 
			
		||||
	return false, fmt.Errorf("Package version comparison not supported: %s", family)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1017
									
								
								oval/util_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -20,30 +20,103 @@ package report
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Azure/azure-sdk-for-go/storage"
 | 
			
		||||
	storage "github.com/Azure/azure-sdk-for-go/storage"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AzureBlobWriter writes results to AzureBlob
 | 
			
		||||
type AzureBlobWriter struct{}
 | 
			
		||||
 | 
			
		||||
// Write results to Azure Blob storage
 | 
			
		||||
func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	if len(rs) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cli, err := getBlobClient()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.FormatOneLineText {
 | 
			
		||||
		timestr := rs[0].ScannedAt.Format(time.RFC3339)
 | 
			
		||||
		k := fmt.Sprintf(timestr + "/summary.txt")
 | 
			
		||||
		text := formatOneLineSummary(rs...)
 | 
			
		||||
		b := []byte(text)
 | 
			
		||||
		if err := createBlockBlob(cli, k, b); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		key := r.ReportKeyName()
 | 
			
		||||
		if c.Conf.FormatJSON {
 | 
			
		||||
			k := key + ".json"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = json.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			if err := createBlockBlob(cli, k, b); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatShortText {
 | 
			
		||||
			k := key + "_short.txt"
 | 
			
		||||
			b := []byte(formatShortPlainText(r))
 | 
			
		||||
			if err := createBlockBlob(cli, k, b); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatFullText {
 | 
			
		||||
			k := key + "_full.txt"
 | 
			
		||||
			b := []byte(formatFullPlainText(r))
 | 
			
		||||
			if err := createBlockBlob(cli, k, b); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatXML {
 | 
			
		||||
			k := key + ".xml"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = xml.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to XML: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
 | 
			
		||||
			if err := createBlockBlob(cli, k, allBytes); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfAzureContainerExists check the existence of Azure storage container
 | 
			
		||||
func CheckIfAzureContainerExists() error {
 | 
			
		||||
	cli, err := getBlobClient()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	ok, err := cli.ContainerExists(c.Conf.AzureContainer)
 | 
			
		||||
	r, err := cli.ListContainers(storage.ListContainersParameters{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
 | 
			
		||||
	found := false
 | 
			
		||||
	for _, con := range r.Containers {
 | 
			
		||||
		if con.Name == c.Conf.AzureContainer {
 | 
			
		||||
			found = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		return fmt.Errorf("Container not found. Container: %s", c.Conf.AzureContainer)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -57,84 +130,20 @@ func getBlobClient() (storage.BlobStorageClient, error) {
 | 
			
		||||
	return api.GetBlobService(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write results to Azure Blob storage
 | 
			
		||||
func (w AzureBlobWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
	reqChan := make(chan models.ScanResult, len(scanResults))
 | 
			
		||||
	resChan := make(chan bool)
 | 
			
		||||
	errChan := make(chan error, len(scanResults))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(10 * 60 * time.Second)
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, r := range scanResults {
 | 
			
		||||
			reqChan <- r
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	for range scanResults {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case sresult := <-reqChan:
 | 
			
		||||
				func(r models.ScanResult) {
 | 
			
		||||
					err := w.upload(r)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						errChan <- err
 | 
			
		||||
					}
 | 
			
		||||
					resChan <- true
 | 
			
		||||
				}(sresult)
 | 
			
		||||
			}
 | 
			
		||||
func createBlockBlob(cli storage.BlobStorageClient, k string, b []byte) error {
 | 
			
		||||
	var err error
 | 
			
		||||
	if c.Conf.GZIP {
 | 
			
		||||
		if b, err = gz(b); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		k = k + ".gz"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
	for i := 0; i < len(scanResults); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-resChan:
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			errs = append(errs, fmt.Errorf("Timeout while uploading to azure Blob"))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		return fmt.Errorf("Failed to upload json to Azure Blob: %v", errs)
 | 
			
		||||
	ref := cli.GetContainerReference(c.Conf.AzureContainer)
 | 
			
		||||
	blob := ref.GetBlobReference(k)
 | 
			
		||||
	if err := blob.CreateBlockBlobFromReader(bytes.NewReader(b), nil); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to upload data to %s/%s, %s",
 | 
			
		||||
			c.Conf.AzureContainer, k, err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w AzureBlobWriter) upload(res models.ScanResult) (err error) {
 | 
			
		||||
	cli, err := getBlobClient()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	timestr := time.Now().Format("20060102_1504")
 | 
			
		||||
	name := ""
 | 
			
		||||
	if res.Container.ContainerID == "" {
 | 
			
		||||
		name = fmt.Sprintf("%s/%s.json", timestr, res.ServerName)
 | 
			
		||||
	} else {
 | 
			
		||||
		name = fmt.Sprintf("%s/%s_%s.json", timestr, res.ServerName, res.Container.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	jsonBytes, err := json.Marshal(res)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = cli.CreateBlockBlobFromReader(
 | 
			
		||||
		c.Conf.AzureContainer,
 | 
			
		||||
		name,
 | 
			
		||||
		uint64(len(jsonBytes)),
 | 
			
		||||
		bytes.NewReader(jsonBytes),
 | 
			
		||||
		map[string]string{},
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return fmt.Errorf("%s/%s, %s",
 | 
			
		||||
			c.Conf.AzureContainer, name, err)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,24 +15,23 @@ 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 cveapi
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	cveconfig "github.com/kotakanbe/go-cve-dictionary/config"
 | 
			
		||||
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CveClient is api client of CVE disctionary service.
 | 
			
		||||
@@ -44,13 +43,13 @@ type cvedictClient struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api *cvedictClient) initialize() {
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
	api.baseURL = config.Conf.CveDBURL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) CheckHealth() (ok bool, err error) {
 | 
			
		||||
	if config.Conf.CveDBPath != "" {
 | 
			
		||||
		log.Debugf("get cve-dictionary from sqlite3")
 | 
			
		||||
		return true, nil
 | 
			
		||||
func (api cvedictClient) CheckHealth() error {
 | 
			
		||||
	if !api.isFetchViaHTTP() {
 | 
			
		||||
		util.Log.Debugf("get cve-dictionary from %s", config.Conf.CveDBType)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.initialize()
 | 
			
		||||
@@ -59,10 +58,11 @@ func (api cvedictClient) CheckHealth() (ok bool, err error) {
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if len(errs) > 0 || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		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 fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v",
 | 
			
		||||
			url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return true, nil
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type response struct {
 | 
			
		||||
@@ -70,12 +70,12 @@ type response struct {
 | 
			
		||||
	CveDetail cve.CveDetail
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDetails, err error) {
 | 
			
		||||
	if config.Conf.CveDBPath != "" {
 | 
			
		||||
func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails []*cve.CveDetail, err error) {
 | 
			
		||||
	if !api.isFetchViaHTTP() {
 | 
			
		||||
		return api.FetchCveDetailsFromCveDB(cveIDs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
	api.baseURL = config.Conf.CveDBURL
 | 
			
		||||
	reqChan := make(chan string, len(cveIDs))
 | 
			
		||||
	resChan := make(chan response, len(cveIDs))
 | 
			
		||||
	errChan := make(chan error, len(cveIDs))
 | 
			
		||||
@@ -99,7 +99,7 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDet
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					util.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					api.httpGet(cveID, url, resChan, errChan)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -112,48 +112,51 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDet
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-resChan:
 | 
			
		||||
			if len(res.CveDetail.CveID) == 0 {
 | 
			
		||||
				cveDetails = append(cveDetails, cve.CveDetail{
 | 
			
		||||
				cveDetails = append(cveDetails, &cve.CveDetail{
 | 
			
		||||
					CveID: res.Key,
 | 
			
		||||
				})
 | 
			
		||||
			} else {
 | 
			
		||||
				cveDetails = append(cveDetails, res.CveDetail)
 | 
			
		||||
				cveDetails = append(cveDetails, &res.CveDetail)
 | 
			
		||||
			}
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return []cve.CveDetail{}, fmt.Errorf("Timeout Fetching CVE")
 | 
			
		||||
			return []*cve.CveDetail{}, fmt.Errorf("Timeout Fetching CVE")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
		return []*cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to fetch CVE. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// order by CVE ID desc
 | 
			
		||||
	sort.Sort(cveDetails)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails cve.CveDetails, err error) {
 | 
			
		||||
	log.Debugf("open cve-dictionary db")
 | 
			
		||||
	cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	if err := cvedb.OpenDB(); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to open DB. err: %s", err)
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails []*cve.CveDetail, err error) {
 | 
			
		||||
	util.Log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
 | 
			
		||||
	cveconfig.Conf.DBType = config.Conf.CveDBType
 | 
			
		||||
	if config.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	} else {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBURL
 | 
			
		||||
	}
 | 
			
		||||
	cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
 | 
			
		||||
 | 
			
		||||
	var driver cvedb.DB
 | 
			
		||||
	if driver, err = cvedb.NewDB(cveconfig.Conf.DBType, cveconfig.Conf.DBPath, cveconfig.Conf.DebugSQL); err != nil {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
		return []*cve.CveDetail{}, fmt.Errorf("Failed to New DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, cveID := range cveIDs {
 | 
			
		||||
		cveDetail := cvedb.Get(cveID)
 | 
			
		||||
		cveDetail := driver.Get(cveID)
 | 
			
		||||
		if len(cveDetail.CveID) == 0 {
 | 
			
		||||
			cveDetails = append(cveDetails, cve.CveDetail{
 | 
			
		||||
			cveDetails = append(cveDetails, &cve.CveDetail{
 | 
			
		||||
				CveID: cveID,
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			cveDetails = append(cveDetails, cveDetail)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// order by CVE ID desc
 | 
			
		||||
	sort.Sort(cveDetails)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -165,20 +168,25 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
 | 
			
		||||
		//  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 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 HTTP GET. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
		util.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s",
 | 
			
		||||
			t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errChan <- fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	cveDetail := cve.CveDetail{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
 | 
			
		||||
		errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
 | 
			
		||||
		errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s",
 | 
			
		||||
			body, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resChan <- response{
 | 
			
		||||
		key,
 | 
			
		||||
@@ -186,99 +194,83 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  func (api cvedictClient) httpGet(key, url string, query map[string]string, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
 | 
			
		||||
//      var body string
 | 
			
		||||
//      var errs []error
 | 
			
		||||
//      var resp *http.Response
 | 
			
		||||
//      f := func() (err error) {
 | 
			
		||||
//          req := gorequest.New().SetDebug(true).Proxy(api.httpProxy).Get(url)
 | 
			
		||||
//          for key := range query {
 | 
			
		||||
//              req = req.Query(fmt.Sprintf("%s=%s", key, query[key])).Set("Content-Type", "application/x-www-form-urlencoded")
 | 
			
		||||
//          }
 | 
			
		||||
//          pp.Println(req)
 | 
			
		||||
//          resp, body, errs = req.End()
 | 
			
		||||
//          if len(errs) > 0 || resp.StatusCode != 200 {
 | 
			
		||||
//              errChan <- fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
 | 
			
		||||
//          }
 | 
			
		||||
//          return nil
 | 
			
		||||
//      }
 | 
			
		||||
//      notify := func(err error, t time.Duration) {
 | 
			
		||||
//          log.Warnf("Failed to get. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
//      }
 | 
			
		||||
//      err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
//      if err != nil {
 | 
			
		||||
//          errChan <- fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
//      }
 | 
			
		||||
//      //  resChan <- body
 | 
			
		||||
//      cveDetail := cve.CveDetail{}
 | 
			
		||||
//      if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
 | 
			
		||||
//          errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
 | 
			
		||||
//      }
 | 
			
		||||
//      resChan <- response{
 | 
			
		||||
//          key,
 | 
			
		||||
//          cveDetail,
 | 
			
		||||
//      }
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
type responseGetCveDetailByCpeName struct {
 | 
			
		||||
	CpeName    string
 | 
			
		||||
	CveDetails []cve.CveDetail
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeName(cpeName string) ([]cve.CveDetail, error) {
 | 
			
		||||
	if config.Conf.CveDBPath != "" {
 | 
			
		||||
		return api.FetchCveDetailsByCpeNameFromDB(cpeName)
 | 
			
		||||
func (api cvedictClient) isFetchViaHTTP() bool {
 | 
			
		||||
	// Default value of CveDBType is sqlite3
 | 
			
		||||
	if config.Conf.CveDBURL != "" && config.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
	url, err := util.URLPathJoin(api.baseURL, "cpes")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []cve.CveDetail{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	query := map[string]string{"name": cpeName}
 | 
			
		||||
	log.Debugf("HTTP Request to %s, query: %#v", url, query)
 | 
			
		||||
	return api.httpPost(cpeName, url, query)
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]cve.CveDetail, error) {
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeName(cpeName string) ([]*cve.CveDetail, error) {
 | 
			
		||||
	if api.isFetchViaHTTP() {
 | 
			
		||||
		api.baseURL = config.Conf.CveDBURL
 | 
			
		||||
		url, err := util.URLPathJoin(api.baseURL, "cpes")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return []*cve.CveDetail{}, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		query := map[string]string{"name": cpeName}
 | 
			
		||||
		util.Log.Debugf("HTTP Request to %s, query: %#v", url, query)
 | 
			
		||||
		return api.httpPost(cpeName, url, query)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return api.FetchCveDetailsByCpeNameFromDB(cpeName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]*cve.CveDetail, error) {
 | 
			
		||||
	var body string
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		req := gorequest.New().SetDebug(config.Conf.Debug).Post(url)
 | 
			
		||||
		//  req := gorequest.New().SetDebug(config.Conf.Debug).Post(url)
 | 
			
		||||
		req := gorequest.New().Post(url)
 | 
			
		||||
		for key := range query {
 | 
			
		||||
			req = req.Send(fmt.Sprintf("%s=%s", key, query[key])).Type("json")
 | 
			
		||||
		}
 | 
			
		||||
		resp, body, errs = req.End()
 | 
			
		||||
		if len(errs) > 0 || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return fmt.Errorf("HTTP POST error: %v, url: %s, resp: %v", errs, url, resp)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
		util.Log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []cve.CveDetail{}, fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
		return []*cve.CveDetail{}, fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveDetails := []cve.CveDetail{}
 | 
			
		||||
	cveDetails := []*cve.CveDetail{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &cveDetails); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
		return []*cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
 | 
			
		||||
	}
 | 
			
		||||
	return cveDetails, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeNameFromDB(cpeName string) ([]cve.CveDetail, error) {
 | 
			
		||||
	log.Debugf("open cve-dictionary db")
 | 
			
		||||
	cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	if err := cvedb.OpenDB(); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to open DB. err: %s", err)
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeNameFromDB(cpeName string) (cveDetails []*cve.CveDetail, err error) {
 | 
			
		||||
	util.Log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
 | 
			
		||||
	cveconfig.Conf.DBType = config.Conf.CveDBType
 | 
			
		||||
	if config.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	} else {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBURL
 | 
			
		||||
	}
 | 
			
		||||
	return cvedb.GetByCpeName(cpeName), nil
 | 
			
		||||
	cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
 | 
			
		||||
 | 
			
		||||
	var driver cvedb.DB
 | 
			
		||||
	if driver, err = cvedb.NewDB(cveconfig.Conf.DBType, cveconfig.Conf.DBPath, cveconfig.Conf.DebugSQL); err != nil {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
		return []*cve.CveDetail{}, fmt.Errorf("Failed to New DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Debugf("Opening DB (%s).", driver.Name())
 | 
			
		||||
	return driver.GetByCpeName(cpeName), nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										150
									
								
								report/email.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,150 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/mail"
 | 
			
		||||
	"net/smtp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// EMailWriter send mail
 | 
			
		||||
type EMailWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf
 | 
			
		||||
	var message string
 | 
			
		||||
	sender := NewEMailSender()
 | 
			
		||||
 | 
			
		||||
	m := map[string]int{}
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		if conf.FormatOneEMail {
 | 
			
		||||
			message += formatFullPlainText(r) + "\r\n\r\n"
 | 
			
		||||
 | 
			
		||||
			mm := r.ScannedCves.CountGroupBySeverity()
 | 
			
		||||
			keys := []string{"High", "Medium", "Low", "Unknown"}
 | 
			
		||||
			for _, k := range keys {
 | 
			
		||||
				m[k] += mm[k]
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			var subject string
 | 
			
		||||
			if len(r.Errors) != 0 {
 | 
			
		||||
				subject = fmt.Sprintf("%s%s An error occurred while scanning",
 | 
			
		||||
					conf.EMail.SubjectPrefix, r.ServerInfo())
 | 
			
		||||
			} else {
 | 
			
		||||
				subject = fmt.Sprintf("%s%s %s",
 | 
			
		||||
					conf.EMail.SubjectPrefix,
 | 
			
		||||
					r.ServerInfo(),
 | 
			
		||||
					r.ScannedCves.FormatCveSummary())
 | 
			
		||||
			}
 | 
			
		||||
			message = formatFullPlainText(r)
 | 
			
		||||
			if err := sender.Send(subject, message); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	summary := ""
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		summary = fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)",
 | 
			
		||||
			m["High"]+m["Medium"]+m["Low"], m["High"], m["Medium"], m["Low"])
 | 
			
		||||
	}
 | 
			
		||||
	summary = fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)",
 | 
			
		||||
		m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
 | 
			
		||||
		m["High"], m["Medium"], m["Low"], m["Unknown"])
 | 
			
		||||
 | 
			
		||||
	if conf.FormatOneEMail {
 | 
			
		||||
		message = fmt.Sprintf(
 | 
			
		||||
			`
 | 
			
		||||
One Line Summary
 | 
			
		||||
================
 | 
			
		||||
%s
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
%s`,
 | 
			
		||||
			formatOneLineSummary(rs...), message)
 | 
			
		||||
 | 
			
		||||
		subject := fmt.Sprintf("%s %s",
 | 
			
		||||
			conf.EMail.SubjectPrefix, summary)
 | 
			
		||||
		return sender.Send(subject, message)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EMailSender is interface of sending e-mail
 | 
			
		||||
type EMailSender interface {
 | 
			
		||||
	Send(subject, body string) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type emailSender struct {
 | 
			
		||||
	conf config.SMTPConf
 | 
			
		||||
	send func(string, smtp.Auth, string, []string, []byte) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *emailSender) Send(subject, body string) (err error) {
 | 
			
		||||
	emailConf := e.conf
 | 
			
		||||
	to := strings.Join(emailConf.To[:], ", ")
 | 
			
		||||
	cc := strings.Join(emailConf.Cc[:], ", ")
 | 
			
		||||
	mailAddresses := append(emailConf.To, emailConf.Cc...)
 | 
			
		||||
	if _, err := mail.ParseAddressList(strings.Join(mailAddresses[:], ", ")); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to parse email addresses: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	headers := make(map[string]string)
 | 
			
		||||
	headers["From"] = emailConf.From
 | 
			
		||||
	headers["To"] = to
 | 
			
		||||
	headers["Cc"] = cc
 | 
			
		||||
	headers["Subject"] = subject
 | 
			
		||||
	headers["Date"] = time.Now().Format(time.RFC1123Z)
 | 
			
		||||
	headers["Content-Type"] = "text/plain; charset=utf-8"
 | 
			
		||||
 | 
			
		||||
	var header string
 | 
			
		||||
	for k, v := range headers {
 | 
			
		||||
		header += fmt.Sprintf("%s: %s\r\n", k, v)
 | 
			
		||||
	}
 | 
			
		||||
	message := fmt.Sprintf("%s\r\n%s", header, body)
 | 
			
		||||
 | 
			
		||||
	smtpServer := net.JoinHostPort(emailConf.SMTPAddr, emailConf.SMTPPort)
 | 
			
		||||
	err = e.send(
 | 
			
		||||
		smtpServer,
 | 
			
		||||
		smtp.PlainAuth(
 | 
			
		||||
			"",
 | 
			
		||||
			emailConf.User,
 | 
			
		||||
			emailConf.Password,
 | 
			
		||||
			emailConf.SMTPAddr,
 | 
			
		||||
		),
 | 
			
		||||
		emailConf.From,
 | 
			
		||||
		mailAddresses,
 | 
			
		||||
		[]byte(message),
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to send emails: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewEMailSender creates emailSender
 | 
			
		||||
func NewEMailSender() EMailSender {
 | 
			
		||||
	return &emailSender{config.Conf.EMail, smtp.SendMail}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										132
									
								
								report/email_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,132 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/smtp"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type emailRecorder struct {
 | 
			
		||||
	addr string
 | 
			
		||||
	auth smtp.Auth
 | 
			
		||||
	from string
 | 
			
		||||
	to   []string
 | 
			
		||||
	body string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type mailTest struct {
 | 
			
		||||
	in  config.SMTPConf
 | 
			
		||||
	out emailRecorder
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var mailTests = []mailTest{
 | 
			
		||||
	{
 | 
			
		||||
		config.SMTPConf{
 | 
			
		||||
			SMTPAddr: "127.0.0.1",
 | 
			
		||||
			SMTPPort: "25",
 | 
			
		||||
 | 
			
		||||
			From: "from@address.com",
 | 
			
		||||
			To:   []string{"to@address.com"},
 | 
			
		||||
			Cc:   []string{"cc@address.com"},
 | 
			
		||||
		},
 | 
			
		||||
		emailRecorder{
 | 
			
		||||
			addr: "127.0.0.1:25",
 | 
			
		||||
			auth: smtp.PlainAuth("", "", "", "127.0.0.1"),
 | 
			
		||||
			from: "from@address.com",
 | 
			
		||||
			to:   []string{"to@address.com", "cc@address.com"},
 | 
			
		||||
			body: "body",
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		config.SMTPConf{
 | 
			
		||||
			SMTPAddr: "127.0.0.1",
 | 
			
		||||
			SMTPPort: "25",
 | 
			
		||||
 | 
			
		||||
			User:     "vuls",
 | 
			
		||||
			Password: "password",
 | 
			
		||||
 | 
			
		||||
			From: "from@address.com",
 | 
			
		||||
			To:   []string{"to1@address.com", "to2@address.com"},
 | 
			
		||||
			Cc:   []string{"cc1@address.com", "cc2@address.com"},
 | 
			
		||||
		},
 | 
			
		||||
		emailRecorder{
 | 
			
		||||
			addr: "127.0.0.1:25",
 | 
			
		||||
			auth: smtp.PlainAuth(
 | 
			
		||||
				"",
 | 
			
		||||
				"vuls",
 | 
			
		||||
				"password",
 | 
			
		||||
				"127.0.0.1",
 | 
			
		||||
			),
 | 
			
		||||
			from: "from@address.com",
 | 
			
		||||
			to: []string{"to1@address.com", "to2@address.com",
 | 
			
		||||
				"cc1@address.com", "cc2@address.com"},
 | 
			
		||||
			body: "body",
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSend(t *testing.T) {
 | 
			
		||||
	for i, test := range mailTests {
 | 
			
		||||
		f, r := mockSend(nil)
 | 
			
		||||
		sender := &emailSender{conf: test.in, send: f}
 | 
			
		||||
 | 
			
		||||
		subject := "subject"
 | 
			
		||||
		body := "body"
 | 
			
		||||
		if err := sender.Send(subject, body); err != nil {
 | 
			
		||||
			t.Errorf("unexpected error: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if r.addr != test.out.addr {
 | 
			
		||||
			t.Errorf("#%d: wrong 'addr' field.\r\nexpected: %s\n got: %s", i, test.out.addr, r.addr)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !reflect.DeepEqual(r.auth, test.out.auth) {
 | 
			
		||||
			t.Errorf("#%d: wrong 'auth' field.\r\nexpected: %v\n got: %v", i, test.out.auth, r.auth)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if r.from != test.out.from {
 | 
			
		||||
			t.Errorf("#%d: wrong 'from' field.\r\nexpected: %v\n got: %v", i, test.out.from, r.from)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !reflect.DeepEqual(r.to, test.out.to) {
 | 
			
		||||
			t.Errorf("#%d: wrong 'to' field.\r\nexpected: %v\n got: %v", i, test.out.to, r.to)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if r.body != test.out.body {
 | 
			
		||||
			t.Errorf("#%d: wrong 'body' field.\r\nexpected: %v\n got: %v", i, test.out.body, r.body)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mockSend(errToReturn error) (func(string, smtp.Auth, string, []string, []byte) error, *emailRecorder) {
 | 
			
		||||
	r := new(emailRecorder)
 | 
			
		||||
	return func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
 | 
			
		||||
		// Split into header and body
 | 
			
		||||
		messages := strings.Split(string(msg), "\r\n\r\n")
 | 
			
		||||
		body := messages[1]
 | 
			
		||||
		*r = emailRecorder{addr, a, from, to, body}
 | 
			
		||||
		return errToReturn
 | 
			
		||||
	}, r
 | 
			
		||||
}
 | 
			
		||||
@@ -1,62 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// JSONWriter writes results to file.
 | 
			
		||||
type JSONWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
 | 
			
		||||
	path, err := ensureResultDir()
 | 
			
		||||
 | 
			
		||||
	var jsonBytes []byte
 | 
			
		||||
	if jsonBytes, err = json.Marshal(scanResults); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	all := filepath.Join(path, "all.json")
 | 
			
		||||
	if err := ioutil.WriteFile(all, jsonBytes, 0644); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to write JSON. path: %s, err: %s", all, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range scanResults {
 | 
			
		||||
		jsonPath := ""
 | 
			
		||||
		if r.Container.ContainerID == "" {
 | 
			
		||||
			jsonPath = filepath.Join(path, fmt.Sprintf("%s.json", r.ServerName))
 | 
			
		||||
		} else {
 | 
			
		||||
			jsonPath = filepath.Join(path,
 | 
			
		||||
				fmt.Sprintf("%s_%s.json", r.ServerName, r.Container.Name))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if jsonBytes, err = json.Marshal(r); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		if err := ioutil.WriteFile(jsonPath, jsonBytes, 0644); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to write JSON. path: %s, err: %s", jsonPath, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										141
									
								
								report/localfile.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,141 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LocalFileWriter writes results to a local file.
 | 
			
		||||
type LocalFileWriter struct {
 | 
			
		||||
	CurrentDir string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	if c.Conf.FormatOneLineText {
 | 
			
		||||
		path := filepath.Join(w.CurrentDir, "summary.txt")
 | 
			
		||||
		text := formatOneLineSummary(rs...)
 | 
			
		||||
		if err := writeFile(path, []byte(text), 0600); err != nil {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"Failed to write to file. path: %s, err: %s",
 | 
			
		||||
				path, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		path := filepath.Join(w.CurrentDir, r.ReportFileName())
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatJSON {
 | 
			
		||||
			var p string
 | 
			
		||||
			if c.Conf.Diff {
 | 
			
		||||
				p = path + "_diff.json"
 | 
			
		||||
			} else {
 | 
			
		||||
				p = path + ".json"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if c.Conf.Debug {
 | 
			
		||||
				if b, err = json.MarshalIndent(r, "", "    "); err != nil {
 | 
			
		||||
					return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				if b, err = json.Marshal(r); err != nil {
 | 
			
		||||
					return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if err := writeFile(p, b, 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to write JSON. path: %s, err: %s", p, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatShortText {
 | 
			
		||||
			var p string
 | 
			
		||||
			if c.Conf.Diff {
 | 
			
		||||
				p = path + "_short_diff.txt"
 | 
			
		||||
			} else {
 | 
			
		||||
				p = path + "_short.txt"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := writeFile(
 | 
			
		||||
				p, []byte(formatShortPlainText(r)), 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"Failed to write text files. path: %s, err: %s", p, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatFullText {
 | 
			
		||||
			var p string
 | 
			
		||||
			if c.Conf.Diff {
 | 
			
		||||
				p = path + "_full_diff.txt"
 | 
			
		||||
			} else {
 | 
			
		||||
				p = path + "_full.txt"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := writeFile(
 | 
			
		||||
				p, []byte(formatFullPlainText(r)), 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"Failed to write text files. path: %s, err: %s", p, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatXML {
 | 
			
		||||
			var p string
 | 
			
		||||
			if c.Conf.Diff {
 | 
			
		||||
				p = path + "_diff.xml"
 | 
			
		||||
			} else {
 | 
			
		||||
				p = path + ".xml"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = xml.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to XML: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
 | 
			
		||||
			if err := writeFile(p, allBytes, 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to write XML. path: %s, err: %s", p, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func writeFile(path string, data []byte, perm os.FileMode) error {
 | 
			
		||||
	var err error
 | 
			
		||||
	if c.Conf.GZIP {
 | 
			
		||||
		if data, err = gz(data); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		path = path + ".gz"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ioutil.WriteFile(
 | 
			
		||||
		path, []byte(data), perm); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,70 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"gopkg.in/gomail.v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MailWriter send mail
 | 
			
		||||
type MailWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w MailWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		m := gomail.NewMessage()
 | 
			
		||||
		m.SetHeader("From", conf.Mail.From)
 | 
			
		||||
		m.SetHeader("To", conf.Mail.To...)
 | 
			
		||||
		m.SetHeader("Cc", conf.Mail.Cc...)
 | 
			
		||||
 | 
			
		||||
		subject := fmt.Sprintf("%s%s %s",
 | 
			
		||||
			conf.Mail.SubjectPrefix,
 | 
			
		||||
			s.ServerInfo(),
 | 
			
		||||
			s.CveSummary(),
 | 
			
		||||
		)
 | 
			
		||||
		m.SetHeader("Subject", subject)
 | 
			
		||||
 | 
			
		||||
		var body string
 | 
			
		||||
		if body, err = toPlainText(s); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		m.SetBody("text/plain", body)
 | 
			
		||||
		port, _ := strconv.Atoi(conf.Mail.SMTPPort)
 | 
			
		||||
		d := gomail.NewPlainDialer(
 | 
			
		||||
			conf.Mail.SMTPAddr,
 | 
			
		||||
			port,
 | 
			
		||||
			conf.Mail.User,
 | 
			
		||||
			conf.Mail.Password,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		d.TLSConfig = &tls.Config{
 | 
			
		||||
			InsecureSkipVerify: true,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := d.DialAndSend(m); err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										240
									
								
								report/report.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,240 @@
 | 
			
		||||
/* 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"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/oval"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	vulsOpenTag  = "<vulsreport>"
 | 
			
		||||
	vulsCloseTag = "</vulsreport>"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FillCveInfos fills CVE Detailed Information
 | 
			
		||||
func FillCveInfos(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
 | 
			
		||||
	var filled []models.ScanResult
 | 
			
		||||
	reportedAt := time.Now()
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		if c.Conf.RefreshCve || needToRefreshCve(r) {
 | 
			
		||||
			if err := FillCveInfo(&r); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			r.Lang = c.Conf.Lang
 | 
			
		||||
			r.ReportedAt = reportedAt
 | 
			
		||||
			r.Config.Report = c.Conf
 | 
			
		||||
			r.Config.Report.Servers = map[string]c.ServerInfo{
 | 
			
		||||
				r.ServerName: c.Conf.Servers[r.ServerName],
 | 
			
		||||
			}
 | 
			
		||||
			if err := overwriteJSONFile(dir, r); err != nil {
 | 
			
		||||
				return nil, fmt.Errorf("Failed to write JSON: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			filled = append(filled, r)
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Debugf("No need to refresh")
 | 
			
		||||
			filled = append(filled, r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Diff {
 | 
			
		||||
		previous, err := loadPrevious(filled)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		diff, err := diff(filled, previous)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		filled = []models.ScanResult{}
 | 
			
		||||
		for _, r := range diff {
 | 
			
		||||
			if err := fillCveDetail(&r); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			filled = append(filled, r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filtered := []models.ScanResult{}
 | 
			
		||||
	for _, r := range filled {
 | 
			
		||||
		r = r.FilterByCvssOver(c.Conf.CvssScoreOver)
 | 
			
		||||
		r = r.FilterIgnoreCves(c.Conf.Servers[r.ServerName].IgnoreCves)
 | 
			
		||||
		r = r.FilterUnfixed()
 | 
			
		||||
		if c.Conf.IgnoreUnscoredCves {
 | 
			
		||||
			r.ScannedCves = r.ScannedCves.FindScoredVulns()
 | 
			
		||||
		}
 | 
			
		||||
		filtered = append(filtered, r)
 | 
			
		||||
	}
 | 
			
		||||
	return filtered, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillCveInfo fill scanResult with cve info.
 | 
			
		||||
func FillCveInfo(r *models.ScanResult) error {
 | 
			
		||||
	util.Log.Debugf("need to refresh")
 | 
			
		||||
 | 
			
		||||
	util.Log.Infof("Fill CVE detailed information with OVAL")
 | 
			
		||||
	if err := FillWithOval(r); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to fill OVAL information: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Infof("Fill CVE detailed information with CVE-DB")
 | 
			
		||||
	if err := fillWithCveDB(r); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to fill CVE information: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for cveID := range r.ScannedCves {
 | 
			
		||||
		vinfo := r.ScannedCves[cveID]
 | 
			
		||||
		r.ScannedCves[cveID] = *vinfo.NilToEmpty()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fillCveDetail fetches NVD, JVN from CVE Database, and then set to fields.
 | 
			
		||||
func fillCveDetail(r *models.ScanResult) error {
 | 
			
		||||
	var cveIDs []string
 | 
			
		||||
	for _, v := range r.ScannedCves {
 | 
			
		||||
		cveIDs = append(cveIDs, v.CveID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ds, err := CveClient.FetchCveDetails(cveIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, d := range ds {
 | 
			
		||||
		nvd := models.ConvertNvdToModel(d.CveID, d.Nvd)
 | 
			
		||||
		jvn := models.ConvertJvnToModel(d.CveID, d.Jvn)
 | 
			
		||||
		for cveID, vinfo := range r.ScannedCves {
 | 
			
		||||
			if vinfo.CveID == d.CveID {
 | 
			
		||||
				if vinfo.CveContents == nil {
 | 
			
		||||
					vinfo.CveContents = models.CveContents{}
 | 
			
		||||
				}
 | 
			
		||||
				for _, con := range []models.CveContent{*nvd, *jvn} {
 | 
			
		||||
					if !con.Empty() {
 | 
			
		||||
						vinfo.CveContents[con.Type] = con
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				r.ScannedCves[cveID] = vinfo
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fillWithCveDB(r *models.ScanResult) error {
 | 
			
		||||
	sInfo := c.Conf.Servers[r.ServerName]
 | 
			
		||||
	if err := fillVulnByCpeNames(sInfo.CpeNames, r.ScannedCves); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := fillCveDetail(r); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval fetches OVAL database, and then set to fields.
 | 
			
		||||
func FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
	var ovalClient oval.Client
 | 
			
		||||
	var ovalFamily string
 | 
			
		||||
 | 
			
		||||
	// TODO
 | 
			
		||||
	switch r.Family {
 | 
			
		||||
	case c.Debian:
 | 
			
		||||
		ovalClient = oval.NewDebian()
 | 
			
		||||
		ovalFamily = c.Debian
 | 
			
		||||
	case c.Ubuntu:
 | 
			
		||||
		ovalClient = oval.NewUbuntu()
 | 
			
		||||
		ovalFamily = c.Ubuntu
 | 
			
		||||
	case c.RedHat:
 | 
			
		||||
		ovalClient = oval.NewRedhat()
 | 
			
		||||
		ovalFamily = c.RedHat
 | 
			
		||||
	case c.CentOS:
 | 
			
		||||
		ovalClient = oval.NewCentOS()
 | 
			
		||||
		//use RedHat's OVAL
 | 
			
		||||
		ovalFamily = c.RedHat
 | 
			
		||||
	case c.Oracle:
 | 
			
		||||
		ovalClient = oval.NewOracle()
 | 
			
		||||
		ovalFamily = c.Oracle
 | 
			
		||||
	case c.SUSEEnterpriseServer:
 | 
			
		||||
		// TODO other suse family
 | 
			
		||||
		ovalClient = oval.NewSUSE()
 | 
			
		||||
		ovalFamily = c.SUSEEnterpriseServer
 | 
			
		||||
	case c.Alpine:
 | 
			
		||||
		ovalClient = oval.NewAlpine()
 | 
			
		||||
		ovalFamily = c.Alpine
 | 
			
		||||
	case c.Amazon, c.Raspbian, c.FreeBSD, c.Windows:
 | 
			
		||||
		return nil
 | 
			
		||||
	case c.ServerTypePseudo:
 | 
			
		||||
		return nil
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("OVAL for %s is not implemented yet", r.Family)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Debugf("Check whether oval is already fetched: %s %s",
 | 
			
		||||
		ovalFamily, r.Release)
 | 
			
		||||
	ok, err := ovalClient.CheckIfOvalFetched(ovalFamily, r.Release)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
		util.Log.Warnf("OVAL entries of %s %s are not found. It's recommended to use OVAL to improve scanning accuracy. For details, see https://github.com/kotakanbe/goval-dictionary#usage , Then report with --ovaldb-path or --ovaldb-url flag", ovalFamily, r.Release)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = ovalClient.CheckIfOvalFresh(ovalFamily, r.Release)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ovalClient.FillWithOval(r); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fillVulnByCpeNames(cpeNames []string, scannedVulns models.VulnInfos) error {
 | 
			
		||||
	for _, name := range cpeNames {
 | 
			
		||||
		details, err := CveClient.FetchCveDetailsByCpeName(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for _, detail := range details {
 | 
			
		||||
			if val, ok := scannedVulns[detail.CveID]; ok {
 | 
			
		||||
				names := val.CpeNames
 | 
			
		||||
				names = util.AppendIfMissing(names, name)
 | 
			
		||||
				val.CpeNames = names
 | 
			
		||||
				val.Confidence = models.CpeNameMatch
 | 
			
		||||
				scannedVulns[detail.CveID] = val
 | 
			
		||||
			} else {
 | 
			
		||||
				v := models.VulnInfo{
 | 
			
		||||
					CveID:      detail.CveID,
 | 
			
		||||
					CpeNames:   []string{name},
 | 
			
		||||
					Confidence: models.CpeNameMatch,
 | 
			
		||||
				}
 | 
			
		||||
				scannedVulns[detail.CveID] = v
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								report/report_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
package report
 | 
			
		||||
							
								
								
									
										143
									
								
								report/s3.go
									
									
									
									
									
								
							
							
						
						@@ -20,11 +20,15 @@ package report
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"path"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/credentials"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/session"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/service/s3"
 | 
			
		||||
 | 
			
		||||
@@ -32,6 +36,83 @@ import (
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// S3Writer writes results to S3
 | 
			
		||||
type S3Writer struct{}
 | 
			
		||||
 | 
			
		||||
func getS3() *s3.S3 {
 | 
			
		||||
	Config := &aws.Config{
 | 
			
		||||
		Region: aws.String(c.Conf.AwsRegion),
 | 
			
		||||
		Credentials: credentials.NewChainCredentials([]credentials.Provider{
 | 
			
		||||
			&credentials.EnvProvider{},
 | 
			
		||||
			&credentials.SharedCredentialsProvider{Filename: "", Profile: c.Conf.AwsProfile},
 | 
			
		||||
			&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(session.New())},
 | 
			
		||||
		}),
 | 
			
		||||
	}
 | 
			
		||||
	return s3.New(session.New(Config))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write results to S3
 | 
			
		||||
// http://docs.aws.amazon.com/sdk-for-go/latest/v1/developerguide/common-examples.title.html
 | 
			
		||||
func (w S3Writer) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	if len(rs) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	svc := getS3()
 | 
			
		||||
 | 
			
		||||
	if c.Conf.FormatOneLineText {
 | 
			
		||||
		timestr := rs[0].ScannedAt.Format(time.RFC3339)
 | 
			
		||||
		k := fmt.Sprintf(timestr + "/summary.txt")
 | 
			
		||||
		text := formatOneLineSummary(rs...)
 | 
			
		||||
		if err := putObject(svc, k, []byte(text)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		key := r.ReportKeyName()
 | 
			
		||||
		if c.Conf.FormatJSON {
 | 
			
		||||
			k := key + ".json"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = json.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			if err := putObject(svc, k, b); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatShortText {
 | 
			
		||||
			k := key + "_short.txt"
 | 
			
		||||
			text := formatShortPlainText(r)
 | 
			
		||||
			if err := putObject(svc, k, []byte(text)); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatFullText {
 | 
			
		||||
			k := key + "_full.txt"
 | 
			
		||||
			text := formatFullPlainText(r)
 | 
			
		||||
			if err := putObject(svc, k, []byte(text)); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatXML {
 | 
			
		||||
			k := key + ".xml"
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = xml.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to XML: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
 | 
			
		||||
			if err := putObject(svc, k, allBytes); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfBucketExists check the existence of S3 bucket
 | 
			
		||||
func CheckIfBucketExists() error {
 | 
			
		||||
	svc := getS3()
 | 
			
		||||
@@ -57,56 +138,22 @@ func CheckIfBucketExists() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// S3Writer writes results to S3
 | 
			
		||||
type S3Writer struct{}
 | 
			
		||||
 | 
			
		||||
func getS3() *s3.S3 {
 | 
			
		||||
	return s3.New(session.New(&aws.Config{
 | 
			
		||||
		Region:      aws.String(c.Conf.AwsRegion),
 | 
			
		||||
		Credentials: credentials.NewSharedCredentials("", c.Conf.AwsProfile),
 | 
			
		||||
	}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write results to S3
 | 
			
		||||
func (w S3Writer) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
 | 
			
		||||
	var jsonBytes []byte
 | 
			
		||||
	if jsonBytes, err = json.Marshal(scanResults); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
func putObject(svc *s3.S3, k string, b []byte) error {
 | 
			
		||||
	var err error
 | 
			
		||||
	if c.Conf.GZIP {
 | 
			
		||||
		if b, err = gz(b); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		k = k + ".gz"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// http://docs.aws.amazon.com/sdk-for-go/latest/v1/developerguide/common-examples.title.html
 | 
			
		||||
	svc := getS3()
 | 
			
		||||
	timestr := time.Now().Format("20060102_1504")
 | 
			
		||||
	key := fmt.Sprintf("%s/%s", timestr, "all.json")
 | 
			
		||||
	_, err = svc.PutObject(&s3.PutObjectInput{
 | 
			
		||||
		Bucket: &c.Conf.S3Bucket,
 | 
			
		||||
		Key:    &key,
 | 
			
		||||
		Body:   bytes.NewReader(jsonBytes),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to upload data to %s/%s, %s", c.Conf.S3Bucket, key, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range scanResults {
 | 
			
		||||
		key := ""
 | 
			
		||||
		if r.Container.ContainerID == "" {
 | 
			
		||||
			key = fmt.Sprintf("%s/%s.json", timestr, r.ServerName)
 | 
			
		||||
		} else {
 | 
			
		||||
			key = fmt.Sprintf("%s/%s_%s.json", timestr, r.ServerName, r.Container.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if jsonBytes, err = json.Marshal(r); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		_, err = svc.PutObject(&s3.PutObjectInput{
 | 
			
		||||
			Bucket: &c.Conf.S3Bucket,
 | 
			
		||||
			Key:    &key,
 | 
			
		||||
			Body:   bytes.NewReader(jsonBytes),
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to upload data to %s/%s, %s", c.Conf.S3Bucket, key, err)
 | 
			
		||||
		}
 | 
			
		||||
	if _, err := svc.PutObject(&s3.PutObjectInput{
 | 
			
		||||
		Bucket: aws.String(c.Conf.S3Bucket),
 | 
			
		||||
		Key:    aws.String(path.Join(c.Conf.S3ResultsDir, 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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										341
									
								
								report/slack.go
									
									
									
									
									
								
							
							
						
						@@ -20,14 +20,16 @@ package report
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/nlopes/slack"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type field struct {
 | 
			
		||||
@@ -35,122 +37,207 @@ type field struct {
 | 
			
		||||
	Value string `json:"value"`
 | 
			
		||||
	Short bool   `json:"short"`
 | 
			
		||||
}
 | 
			
		||||
type attachment struct {
 | 
			
		||||
	Title     string   `json:"title"`
 | 
			
		||||
	TitleLink string   `json:"title_link"`
 | 
			
		||||
	Fallback  string   `json:"fallback"`
 | 
			
		||||
	Text      string   `json:"text"`
 | 
			
		||||
	Pretext   string   `json:"pretext"`
 | 
			
		||||
	Color     string   `json:"color"`
 | 
			
		||||
	Fields    []*field `json:"fields"`
 | 
			
		||||
	MrkdwnIn  []string `json:"mrkdwn_in"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type message struct {
 | 
			
		||||
	Text        string        `json:"text"`
 | 
			
		||||
	Username    string        `json:"username"`
 | 
			
		||||
	IconEmoji   string        `json:"icon_emoji"`
 | 
			
		||||
	Channel     string        `json:"channel"`
 | 
			
		||||
	Attachments []*attachment `json:"attachments"`
 | 
			
		||||
	Text            string             `json:"text"`
 | 
			
		||||
	Username        string             `json:"username"`
 | 
			
		||||
	IconEmoji       string             `json:"icon_emoji"`
 | 
			
		||||
	Channel         string             `json:"channel"`
 | 
			
		||||
	ThreadTimeStamp string             `json:"thread_ts"`
 | 
			
		||||
	Attachments     []slack.Attachment `json:"attachments"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SlackWriter send report to slack
 | 
			
		||||
type SlackWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w SlackWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
func (w SlackWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf.Slack
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
	channel := conf.Channel
 | 
			
		||||
	token := conf.LegacyToken
 | 
			
		||||
 | 
			
		||||
		channel := conf.Channel
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		if channel == "${servername}" {
 | 
			
		||||
			channel = fmt.Sprintf("#%s", s.ServerName)
 | 
			
		||||
			channel = fmt.Sprintf("#%s", r.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		msg := message{
 | 
			
		||||
			Text:        msgText(s),
 | 
			
		||||
			Username:    conf.AuthUser,
 | 
			
		||||
			IconEmoji:   conf.IconEmoji,
 | 
			
		||||
			Channel:     channel,
 | 
			
		||||
			Attachments: toSlackAttachments(s),
 | 
			
		||||
		if 0 < len(r.Errors) {
 | 
			
		||||
			serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
 | 
			
		||||
			notifyUsers := getNotifyUsers(config.Conf.Slack.NotifyUsers)
 | 
			
		||||
			txt := fmt.Sprintf("%s\n%s\nError: %s",
 | 
			
		||||
				notifyUsers, serverInfo, r.Errors)
 | 
			
		||||
			msg := message{
 | 
			
		||||
				Text:      txt,
 | 
			
		||||
				Username:  conf.AuthUser,
 | 
			
		||||
				IconEmoji: conf.IconEmoji,
 | 
			
		||||
				Channel:   channel,
 | 
			
		||||
			}
 | 
			
		||||
			if err = send(msg); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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]
 | 
			
		||||
		// A maximum of 100 attachments are allowed on a message.
 | 
			
		||||
		// Split into chunks with 100 elements
 | 
			
		||||
		// https://api.slack.com/methods/chat.postMessage
 | 
			
		||||
		maxAttachments := 100
 | 
			
		||||
		m := map[int][]slack.Attachment{}
 | 
			
		||||
		for i, a := range toSlackAttachments(r) {
 | 
			
		||||
			m[i/maxAttachments] = append(m[i/maxAttachments], a)
 | 
			
		||||
		}
 | 
			
		||||
		chunkKeys := []int{}
 | 
			
		||||
		for k := range m {
 | 
			
		||||
			chunkKeys = append(chunkKeys, k)
 | 
			
		||||
		}
 | 
			
		||||
		sort.Ints(chunkKeys)
 | 
			
		||||
 | 
			
		||||
		// Send slack by API
 | 
			
		||||
		if 0 < len(token) {
 | 
			
		||||
			api := slack.New(token)
 | 
			
		||||
			ParentMsg := slack.PostMessageParameters{
 | 
			
		||||
				//			Text:      msgText(r),
 | 
			
		||||
				Username:  conf.AuthUser,
 | 
			
		||||
				IconEmoji: conf.IconEmoji,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var ts string
 | 
			
		||||
			if _, ts, err = api.PostMessage(channel, msgText(r), ParentMsg); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, k := range chunkKeys {
 | 
			
		||||
				params := slack.PostMessageParameters{
 | 
			
		||||
					//		Text:            msgText(r),
 | 
			
		||||
					Username:        conf.AuthUser,
 | 
			
		||||
					IconEmoji:       conf.IconEmoji,
 | 
			
		||||
					Attachments:     m[k],
 | 
			
		||||
					ThreadTimestamp: ts,
 | 
			
		||||
				}
 | 
			
		||||
				if _, _, err = api.PostMessage(channel, msgText(r), params); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			for i, k := range chunkKeys {
 | 
			
		||||
				txt := ""
 | 
			
		||||
				if i == 0 {
 | 
			
		||||
					txt = msgText(r)
 | 
			
		||||
				}
 | 
			
		||||
				msg := message{
 | 
			
		||||
					Text:        txt,
 | 
			
		||||
					Username:    conf.AuthUser,
 | 
			
		||||
					IconEmoji:   conf.IconEmoji,
 | 
			
		||||
					Channel:     channel,
 | 
			
		||||
					Attachments: m[k],
 | 
			
		||||
				}
 | 
			
		||||
				if err = send(msg); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		notify := func(err error, t time.Duration) {
 | 
			
		||||
			log.Warn("Retrying in ", t)
 | 
			
		||||
		}
 | 
			
		||||
		if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
 | 
			
		||||
			return fmt.Errorf("HTTP Error: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func msgText(r models.ScanResult) string {
 | 
			
		||||
func send(msg message) error {
 | 
			
		||||
	conf := config.Conf.Slack
 | 
			
		||||
	count, retryMax := 0, 10
 | 
			
		||||
 | 
			
		||||
	notifyUsers := ""
 | 
			
		||||
	if 0 < len(r.KnownCves) || 0 < len(r.UnknownCves) {
 | 
			
		||||
		notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers)
 | 
			
		||||
	bytes, _ := json.Marshal(msg)
 | 
			
		||||
	jsonBody := string(bytes)
 | 
			
		||||
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).Send(string(jsonBody)).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			count++
 | 
			
		||||
			if count == retryMax {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"HTTP POST error: %v, url: %s, resp: %v, body: %s",
 | 
			
		||||
				errs, conf.HookURL, resp, body)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, serverInfo, r.CveSummary())
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		log.Warnf("Error %s", err)
 | 
			
		||||
		log.Warn("Retrying in ", t)
 | 
			
		||||
	}
 | 
			
		||||
	boff := backoff.NewExponentialBackOff()
 | 
			
		||||
	if err := backoff.RetryNotify(f, boff, notify); err != nil {
 | 
			
		||||
		return fmt.Errorf("HTTP error: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if count == retryMax {
 | 
			
		||||
		return fmt.Errorf("Retry count exceeded")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
 | 
			
		||||
 | 
			
		||||
	cves := scanResult.KnownCves
 | 
			
		||||
	if !config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		cves = append(cves, scanResult.UnknownCves...)
 | 
			
		||||
func msgText(r models.ScanResult) string {
 | 
			
		||||
	notifyUsers := ""
 | 
			
		||||
	if 0 < len(r.ScannedCves) {
 | 
			
		||||
		notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers)
 | 
			
		||||
	}
 | 
			
		||||
	serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n>%s",
 | 
			
		||||
		notifyUsers,
 | 
			
		||||
		serverInfo,
 | 
			
		||||
		r.ScannedCves.FormatCveSummary())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	for _, cveInfo := range cves {
 | 
			
		||||
		cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
 | 
			
		||||
		curentPackages := []string{}
 | 
			
		||||
		for _, p := range cveInfo.Packages {
 | 
			
		||||
			curentPackages = append(curentPackages, p.ToStringCurrentVersion())
 | 
			
		||||
func toSlackAttachments(r models.ScanResult) (attaches []slack.Attachment) {
 | 
			
		||||
	vinfos := r.ScannedCves.ToSortedSlice()
 | 
			
		||||
	for _, vinfo := range vinfos {
 | 
			
		||||
		curent := []string{}
 | 
			
		||||
		for _, affected := range vinfo.AffectedPackages {
 | 
			
		||||
			if p, ok := r.Packages[affected.Name]; ok {
 | 
			
		||||
				curent = append(curent,
 | 
			
		||||
					fmt.Sprintf("%s-%s", p.Name, p.FormatVer()))
 | 
			
		||||
			} else {
 | 
			
		||||
				curent = append(curent, affected.Name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for _, cpename := range cveInfo.CpeNames {
 | 
			
		||||
			curentPackages = append(curentPackages, cpename.Name)
 | 
			
		||||
		for _, n := range vinfo.CpeNames {
 | 
			
		||||
			curent = append(curent, n)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		newPackages := []string{}
 | 
			
		||||
		for _, p := range cveInfo.Packages {
 | 
			
		||||
			newPackages = append(newPackages, p.ToStringNewVersion())
 | 
			
		||||
		new := []string{}
 | 
			
		||||
		for _, affected := range vinfo.AffectedPackages {
 | 
			
		||||
			if p, ok := r.Packages[affected.Name]; ok {
 | 
			
		||||
				if affected.NotFixedYet {
 | 
			
		||||
					new = append(new, "Not Fixed Yet")
 | 
			
		||||
				} else {
 | 
			
		||||
					new = append(new, p.FormatNewVer())
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				new = append(new, "?")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for range vinfo.CpeNames {
 | 
			
		||||
			new = append(new, "?")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a := attachment{
 | 
			
		||||
			Title:     cveID,
 | 
			
		||||
			TitleLink: fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID),
 | 
			
		||||
			Text:      attachmentText(cveInfo, scanResult.Family),
 | 
			
		||||
			MrkdwnIn:  []string{"text", "pretext"},
 | 
			
		||||
			Fields: []*field{
 | 
			
		||||
		a := slack.Attachment{
 | 
			
		||||
			Title:      vinfo.CveID,
 | 
			
		||||
			TitleLink:  "https://nvd.nist.gov/vuln/detail/" + vinfo.CveID,
 | 
			
		||||
			Text:       attachmentText(vinfo, r.Family),
 | 
			
		||||
			MarkdownIn: []string{"text", "pretext"},
 | 
			
		||||
			Fields: []slack.AttachmentField{
 | 
			
		||||
				{
 | 
			
		||||
					//  Title: "Current Package/CPE",
 | 
			
		||||
					// Title: "Current Package/CPE",
 | 
			
		||||
					Title: "Installed",
 | 
			
		||||
					Value: strings.Join(curentPackages, "\n"),
 | 
			
		||||
					Value: strings.Join(curent, "\n"),
 | 
			
		||||
					Short: true,
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Title: "Candidate",
 | 
			
		||||
					Value: strings.Join(newPackages, "\n"),
 | 
			
		||||
					Value: strings.Join(new, "\n"),
 | 
			
		||||
					Short: true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Color: color(cveInfo.CveDetail.CvssScore(config.Conf.Lang)),
 | 
			
		||||
			Color: color(vinfo.MaxCvssScore().Value.Score),
 | 
			
		||||
		}
 | 
			
		||||
		attaches = append(attaches, &a)
 | 
			
		||||
		attaches = append(attaches, a)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
@@ -169,58 +256,72 @@ func color(cvssScore float64) string {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func attachmentText(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
func attachmentText(vinfo models.VulnInfo, osFamily string) string {
 | 
			
		||||
	maxCvss := vinfo.MaxCvssScore()
 | 
			
		||||
	vectors := []string{}
 | 
			
		||||
	for _, cvss := range vinfo.Cvss2Scores() {
 | 
			
		||||
		calcURL := ""
 | 
			
		||||
		switch cvss.Value.Type {
 | 
			
		||||
		case models.CVSS2:
 | 
			
		||||
			calcURL = fmt.Sprintf(
 | 
			
		||||
				"https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=%s",
 | 
			
		||||
				vinfo.CveID)
 | 
			
		||||
		case models.CVSS3:
 | 
			
		||||
			calcURL = fmt.Sprintf(
 | 
			
		||||
				"https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=%s",
 | 
			
		||||
				vinfo.CveID)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	linkText := links(cveInfo, osFamily)
 | 
			
		||||
		if cont, ok := vinfo.CveContents[cvss.Type]; ok {
 | 
			
		||||
			v := fmt.Sprintf("<%s|%s> (<%s|%s>)",
 | 
			
		||||
				calcURL,
 | 
			
		||||
				cvss.Value.Format(),
 | 
			
		||||
				cont.SourceLink,
 | 
			
		||||
				cvss.Type)
 | 
			
		||||
			vectors = append(vectors, v)
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case config.Conf.Lang == "ja" &&
 | 
			
		||||
		0 < cveInfo.CveDetail.Jvn.CvssScore():
 | 
			
		||||
		} else {
 | 
			
		||||
			if 0 < len(vinfo.DistroAdvisories) {
 | 
			
		||||
				links := []string{}
 | 
			
		||||
				for k, v := range vinfo.VendorLinks(osFamily) {
 | 
			
		||||
					links = append(links, fmt.Sprintf("<%s|%s>",
 | 
			
		||||
						v, k))
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
		jvn := cveInfo.CveDetail.Jvn
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
 | 
			
		||||
			cveInfo.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
			jvn.CvssSeverity(),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, jvn.CvssVector()),
 | 
			
		||||
			jvn.CvssVector(),
 | 
			
		||||
			jvn.CveTitle(),
 | 
			
		||||
			linkText,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
	case 0 < cveInfo.CveDetail.CvssScore("en"):
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
 | 
			
		||||
			cveInfo.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
			nvd.CvssSeverity(),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, nvd.CvssVector()),
 | 
			
		||||
			nvd.CvssVector(),
 | 
			
		||||
			nvd.CveSummary(),
 | 
			
		||||
			linkText,
 | 
			
		||||
		)
 | 
			
		||||
	default:
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		return fmt.Sprintf("?\n%s\n%s", nvd.CveSummary(), linkText)
 | 
			
		||||
				v := fmt.Sprintf("<%s|%s> (%s)",
 | 
			
		||||
					calcURL,
 | 
			
		||||
					cvss.Value.Format(),
 | 
			
		||||
					strings.Join(links, ", "))
 | 
			
		||||
				vectors = append(vectors, v)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	severity := strings.ToUpper(maxCvss.Value.Severity)
 | 
			
		||||
	if severity == "" {
 | 
			
		||||
		severity = "?"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("*%4.1f (%s)* %s\n%s\n```%s```",
 | 
			
		||||
		maxCvss.Value.Score,
 | 
			
		||||
		severity,
 | 
			
		||||
		cweIDs(vinfo, osFamily),
 | 
			
		||||
		strings.Join(vectors, "\n"),
 | 
			
		||||
		vinfo.Summaries(config.Conf.Lang, osFamily)[0].Value,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func links(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
func cweIDs(vinfo models.VulnInfo, osFamily string) string {
 | 
			
		||||
	links := []string{}
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
	if config.Conf.Lang == "ja" && 0 < len(cveInfo.CveDetail.Jvn.Link()) {
 | 
			
		||||
		jvn := fmt.Sprintf("<%s|JVN>", cveInfo.CveDetail.Jvn.Link())
 | 
			
		||||
		links = append(links, jvn)
 | 
			
		||||
	for _, cwe := range vinfo.CveContents.CweIDs(osFamily) {
 | 
			
		||||
		if config.Conf.Lang == "ja" {
 | 
			
		||||
			links = append(links, fmt.Sprintf("<%s|%s>",
 | 
			
		||||
				cweJvnURL(cwe.Value), cwe.Value))
 | 
			
		||||
		} else {
 | 
			
		||||
			links = append(links, fmt.Sprintf("<%s|%s>",
 | 
			
		||||
				cweURL(cwe.Value), cwe.Value))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	links = append(links, fmt.Sprintf("<%s|CVEDetails>",
 | 
			
		||||
		fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID)))
 | 
			
		||||
	links = append(links, fmt.Sprintf("<%s|MITRE>",
 | 
			
		||||
		fmt.Sprintf("%s%s", mitreBaseURL, cveID)))
 | 
			
		||||
 | 
			
		||||
	dlinks := distroLinks(cveInfo, osFamily)
 | 
			
		||||
	for _, link := range dlinks {
 | 
			
		||||
		links = append(links,
 | 
			
		||||
			fmt.Sprintf("<%s|%s>", link.url, link.title))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return strings.Join(links, " / ")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,19 +20,40 @@ package report
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// StdoutWriter write to stdout
 | 
			
		||||
type StdoutWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w StdoutWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		text, err := toPlainText(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
// WriteScanSummary prints Scan summary at the end of scan
 | 
			
		||||
func (w StdoutWriter) WriteScanSummary(rs ...models.ScanResult) {
 | 
			
		||||
	fmt.Printf("\n\n")
 | 
			
		||||
	fmt.Println("One Line Summary")
 | 
			
		||||
	fmt.Println("================")
 | 
			
		||||
	fmt.Printf("%s\n", formatScanSummary(rs...))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w StdoutWriter) Write(rs ...models.ScanResult) error {
 | 
			
		||||
	if c.Conf.FormatOneLineText {
 | 
			
		||||
		fmt.Print("\n\n")
 | 
			
		||||
		fmt.Println("One Line Summary")
 | 
			
		||||
		fmt.Println("================")
 | 
			
		||||
		fmt.Println(formatOneLineSummary(rs...))
 | 
			
		||||
		fmt.Print("\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.FormatShortText {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			fmt.Println(formatShortPlainText(r))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.FormatFullText {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			fmt.Println(formatFullPlainText(r))
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println(text)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,63 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TextFileWriter writes results to file.
 | 
			
		||||
type TextFileWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w TextFileWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
 | 
			
		||||
	path, err := ensureResultDir()
 | 
			
		||||
 | 
			
		||||
	all := []string{}
 | 
			
		||||
	for _, r := range scanResults {
 | 
			
		||||
		textFilePath := ""
 | 
			
		||||
		if r.Container.ContainerID == "" {
 | 
			
		||||
			textFilePath = filepath.Join(path, fmt.Sprintf("%s.txt", r.ServerName))
 | 
			
		||||
		} else {
 | 
			
		||||
			textFilePath = filepath.Join(path,
 | 
			
		||||
				fmt.Sprintf("%s_%s.txt", r.ServerName, r.Container.Name))
 | 
			
		||||
		}
 | 
			
		||||
		text, err := toPlainText(r)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		all = append(all, text)
 | 
			
		||||
		b := []byte(text)
 | 
			
		||||
		if err := ioutil.WriteFile(textFilePath, b, 0644); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to write text files. path: %s, err: %s", textFilePath, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	text := strings.Join(all, "\n\n")
 | 
			
		||||
	b := []byte(text)
 | 
			
		||||
	allPath := filepath.Join(path, "all.txt")
 | 
			
		||||
	if err := ioutil.WriteFile(allPath, b, 0644); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to write text files. path: %s, err: %s", allPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										451
									
								
								report/tui.go
									
									
									
									
									
								
							
							
						
						@@ -20,65 +20,64 @@ package report
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"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/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"github.com/gosuri/uitable"
 | 
			
		||||
	"github.com/jroimartin/gocui"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var scanHistory models.ScanHistory
 | 
			
		||||
var scanResults models.ScanResults
 | 
			
		||||
var currentScanResult models.ScanResult
 | 
			
		||||
var currentCveInfo int
 | 
			
		||||
var vinfos []models.VulnInfo
 | 
			
		||||
var currentVinfo int
 | 
			
		||||
var currentDetailLimitY int
 | 
			
		||||
var currentChangelogLimitY int
 | 
			
		||||
 | 
			
		||||
// RunTui execute main logic
 | 
			
		||||
func RunTui(historyID string) subcommands.ExitStatus {
 | 
			
		||||
	var err error
 | 
			
		||||
	scanHistory, err = selectScanHistory(historyID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
func RunTui(results models.ScanResults) subcommands.ExitStatus {
 | 
			
		||||
	scanResults = results
 | 
			
		||||
	sort.Slice(scanResults, func(i, j int) bool {
 | 
			
		||||
		if scanResults[i].ServerName == scanResults[j].ServerName {
 | 
			
		||||
			return scanResults[i].Container.Name < scanResults[j].Container.Name
 | 
			
		||||
		}
 | 
			
		||||
		return scanResults[i].ServerName < scanResults[j].ServerName
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// g, err := gocui.NewGui(gocui.OutputNormal)
 | 
			
		||||
	g := gocui.NewGui()
 | 
			
		||||
	if err := g.Init(); err != nil {
 | 
			
		||||
		log.Panicln(err)
 | 
			
		||||
		log.Errorf("%s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	defer g.Close()
 | 
			
		||||
 | 
			
		||||
	g.SetLayout(layout)
 | 
			
		||||
	// g.SetManagerFunc(layout)
 | 
			
		||||
	if err := keybindings(g); err != nil {
 | 
			
		||||
		log.Panicln(err)
 | 
			
		||||
		log.Errorf("%s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	g.SelBgColor = gocui.ColorGreen
 | 
			
		||||
	g.SelFgColor = gocui.ColorBlack
 | 
			
		||||
	g.Cursor = true
 | 
			
		||||
 | 
			
		||||
	if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
 | 
			
		||||
		log.Panicln(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	if err := g.MainLoop(); err != nil {
 | 
			
		||||
		g.Close()
 | 
			
		||||
		log.Errorf("%s", err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func selectScanHistory(historyID string) (latest models.ScanHistory, err error) {
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		return latest, fmt.Errorf(
 | 
			
		||||
			"Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	latest, err = db.SelectScanHistory(historyID)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func keybindings(g *gocui.Gui) (err error) {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
@@ -145,6 +144,27 @@ func keybindings(g *gocui.Gui) (err error) {
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, nextView))
 | 
			
		||||
 | 
			
		||||
	// changelog
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyTab, gocui.ModNone, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlQ, gocui.ModNone, previousView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlH, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlL, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowUp, gocui.ModAlt, previousView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowLeft, gocui.ModAlt, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeySpace, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyEnter, gocui.ModNone, nextView))
 | 
			
		||||
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, showMsg))
 | 
			
		||||
 | 
			
		||||
@@ -163,56 +183,71 @@ func keybindings(g *gocui.Gui) (err error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func nextView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		return g.SetCurrentView("summary")
 | 
			
		||||
		err = g.SetCurrentView("summary")
 | 
			
		||||
	case "summary":
 | 
			
		||||
		return g.SetCurrentView("detail")
 | 
			
		||||
		err = g.SetCurrentView("detail")
 | 
			
		||||
	case "detail":
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
		err = g.SetCurrentView("changelog")
 | 
			
		||||
	case "changelog":
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
	default:
 | 
			
		||||
		return g.SetCurrentView("summary")
 | 
			
		||||
		err = g.SetCurrentView("summary")
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func previousView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
	case "summary":
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
	case "detail":
 | 
			
		||||
		return g.SetCurrentView("summary")
 | 
			
		||||
		err = g.SetCurrentView("summary")
 | 
			
		||||
	case "changelog":
 | 
			
		||||
		err = g.SetCurrentView("detail")
 | 
			
		||||
	default:
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		yLimit = len(scanHistory.ScanResults) - 1
 | 
			
		||||
		yLimit = len(scanResults) - 1
 | 
			
		||||
		if yLimit < nextY {
 | 
			
		||||
			return false, yLimit
 | 
			
		||||
		}
 | 
			
		||||
		return true, yLimit
 | 
			
		||||
	case "summary":
 | 
			
		||||
		yLimit = len(currentScanResult.KnownCves) - 1
 | 
			
		||||
		yLimit = len(currentScanResult.ScannedCves) - 1
 | 
			
		||||
		if yLimit < nextY {
 | 
			
		||||
			return false, yLimit
 | 
			
		||||
		}
 | 
			
		||||
		return true, yLimit
 | 
			
		||||
	case "detail":
 | 
			
		||||
		if currentDetailLimitY < nextY {
 | 
			
		||||
			return false, currentDetailLimitY
 | 
			
		||||
		}
 | 
			
		||||
		// if currentDetailLimitY < nextY {
 | 
			
		||||
		// return false, currentDetailLimitY
 | 
			
		||||
		// }
 | 
			
		||||
		return true, currentDetailLimitY
 | 
			
		||||
	case "changelog":
 | 
			
		||||
		// if currentChangelogLimitY < nextY {
 | 
			
		||||
		// return false, currentChangelogLimitY
 | 
			
		||||
		// }
 | 
			
		||||
		return true, currentChangelogLimitY
 | 
			
		||||
	default:
 | 
			
		||||
		return true, 0
 | 
			
		||||
	}
 | 
			
		||||
@@ -223,7 +258,7 @@ func pageUpDownJumpCount(v *gocui.View) int {
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side", "summary":
 | 
			
		||||
		jump = 8
 | 
			
		||||
	case "detail":
 | 
			
		||||
	case "detail", "changelog":
 | 
			
		||||
		jump = 30
 | 
			
		||||
	default:
 | 
			
		||||
		jump = 8
 | 
			
		||||
@@ -238,6 +273,9 @@ func onMovingCursorRedrawView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
		if err := redrawDetail(g); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := redrawChangelog(g); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case "side":
 | 
			
		||||
		if err := changeHost(g, v); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
@@ -253,7 +291,7 @@ func cursorDown(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
		//  ok,  := movable(v, oy+cy+1)
 | 
			
		||||
		//  _, maxY := v.Size()
 | 
			
		||||
		ok, _ := movable(v, oy+cy+1)
 | 
			
		||||
		//  log.Info(cy, oy, maxY, yLimit)
 | 
			
		||||
		//  log.Info(cy, oy)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
@@ -264,6 +302,10 @@ func cursorDown(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
		}
 | 
			
		||||
		onMovingCursorRedrawView(g, v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cx, cy := v.Cursor()
 | 
			
		||||
	ox, oy := v.Origin()
 | 
			
		||||
	debug(g, fmt.Sprintf("%v, %v, %v, %v", cx, cy, ox, oy))
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -332,7 +374,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
 | 
			
		||||
			}
 | 
			
		||||
@@ -401,6 +443,9 @@ func changeHost(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if err := g.DeleteView("detail"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := g.DeleteView("changelog"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, cy := v.Cursor()
 | 
			
		||||
	l, err := v.Line(cy)
 | 
			
		||||
@@ -409,9 +454,10 @@ func changeHost(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	}
 | 
			
		||||
	serverName := strings.TrimSpace(l)
 | 
			
		||||
 | 
			
		||||
	for _, r := range scanHistory.ScanResults {
 | 
			
		||||
	for _, r := range scanResults {
 | 
			
		||||
		if serverName == strings.TrimSpace(r.ServerInfoTui()) {
 | 
			
		||||
			currentScanResult = r
 | 
			
		||||
			vinfos = r.ScannedCves.ToSortedSlice()
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -422,6 +468,9 @@ func changeHost(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if err := setDetailLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := setChangelogLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -436,6 +485,17 @@ func redrawDetail(g *gocui.Gui) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func redrawChangelog(g *gocui.Gui) error {
 | 
			
		||||
	if err := g.DeleteView("changelog"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := setChangelogLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getLine(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	var l string
 | 
			
		||||
	var err error
 | 
			
		||||
@@ -466,7 +526,8 @@ func showMsg(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	//  maxX, maxY := v.Size()
 | 
			
		||||
	_, maxY := v.Size()
 | 
			
		||||
 | 
			
		||||
	l := fmt.Sprintf("cy: %d, oy: %d, maxY: %d, yLimit: %d, curCve %d, ok: %v", cy, oy, maxY, yLimit, currentCveInfo, ok)
 | 
			
		||||
	l := fmt.Sprintf("cy: %d, oy: %d, maxY: %d, yLimit: %d, curCve %d, ok: %v",
 | 
			
		||||
		cy, oy, maxY, yLimit, currentVinfo, ok)
 | 
			
		||||
	//  if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
 | 
			
		||||
	if v, err := g.SetView("msg", 10, maxY/2, 10+50, maxY/2+2); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
@@ -504,21 +565,42 @@ func layout(g *gocui.Gui) error {
 | 
			
		||||
	if err := setDetailLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := setChangelogLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func debug(g *gocui.Gui, str string) error {
 | 
			
		||||
	if config.Conf.Debug {
 | 
			
		||||
		maxX, maxY := g.Size()
 | 
			
		||||
		if _, err := g.View("debug"); err != gocui.ErrUnknownView {
 | 
			
		||||
			g.DeleteView("debug")
 | 
			
		||||
		}
 | 
			
		||||
		if v, err := g.SetView("debug", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil {
 | 
			
		||||
			fmt.Fprintf(v, str)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setSideLayout(g *gocui.Gui) error {
 | 
			
		||||
	_, maxY := g.Size()
 | 
			
		||||
	if v, err := g.SetView("side", -1, -1, 40, maxY); err != nil {
 | 
			
		||||
	if v, err := g.SetView("side", -1, -1, 40, int(float64(maxY)*0.2)); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		v.Highlight = true
 | 
			
		||||
 | 
			
		||||
		for _, result := range scanHistory.ScanResults {
 | 
			
		||||
		for _, result := range scanResults {
 | 
			
		||||
			fmt.Fprintln(v, result.ServerInfoTui())
 | 
			
		||||
		}
 | 
			
		||||
		currentScanResult = scanHistory.ScanResults[0]
 | 
			
		||||
		if len(scanResults) == 0 {
 | 
			
		||||
			return fmt.Errorf("No scan results")
 | 
			
		||||
		}
 | 
			
		||||
		currentScanResult = scanResults[0]
 | 
			
		||||
		vinfos = scanResults[0].ScannedCves.ToSortedSlice()
 | 
			
		||||
		if err := g.SetCurrentView("side"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
@@ -533,7 +615,7 @@ func setSummaryLayout(g *gocui.Gui) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lines := summaryLines(currentScanResult)
 | 
			
		||||
		lines := summaryLines()
 | 
			
		||||
		fmt.Fprintf(v, lines)
 | 
			
		||||
 | 
			
		||||
		v.Highlight = true
 | 
			
		||||
@@ -543,59 +625,38 @@ func setSummaryLayout(g *gocui.Gui) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func summaryLines(data models.ScanResult) string {
 | 
			
		||||
func summaryLines() string {
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = 1000
 | 
			
		||||
	stable.Wrap = false
 | 
			
		||||
 | 
			
		||||
	if len(currentScanResult.Errors) != 0 {
 | 
			
		||||
		return "Error: Scan with --debug to view the details"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	indexFormat := ""
 | 
			
		||||
	if len(data.KnownCves) < 10 {
 | 
			
		||||
	if len(currentScanResult.ScannedCves) < 10 {
 | 
			
		||||
		indexFormat = "[%1d]"
 | 
			
		||||
	} else if len(data.KnownCves) < 100 {
 | 
			
		||||
	} else if len(currentScanResult.ScannedCves) < 100 {
 | 
			
		||||
		indexFormat = "[%2d]"
 | 
			
		||||
	} else {
 | 
			
		||||
		indexFormat = "[%3d]"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, d := range data.KnownCves {
 | 
			
		||||
	for i, vinfo := range vinfos {
 | 
			
		||||
		summary := vinfo.Titles(
 | 
			
		||||
			config.Conf.Lang, currentScanResult.Family)[0].Value
 | 
			
		||||
		cvssScore := fmt.Sprintf("| %4.1f",
 | 
			
		||||
			vinfo.MaxCvssScore().Value.Score)
 | 
			
		||||
 | 
			
		||||
		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.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.CvssSeverity(),
 | 
			
		||||
				),
 | 
			
		||||
				//  strings.Join(packs, ","),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			summary := d.CveDetail.Nvd.CveSummary()
 | 
			
		||||
 | 
			
		||||
			var cvssScore string
 | 
			
		||||
			if d.CveDetail.CvssScore("en") <= 0 {
 | 
			
		||||
				cvssScore = "| ?"
 | 
			
		||||
			} else {
 | 
			
		||||
				cvssScore = fmt.Sprintf("| %-4.1f(%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Nvd.CvssSeverity(),
 | 
			
		||||
				)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			cols = []string{
 | 
			
		||||
				fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				cvssScore,
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		cols = []string{
 | 
			
		||||
			fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
			vinfo.CveID,
 | 
			
		||||
			cvssScore,
 | 
			
		||||
			fmt.Sprintf("| %3d |", vinfo.Confidence.Score),
 | 
			
		||||
			summary,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		icols := make([]interface{}, len(cols))
 | 
			
		||||
		for j := range cols {
 | 
			
		||||
			icols[j] = cols[j]
 | 
			
		||||
@@ -614,16 +675,12 @@ func setDetailLayout(g *gocui.Gui) error {
 | 
			
		||||
	}
 | 
			
		||||
	_, cy := summaryView.Cursor()
 | 
			
		||||
	_, oy := summaryView.Origin()
 | 
			
		||||
	currentCveInfo = cy + oy
 | 
			
		||||
	currentVinfo = cy + oy
 | 
			
		||||
 | 
			
		||||
	if v, err := g.SetView("detail", 40, int(float64(maxY)*0.2), maxX, maxY); err != nil {
 | 
			
		||||
	if v, err := g.SetView("detail", -1, int(float64(maxY)*0.2), int(float64(maxX)*0.5), maxY); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		//  text := report.ToPlainTextDetailsLangEn(
 | 
			
		||||
		//      currentScanResult.KnownCves[currentCveInfo],
 | 
			
		||||
		//      currentScanResult.Family)
 | 
			
		||||
 | 
			
		||||
		text, err := detailLines()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
@@ -637,88 +694,133 @@ func setDetailLayout(g *gocui.Gui) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setChangelogLayout(g *gocui.Gui) error {
 | 
			
		||||
	maxX, maxY := g.Size()
 | 
			
		||||
 | 
			
		||||
	summaryView, err := g.View("summary")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	_, cy := summaryView.Cursor()
 | 
			
		||||
	_, oy := summaryView.Origin()
 | 
			
		||||
	currentVinfo = cy + oy
 | 
			
		||||
 | 
			
		||||
	if v, err := g.SetView("changelog", int(float64(maxX)*0.5), int(float64(maxY)*0.2), maxX, maxY); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if len(currentScanResult.Errors) != 0 || len(currentScanResult.ScannedCves) == 0 {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lines := []string{}
 | 
			
		||||
		vinfo := vinfos[currentVinfo]
 | 
			
		||||
		for _, adv := range vinfo.DistroAdvisories {
 | 
			
		||||
			lines = append(lines, adv.Format())
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, affected := range vinfo.AffectedPackages {
 | 
			
		||||
			pack := currentScanResult.Packages[affected.Name]
 | 
			
		||||
			for _, p := range currentScanResult.Packages {
 | 
			
		||||
				if pack.Name == p.Name {
 | 
			
		||||
					lines = append(lines, p.FormatChangelog(), "\n")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		text := strings.Join(lines, "\n")
 | 
			
		||||
		fmt.Fprint(v, text)
 | 
			
		||||
		v.Editable = false
 | 
			
		||||
		v.Wrap = true
 | 
			
		||||
 | 
			
		||||
		currentChangelogLimitY = len(strings.Split(text, "\n")) - 1
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dataForTmpl struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	CvssScore        string
 | 
			
		||||
	CvssVector       string
 | 
			
		||||
	CvssSeverity     string
 | 
			
		||||
	Cvsses           string
 | 
			
		||||
	Summary          string
 | 
			
		||||
	VulnSiteLinks    []string
 | 
			
		||||
	References       []cve.Reference
 | 
			
		||||
	Confidence       models.Confidence
 | 
			
		||||
	Cwes             []models.CveContentStr
 | 
			
		||||
	Links            []string
 | 
			
		||||
	References       []models.Reference
 | 
			
		||||
	Packages         []string
 | 
			
		||||
	CpeNames         []models.CpeName
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
	PublishedDate    time.Time
 | 
			
		||||
	LastModifiedDate time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detailLines() (string, error) {
 | 
			
		||||
	if len(currentScanResult.KnownCves) == 0 {
 | 
			
		||||
	r := currentScanResult
 | 
			
		||||
	if len(r.Errors) != 0 {
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(r.ScannedCves) == 0 {
 | 
			
		||||
		return "No vulnerable packages", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveInfo := currentScanResult.KnownCves[currentCveInfo]
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
 | 
			
		||||
	tmpl, err := template.New("detail").Parse(detailTemplate())
 | 
			
		||||
	tmpl, err := template.New("detail").Parse(mdTemplate)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cvssSeverity, cvssVector, summary string
 | 
			
		||||
	var refs []cve.Reference
 | 
			
		||||
	switch {
 | 
			
		||||
	case config.Conf.Lang == "ja" &&
 | 
			
		||||
		0 < cveInfo.CveDetail.Jvn.CvssScore():
 | 
			
		||||
		jvn := cveInfo.CveDetail.Jvn
 | 
			
		||||
		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.CvssSeverity()
 | 
			
		||||
		cvssVector = nvd.CvssVector()
 | 
			
		||||
		summary = nvd.CveSummary()
 | 
			
		||||
		refs = nvd.VulnSiteReferences()
 | 
			
		||||
	vinfo := vinfos[currentVinfo]
 | 
			
		||||
 | 
			
		||||
	packsVer := []string{}
 | 
			
		||||
	vinfo.AffectedPackages.Sort()
 | 
			
		||||
	for _, affected := range vinfo.AffectedPackages {
 | 
			
		||||
		// packages detected by OVAL may not be actually installed
 | 
			
		||||
		if pack, ok := r.Packages[affected.Name]; ok {
 | 
			
		||||
			packsVer = append(packsVer, pack.FormatVersionFromTo(affected.NotFixedYet))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sort.Strings(vinfo.CpeNames)
 | 
			
		||||
	for _, name := range vinfo.CpeNames {
 | 
			
		||||
		packsVer = append(packsVer, name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	links := []string{
 | 
			
		||||
		fmt.Sprintf("[NVD]( %s )", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID)),
 | 
			
		||||
		fmt.Sprintf("[MITRE]( %s )", fmt.Sprintf("%s%s", mitreBaseURL, cveID)),
 | 
			
		||||
		fmt.Sprintf("[CveDetais]( %s )", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID)),
 | 
			
		||||
		fmt.Sprintf("[CVSSv2 Caluclator]( %s )", fmt.Sprintf(cvssV2CalcURLTemplate, cveID, cvssVector)),
 | 
			
		||||
	}
 | 
			
		||||
	dlinks := distroLinks(cveInfo, currentScanResult.Family)
 | 
			
		||||
	for _, link := range dlinks {
 | 
			
		||||
		links = append(links, fmt.Sprintf("[%s]( %s )", link.title, link.url))
 | 
			
		||||
	links := []string{vinfo.CveContents.SourceLinks(
 | 
			
		||||
		config.Conf.Lang, r.Family, vinfo.CveID)[0].Value,
 | 
			
		||||
		vinfo.Cvss2CalcURL(),
 | 
			
		||||
		vinfo.Cvss3CalcURL()}
 | 
			
		||||
	for _, url := range vinfo.VendorLinks(r.Family) {
 | 
			
		||||
		links = append(links, url)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cvssScore string
 | 
			
		||||
	if cveInfo.CveDetail.CvssScore(config.Conf.Lang) == -1 {
 | 
			
		||||
		cvssScore = "?"
 | 
			
		||||
	} else {
 | 
			
		||||
		cvssScore = fmt.Sprintf("%4.1f", cveInfo.CveDetail.CvssScore(config.Conf.Lang))
 | 
			
		||||
	refs := []models.Reference{}
 | 
			
		||||
	for _, rr := range vinfo.CveContents.References(r.Family) {
 | 
			
		||||
		for _, ref := range rr.Value {
 | 
			
		||||
			refs = append(refs, ref)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packages := []string{}
 | 
			
		||||
	for _, pack := range cveInfo.Packages {
 | 
			
		||||
		packages = append(packages,
 | 
			
		||||
			fmt.Sprintf(
 | 
			
		||||
				"%s -> %s",
 | 
			
		||||
				pack.ToStringCurrentVersion(),
 | 
			
		||||
				pack.ToStringNewVersion()))
 | 
			
		||||
	summary := vinfo.Summaries(r.Lang, r.Family)[0]
 | 
			
		||||
 | 
			
		||||
	table := uitable.New()
 | 
			
		||||
	table.MaxColWidth = maxColWidth
 | 
			
		||||
	table.Wrap = true
 | 
			
		||||
	scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores()...)
 | 
			
		||||
	var cols []interface{}
 | 
			
		||||
	for _, score := range scores {
 | 
			
		||||
		cols = []interface{}{
 | 
			
		||||
			score.Value.Severity,
 | 
			
		||||
			score.Value.Format(),
 | 
			
		||||
			score.Type,
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow(cols...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data := dataForTmpl{
 | 
			
		||||
		CveID:         cveID,
 | 
			
		||||
		CvssScore:     cvssScore,
 | 
			
		||||
		CvssSeverity:  cvssSeverity,
 | 
			
		||||
		CvssVector:    cvssVector,
 | 
			
		||||
		Summary:       summary,
 | 
			
		||||
		VulnSiteLinks: links,
 | 
			
		||||
		References:    refs,
 | 
			
		||||
		Packages:      packages,
 | 
			
		||||
		CpeNames:      cveInfo.CpeNames,
 | 
			
		||||
		CveID:      vinfo.CveID,
 | 
			
		||||
		Cvsses:     fmt.Sprintf("%s\n", table),
 | 
			
		||||
		Summary:    fmt.Sprintf("%s (%s)", summary.Value, summary.Type),
 | 
			
		||||
		Confidence: vinfo.Confidence,
 | 
			
		||||
		Cwes:       vinfo.CveContents.CweIDs(r.Family),
 | 
			
		||||
		Links:      util.Distinct(links),
 | 
			
		||||
		Packages:   packsVer,
 | 
			
		||||
		References: refs,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := bytes.NewBuffer(nil) // create empty buffer
 | 
			
		||||
@@ -729,44 +831,49 @@ func detailLines() (string, error) {
 | 
			
		||||
	return string(buf.Bytes()), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  * {{.Name}}-{{.Version}}-{{.Release}}
 | 
			
		||||
 | 
			
		||||
func detailTemplate() string {
 | 
			
		||||
	return `
 | 
			
		||||
const mdTemplate = `
 | 
			
		||||
{{.CveID}}
 | 
			
		||||
==============
 | 
			
		||||
 | 
			
		||||
CVSS Score
 | 
			
		||||
CVSS Scores
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{.CvssScore}} ({{.CvssSeverity}}) {{.CvssVector}}
 | 
			
		||||
{{.Cvsses }}
 | 
			
		||||
 | 
			
		||||
Summary
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
 {{.Summary }}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Links
 | 
			
		||||
--------------
 | 
			
		||||
{{range $link := .Links -}}
 | 
			
		||||
* {{$link}}
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
CWE
 | 
			
		||||
--------------
 | 
			
		||||
{{range .Cwes -}}
 | 
			
		||||
* {{.Value}} ({{.Type}})
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
Package/CPE
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range $pack := .Packages -}}
 | 
			
		||||
* {{$pack}}
 | 
			
		||||
{{end -}}
 | 
			
		||||
{{range .CpeNames -}}
 | 
			
		||||
* {{.Name}}
 | 
			
		||||
{{range $name := .CpeNames -}}
 | 
			
		||||
* {{$name}}
 | 
			
		||||
{{end}}
 | 
			
		||||
Links
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range $link := .VulnSiteLinks -}}
 | 
			
		||||
* {{$link}}
 | 
			
		||||
{{end}}
 | 
			
		||||
Confidence
 | 
			
		||||
--------------
 | 
			
		||||
 {{.Confidence }}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
References
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range .References -}}
 | 
			
		||||
* [{{.Source}}]( {{.Link}} )
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										750
									
								
								report/util.go
									
									
									
									
									
								
							
							
						
						@@ -18,361 +18,485 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/gosuri/uitable"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ensureResultDir() (path string, err error) {
 | 
			
		||||
	if resultDirPath != "" {
 | 
			
		||||
		return resultDirPath, nil
 | 
			
		||||
	}
 | 
			
		||||
const maxColWidth = 80
 | 
			
		||||
 | 
			
		||||
	const timeLayout = "20060102_1504"
 | 
			
		||||
	timedir := time.Now().Format(timeLayout)
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	dir := filepath.Join(wd, "results", timedir)
 | 
			
		||||
	if err := os.MkdirAll(dir, 0755); err != nil {
 | 
			
		||||
		return "", fmt.Errorf("Failed to create dir: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	symlinkPath := filepath.Join(wd, "results", "current")
 | 
			
		||||
	if _, err := os.Stat(symlinkPath); err == nil {
 | 
			
		||||
		if err := os.Remove(symlinkPath); err != nil {
 | 
			
		||||
			return "", fmt.Errorf(
 | 
			
		||||
				"Failed to remove symlink. path: %s, err: %s", symlinkPath, err)
 | 
			
		||||
func formatScanSummary(rs ...models.ScanResult) string {
 | 
			
		||||
	table := uitable.New()
 | 
			
		||||
	table.MaxColWidth = maxColWidth
 | 
			
		||||
	table.Wrap = true
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		var cols []interface{}
 | 
			
		||||
		if len(r.Errors) == 0 {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
				r.FormatServerName(),
 | 
			
		||||
				fmt.Sprintf("%s%s", r.Family, r.Release),
 | 
			
		||||
				r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
				r.FormatServerName(),
 | 
			
		||||
				"Error",
 | 
			
		||||
				"",
 | 
			
		||||
				"Run with --debug to view the details",
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow(cols...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := os.Symlink(dir, symlinkPath); err != nil {
 | 
			
		||||
		return "", fmt.Errorf(
 | 
			
		||||
			"Failed to create symlink: path: %s, err: %s", symlinkPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	return dir, nil
 | 
			
		||||
	return fmt.Sprintf("%s\n", table)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toPlainText(scanResult models.ScanResult) (string, error) {
 | 
			
		||||
	serverInfo := scanResult.ServerInfo()
 | 
			
		||||
 | 
			
		||||
	var buffer bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(serverInfo); i++ {
 | 
			
		||||
		buffer.WriteString("=")
 | 
			
		||||
func formatOneLineSummary(rs ...models.ScanResult) string {
 | 
			
		||||
	table := uitable.New()
 | 
			
		||||
	table.MaxColWidth = maxColWidth
 | 
			
		||||
	table.Wrap = true
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		var cols []interface{}
 | 
			
		||||
		if len(r.Errors) == 0 {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
				r.FormatServerName(),
 | 
			
		||||
				r.ScannedCves.FormatCveSummary(),
 | 
			
		||||
				r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
				r.FormatServerName(),
 | 
			
		||||
				"Error: Scan with --debug to view the details",
 | 
			
		||||
				"",
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow(cols...)
 | 
			
		||||
	}
 | 
			
		||||
	header := fmt.Sprintf("%s\n%s", serverInfo, buffer.String())
 | 
			
		||||
	return fmt.Sprintf("%s\n", table)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	if len(scanResult.KnownCves) == 0 && len(scanResult.UnknownCves) == 0 {
 | 
			
		||||
func formatShortPlainText(r models.ScanResult) string {
 | 
			
		||||
	header := r.FormatTextReportHeadedr()
 | 
			
		||||
	if len(r.Errors) != 0 {
 | 
			
		||||
		return fmt.Sprintf(
 | 
			
		||||
			"%s\nError: Scan with --debug to view the details\n%s\n\n",
 | 
			
		||||
			header, r.Errors)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(r.ScannedCves) == 0 {
 | 
			
		||||
		return fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
No unsecure packages.
 | 
			
		||||
`, header), nil
 | 
			
		||||
No CVE-IDs are found in updatable packages.
 | 
			
		||||
%s
 | 
			
		||||
	 `, header, r.Packages.FormatUpdatablePacksSummary())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	summary := ToPlainTextSummary(scanResult)
 | 
			
		||||
	scoredReport, unscoredReport := []string{}, []string{}
 | 
			
		||||
	scoredReport, unscoredReport = toPlainTextDetails(scanResult, scanResult.Family)
 | 
			
		||||
 | 
			
		||||
	scored := strings.Join(scoredReport, "\n\n")
 | 
			
		||||
 | 
			
		||||
	unscored := ""
 | 
			
		||||
	if !config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		unscored = strings.Join(unscoredReport, "\n\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	detail := fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
 | 
			
		||||
%s
 | 
			
		||||
`,
 | 
			
		||||
		scored,
 | 
			
		||||
		unscored,
 | 
			
		||||
	)
 | 
			
		||||
	text := fmt.Sprintf("%s\n%s\n%s\n", header, summary, detail)
 | 
			
		||||
 | 
			
		||||
	return text, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToPlainTextSummary format summary for plain text.
 | 
			
		||||
func ToPlainTextSummary(r models.ScanResult) string {
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = 84
 | 
			
		||||
	stable.MaxColWidth = maxColWidth
 | 
			
		||||
	stable.Wrap = true
 | 
			
		||||
	for _, vuln := range r.ScannedCves.ToSortedSlice() {
 | 
			
		||||
		summaries := vuln.Summaries(config.Conf.Lang, r.Family)
 | 
			
		||||
		links := vuln.CveContents.SourceLinks(
 | 
			
		||||
			config.Conf.Lang, r.Family, vuln.CveID)
 | 
			
		||||
 | 
			
		||||
	cves := r.KnownCves
 | 
			
		||||
	if !config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		cves = append(cves, r.UnknownCves...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, d := range cves {
 | 
			
		||||
		var scols []string
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case config.Conf.Lang == "ja" &&
 | 
			
		||||
			0 < d.CveDetail.Jvn.CvssScore():
 | 
			
		||||
 | 
			
		||||
			summary := d.CveDetail.Jvn.CveTitle()
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("%-4.1f (%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Jvn.CvssSeverity(),
 | 
			
		||||
				),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		case 0 < d.CveDetail.CvssScore("en"):
 | 
			
		||||
			summary := d.CveDetail.Nvd.CveSummary()
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("%-4.1f (%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Nvd.CvssSeverity(),
 | 
			
		||||
				),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				"?",
 | 
			
		||||
				d.CveDetail.Nvd.CveSummary(),
 | 
			
		||||
			}
 | 
			
		||||
		vlinks := []string{}
 | 
			
		||||
		for name, url := range vuln.VendorLinks(r.Family) {
 | 
			
		||||
			vlinks = append(vlinks, fmt.Sprintf("%s (%s)", url, name))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cvsses := ""
 | 
			
		||||
		for _, cvss := range vuln.Cvss2Scores() {
 | 
			
		||||
			cvsses += fmt.Sprintf("%s (%s)\n", cvss.Value.Format(), cvss.Type)
 | 
			
		||||
		}
 | 
			
		||||
		cvsses += vuln.Cvss2CalcURL() + "\n"
 | 
			
		||||
		for _, cvss := range vuln.Cvss3Scores() {
 | 
			
		||||
			cvsses += fmt.Sprintf("%s (%s)\n", cvss.Value.Format(), cvss.Type)
 | 
			
		||||
		}
 | 
			
		||||
		if 0 < len(vuln.Cvss3Scores()) {
 | 
			
		||||
			cvsses += vuln.Cvss3CalcURL() + "\n"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		maxCvss := vuln.FormatMaxCvssScore()
 | 
			
		||||
		rightCol := fmt.Sprintf(`%s
 | 
			
		||||
%s
 | 
			
		||||
---
 | 
			
		||||
%s
 | 
			
		||||
%s
 | 
			
		||||
%sConfidence: %v`,
 | 
			
		||||
			maxCvss,
 | 
			
		||||
			summaries[0].Value,
 | 
			
		||||
			links[0].Value,
 | 
			
		||||
			strings.Join(vlinks, "\n"),
 | 
			
		||||
			cvsses,
 | 
			
		||||
			//  packsVer,
 | 
			
		||||
			vuln.Confidence,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		leftCol := fmt.Sprintf("%s", vuln.CveID)
 | 
			
		||||
		scols := []string{leftCol, rightCol}
 | 
			
		||||
		cols := make([]interface{}, len(scols))
 | 
			
		||||
		for i := range cols {
 | 
			
		||||
			cols[i] = scols[i]
 | 
			
		||||
		}
 | 
			
		||||
		stable.AddRow(cols...)
 | 
			
		||||
		stable.AddRow("")
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s", stable)
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n", header, stable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
 | 
			
		||||
	for _, cve := range data.KnownCves {
 | 
			
		||||
		switch config.Conf.Lang {
 | 
			
		||||
		case "en":
 | 
			
		||||
			if 0 < cve.CveDetail.Nvd.CvssScore() {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
			} else {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
func formatFullPlainText(r models.ScanResult) string {
 | 
			
		||||
	header := r.FormatTextReportHeadedr()
 | 
			
		||||
	if len(r.Errors) != 0 {
 | 
			
		||||
		return fmt.Sprintf(
 | 
			
		||||
			"%s\nError: Scan with --debug to view the details\n%s\n\n",
 | 
			
		||||
			header, r.Errors)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(r.ScannedCves) == 0 {
 | 
			
		||||
		return fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
No CVE-IDs are found in updatable packages.
 | 
			
		||||
%s
 | 
			
		||||
	 `, header, r.Packages.FormatUpdatablePacksSummary())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	table := uitable.New()
 | 
			
		||||
	table.MaxColWidth = maxColWidth
 | 
			
		||||
	table.Wrap = true
 | 
			
		||||
	for _, vuln := range r.ScannedCves.ToSortedSlice() {
 | 
			
		||||
		table.AddRow(vuln.CveID)
 | 
			
		||||
		table.AddRow("----------------")
 | 
			
		||||
		table.AddRow("Max Score", vuln.FormatMaxCvssScore())
 | 
			
		||||
		for _, cvss := range vuln.Cvss2Scores() {
 | 
			
		||||
			table.AddRow(cvss.Type, cvss.Value.Format())
 | 
			
		||||
		}
 | 
			
		||||
		for _, cvss := range vuln.Cvss3Scores() {
 | 
			
		||||
			table.AddRow(cvss.Type, cvss.Value.Format())
 | 
			
		||||
		}
 | 
			
		||||
		if 0 < len(vuln.Cvss2Scores()) {
 | 
			
		||||
			table.AddRow("CVSSv2 Calc", vuln.Cvss2CalcURL())
 | 
			
		||||
		}
 | 
			
		||||
		if 0 < len(vuln.Cvss3Scores()) {
 | 
			
		||||
			table.AddRow("CVSSv3 Calc", vuln.Cvss3CalcURL())
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow("Summary", vuln.Summaries(
 | 
			
		||||
			config.Conf.Lang, r.Family)[0].Value)
 | 
			
		||||
 | 
			
		||||
		links := vuln.CveContents.SourceLinks(
 | 
			
		||||
			config.Conf.Lang, r.Family, vuln.CveID)
 | 
			
		||||
		table.AddRow("Source", links[0].Value)
 | 
			
		||||
 | 
			
		||||
		vlinks := vuln.VendorLinks(r.Family)
 | 
			
		||||
		for name, url := range vlinks {
 | 
			
		||||
			table.AddRow(name, url)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, v := range vuln.CveContents.CweIDs(r.Family) {
 | 
			
		||||
			table.AddRow(fmt.Sprintf("%s (%s)", v.Value, v.Type), cweURL(v.Value))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		packsVer := []string{}
 | 
			
		||||
		vuln.AffectedPackages.Sort()
 | 
			
		||||
		for _, affected := range vuln.AffectedPackages {
 | 
			
		||||
			if pack, ok := r.Packages[affected.Name]; ok {
 | 
			
		||||
				packsVer = append(packsVer, pack.FormatVersionFromTo(affected.NotFixedYet))
 | 
			
		||||
			}
 | 
			
		||||
		case "ja":
 | 
			
		||||
			if 0 < cve.CveDetail.Jvn.CvssScore() {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangJa(cve, osFamily))
 | 
			
		||||
			} else if 0 < cve.CveDetail.Nvd.CvssScore() {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
			} else {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
		}
 | 
			
		||||
		sort.Strings(vuln.CpeNames)
 | 
			
		||||
		for _, name := range vuln.CpeNames {
 | 
			
		||||
			packsVer = append(packsVer, name)
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow("Package/CPE", strings.Join(packsVer, "\n"))
 | 
			
		||||
		table.AddRow("Confidence", vuln.Confidence)
 | 
			
		||||
 | 
			
		||||
		table.AddRow("\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s\n%s", header, table)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cweURL(cweID string) string {
 | 
			
		||||
	return fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html",
 | 
			
		||||
		strings.TrimPrefix(cweID, "CWE-"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cweJvnURL(cweID string) string {
 | 
			
		||||
	return fmt.Sprintf("http://jvndb.jvn.jp/ja/cwe/%s.html", cweID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatChangelogs(r models.ScanResult) string {
 | 
			
		||||
	buf := []string{}
 | 
			
		||||
	for _, p := range r.Packages {
 | 
			
		||||
		if p.NewVersion == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		clog := p.FormatChangelog()
 | 
			
		||||
		buf = append(buf, clog, "\n\n")
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(buf, "\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func needToRefreshCve(r models.ScanResult) bool {
 | 
			
		||||
	if r.Lang != config.Conf.Lang {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, cve := range r.ScannedCves {
 | 
			
		||||
		if 0 < len(cve.CveContents) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func overwriteJSONFile(dir string, r models.ScanResult) error {
 | 
			
		||||
	before := config.Conf.FormatJSON
 | 
			
		||||
	beforeDiff := config.Conf.Diff
 | 
			
		||||
	config.Conf.FormatJSON = true
 | 
			
		||||
	config.Conf.Diff = false
 | 
			
		||||
	w := LocalFileWriter{CurrentDir: dir}
 | 
			
		||||
	if err := w.Write(r); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to write summary report: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	config.Conf.FormatJSON = before
 | 
			
		||||
	config.Conf.Diff = beforeDiff
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadPrevious(current models.ScanResults) (previous models.ScanResults, err error) {
 | 
			
		||||
	dirs, err := ListValidJSONDirs()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, result := range current {
 | 
			
		||||
		for _, dir := range dirs[1:] {
 | 
			
		||||
			var r *models.ScanResult
 | 
			
		||||
			path := filepath.Join(dir, result.ServerName+".json")
 | 
			
		||||
			if r, err = loadOneServerScanResult(path); err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if r.Family == result.Family && r.Release == result.Release {
 | 
			
		||||
				previous = append(previous, *r)
 | 
			
		||||
				util.Log.Infof("Previous json found: %s", path)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cve := range data.UnknownCves {
 | 
			
		||||
		unscoredReport = append(
 | 
			
		||||
			unscoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
	return previous, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func diff(curResults, preResults models.ScanResults) (diffed models.ScanResults, err error) {
 | 
			
		||||
	for _, current := range curResults {
 | 
			
		||||
		found := false
 | 
			
		||||
		var previous models.ScanResult
 | 
			
		||||
		for _, r := range preResults {
 | 
			
		||||
			if current.ServerName == r.ServerName {
 | 
			
		||||
				found = true
 | 
			
		||||
				previous = r
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if found {
 | 
			
		||||
			current.ScannedCves = getDiffCves(previous, current)
 | 
			
		||||
			packages := models.Packages{}
 | 
			
		||||
			for _, s := range current.ScannedCves {
 | 
			
		||||
				for _, affected := range s.AffectedPackages {
 | 
			
		||||
					p := current.Packages[affected.Name]
 | 
			
		||||
					packages[affected.Name] = p
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			current.Packages = packages
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		diffed = append(diffed, current)
 | 
			
		||||
	}
 | 
			
		||||
	return diffed, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getDiffCves(previous, current models.ScanResult) models.VulnInfos {
 | 
			
		||||
	previousCveIDsSet := map[string]bool{}
 | 
			
		||||
	for _, previousVulnInfo := range previous.ScannedCves {
 | 
			
		||||
		previousCveIDsSet[previousVulnInfo.CveID] = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	new := models.VulnInfos{}
 | 
			
		||||
	updated := models.VulnInfos{}
 | 
			
		||||
	for _, v := range current.ScannedCves {
 | 
			
		||||
		if previousCveIDsSet[v.CveID] {
 | 
			
		||||
			if isCveInfoUpdated(v.CveID, previous, current) {
 | 
			
		||||
				updated[v.CveID] = v
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			new[v.CveID] = v
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for cveID, vuln := range new {
 | 
			
		||||
		updated[cveID] = vuln
 | 
			
		||||
	}
 | 
			
		||||
	return updated
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isCveInfoUpdated(cveID string, previous, current models.ScanResult) bool {
 | 
			
		||||
	cTypes := []models.CveContentType{
 | 
			
		||||
		models.NVD,
 | 
			
		||||
		models.JVN,
 | 
			
		||||
		models.NewCveContentType(current.Family),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	prevLastModified := map[models.CveContentType]time.Time{}
 | 
			
		||||
	for _, c := range previous.ScannedCves {
 | 
			
		||||
		if cveID == c.CveID {
 | 
			
		||||
			for _, cType := range cTypes {
 | 
			
		||||
				content, _ := c.CveContents[cType]
 | 
			
		||||
				prevLastModified[cType] = content.LastModified
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	curLastModified := map[models.CveContentType]time.Time{}
 | 
			
		||||
	for _, c := range current.ScannedCves {
 | 
			
		||||
		if cveID == c.CveID {
 | 
			
		||||
			for _, cType := range cTypes {
 | 
			
		||||
				content, _ := c.CveContents[cType]
 | 
			
		||||
				curLastModified[cType] = content.LastModified
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cType := range cTypes {
 | 
			
		||||
		if equal := prevLastModified[cType].Equal(curLastModified[cType]); !equal {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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})$`)
 | 
			
		||||
 | 
			
		||||
// ListValidJSONDirs returns valid json directory as array
 | 
			
		||||
// Returned array is sorted so that recent directories are at the head
 | 
			
		||||
func ListValidJSONDirs() (dirs []string, err error) {
 | 
			
		||||
	var dirInfo []os.FileInfo
 | 
			
		||||
	if dirInfo, err = ioutil.ReadDir(config.Conf.ResultsDir); err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to read %s: %s",
 | 
			
		||||
			config.Conf.ResultsDir, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, d := range dirInfo {
 | 
			
		||||
		if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
 | 
			
		||||
			jsonDir := filepath.Join(config.Conf.ResultsDir, d.Name())
 | 
			
		||||
			dirs = append(dirs, jsonDir)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(dirs, func(i, j int) bool {
 | 
			
		||||
		return dirs[j] < dirs[i]
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSONDir returns
 | 
			
		||||
// If there is an arg, check if it is a valid format and return the corresponding path under results.
 | 
			
		||||
// If arg passed via PIPE (such as history subcommand), return that path.
 | 
			
		||||
// Otherwise, returns the path of the latest directory
 | 
			
		||||
func JSONDir(args []string) (string, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	dirs := []string{}
 | 
			
		||||
 | 
			
		||||
	if 0 < len(args) {
 | 
			
		||||
		if dirs, err = ListValidJSONDirs(); err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		path := filepath.Join(config.Conf.ResultsDir, args[0])
 | 
			
		||||
		for _, d := range dirs {
 | 
			
		||||
			ss := strings.Split(d, string(os.PathSeparator))
 | 
			
		||||
			timedir := ss[len(ss)-1]
 | 
			
		||||
			if timedir == args[0] {
 | 
			
		||||
				return path, nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return "", fmt.Errorf("Invalid path: %s", path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// PIPE
 | 
			
		||||
	if config.Conf.Pipe {
 | 
			
		||||
		bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", fmt.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		fields := strings.Fields(string(bytes))
 | 
			
		||||
		if 0 < len(fields) {
 | 
			
		||||
			return filepath.Join(config.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 = ListValidJSONDirs(); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if len(dirs) == 0 {
 | 
			
		||||
		return "", fmt.Errorf("No results under %s",
 | 
			
		||||
			config.Conf.ResultsDir)
 | 
			
		||||
	}
 | 
			
		||||
	return dirs[0], nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadScanResults read JSON data
 | 
			
		||||
func LoadScanResults(jsonDir string) (results models.ScanResults, err error) {
 | 
			
		||||
	var files []os.FileInfo
 | 
			
		||||
	if files, err = ioutil.ReadDir(jsonDir); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to read %s: %s", jsonDir, err)
 | 
			
		||||
	}
 | 
			
		||||
	for _, f := range files {
 | 
			
		||||
		if filepath.Ext(f.Name()) != ".json" || strings.HasSuffix(f.Name(), "_diff.json") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var r *models.ScanResult
 | 
			
		||||
		path := filepath.Join(jsonDir, f.Name())
 | 
			
		||||
		if r, err = loadOneServerScanResult(path); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		results = append(results, *r)
 | 
			
		||||
	}
 | 
			
		||||
	if len(results) == 0 {
 | 
			
		||||
		return nil, fmt.Errorf("There is no json file under %s", jsonDir)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
	dtable := uitable.New()
 | 
			
		||||
	dtable.MaxColWidth = 100
 | 
			
		||||
	dtable.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
	dtable.AddRow("Score", "?")
 | 
			
		||||
	dtable.AddRow("NVD",
 | 
			
		||||
		fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVE Details",
 | 
			
		||||
		fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
 | 
			
		||||
 | 
			
		||||
	dlinks := distroLinks(cveInfo, osFamily)
 | 
			
		||||
	for _, link := range dlinks {
 | 
			
		||||
		dtable.AddRow(link.title, link.url)
 | 
			
		||||
// loadOneServerScanResult read JSON data of one server
 | 
			
		||||
func loadOneServerScanResult(jsonFile string) (*models.ScanResult, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		data []byte
 | 
			
		||||
		err  error
 | 
			
		||||
	)
 | 
			
		||||
	if data, err = ioutil.ReadFile(jsonFile); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to read %s: %s", jsonFile, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
	if score := cveDetail.Jvn.CvssScore(); 0 < score {
 | 
			
		||||
		dtable.AddRow("Score",
 | 
			
		||||
			fmt.Sprintf("%4.1f (%s)",
 | 
			
		||||
				cveDetail.Jvn.CvssScore(),
 | 
			
		||||
				jvn.CvssSeverity(),
 | 
			
		||||
			))
 | 
			
		||||
	} else {
 | 
			
		||||
		dtable.AddRow("Score", "?")
 | 
			
		||||
	}
 | 
			
		||||
	dtable.AddRow("Vector", jvn.CvssVector())
 | 
			
		||||
	dtable.AddRow("Title", jvn.CveTitle())
 | 
			
		||||
	dtable.AddRow("Description", jvn.CveSummary())
 | 
			
		||||
 | 
			
		||||
	dtable.AddRow("JVN", jvn.Link())
 | 
			
		||||
	dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("MITRE", fmt.Sprintf("%s%s", mitreBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVE Details", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVSS Claculator", cveDetail.CvssV2CalculatorLink("ja"))
 | 
			
		||||
 | 
			
		||||
	dlinks := distroLinks(cveInfo, osFamily)
 | 
			
		||||
	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 toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
 | 
			
		||||
	cveDetail := d.CveDetail
 | 
			
		||||
	cveID := cveDetail.CveID
 | 
			
		||||
	nvd := cveDetail.Nvd
 | 
			
		||||
 | 
			
		||||
	dtable := uitable.New()
 | 
			
		||||
	//TODO resize
 | 
			
		||||
	dtable.MaxColWidth = 100
 | 
			
		||||
	dtable.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
 | 
			
		||||
	if score := cveDetail.Nvd.CvssScore(); 0 < score {
 | 
			
		||||
		dtable.AddRow("Score",
 | 
			
		||||
			fmt.Sprintf("%4.1f (%s)",
 | 
			
		||||
				cveDetail.Nvd.CvssScore(),
 | 
			
		||||
				nvd.CvssSeverity(),
 | 
			
		||||
			))
 | 
			
		||||
	} else {
 | 
			
		||||
		dtable.AddRow("Score", "?")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dtable.AddRow("Vector", nvd.CvssVector())
 | 
			
		||||
	dtable.AddRow("Summary", nvd.CveSummary())
 | 
			
		||||
	dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("MITRE", fmt.Sprintf("%s%s", mitreBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVE Details", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVSS Claculator", cveDetail.CvssV2CalculatorLink("en"))
 | 
			
		||||
 | 
			
		||||
	links := distroLinks(d, osFamily)
 | 
			
		||||
	for _, link := range links {
 | 
			
		||||
		dtable.AddRow(link.title, link.url)
 | 
			
		||||
	}
 | 
			
		||||
	dtable = addPackageInfos(dtable, d.Packages)
 | 
			
		||||
	dtable = addCpeNames(dtable, d.CpeNames)
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s\n", dtable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type distroLink struct {
 | 
			
		||||
	title string
 | 
			
		||||
	url   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// addVendorSite add Vendor site of the CVE to table
 | 
			
		||||
func distroLinks(cveInfo models.CveInfo, osFamily string) []distroLink {
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
	switch osFamily {
 | 
			
		||||
	case "rhel", "centos":
 | 
			
		||||
		links := []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"RHEL-CVE",
 | 
			
		||||
				fmt.Sprintf("%s/%s", redhatSecurityBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		for _, advisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			aidURL := strings.Replace(advisory.AdvisoryID, ":", "-", -1)
 | 
			
		||||
			links = append(links, distroLink{
 | 
			
		||||
				//  "RHEL-errata",
 | 
			
		||||
				advisory.AdvisoryID,
 | 
			
		||||
				fmt.Sprintf(redhatRHSABaseBaseURL, aidURL),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case "amazon":
 | 
			
		||||
		links := []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"RHEL-CVE",
 | 
			
		||||
				fmt.Sprintf("%s/%s", redhatSecurityBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		for _, advisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			links = append(links, distroLink{
 | 
			
		||||
				//  "Amazon-ALAS",
 | 
			
		||||
				advisory.AdvisoryID,
 | 
			
		||||
				fmt.Sprintf(amazonSecurityBaseURL, advisory.AdvisoryID),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		return []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"Ubuntu-CVE",
 | 
			
		||||
				fmt.Sprintf("%s/%s", ubuntuSecurityBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
			//TODO Ubuntu USN
 | 
			
		||||
		}
 | 
			
		||||
	case "debian":
 | 
			
		||||
		return []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"Debian-CVE",
 | 
			
		||||
				fmt.Sprintf("%s/%s", debianTrackerBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
			//  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"
 | 
			
		||||
		}
 | 
			
		||||
		ver := fmt.Sprintf(
 | 
			
		||||
			"%s -> %s", p.ToStringCurrentVersion(), p.ToStringNewVersion())
 | 
			
		||||
		table.AddRow(title, ver)
 | 
			
		||||
	}
 | 
			
		||||
	return table
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func addCpeNames(table *uitable.Table, names []models.CpeName) *uitable.Table {
 | 
			
		||||
	for _, p := range names {
 | 
			
		||||
		table.AddRow("CPE", fmt.Sprintf("%s", p.Name))
 | 
			
		||||
	}
 | 
			
		||||
	return table
 | 
			
		||||
	result := &models.ScanResult{}
 | 
			
		||||
	if err := json.Unmarshal(data, result); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to parse %s: %s", jsonFile, err)
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										327
									
								
								report/util_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,327 @@
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestIsCveInfoUpdated(t *testing.T) {
 | 
			
		||||
	f := "2006-01-02"
 | 
			
		||||
	old, _ := time.Parse(f, "2015-12-15")
 | 
			
		||||
	new, _ := time.Parse(f, "2015-12-16")
 | 
			
		||||
 | 
			
		||||
	type In struct {
 | 
			
		||||
		cveID string
 | 
			
		||||
		cur   models.ScanResult
 | 
			
		||||
		prev  models.ScanResult
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       In
 | 
			
		||||
		expected bool
 | 
			
		||||
	}{
 | 
			
		||||
		// NVD compare non-initialized times
 | 
			
		||||
		{
 | 
			
		||||
			in: In{
 | 
			
		||||
				cveID: "CVE-2017-0001",
 | 
			
		||||
				cur: models.ScanResult{
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0001",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				prev: models.ScanResult{
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0001",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
		// JVN not updated
 | 
			
		||||
		{
 | 
			
		||||
			in: In{
 | 
			
		||||
				cveID: "CVE-2017-0002",
 | 
			
		||||
				cur: models.ScanResult{
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0002": {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: old,
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				prev: models.ScanResult{
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0002": {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: old,
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
		// OVAL updated
 | 
			
		||||
		{
 | 
			
		||||
			in: In{
 | 
			
		||||
				cveID: "CVE-2017-0003",
 | 
			
		||||
				cur: models.ScanResult{
 | 
			
		||||
					Family: "ubuntu",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0003": {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: new,
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				prev: models.ScanResult{
 | 
			
		||||
					Family: "ubuntu",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0003": {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: old,
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		// OVAL newly detected
 | 
			
		||||
		{
 | 
			
		||||
			in: In{
 | 
			
		||||
				cveID: "CVE-2017-0004",
 | 
			
		||||
				cur: models.ScanResult{
 | 
			
		||||
					Family: "redhat",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0004": {
 | 
			
		||||
							CveID: "CVE-2017-0004",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: old,
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				prev: models.ScanResult{
 | 
			
		||||
					Family:      "redhat",
 | 
			
		||||
					ScannedCves: models.VulnInfos{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual := isCveInfoUpdated(tt.in.cveID, tt.in.prev, tt.in.cur)
 | 
			
		||||
		if actual != tt.expected {
 | 
			
		||||
			t.Errorf("[%d] actual: %t, expected: %t", i, actual, tt.expected)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDiff(t *testing.T) {
 | 
			
		||||
	atCurrent, _ := time.Parse("2006-01-02", "2014-12-31")
 | 
			
		||||
	atPrevious, _ := time.Parse("2006-01-02", "2014-11-31")
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		inCurrent  models.ScanResults
 | 
			
		||||
		inPrevious models.ScanResults
 | 
			
		||||
		out        models.ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			inCurrent: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atCurrent,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					Family:     "ubuntu",
 | 
			
		||||
					Release:    "16.04",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2012-6702": {
 | 
			
		||||
							CveID:            "CVE-2012-6702",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "libexpat1"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2014-9761": {
 | 
			
		||||
							CveID:            "CVE-2014-9761",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "libc-bin"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{},
 | 
			
		||||
					Errors:   []string{},
 | 
			
		||||
					Optional: [][]interface{}{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			inPrevious: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atPrevious,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					Family:     "ubuntu",
 | 
			
		||||
					Release:    "16.04",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2012-6702": {
 | 
			
		||||
							CveID:            "CVE-2012-6702",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "libexpat1"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2014-9761": {
 | 
			
		||||
							CveID:            "CVE-2014-9761",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "libc-bin"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{},
 | 
			
		||||
					Errors:   []string{},
 | 
			
		||||
					Optional: [][]interface{}{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedAt:   atCurrent,
 | 
			
		||||
				ServerName:  "u16",
 | 
			
		||||
				Family:      "ubuntu",
 | 
			
		||||
				Release:     "16.04",
 | 
			
		||||
				Packages:    models.Packages{},
 | 
			
		||||
				ScannedCves: models.VulnInfos{},
 | 
			
		||||
				Errors:      []string{},
 | 
			
		||||
				Optional:    [][]interface{}{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			inCurrent: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atCurrent,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					Family:     "ubuntu",
 | 
			
		||||
					Release:    "16.04",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2016-6662": {
 | 
			
		||||
							CveID:            "CVE-2016-6662",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "mysql-libs"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{
 | 
			
		||||
						"mysql-libs": {
 | 
			
		||||
							Name:       "mysql-libs",
 | 
			
		||||
							Version:    "5.1.73",
 | 
			
		||||
							Release:    "7.el6",
 | 
			
		||||
							NewVersion: "5.1.73",
 | 
			
		||||
							NewRelease: "8.el6_8",
 | 
			
		||||
							Repository: "",
 | 
			
		||||
							Changelog: models.Changelog{
 | 
			
		||||
								Contents: "",
 | 
			
		||||
								Method:   "",
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			inPrevious: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:   atPrevious,
 | 
			
		||||
					ServerName:  "u16",
 | 
			
		||||
					Family:      "ubuntu",
 | 
			
		||||
					Release:     "16.04",
 | 
			
		||||
					ScannedCves: models.VulnInfos{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedAt:  atCurrent,
 | 
			
		||||
				ServerName: "u16",
 | 
			
		||||
				Family:     "ubuntu",
 | 
			
		||||
				Release:    "16.04",
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2016-6662": {
 | 
			
		||||
						CveID:            "CVE-2016-6662",
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{{Name: "mysql-libs"}},
 | 
			
		||||
						DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
						CpeNames:         []string{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				Packages: models.Packages{
 | 
			
		||||
					"mysql-libs": {
 | 
			
		||||
						Name:       "mysql-libs",
 | 
			
		||||
						Version:    "5.1.73",
 | 
			
		||||
						Release:    "7.el6",
 | 
			
		||||
						NewVersion: "5.1.73",
 | 
			
		||||
						NewRelease: "8.el6_8",
 | 
			
		||||
						Repository: "",
 | 
			
		||||
						Changelog: models.Changelog{
 | 
			
		||||
							Contents: "",
 | 
			
		||||
							Method:   "",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		diff, _ := diff(tt.inCurrent, tt.inPrevious)
 | 
			
		||||
		for _, actual := range diff {
 | 
			
		||||
			if !reflect.DeepEqual(actual.ScannedCves, tt.out.ScannedCves) {
 | 
			
		||||
				h := pp.Sprint(actual.ScannedCves)
 | 
			
		||||
				x := pp.Sprint(tt.out.ScannedCves)
 | 
			
		||||
				t.Errorf("[%d] cves actual: \n %s \n expected: \n %s", i, h, x)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for j := range tt.out.Packages {
 | 
			
		||||
				if !reflect.DeepEqual(tt.out.Packages[j], actual.Packages[j]) {
 | 
			
		||||
					h := pp.Sprint(tt.out.Packages[j])
 | 
			
		||||
					x := pp.Sprint(actual.Packages[j])
 | 
			
		||||
					t.Errorf("[%d] packages actual: \n %s \n expected: \n %s", i, x, h)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -17,27 +17,29 @@ 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"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	nvdBaseURL            = "https://web.nvd.nist.gov/view/vuln/detail"
 | 
			
		||||
	mitreBaseURL          = "https://cve.mitre.org/cgi-bin/cvename.cgi?name="
 | 
			
		||||
	cveDetailsBaseURL     = "http://www.cvedetails.com/cve"
 | 
			
		||||
	cvssV2CalcURLTemplate = "https://nvd.nist.gov/cvss/v2-calculator?name=%s&vector=%s"
 | 
			
		||||
 | 
			
		||||
	redhatSecurityBaseURL = "https://access.redhat.com/security/cve"
 | 
			
		||||
	redhatRHSABaseBaseURL = "https://rhn.redhat.com/errata/%s.html"
 | 
			
		||||
	amazonSecurityBaseURL = "https://alas.aws.amazon.com/%s.html"
 | 
			
		||||
 | 
			
		||||
	ubuntuSecurityBaseURL = "http://people.ubuntu.com/~ubuntu-security/cve"
 | 
			
		||||
	debianTrackerBaseURL  = "https://security-tracker.debian.org/tracker"
 | 
			
		||||
 | 
			
		||||
	freeBSDVuXMLBaseURL = "https://vuxml.freebsd.org/freebsd/%s.html"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ResultWriter Interface
 | 
			
		||||
type ResultWriter interface {
 | 
			
		||||
	Write([]models.ScanResult) error
 | 
			
		||||
	Write(...models.ScanResult) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var resultDirPath string
 | 
			
		||||
func gz(data []byte) ([]byte, error) {
 | 
			
		||||
	var b bytes.Buffer
 | 
			
		||||
	gz := gzip.NewWriter(&b)
 | 
			
		||||
	if _, err := gz.Write(data); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := gz.Flush(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := gz.Close(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return b.Bytes(), nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										171
									
								
								scan/alpine.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,171 @@
 | 
			
		||||
/* 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"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type alpine struct {
 | 
			
		||||
	base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAlpine is constructor
 | 
			
		||||
func newAlpine(c config.ServerInfo) *alpine {
 | 
			
		||||
	d := &alpine{
 | 
			
		||||
		base: base{
 | 
			
		||||
			osPackages: osPackages{
 | 
			
		||||
				Packages:  models.Packages{},
 | 
			
		||||
				VulnInfos: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Alpine
 | 
			
		||||
// https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/alpine.rb
 | 
			
		||||
func detectAlpine(c config.ServerInfo) (itsMe bool, os osTypeInterface) {
 | 
			
		||||
	os = newAlpine(c)
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "ls /etc/alpine-release", noSudo); !r.isSuccess() {
 | 
			
		||||
		return false, os
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "cat /etc/alpine-release", noSudo); r.isSuccess() {
 | 
			
		||||
		os.setDistro(config.Alpine, strings.TrimSpace(r.Stdout))
 | 
			
		||||
		return true, os
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false, os
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) checkDependencies() error {
 | 
			
		||||
	o.log.Infof("Dependencies... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) checkIfSudoNoPasswd() error {
 | 
			
		||||
	o.log.Infof("sudo ... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) apkUpdate() error {
 | 
			
		||||
	r := o.exec("apk update", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) scanPackages() error {
 | 
			
		||||
	if err := o.apkUpdate(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// collect the running kernel information
 | 
			
		||||
	release, version, err := o.runningKernel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan the running kernel version: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.Kernel = models.Kernel{
 | 
			
		||||
		Release: release,
 | 
			
		||||
		Version: version,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	installed, err := o.scanInstalledPackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	updatable, err := o.scanUpdatablePackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	installed.MergeNewVersion(updatable)
 | 
			
		||||
	o.Packages = installed
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) scanInstalledPackages() (models.Packages, error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("apk info -v")
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.parseApkInfo(r.Stdout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) parseApkInfo(stdout string) (models.Packages, error) {
 | 
			
		||||
	packs := models.Packages{}
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		ss := strings.Split(line, "-")
 | 
			
		||||
		if len(ss) < 3 {
 | 
			
		||||
			return nil, fmt.Errorf("Failed to parse apk info -v: %s", line)
 | 
			
		||||
		}
 | 
			
		||||
		name := strings.Join(ss[:len(ss)-2], "-")
 | 
			
		||||
		packs[name] = models.Package{
 | 
			
		||||
			Name:    name,
 | 
			
		||||
			Version: strings.Join(ss[len(ss)-2:], "-"),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return packs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) scanUpdatablePackages() (models.Packages, error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("apk version")
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.parseApkVersion(r.Stdout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) parseApkVersion(stdout string) (models.Packages, error) {
 | 
			
		||||
	packs := models.Packages{}
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		if !strings.Contains(line, "<") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		ss := strings.Split(line, "<")
 | 
			
		||||
		namever := strings.TrimSpace(ss[0])
 | 
			
		||||
		tt := strings.Split(namever, "-")
 | 
			
		||||
		name := strings.Join(tt[:len(tt)-2], "-")
 | 
			
		||||
		packs[name] = models.Package{
 | 
			
		||||
			Name:       name,
 | 
			
		||||
			NewVersion: strings.TrimSpace(ss[1]),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return packs, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								scan/alpine_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,75 @@
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseApkInfo(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in    string
 | 
			
		||||
		packs models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: `musl-1.1.16-r14
 | 
			
		||||
busybox-1.26.2-r7
 | 
			
		||||
`,
 | 
			
		||||
			packs: models.Packages{
 | 
			
		||||
				"musl": {
 | 
			
		||||
					Name:    "musl",
 | 
			
		||||
					Version: "1.1.16-r14",
 | 
			
		||||
				},
 | 
			
		||||
				"busybox": {
 | 
			
		||||
					Name:    "busybox",
 | 
			
		||||
					Version: "1.26.2-r7",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d := newAlpine(config.ServerInfo{})
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		pkgs, _ := d.parseApkInfo(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(tt.packs, pkgs) {
 | 
			
		||||
			t.Errorf("[%d] expected %v, actual %v", i, tt.packs, pkgs)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseApkVersion(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in    string
 | 
			
		||||
		packs models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: `Installed:                                Available:
 | 
			
		||||
libcrypto1.0-1.0.1q-r0                  < 1.0.2m-r0
 | 
			
		||||
libssl1.0-1.0.1q-r0                     < 1.0.2m-r0
 | 
			
		||||
nrpe-2.14-r2                            < 2.15-r5
 | 
			
		||||
`,
 | 
			
		||||
			packs: models.Packages{
 | 
			
		||||
				"libcrypto1.0": {
 | 
			
		||||
					Name:       "libcrypto1.0",
 | 
			
		||||
					NewVersion: "1.0.2m-r0",
 | 
			
		||||
				},
 | 
			
		||||
				"libssl1.0": {
 | 
			
		||||
					Name:       "libssl1.0",
 | 
			
		||||
					NewVersion: "1.0.2m-r0",
 | 
			
		||||
				},
 | 
			
		||||
				"nrpe": {
 | 
			
		||||
					Name:       "nrpe",
 | 
			
		||||
					NewVersion: "2.15-r5",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d := newAlpine(config.ServerInfo{})
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		pkgs, _ := d.parseApkVersion(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(tt.packs, pkgs) {
 | 
			
		||||
			t.Errorf("[%d] expected %v, actual %v", i, tt.packs, pkgs)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										263
									
								
								scan/base.go
									
									
									
									
									
								
							
							
						
						@@ -19,107 +19,161 @@ package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type base struct {
 | 
			
		||||
	ServerInfo config.ServerInfo
 | 
			
		||||
	Distro     config.Distro
 | 
			
		||||
	Platform   models.Platform
 | 
			
		||||
 | 
			
		||||
	Family   string
 | 
			
		||||
	Release  string
 | 
			
		||||
	Platform models.Platform
 | 
			
		||||
	osPackages
 | 
			
		||||
 | 
			
		||||
	log  *logrus.Entry
 | 
			
		||||
	errs []error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) ssh(cmd string, sudo bool) sshResult {
 | 
			
		||||
	return sshExec(l.ServerInfo, cmd, sudo, l.log)
 | 
			
		||||
func (l *base) exec(cmd string, sudo bool) execResult {
 | 
			
		||||
	return exec(l.ServerInfo, cmd, sudo, l.log)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setServerInfo(c config.ServerInfo) {
 | 
			
		||||
	l.ServerInfo = c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getServerInfo() config.ServerInfo {
 | 
			
		||||
func (l *base) getServerInfo() config.ServerInfo {
 | 
			
		||||
	return l.ServerInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setDistributionInfo(fam, rel string) {
 | 
			
		||||
	l.Family = fam
 | 
			
		||||
	l.Release = rel
 | 
			
		||||
func (l *base) setDistro(fam, rel string) {
 | 
			
		||||
	d := config.Distro{
 | 
			
		||||
		Family:  fam,
 | 
			
		||||
		Release: rel,
 | 
			
		||||
	}
 | 
			
		||||
	l.Distro = d
 | 
			
		||||
 | 
			
		||||
	s := l.getServerInfo()
 | 
			
		||||
	s.Distro = d
 | 
			
		||||
	l.setServerInfo(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getDistributionInfo() string {
 | 
			
		||||
	return fmt.Sprintf("%s %s", l.Family, l.Release)
 | 
			
		||||
func (l *base) getDistro() config.Distro {
 | 
			
		||||
	return l.Distro
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setPlatform(p models.Platform) {
 | 
			
		||||
	l.Platform = p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getPlatform() models.Platform {
 | 
			
		||||
func (l *base) getPlatform() models.Platform {
 | 
			
		||||
	return l.Platform
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) allContainers() (containers []config.Container, err error) {
 | 
			
		||||
	switch l.ServerInfo.Container.Type {
 | 
			
		||||
func (l *base) runningKernel() (release, version string, err error) {
 | 
			
		||||
	r := l.exec("uname -r", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return "", "", fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	release = strings.TrimSpace(r.Stdout)
 | 
			
		||||
 | 
			
		||||
	switch l.Distro.Family {
 | 
			
		||||
	case config.Debian:
 | 
			
		||||
		r := l.exec("uname -a", noSudo)
 | 
			
		||||
		if !r.isSuccess() {
 | 
			
		||||
			return "", "", fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
		}
 | 
			
		||||
		ss := strings.Fields(r.Stdout)
 | 
			
		||||
		if 6 < len(ss) {
 | 
			
		||||
			version = ss[6]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) allContainers() (containers []config.Container, err error) {
 | 
			
		||||
	switch l.ServerInfo.Containers.Type {
 | 
			
		||||
	case "", "docker":
 | 
			
		||||
		stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}}'")
 | 
			
		||||
		stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}} {{.Image}}'")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseDockerPs(stdout)
 | 
			
		||||
	case "lxd":
 | 
			
		||||
		stdout, err := l.lxdPs("-c n")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseLxdPs(stdout)
 | 
			
		||||
	default:
 | 
			
		||||
		return containers, fmt.Errorf(
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Container.Type)
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Containers.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) runningContainers() (containers []config.Container, err error) {
 | 
			
		||||
	switch l.ServerInfo.Container.Type {
 | 
			
		||||
	switch l.ServerInfo.Containers.Type {
 | 
			
		||||
	case "", "docker":
 | 
			
		||||
		stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}}'")
 | 
			
		||||
		stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}} {{.Image}}'")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseDockerPs(stdout)
 | 
			
		||||
	case "lxd":
 | 
			
		||||
		stdout, err := l.lxdPs("volatile.last_state.power=RUNNING -c n")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseLxdPs(stdout)
 | 
			
		||||
	default:
 | 
			
		||||
		return containers, fmt.Errorf(
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Container.Type)
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Containers.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) exitedContainers() (containers []config.Container, err error) {
 | 
			
		||||
	switch l.ServerInfo.Container.Type {
 | 
			
		||||
	switch l.ServerInfo.Containers.Type {
 | 
			
		||||
	case "", "docker":
 | 
			
		||||
		stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}}'")
 | 
			
		||||
		stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}} {{.Image}}'")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseDockerPs(stdout)
 | 
			
		||||
	case "lxd":
 | 
			
		||||
		stdout, err := l.lxdPs("volatile.last_state.power=STOPPED -c n")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return containers, err
 | 
			
		||||
		}
 | 
			
		||||
		return l.parseLxdPs(stdout)
 | 
			
		||||
	default:
 | 
			
		||||
		return containers, fmt.Errorf(
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Container.Type)
 | 
			
		||||
			"Not supported yet: %s", l.ServerInfo.Containers.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) dockerPs(option string) (string, error) {
 | 
			
		||||
	cmd := fmt.Sprintf("docker ps %s", option)
 | 
			
		||||
	r := l.ssh(cmd, noSudo)
 | 
			
		||||
	r := l.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return "", fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return r.Stdout, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) lxdPs(option string) (string, error) {
 | 
			
		||||
	cmd := fmt.Sprintf("lxc list %s", option)
 | 
			
		||||
	r := l.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return "", fmt.Errorf("failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return r.Stdout, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) parseDockerPs(stdout string) (containers []config.Container, err error) {
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
@@ -127,46 +181,65 @@ func (l *base) parseDockerPs(stdout string) (containers []config.Container, err
 | 
			
		||||
		if len(fields) == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if len(fields) != 2 {
 | 
			
		||||
		if len(fields) != 3 {
 | 
			
		||||
			return containers, fmt.Errorf("Unknown format: %s", line)
 | 
			
		||||
		}
 | 
			
		||||
		containers = append(containers, config.Container{
 | 
			
		||||
			ContainerID: fields[0],
 | 
			
		||||
			Name:        fields[1],
 | 
			
		||||
			Image:       fields[2],
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) detectPlatform() error {
 | 
			
		||||
func (l *base) parseLxdPs(stdout string) (containers []config.Container, err error) {
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for i, line := range lines[3:] {
 | 
			
		||||
		if i%2 == 1 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		fields := strings.Fields(strings.Replace(line, "|", " ", -1))
 | 
			
		||||
		if len(fields) == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if len(fields) != 1 {
 | 
			
		||||
			return containers, fmt.Errorf("Unknown format: %s", line)
 | 
			
		||||
		}
 | 
			
		||||
		containers = append(containers, config.Container{
 | 
			
		||||
			ContainerID: fields[0],
 | 
			
		||||
			Name:        fields[0],
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) detectPlatform() {
 | 
			
		||||
	ok, instanceID, err := l.detectRunningOnAws()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		l.setPlatform(models.Platform{Name: "other"})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if ok {
 | 
			
		||||
		l.setPlatform(models.Platform{
 | 
			
		||||
			Name:       "aws",
 | 
			
		||||
			InstanceID: instanceID,
 | 
			
		||||
		})
 | 
			
		||||
		return nil
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//TODO Azure, GCP...
 | 
			
		||||
	l.setPlatform(models.Platform{
 | 
			
		||||
		Name: "other",
 | 
			
		||||
	})
 | 
			
		||||
	return nil
 | 
			
		||||
	l.setPlatform(models.Platform{Name: "other"})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) detectRunningOnAws() (ok bool, instanceID string, err error) {
 | 
			
		||||
	if r := l.ssh("type curl", noSudo); r.isSuccess() {
 | 
			
		||||
func (l *base) detectRunningOnAws() (ok bool, instanceID string, err error) {
 | 
			
		||||
	if r := l.exec("type curl", noSudo); r.isSuccess() {
 | 
			
		||||
		cmd := "curl --max-time 1 --retry 3 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id"
 | 
			
		||||
		r := l.ssh(cmd, noSudo)
 | 
			
		||||
		r := l.exec(cmd, noSudo)
 | 
			
		||||
		if r.isSuccess() {
 | 
			
		||||
			id := strings.TrimSpace(r.Stdout)
 | 
			
		||||
 | 
			
		||||
			if id == "not found" {
 | 
			
		||||
				// status: 0, stdout: "not found" on degitalocean or Azure
 | 
			
		||||
			if !l.isAwsInstanceID(id) {
 | 
			
		||||
				return false, "", nil
 | 
			
		||||
			}
 | 
			
		||||
			return true, id, nil
 | 
			
		||||
@@ -181,11 +254,14 @@ func (l base) detectRunningOnAws() (ok bool, instanceID string, err error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := l.ssh("type wget", noSudo); r.isSuccess() {
 | 
			
		||||
	if r := l.exec("type wget", noSudo); r.isSuccess() {
 | 
			
		||||
		cmd := "wget --tries=3 --timeout=1 --no-proxy -q -O - http://169.254.169.254/latest/meta-data/instance-id"
 | 
			
		||||
		r := l.ssh(cmd, noSudo)
 | 
			
		||||
		r := l.exec(cmd, noSudo)
 | 
			
		||||
		if r.isSuccess() {
 | 
			
		||||
			id := strings.TrimSpace(r.Stdout)
 | 
			
		||||
			if !l.isAwsInstanceID(id) {
 | 
			
		||||
				return false, "", nil
 | 
			
		||||
			}
 | 
			
		||||
			return true, id, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -202,96 +278,51 @@ func (l base) detectRunningOnAws() (ok bool, instanceID string, err error) {
 | 
			
		||||
		l.ServerInfo.ServerName, l.ServerInfo.Container.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) convertToModel() (models.ScanResult, error) {
 | 
			
		||||
	var scoredCves, unscoredCves models.CveInfos
 | 
			
		||||
	for _, p := range l.UnsecurePackages {
 | 
			
		||||
		if p.CveDetail.CvssScore(config.Conf.Lang) <= 0 {
 | 
			
		||||
			unscoredCves = append(unscoredCves, models.CveInfo{
 | 
			
		||||
				CveDetail:        p.CveDetail,
 | 
			
		||||
				Packages:         p.Packs,
 | 
			
		||||
				DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
 | 
			
		||||
			})
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/resource-ids.html
 | 
			
		||||
var awsInstanceIDPattern = regexp.MustCompile(`^i-[0-9a-f]+$`)
 | 
			
		||||
 | 
			
		||||
		cpenames := []models.CpeName{}
 | 
			
		||||
		for _, cpename := range p.CpeNames {
 | 
			
		||||
			cpenames = append(cpenames,
 | 
			
		||||
				models.CpeName{Name: cpename})
 | 
			
		||||
		}
 | 
			
		||||
func (l *base) isAwsInstanceID(str string) bool {
 | 
			
		||||
	return awsInstanceIDPattern.MatchString(str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		cve := models.CveInfo{
 | 
			
		||||
			CveDetail:        p.CveDetail,
 | 
			
		||||
			Packages:         p.Packs,
 | 
			
		||||
			DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
 | 
			
		||||
			CpeNames:         cpenames,
 | 
			
		||||
		}
 | 
			
		||||
		scoredCves = append(scoredCves, cve)
 | 
			
		||||
func (l *base) convertToModel() models.ScanResult {
 | 
			
		||||
	ctype := l.ServerInfo.Containers.Type
 | 
			
		||||
	if l.ServerInfo.Container.ContainerID != "" && ctype == "" {
 | 
			
		||||
		ctype = "docker"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	container := models.Container{
 | 
			
		||||
		ContainerID: l.ServerInfo.Container.ContainerID,
 | 
			
		||||
		Name:        l.ServerInfo.Container.Name,
 | 
			
		||||
		Image:       l.ServerInfo.Container.Image,
 | 
			
		||||
		Type:        ctype,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Sort(scoredCves)
 | 
			
		||||
	sort.Sort(unscoredCves)
 | 
			
		||||
	errs := []string{}
 | 
			
		||||
	for _, e := range l.errs {
 | 
			
		||||
		errs = append(errs, fmt.Sprintf("%s", e))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return models.ScanResult{
 | 
			
		||||
		ServerName:  l.ServerInfo.ServerName,
 | 
			
		||||
		Family:      l.Family,
 | 
			
		||||
		Release:     l.Release,
 | 
			
		||||
		Container:   container,
 | 
			
		||||
		Platform:    l.Platform,
 | 
			
		||||
		KnownCves:   scoredCves,
 | 
			
		||||
		UnknownCves: unscoredCves,
 | 
			
		||||
		Optional:    l.ServerInfo.Optional,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
 | 
			
		||||
func (l *base) scanVulnByCpeName() error {
 | 
			
		||||
	unsecurePacks := CvePacksList{}
 | 
			
		||||
 | 
			
		||||
	serverInfo := l.getServerInfo()
 | 
			
		||||
	cpeNames := serverInfo.CpeNames
 | 
			
		||||
 | 
			
		||||
	// remove duplicate
 | 
			
		||||
	set := map[string]CvePacksInfo{}
 | 
			
		||||
 | 
			
		||||
	for _, name := range cpeNames {
 | 
			
		||||
		details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for _, detail := range details {
 | 
			
		||||
			if val, ok := set[detail.CveID]; ok {
 | 
			
		||||
				names := val.CpeNames
 | 
			
		||||
				names = append(names, name)
 | 
			
		||||
				val.CpeNames = names
 | 
			
		||||
				set[detail.CveID] = val
 | 
			
		||||
			} else {
 | 
			
		||||
				set[detail.CveID] = CvePacksInfo{
 | 
			
		||||
					CveID:     detail.CveID,
 | 
			
		||||
					CveDetail: detail,
 | 
			
		||||
					CpeNames:  []string{name},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		JSONVersion:   models.JSONVersion,
 | 
			
		||||
		ServerName:    l.ServerInfo.ServerName,
 | 
			
		||||
		ScannedAt:     time.Now(),
 | 
			
		||||
		Family:        l.Distro.Family,
 | 
			
		||||
		Release:       l.Distro.Release,
 | 
			
		||||
		Container:     container,
 | 
			
		||||
		Platform:      l.Platform,
 | 
			
		||||
		ScannedCves:   l.VulnInfos,
 | 
			
		||||
		RunningKernel: l.Kernel,
 | 
			
		||||
		Packages:      l.Packages,
 | 
			
		||||
		SrcPackages:   l.SrcPackages,
 | 
			
		||||
		Optional:      l.ServerInfo.Optional,
 | 
			
		||||
		Errors:        errs,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for key := range set {
 | 
			
		||||
		unsecurePacks = append(unsecurePacks, set[key])
 | 
			
		||||
	}
 | 
			
		||||
	unsecurePacks = append(unsecurePacks, l.UnsecurePackages...)
 | 
			
		||||
	l.setUnsecurePackages(unsecurePacks)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setErrs(errs []error) {
 | 
			
		||||
	l.errs = errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getErrs() []error {
 | 
			
		||||
func (l *base) getErrs() []error {
 | 
			
		||||
	return l.errs
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -30,16 +30,18 @@ func TestParseDockerPs(t *testing.T) {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []config.Container
 | 
			
		||||
	}{
 | 
			
		||||
		`c7ca0992415a romantic_goldberg
 | 
			
		||||
f570ae647edc agitated_lovelace`,
 | 
			
		||||
		`c7ca0992415a romantic_goldberg ubuntu:14.04.5
 | 
			
		||||
f570ae647edc agitated_lovelace centos:latest`,
 | 
			
		||||
		[]config.Container{
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "c7ca0992415a",
 | 
			
		||||
				Name:        "romantic_goldberg",
 | 
			
		||||
				Image:       "ubuntu:14.04.5",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "f570ae647edc",
 | 
			
		||||
				Name:        "agitated_lovelace",
 | 
			
		||||
				Image:       "centos:latest",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@@ -56,3 +58,63 @@ f570ae647edc agitated_lovelace`,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseLxdPs(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []config.Container
 | 
			
		||||
	}{
 | 
			
		||||
		`+-------+
 | 
			
		||||
| NAME  |
 | 
			
		||||
+-------+
 | 
			
		||||
| test1 |
 | 
			
		||||
+-------+
 | 
			
		||||
| test2 |
 | 
			
		||||
+-------+`,
 | 
			
		||||
		[]config.Container{
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "test1",
 | 
			
		||||
				Name:        "test1",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "test2",
 | 
			
		||||
				Name:        "test2",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	actual, err := r.parseLxdPs(test.in)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Error occurred. in: %s, err: %s", test.in, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for i, e := range test.expected {
 | 
			
		||||
		if !reflect.DeepEqual(e, actual[i]) {
 | 
			
		||||
			t.Errorf("expected %v, actual %v", e, actual[i])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsAwsInstanceID(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected bool
 | 
			
		||||
	}{
 | 
			
		||||
		{"i-1234567a", true},
 | 
			
		||||
		{"i-1234567890abcdef0", true},
 | 
			
		||||
		{"i-1234567890abcdef0000000", true},
 | 
			
		||||
		{"e-1234567890abcdef0", false},
 | 
			
		||||
		{"i-1234567890abcdef0 foo bar", false},
 | 
			
		||||
		{"no data", false},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := r.isAwsInstanceID(tt.in)
 | 
			
		||||
		if tt.expected != actual {
 | 
			
		||||
			t.Errorf("expected %t, actual %t, str: %s", tt.expected, actual, tt.in)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										887
									
								
								scan/debian.go
									
									
									
									
									
								
							
							
						
						@@ -18,50 +18,26 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/cache"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseScannedPackagesLineDebian(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var packagetests = []struct {
 | 
			
		||||
		in      string
 | 
			
		||||
		name    string
 | 
			
		||||
		version string
 | 
			
		||||
	}{
 | 
			
		||||
		{"base-passwd	3.5.33", "base-passwd", "3.5.33"},
 | 
			
		||||
		{"bzip2	1.0.6-5", "bzip2", "1.0.6-5"},
 | 
			
		||||
		{"adduser	3.113+nmu3ubuntu3", "adduser", "3.113+nmu3ubuntu3"},
 | 
			
		||||
		{"bash	4.3-7ubuntu1.5", "bash", "4.3-7ubuntu1.5"},
 | 
			
		||||
		{"bsdutils	1:2.20.1-5.1ubuntu20.4", "bsdutils", "1:2.20.1-5.1ubuntu20.4"},
 | 
			
		||||
		{"ca-certificates	20141019ubuntu0.14.04.1", "ca-certificates", "20141019ubuntu0.14.04.1"},
 | 
			
		||||
		{"apt	1.0.1ubuntu2.8", "apt", "1.0.1ubuntu2.8"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range packagetests {
 | 
			
		||||
		n, v, _ := d.parseScannedPackagesLine(tt.in)
 | 
			
		||||
		if n != tt.name {
 | 
			
		||||
			t.Errorf("name: expected %s, actual %s", tt.name, n)
 | 
			
		||||
		}
 | 
			
		||||
		if v != tt.version {
 | 
			
		||||
			t.Errorf("version: expected %s, actual %s", tt.version, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestgetCveIDParsingChangelog(t *testing.T) {
 | 
			
		||||
func TestGetCveIDsFromChangelog(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       []string
 | 
			
		||||
		expected []string
 | 
			
		||||
		in        []string
 | 
			
		||||
		cveIDs    []DetectedCveID
 | 
			
		||||
		changelog models.Changelog
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			// verubuntu1
 | 
			
		||||
			//0 verubuntu1
 | 
			
		||||
			[]string{
 | 
			
		||||
				"systemd",
 | 
			
		||||
				"228-4ubuntu1",
 | 
			
		||||
@@ -73,133 +49,214 @@ CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
systemd (228-5) unstable; urgency=medium
 | 
			
		||||
systemd (228-4) unstable; urgency=medium
 | 
			
		||||
systemd (228-3) unstable; urgency=medium
 | 
			
		||||
systemd (228-2) unstable; urgency=medium
 | 
			
		||||
systemd (228-1) unstable; urgency=medium
 | 
			
		||||
systemd (227-3) unstable; urgency=medium
 | 
			
		||||
systemd (227-2) unstable; urgency=medium
 | 
			
		||||
systemd (227-1) unstable; urgency=medium`,
 | 
			
		||||
systemd (228-3) unstable; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogExactMatch},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// ver
 | 
			
		||||
			[]string{
 | 
			
		||||
				"libpcre3",
 | 
			
		||||
				"2:8.38-1ubuntu1",
 | 
			
		||||
				`pcre3 (2:8.38-2) unstable; urgency=low
 | 
			
		||||
pcre3 (2:8.38-1) unstable; urgency=low
 | 
			
		||||
pcre3 (2:8.35-8) unstable; urgency=low
 | 
			
		||||
pcre3 (2:8.35-7.4) unstable; urgency=medium
 | 
			
		||||
pcre3 (2:8.35-7.3) unstable; urgency=medium
 | 
			
		||||
pcre3 (2:8.35-7.2) unstable; urgency=low
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `systemd (229-2) unstable; urgency=medium
 | 
			
		||||
systemd (229-1) unstable; urgency=medium
 | 
			
		||||
systemd (228-6) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
pcre3 (2:8.35-7.1) unstable; urgency=medium
 | 
			
		||||
pcre3 (2:8.35-7) unstable; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
systemd (228-5) unstable; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// ver-ubuntu3
 | 
			
		||||
			//1 ver
 | 
			
		||||
			[]string{
 | 
			
		||||
				"libpcre3",
 | 
			
		||||
				"2:8.35-7.1ubuntu1",
 | 
			
		||||
				`pcre3 (2:8.38-2) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.38-1) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.35-8) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.35-7.4) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7.3) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7.2) unstable; urgency=low
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 pcre3 (2:8.35-7.1) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7) unstable; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogExactMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `pcre3 (2:8.38-2) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.38-1) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.35-8) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.35-7.4) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7.3) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7.2) unstable; urgency=low
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//2 ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
				"sysvinit",
 | 
			
		||||
				"2.88dsf-59.2ubuntu3",
 | 
			
		||||
				`sysvinit (2.88dsf-59.3ubuntu1) xenial; urgency=low
 | 
			
		||||
sysvinit (2.88dsf-59.3) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.2ubuntu2) wily; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.2ubuntu1) wily; urgency=medium
 | 
			
		||||
CVE-2015-2321: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
sysvinit (2.88dsf-59.2) unstable; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.1ubuntu3) wily; urgency=medium
 | 
			
		||||
CVE-2015-2322: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
sysvinit (2.88dsf-59.1ubuntu2) wily; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.1ubuntu1) wily; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.1) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
sysvinit (2.88dsf-59) unstable; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-58) unstable; urgency=low
 | 
			
		||||
sysvinit (2.88dsf-57) unstable; urgency=low`,
 | 
			
		||||
		 sysvinit (2.88dsf-59.3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.2ubuntu2) wily; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.2ubuntu1) wily; urgency=medium
 | 
			
		||||
		 CVE-2015-2321: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 sysvinit (2.88dsf-59.2) unstable; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.1ubuntu3) wily; urgency=medium
 | 
			
		||||
		 CVE-2015-2322: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 sysvinit (2.88dsf-59.1ubuntu2) wily; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.1ubuntu1) wily; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.1) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 sysvinit (2.88dsf-59) unstable; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-58) unstable; urgency=low
 | 
			
		||||
		 sysvinit (2.88dsf-57) unstable; urgency=low`,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogExactMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `sysvinit (2.88dsf-59.3ubuntu1) xenial; urgency=low
 | 
			
		||||
		 sysvinit (2.88dsf-59.3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// 1:ver-ubuntu3
 | 
			
		||||
			//3  1:ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
				"bsdutils",
 | 
			
		||||
				"1:2.27.1-1ubuntu3",
 | 
			
		||||
				` util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27-3) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27-2) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27-1) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27~rc2-2) experimental; urgency=medium
 | 
			
		||||
util-linux (2.27~rc2-1) experimental; urgency=medium
 | 
			
		||||
util-linux (2.27~rc1-1) experimental; urgency=medium
 | 
			
		||||
util-linux (2.26.2-9) unstable; urgency=medium
 | 
			
		||||
util-linux (2.26.2-8) experimental; urgency=medium
 | 
			
		||||
util-linux (2.26.2-7) experimental; urgency=medium
 | 
			
		||||
util-linux (2.26.2-6ubuntu3) wily; urgency=medium
 | 
			
		||||
CVE-2015-2329: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
util-linux (2.26.2-6ubuntu2) wily; urgency=medium
 | 
			
		||||
util-linux (2.26.2-6ubuntu1) wily; urgency=medium
 | 
			
		||||
util-linux (2.26.2-6) unstable; urgency=medium`,
 | 
			
		||||
				`util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27-3ubuntu1) xenial; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
			// {"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2016-1000000", models.ChangelogLenientMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				// Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
				// CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
				// CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
				// CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
				// util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu3) xenial; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//4 1:ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
				"bsdutils",
 | 
			
		||||
				"1:2.27-3ubuntu3",
 | 
			
		||||
				`util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27-3) xenial; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
			// {"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2016-1000000", models.ChangelogLenientMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				// Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
				// CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
				// CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
				// CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
				// util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1) unstable; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//5 https://github.com/future-architect/vuls/pull/350
 | 
			
		||||
			[]string{
 | 
			
		||||
				"tar",
 | 
			
		||||
				"1.27.1-2+b1",
 | 
			
		||||
				`tar (1.27.1-2+deb8u1) jessie-security; urgency=high
 | 
			
		||||
		   * CVE-2016-6321: Bypassing the extract path name.
 | 
			
		||||
		 tar (1.27.1-2) unstable; urgency=low`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2016-6321", models.ChangelogExactMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `tar (1.27.1-2+deb8u1) jessie-security; urgency=high
 | 
			
		||||
		   * CVE-2016-6321: Bypassing the extract path name.`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, _ := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], tt.in[1])
 | 
			
		||||
		if len(actual) != len(tt.expected) {
 | 
			
		||||
			t.Errorf("Len of return array are'nt same. expected %#v, actual %#v", tt.expected, actual)
 | 
			
		||||
	d.Distro.Family = "ubuntu"
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		aCveIDs, aPack := d.getCveIDsFromChangelog(tt.in[2], tt.in[0], tt.in[1])
 | 
			
		||||
		if len(aCveIDs) != len(tt.cveIDs) {
 | 
			
		||||
			t.Errorf("[%d] Len of return array are'nt same. expected %#v, actual %#v", i, tt.cveIDs, aCveIDs)
 | 
			
		||||
			t.Errorf(pp.Sprintf("%s", tt.in))
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		for i := range tt.expected {
 | 
			
		||||
			if actual[i] != tt.expected[i] {
 | 
			
		||||
				t.Errorf("expected %s, actual %s", tt.expected[i], actual[i])
 | 
			
		||||
		for j := range tt.cveIDs {
 | 
			
		||||
			if !reflect.DeepEqual(tt.cveIDs[j], aCveIDs[j]) {
 | 
			
		||||
				t.Errorf("[%d] expected %v, actual %v", i, tt.cveIDs[j], aCveIDs[j])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		_, err := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], "version number do'nt match case")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Returning error is unexpected")
 | 
			
		||||
		if aPack.Changelog.Contents != tt.changelog.Contents {
 | 
			
		||||
			t.Error(pp.Sprintf("[%d] expected: %s, actual: %s", i, tt.changelog.Contents, aPack.Changelog.Contents))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if aPack.Changelog.Method != tt.changelog.Method {
 | 
			
		||||
			t.Error(pp.Sprintf("[%d] expected: %s, actual: %s", i, tt.changelog.Method, aPack.Changelog.Method))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -217,49 +274,7 @@ Reading state information... Done
 | 
			
		||||
The following packages will be upgraded:
 | 
			
		||||
  apt ca-certificates cpio dpkg e2fslibs e2fsprogs gnupg gpgv libc-bin libc6 libcomerr2 libpcre3
 | 
			
		||||
  libpng12-0 libss2 libssl1.0.0 libudev0 multiarch-support openssl tzdata udev upstart
 | 
			
		||||
21 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
 | 
			
		||||
Inst dpkg [1.16.1.2ubuntu7.5] (1.16.1.2ubuntu7.7 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf dpkg (1.16.1.2ubuntu7.7 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst upstart [1.5-0ubuntu7.2] (1.5-0ubuntu7.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libc-bin [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64]) [libc6:amd64 ]
 | 
			
		||||
Conf libc-bin (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64]) [libc6:amd64 ]
 | 
			
		||||
Inst libc6 [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libc6 (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libudev0 [175-0ubuntu9.9] (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst tzdata [2015a-0ubuntu0.12.04] (2015g-0ubuntu0.12.04 Ubuntu:12.04/precise-updates [all])
 | 
			
		||||
Conf tzdata (2015g-0ubuntu0.12.04 Ubuntu:12.04/precise-updates [all])
 | 
			
		||||
Inst e2fslibs [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64]) [e2fsprogs:amd64 on e2fslibs:amd64] [e2fsprogs:amd64 ]
 | 
			
		||||
Conf e2fslibs (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64]) [e2fsprogs:amd64 ]
 | 
			
		||||
Inst e2fsprogs [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf e2fsprogs (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst gpgv [1.4.11-3ubuntu2.7] (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf gpgv (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst gnupg [1.4.11-3ubuntu2.7] (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf gnupg (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst apt [0.8.16~exp12ubuntu10.22] (0.8.16~exp12ubuntu10.26 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf apt (0.8.16~exp12ubuntu10.26 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libcomerr2 [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libcomerr2 (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libss2 [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libss2 (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libssl1.0.0 [1.0.1-4ubuntu5.21] (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libssl1.0.0 (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libpcre3 [8.12-4] (8.12-4ubuntu0.1 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libpng12-0 [1.2.46-3ubuntu4] (1.2.46-3ubuntu4.2 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst multiarch-support [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf multiarch-support (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst cpio [2.11-7ubuntu3.1] (2.11-7ubuntu3.2 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst udev [175-0ubuntu9.9] (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst openssl [1.0.1-4ubuntu5.33] (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst ca-certificates [20141019ubuntu0.12.04.1] (20160104ubuntu0.12.04.1 Ubuntu:12.04/precise-updates [all])
 | 
			
		||||
Conf libudev0 (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf upstart (1.5-0ubuntu7.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libpcre3 (8.12-4ubuntu0.1 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libpng12-0 (1.2.46-3ubuntu4.2 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf cpio (2.11-7ubuntu3.2 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf udev (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf openssl (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf ca-certificates (20160104ubuntu0.12.04.1 Ubuntu:12.04/precise-updates [all])`,
 | 
			
		||||
21 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
 | 
			
		||||
			[]string{
 | 
			
		||||
				"apt",
 | 
			
		||||
				"ca-certificates",
 | 
			
		||||
@@ -300,124 +315,6 @@ The following packages will be upgraded:
 | 
			
		||||
  ntpdate passwd python3.4 python3.4-minimal rsyslog sudo sysv-rc
 | 
			
		||||
  sysvinit-utils tzdata udev util-linux
 | 
			
		||||
59 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
 | 
			
		||||
Inst base-files [7.2ubuntu5.2] (7.2ubuntu5.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf base-files (7.2ubuntu5.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst coreutils [8.21-1ubuntu5.1] (8.21-1ubuntu5.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf coreutils (8.21-1ubuntu5.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst dpkg [1.17.5ubuntu5.3] (1.17.5ubuntu5.5 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf dpkg (1.17.5ubuntu5.5 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libc-bin [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libc6 [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libgcc1 [1:4.9.1-0ubuntu1] (1:4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst gcc-4.9-base [4.9.1-0ubuntu1] (4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf gcc-4.9-base (4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libgcc1 (1:4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libc6 (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libc-bin (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst e2fslibs [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64]) [e2fsprogs:amd64 on e2fslibs:amd64] [e2fsprogs:amd64 ]
 | 
			
		||||
Conf e2fslibs (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64]) [e2fsprogs:amd64 ]
 | 
			
		||||
Inst e2fsprogs [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf e2fsprogs (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst login [1:4.1.5.1-1ubuntu9] (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf login (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst mount [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf mount (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst tzdata [2015a-0ubuntu0.14.04] (2015g-0ubuntu0.14.04 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Conf tzdata (2015g-0ubuntu0.14.04 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Inst sysvinit-utils [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst sysv-rc [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Conf sysv-rc (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Conf sysvinit-utils (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst util-linux [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf util-linux (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst gcc-4.8-base [4.8.2-19ubuntu1] (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64]) [libstdc++6:amd64 ]
 | 
			
		||||
Conf gcc-4.8-base (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64]) [libstdc++6:amd64 ]
 | 
			
		||||
Inst libstdc++6 [4.8.2-19ubuntu1] (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libstdc++6 (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libapt-pkg4.12 [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libapt-pkg4.12 (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst gpgv [1.4.16-1ubuntu2.1] (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf gpgv (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst gnupg [1.4.16-1ubuntu2.1] (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf gnupg (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst apt [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf apt (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst bsdutils [1:2.20.1-5.1ubuntu20.4] (1:2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf bsdutils (1:2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst passwd [1:4.1.5.1-1ubuntu9] (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf passwd (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libuuid1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libuuid1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libblkid1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libblkid1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libcomerr2 [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libcomerr2 (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libmount1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libmount1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libpcre3 [1:8.31-2ubuntu2] (1:8.31-2ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libpcre3 (1:8.31-2ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libss2 [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libss2 (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libapt-inst1.5 [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libexpat1 [2.1.0-4ubuntu1] (2.1.0-4ubuntu1.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libffi6 [3.1~rc1+r3.0.13-12] (3.1~rc1+r3.0.13-12ubuntu0.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libgcrypt11 [1.5.3-2ubuntu4.1] (1.5.3-2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libtasn1-6 [3.4-3ubuntu0.1] (3.4-3ubuntu0.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libgnutls-openssl27 [2.12.23-12ubuntu2.1] (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libgnutls26 [2.12.23-12ubuntu2.1] (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libsqlite3-0 [3.8.2-1ubuntu2] (3.8.2-1ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst python3.4 [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libpython3.4-stdlib [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst python3.4-minimal [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libssl1.0.0 [1.0.1f-1ubuntu2.8] (1.0.1f-1ubuntu2.16 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libpython3.4-minimal [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst ntpdate [1:4.2.6.p5+dfsg-3ubuntu2.14.04.2] (1:4.2.6.p5+dfsg-3ubuntu2.14.04.8 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libdrm2 [2.4.56-1~ubuntu2] (2.4.64-1~ubuntu14.04.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libpng12-0 [1.2.50-1ubuntu2] (1.2.50-1ubuntu2.14.04.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst initscripts [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libcgmanager0 [0.24-0ubuntu7.3] (0.24-0ubuntu7.5 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst udev [204-5ubuntu20.10] (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libudev1 [204-5ubuntu20.10] (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst multiarch-support [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf multiarch-support (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst apt-utils [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst dh-python [1.20140128-1ubuntu8] (1.20140128-1ubuntu8.2 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Inst iproute2 [3.12.0-2] (3.12.0-2ubuntu1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst ifupdown [0.7.47.2ubuntu4.1] (0.7.47.2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst isc-dhcp-client [4.2.4-7ubuntu12] (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst isc-dhcp-common [4.2.4-7ubuntu12] (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst rsyslog [7.4.4-1ubuntu2.5] (7.4.4-1ubuntu2.6 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst sudo [1.8.9p5-1ubuntu1] (1.8.9p5-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst cpio [2.11+dfsg-1ubuntu1.1] (2.11+dfsg-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libapt-inst1.5 (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libexpat1 (2.1.0-4ubuntu1.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libffi6 (3.1~rc1+r3.0.13-12ubuntu0.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libgcrypt11 (1.5.3-2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libtasn1-6 (3.4-3ubuntu0.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libgnutls26 (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libgnutls-openssl27 (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libsqlite3-0 (3.8.2-1ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libssl1.0.0 (1.0.1f-1ubuntu2.16 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libpython3.4-minimal (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf python3.4-minimal (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libpython3.4-stdlib (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf python3.4 (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf ntpdate (1:4.2.6.p5+dfsg-3ubuntu2.14.04.8 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libdrm2 (2.4.64-1~ubuntu14.04.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libpng12-0 (1.2.50-1ubuntu2.14.04.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf initscripts (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libcgmanager0 (0.24-0ubuntu7.5 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libudev1 (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf udev (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf apt-utils (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf dh-python (1.20140128-1ubuntu8.2 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Conf iproute2 (3.12.0-2ubuntu1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf ifupdown (0.7.47.2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf isc-dhcp-common (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf isc-dhcp-client (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf rsyslog (7.4.4-1ubuntu2.6 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf sudo (1.8.9p5-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf cpio (2.11+dfsg-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
`,
 | 
			
		||||
			[]string{
 | 
			
		||||
				"apt",
 | 
			
		||||
@@ -520,6 +417,97 @@ Calculating upgrade... Done
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetChangelogCache(t *testing.T) {
 | 
			
		||||
	const servername = "server1"
 | 
			
		||||
	pack := models.Package{
 | 
			
		||||
		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.Packages{
 | 
			
		||||
			"apt": pack,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const path = "/tmp/vuls-test-cache-11111111.db"
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	if err := cache.SetupBolt(path, log); err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(path)
 | 
			
		||||
 | 
			
		||||
	if err := cache.DB.EnsureBuckets(meta); err != nil {
 | 
			
		||||
		t.Errorf("Failed to ensure buckets: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	actual := d.getChangelogCache(&meta, pack)
 | 
			
		||||
	if actual != "" {
 | 
			
		||||
		t.Errorf("Failed to get empty stirng from cache:")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clog := "changelog-text"
 | 
			
		||||
	if err := cache.DB.PutChangelog(servername, "apt", clog); err != nil {
 | 
			
		||||
		t.Errorf("Failed to put changelog: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	actual = d.getChangelogCache(&meta, pack)
 | 
			
		||||
	if actual != clog {
 | 
			
		||||
		t.Errorf("Failed to get changelog from cache: %s", actual)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// increment a version of the pack
 | 
			
		||||
	pack.NewVersion = "1.0.2"
 | 
			
		||||
	actual = d.getChangelogCache(&meta, pack)
 | 
			
		||||
	if actual != "" {
 | 
			
		||||
		t.Errorf("The changelog is not invalidated: %s", actual)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// change a name of the pack
 | 
			
		||||
	pack.Name = "bash"
 | 
			
		||||
	actual = d.getChangelogCache(&meta, pack)
 | 
			
		||||
	if actual != "" {
 | 
			
		||||
		t.Errorf("The changelog is not invalidated: %s", actual)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSplitAptCachePolicy(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		stdout   string
 | 
			
		||||
		expected map[string]string
 | 
			
		||||
	}{
 | 
			
		||||
		// This function parse apt-cache policy by using Regexp multi-line mode.
 | 
			
		||||
		// So, test data includes "\r\n"
 | 
			
		||||
		{
 | 
			
		||||
			"apt:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\napt-utils:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\nbase-files:\r\n  Installed: 9.4ubuntu3\r\n  Candidate: 9.4ubuntu4.2\r\n  Version table:\r\n     9.4ubuntu4.2 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     9.4ubuntu4 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 9.4ubuntu3 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
 | 
			
		||||
			map[string]string{
 | 
			
		||||
				"apt": "apt:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
 | 
			
		||||
				"apt-utils": "apt-utils:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
 | 
			
		||||
				"base-files": "base-files:\r\n  Installed: 9.4ubuntu3\r\n  Candidate: 9.4ubuntu4.2\r\n  Version table:\r\n     9.4ubuntu4.2 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     9.4ubuntu4 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 9.4ubuntu3 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := d.splitAptCachePolicy(tt.stdout)
 | 
			
		||||
		if !reflect.DeepEqual(tt.expected, actual) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.expected)
 | 
			
		||||
			a := pp.Sprintf("%v", actual)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseAptCachePolicy(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,7 @@ import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	ex "os/exec"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -34,14 +33,15 @@ import (
 | 
			
		||||
	"golang.org/x/crypto/ssh"
 | 
			
		||||
	"golang.org/x/crypto/ssh/agent"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	conf "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type sshResult struct {
 | 
			
		||||
type execResult struct {
 | 
			
		||||
	Servername string
 | 
			
		||||
	Container  conf.Container
 | 
			
		||||
	Host       string
 | 
			
		||||
	Port       string
 | 
			
		||||
	Cmd        string
 | 
			
		||||
@@ -51,16 +51,20 @@ type sshResult struct {
 | 
			
		||||
	Error      error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s sshResult) String() string {
 | 
			
		||||
func (s execResult) String() string {
 | 
			
		||||
	sname := ""
 | 
			
		||||
	if s.Container.ContainerID == "" {
 | 
			
		||||
		sname = s.Servername
 | 
			
		||||
	} else {
 | 
			
		||||
		sname = s.Container.Name + "@" + s.Servername
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
		"execResult: servername: %s\n  cmd: %s\n  exitstatus: %d\n  stdout: %s\n  stderr: %s\n  err: %s",
 | 
			
		||||
		sname, s.Cmd, s.ExitStatus, s.Stdout, s.Stderr, s.Error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s sshResult) isSuccess(expectedStatusCodes ...int) bool {
 | 
			
		||||
	if s.Error != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
func (s execResult) isSuccess(expectedStatusCodes ...int) bool {
 | 
			
		||||
	if len(expectedStatusCodes) == 0 {
 | 
			
		||||
		return s.ExitStatus == 0
 | 
			
		||||
	}
 | 
			
		||||
@@ -69,38 +73,36 @@ func (s sshResult) isSuccess(expectedStatusCodes ...int) bool {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if s.Error != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sudo is Const value for sudo mode
 | 
			
		||||
// sudo is Const value for sudo mode
 | 
			
		||||
const sudo = true
 | 
			
		||||
 | 
			
		||||
// NoSudo is Const value for normal user mode
 | 
			
		||||
// noSudo is Const value for normal user mode
 | 
			
		||||
const noSudo = false
 | 
			
		||||
 | 
			
		||||
func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []error) {
 | 
			
		||||
	resChan := make(chan string, len(servers))
 | 
			
		||||
	errChan := make(chan error, len(servers))
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
//  Issue commands to the target servers in parallel via SSH or local execution.  If execution fails, the server will be excluded from the target server list(servers) and added to the error server list(errServers).
 | 
			
		||||
func parallelExec(fn func(osTypeInterface) error, timeoutSec ...int) {
 | 
			
		||||
	resChan := make(chan osTypeInterface, len(servers))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		go func(s osTypeInterface) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if p := recover(); p != nil {
 | 
			
		||||
					logrus.Debugf("Panic: %s on %s",
 | 
			
		||||
						p, s.getServerInfo().ServerName)
 | 
			
		||||
					util.Log.Debugf("Panic: %s on %s",
 | 
			
		||||
						p, s.getServerInfo().GetServerName())
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			if err := fn(s); err != nil {
 | 
			
		||||
				errChan <- fmt.Errorf("%s@%s:%s: %s",
 | 
			
		||||
					s.getServerInfo().User,
 | 
			
		||||
					s.getServerInfo().Host,
 | 
			
		||||
					s.getServerInfo().Port,
 | 
			
		||||
					err,
 | 
			
		||||
				)
 | 
			
		||||
				s.setErrs([]error{err})
 | 
			
		||||
				resChan <- s
 | 
			
		||||
			} else {
 | 
			
		||||
				resChan <- s.getServerInfo().ServerName
 | 
			
		||||
				resChan <- s
 | 
			
		||||
			}
 | 
			
		||||
		}(s)
 | 
			
		||||
	}
 | 
			
		||||
@@ -112,61 +114,98 @@ func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []
 | 
			
		||||
		timeout = timeoutSec[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var snames []string
 | 
			
		||||
	var successes []osTypeInterface
 | 
			
		||||
	isTimedout := false
 | 
			
		||||
	for i := 0; i < len(servers); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case s := <-resChan:
 | 
			
		||||
			snames = append(snames, s)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
			if len(s.getErrs()) == 0 {
 | 
			
		||||
				successes = append(successes, s)
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Errorf("Error: %s, err: %s",
 | 
			
		||||
					s.getServerInfo().GetServerName(), s.getErrs())
 | 
			
		||||
				errServers = append(errServers, s)
 | 
			
		||||
			}
 | 
			
		||||
		case <-time.After(time.Duration(timeout) * time.Second):
 | 
			
		||||
			isTimedout = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// collect timed out servernames
 | 
			
		||||
	var timedoutSnames []string
 | 
			
		||||
	if isTimedout {
 | 
			
		||||
		// set timed out error and append to errServers
 | 
			
		||||
		for _, s := range servers {
 | 
			
		||||
			name := s.getServerInfo().ServerName
 | 
			
		||||
			name := s.getServerInfo().GetServerName()
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, t := range snames {
 | 
			
		||||
				if name == t {
 | 
			
		||||
			for _, ss := range successes {
 | 
			
		||||
				if name == ss.getServerInfo().GetServerName() {
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !found {
 | 
			
		||||
				timedoutSnames = append(timedoutSnames, name)
 | 
			
		||||
				msg := fmt.Sprintf("Timed out: %s",
 | 
			
		||||
					s.getServerInfo().GetServerName())
 | 
			
		||||
				util.Log.Errorf(msg)
 | 
			
		||||
				s.setErrs([]error{fmt.Errorf(msg)})
 | 
			
		||||
				errServers = append(errServers, s)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if isTimedout {
 | 
			
		||||
		errs = append(errs, fmt.Errorf(
 | 
			
		||||
			"Timed out: %s", timedoutSnames))
 | 
			
		||||
	}
 | 
			
		||||
	servers = successes
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result sshResult) {
 | 
			
		||||
	if isSSHExecNative() {
 | 
			
		||||
func exec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result execResult) {
 | 
			
		||||
	logger := getSSHLogger(log...)
 | 
			
		||||
	logger.Debugf("Executing... %s", strings.Replace(cmd, "\n", "", -1))
 | 
			
		||||
 | 
			
		||||
	if c.Port == "local" &&
 | 
			
		||||
		(c.Host == "127.0.0.1" || c.Host == "localhost") {
 | 
			
		||||
		result = localExec(c, cmd, sudo)
 | 
			
		||||
	} else if conf.Conf.SSHNative {
 | 
			
		||||
		result = sshExecNative(c, cmd, sudo)
 | 
			
		||||
	} else {
 | 
			
		||||
		result = sshExecExternal(c, cmd, sudo)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger := getSSHLogger(log...)
 | 
			
		||||
	logger.Debug(result)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isSSHExecNative() bool {
 | 
			
		||||
	return runtime.GOOS == "windows" || !conf.Conf.SSHExternal
 | 
			
		||||
func localExec(c conf.ServerInfo, cmdstr string, sudo bool) (result execResult) {
 | 
			
		||||
	cmdstr = decorateCmd(c, cmdstr, sudo)
 | 
			
		||||
	var cmd *ex.Cmd
 | 
			
		||||
	switch c.Distro.Family {
 | 
			
		||||
	// case conf.FreeBSD, conf.Alpine, conf.Debian:
 | 
			
		||||
	// cmd = ex.Command("/bin/sh", "-c", cmdstr)
 | 
			
		||||
	default:
 | 
			
		||||
		cmd = ex.Command("/bin/sh", "-c", cmdstr)
 | 
			
		||||
	}
 | 
			
		||||
	var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
	cmd.Stdout = &stdoutBuf
 | 
			
		||||
	cmd.Stderr = &stderrBuf
 | 
			
		||||
 | 
			
		||||
	if err := cmd.Run(); err != nil {
 | 
			
		||||
		result.Error = err
 | 
			
		||||
		if exitError, ok := err.(*ex.ExitError); ok {
 | 
			
		||||
			waitStatus := exitError.Sys().(syscall.WaitStatus)
 | 
			
		||||
			result.ExitStatus = waitStatus.ExitStatus()
 | 
			
		||||
		} else {
 | 
			
		||||
			result.ExitStatus = 999
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		result.ExitStatus = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result.Stdout = stdoutBuf.String()
 | 
			
		||||
	result.Stderr = stderrBuf.String()
 | 
			
		||||
	result.Cmd = strings.Replace(cmdstr, "\n", "", -1)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult) {
 | 
			
		||||
func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result execResult) {
 | 
			
		||||
	result.Servername = c.ServerName
 | 
			
		||||
	result.Container = c.Container
 | 
			
		||||
	result.Host = c.Host
 | 
			
		||||
	result.Port = c.Port
 | 
			
		||||
 | 
			
		||||
@@ -195,7 +234,7 @@ func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult)
 | 
			
		||||
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
 | 
			
		||||
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
 | 
			
		||||
	}
 | 
			
		||||
	if err = session.RequestPty("xterm", 400, 256, modes); err != nil {
 | 
			
		||||
	if err = session.RequestPty("xterm", 400, 1000, modes); err != nil {
 | 
			
		||||
		result.Error = fmt.Errorf(
 | 
			
		||||
			"Failed to request for pseudo terminal. servername: %s, err: %s",
 | 
			
		||||
			c.ServerName, err)
 | 
			
		||||
@@ -207,7 +246,7 @@ func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult)
 | 
			
		||||
	session.Stdout = &stdoutBuf
 | 
			
		||||
	session.Stderr = &stderrBuf
 | 
			
		||||
 | 
			
		||||
	cmd = decolateCmd(c, cmd, sudo)
 | 
			
		||||
	cmd = decorateCmd(c, cmd, sudo)
 | 
			
		||||
	if err := session.Run(cmd); err != nil {
 | 
			
		||||
		if exitErr, ok := err.(*ssh.ExitError); ok {
 | 
			
		||||
			result.ExitStatus = exitErr.ExitStatus()
 | 
			
		||||
@@ -224,16 +263,15 @@ func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result sshResult) {
 | 
			
		||||
	sshBinaryPath, err := exec.LookPath("ssh")
 | 
			
		||||
func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result execResult) {
 | 
			
		||||
	sshBinaryPath, err := ex.LookPath("ssh")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return sshExecNative(c, cmd, sudo)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defaultSSHArgs := []string{
 | 
			
		||||
		"-t",
 | 
			
		||||
		"-o", "StrictHostKeyChecking=no",
 | 
			
		||||
		"-o", "UserKnownHostsFile=/dev/null",
 | 
			
		||||
		"-tt",
 | 
			
		||||
		"-o", "StrictHostKeyChecking=yes",
 | 
			
		||||
		"-o", "LogLevel=quiet",
 | 
			
		||||
		"-o", "ConnectionAttempts=3",
 | 
			
		||||
		"-o", "ConnectTimeout=10",
 | 
			
		||||
@@ -257,17 +295,17 @@ func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result sshResult
 | 
			
		||||
		args = append(args, "-o", "PasswordAuthentication=no")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd = decolateCmd(c, cmd, sudo)
 | 
			
		||||
	//  cmd = fmt.Sprintf("stty cols 256; set -o pipefail; %s", cmd)
 | 
			
		||||
	cmd = decorateCmd(c, cmd, sudo)
 | 
			
		||||
	cmd = fmt.Sprintf("stty cols 1000; %s", cmd)
 | 
			
		||||
 | 
			
		||||
	args = append(args, cmd)
 | 
			
		||||
	execCmd := exec.Command(sshBinaryPath, args...)
 | 
			
		||||
	execCmd := ex.Command(sshBinaryPath, args...)
 | 
			
		||||
 | 
			
		||||
	var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
	execCmd.Stdout = &stdoutBuf
 | 
			
		||||
	execCmd.Stderr = &stderrBuf
 | 
			
		||||
	if err := execCmd.Run(); err != nil {
 | 
			
		||||
		if e, ok := err.(*exec.ExitError); ok {
 | 
			
		||||
		if e, ok := err.(*ex.ExitError); ok {
 | 
			
		||||
			if s, ok := e.Sys().(syscall.WaitStatus); ok {
 | 
			
		||||
				result.ExitStatus = s.ExitStatus()
 | 
			
		||||
			} else {
 | 
			
		||||
@@ -283,6 +321,7 @@ func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result sshResult
 | 
			
		||||
	result.Stdout = stdoutBuf.String()
 | 
			
		||||
	result.Stderr = stderrBuf.String()
 | 
			
		||||
	result.Servername = c.ServerName
 | 
			
		||||
	result.Container = c.Container
 | 
			
		||||
	result.Host = c.Host
 | 
			
		||||
	result.Port = c.Port
 | 
			
		||||
	result.Cmd = fmt.Sprintf("%s %s", sshBinaryPath, strings.Join(args, " "))
 | 
			
		||||
@@ -296,21 +335,38 @@ func getSSHLogger(log ...*logrus.Entry) *logrus.Entry {
 | 
			
		||||
	return log[0]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func decolateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
 | 
			
		||||
func dockerShell(family string) string {
 | 
			
		||||
	switch family {
 | 
			
		||||
	// case conf.Alpine, conf.Debian:
 | 
			
		||||
	// return "/bin/sh"
 | 
			
		||||
	default:
 | 
			
		||||
		// return "/bin/bash"
 | 
			
		||||
		return "/bin/sh"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func decorateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
 | 
			
		||||
	if sudo && c.User != "root" && !c.IsContainer() {
 | 
			
		||||
		cmd = fmt.Sprintf("sudo -S %s", cmd)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Family != "FreeBSD" {
 | 
			
		||||
		// set pipefail option. Bash only
 | 
			
		||||
		// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
 | 
			
		||||
		cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
 | 
			
		||||
	}
 | 
			
		||||
	// If you are using pipe and you want to detect preprocessing errors, remove comment out
 | 
			
		||||
	//  switch c.Distro.Family {
 | 
			
		||||
	//  case "FreeBSD", "ubuntu", "debian", "raspbian":
 | 
			
		||||
	//  default:
 | 
			
		||||
	//      // set pipefail option. Bash only
 | 
			
		||||
	//      // http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
 | 
			
		||||
	//      cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
 | 
			
		||||
	//  }
 | 
			
		||||
 | 
			
		||||
	if c.IsContainer() {
 | 
			
		||||
		switch c.Container.Type {
 | 
			
		||||
		switch c.Containers.Type {
 | 
			
		||||
		case "", "docker":
 | 
			
		||||
			cmd = fmt.Sprintf(`docker exec %s /bin/bash -c "%s"`, c.Container.ContainerID, cmd)
 | 
			
		||||
			cmd = fmt.Sprintf(`docker exec --user 0 %s %s -c '%s'`,
 | 
			
		||||
				c.Container.ContainerID, dockerShell(c.Distro.Family), cmd)
 | 
			
		||||
		case "lxd":
 | 
			
		||||
			cmd = fmt.Sprintf(`lxc exec %s -- %s -c '%s'`,
 | 
			
		||||
				c.Container.Name, dockerShell(c.Distro.Family), cmd)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	//  cmd = fmt.Sprintf("set -x; %s", cmd)
 | 
			
		||||
@@ -318,7 +374,7 @@ func decolateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAgentAuth() (auth ssh.AuthMethod, ok bool) {
 | 
			
		||||
	if sock := os.Getenv("SSH_AUTH_SOCK"); len(sock) > 0 {
 | 
			
		||||
	if sock := os.Getenv("SSH_AUTH_SOCK"); 0 < len(sock) {
 | 
			
		||||
		if agconn, err := net.Dial("unix", sock); err == nil {
 | 
			
		||||
			ag := agent.NewClient(agconn)
 | 
			
		||||
			auth = ssh.PublicKeysCallback(ag.Signers)
 | 
			
		||||
@@ -331,8 +387,9 @@ func getAgentAuth() (auth ssh.AuthMethod, ok bool) {
 | 
			
		||||
func tryAgentConnect(c conf.ServerInfo) *ssh.Client {
 | 
			
		||||
	if auth, ok := getAgentAuth(); ok {
 | 
			
		||||
		config := &ssh.ClientConfig{
 | 
			
		||||
			User: c.User,
 | 
			
		||||
			Auth: []ssh.AuthMethod{auth},
 | 
			
		||||
			User:            c.User,
 | 
			
		||||
			Auth:            []ssh.AuthMethod{auth},
 | 
			
		||||
			HostKeyCallback: ssh.InsecureIgnoreHostKey(),
 | 
			
		||||
		}
 | 
			
		||||
		client, _ := ssh.Dial("tcp", c.Host+":"+c.Port, config)
 | 
			
		||||
		return client
 | 
			
		||||
@@ -352,8 +409,9 @@ func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
 | 
			
		||||
 | 
			
		||||
	// http://blog.ralch.com/tutorial/golang-ssh-connection/
 | 
			
		||||
	config := &ssh.ClientConfig{
 | 
			
		||||
		User: c.User,
 | 
			
		||||
		Auth: auths,
 | 
			
		||||
		User:            c.User,
 | 
			
		||||
		Auth:            auths,
 | 
			
		||||
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	notifyFunc := func(e error, t time.Duration) {
 | 
			
		||||
							
								
								
									
										188
									
								
								scan/executil_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,188 @@
 | 
			
		||||
/* 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 (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDecorateCmd(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		conf     config.ServerInfo
 | 
			
		||||
		cmd      string
 | 
			
		||||
		sudo     bool
 | 
			
		||||
		expected string
 | 
			
		||||
	}{
 | 
			
		||||
		// root sudo false
 | 
			
		||||
		{
 | 
			
		||||
			conf:     config.ServerInfo{User: "root"},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: "ls",
 | 
			
		||||
		},
 | 
			
		||||
		// root sudo true
 | 
			
		||||
		{
 | 
			
		||||
			conf:     config.ServerInfo{User: "root"},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: "ls",
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo false
 | 
			
		||||
		{
 | 
			
		||||
			conf:     config.ServerInfo{User: "non-roor"},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: "ls",
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true
 | 
			
		||||
		{
 | 
			
		||||
			conf:     config.ServerInfo{User: "non-roor"},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: "sudo -S ls",
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true
 | 
			
		||||
		{
 | 
			
		||||
			conf:     config.ServerInfo{User: "non-roor"},
 | 
			
		||||
			cmd:      "ls | grep hoge",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: "sudo -S ls | grep hoge",
 | 
			
		||||
		},
 | 
			
		||||
		// -------------docker-------------
 | 
			
		||||
		// root sudo false docker
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:       "root",
 | 
			
		||||
				Container:  config.Container{ContainerID: "abc"},
 | 
			
		||||
				Containers: config.Containers{Type: "docker"},
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: `docker exec --user 0 abc /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// root sudo true docker
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:       "root",
 | 
			
		||||
				Container:  config.Container{ContainerID: "abc"},
 | 
			
		||||
				Containers: config.Containers{Type: "docker"},
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `docker exec --user 0 abc /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo false, docker
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:       "non-root",
 | 
			
		||||
				Container:  config.Container{ContainerID: "abc"},
 | 
			
		||||
				Containers: config.Containers{Type: "docker"},
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: `docker exec --user 0 abc /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true, docker
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:       "non-root",
 | 
			
		||||
				Container:  config.Container{ContainerID: "abc"},
 | 
			
		||||
				Containers: config.Containers{Type: "docker"},
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `docker exec --user 0 abc /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true, docker
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:       "non-root",
 | 
			
		||||
				Container:  config.Container{ContainerID: "abc"},
 | 
			
		||||
				Containers: config.Containers{Type: "docker"},
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls | grep hoge",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `docker exec --user 0 abc /bin/sh -c 'ls | grep hoge'`,
 | 
			
		||||
		},
 | 
			
		||||
		// -------------lxd-------------
 | 
			
		||||
		// root sudo false lxd
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:       "root",
 | 
			
		||||
				Container:  config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				Containers: config.Containers{Type: "lxd"},
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: `lxc exec def -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// root sudo true lxd
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:       "root",
 | 
			
		||||
				Container:  config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				Containers: config.Containers{Type: "lxd"},
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `lxc exec def -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo false, lxd
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:       "non-root",
 | 
			
		||||
				Container:  config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				Containers: config.Containers{Type: "lxd"},
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: `lxc exec def -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true, lxd
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:       "non-root",
 | 
			
		||||
				Container:  config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				Containers: config.Containers{Type: "lxd"},
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `lxc exec def -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true lxd
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:       "non-root",
 | 
			
		||||
				Container:  config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				Containers: config.Containers{Type: "lxd"},
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls | grep hoge",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `lxc exec def -- /bin/sh -c 'ls | grep hoge'`,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := decorateCmd(tt.conf, tt.cmd, tt.sudo)
 | 
			
		||||
		if actual != tt.expected {
 | 
			
		||||
			t.Errorf("expected: %s, actual: %s", tt.expected, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										172
									
								
								scan/freebsd.go
									
									
									
									
									
								
							
							
						
						@@ -1,3 +1,20 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@@ -5,7 +22,6 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
@@ -17,85 +33,118 @@ type bsd struct {
 | 
			
		||||
 | 
			
		||||
// NewBSD constructor
 | 
			
		||||
func newBsd(c config.ServerInfo) *bsd {
 | 
			
		||||
	d := &bsd{}
 | 
			
		||||
	d := &bsd{
 | 
			
		||||
		base: base{
 | 
			
		||||
			osPackages: osPackages{
 | 
			
		||||
				Packages:  models.Packages{},
 | 
			
		||||
				VulnInfos: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/freebsd.rb
 | 
			
		||||
func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) {
 | 
			
		||||
	bsd = newBsd(c)
 | 
			
		||||
	c.Family = "FreeBSD"
 | 
			
		||||
	if r := sshExec(c, "uname", noSudo); r.isSuccess() {
 | 
			
		||||
		if strings.Contains(r.Stdout, "FreeBSD") == true {
 | 
			
		||||
			if b := sshExec(c, "uname -r", noSudo); b.isSuccess() {
 | 
			
		||||
				bsd.setDistributionInfo("FreeBSD", strings.TrimSpace(b.Stdout))
 | 
			
		||||
				bsd.setServerInfo(c)
 | 
			
		||||
 | 
			
		||||
	// Prevent from adding `set -o pipefail` option
 | 
			
		||||
	c.Distro = config.Distro{Family: config.FreeBSD}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "uname", noSudo); r.isSuccess() {
 | 
			
		||||
		if strings.Contains(strings.ToLower(r.Stdout), config.FreeBSD) == true {
 | 
			
		||||
			if b := exec(c, "freebsd-version", noSudo); b.isSuccess() {
 | 
			
		||||
				rel := strings.TrimSpace(b.Stdout)
 | 
			
		||||
				bsd.setDistro(config.FreeBSD, rel)
 | 
			
		||||
				return true, bsd
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	Log.Debugf("Not FreeBSD. servernam: %s", c.ServerName)
 | 
			
		||||
	util.Log.Debugf("Not FreeBSD. servernam: %s", c.ServerName)
 | 
			
		||||
	return false, bsd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) checkIfSudoNoPasswd() error {
 | 
			
		||||
	// FreeBSD doesn't need root privilege
 | 
			
		||||
	o.log.Infof("sudo ... OK")
 | 
			
		||||
	o.log.Infof("sudo ... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) install() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) checkRequiredPackagesInstalled() error {
 | 
			
		||||
func (o *bsd) checkDependencies() error {
 | 
			
		||||
	o.log.Infof("Dependencies... No need")
 | 
			
		||||
	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")
 | 
			
		||||
	// collect the running kernel information
 | 
			
		||||
	release, version, err := o.runningKernel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan the running kernel version: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setPackages(packs)
 | 
			
		||||
	o.Kernel = models.Kernel{
 | 
			
		||||
		Release: release,
 | 
			
		||||
		Version: version,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var unsecurePacks []CvePacksInfo
 | 
			
		||||
	if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan vulnerable packages")
 | 
			
		||||
	rebootRequired, err := o.rebootRequired()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to detect the kernel reboot required: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setUnsecurePackages(unsecurePacks)
 | 
			
		||||
	o.Kernel.RebootRequired = rebootRequired
 | 
			
		||||
 | 
			
		||||
	packs, err := o.scanInstalledPackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.Packages = packs
 | 
			
		||||
 | 
			
		||||
	unsecures, err := o.scanUnsecurePackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan vulnerable packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.VulnInfos = unsecures
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanInstalledPackages() ([]models.PackageInfo, error) {
 | 
			
		||||
func (o *bsd) rebootRequired() (bool, error) {
 | 
			
		||||
	r := o.exec("freebsd-version -k", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return false, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.Kernel.Release != strings.TrimSpace(r.Stdout), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanInstalledPackages() (models.Packages, error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("pkg version -v")
 | 
			
		||||
	r := o.ssh(cmd, noSudo)
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.parsePkgVersion(r.Stdout), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
 | 
			
		||||
func (o *bsd) scanUnsecurePackages() (models.VulnInfos, error) {
 | 
			
		||||
	const vulndbPath = "/tmp/vuln.db"
 | 
			
		||||
	cmd := "rm -f " + vulndbPath
 | 
			
		||||
	r := o.ssh(cmd, noSudo)
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess(0) {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd = util.PrependProxyEnv("pkg audit -F -r -f " + vulndbPath)
 | 
			
		||||
	r = o.ssh(cmd, noSudo)
 | 
			
		||||
	r = o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess(0, 1) {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	if r.ExitStatus == 0 {
 | 
			
		||||
		// no vulnerabilities
 | 
			
		||||
		return []CvePacksInfo{}, nil
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var packAdtRslt []pkgAuditResult
 | 
			
		||||
@@ -105,7 +154,7 @@ func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
 | 
			
		||||
		if len(cveIDs) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pack, found := o.Packages.FindByName(name)
 | 
			
		||||
		pack, found := o.Packages[name]
 | 
			
		||||
		if !found {
 | 
			
		||||
			return nil, fmt.Errorf("Vulnerable package: %s is not found", name)
 | 
			
		||||
		}
 | 
			
		||||
@@ -126,41 +175,38 @@ func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveIDs := []string{}
 | 
			
		||||
	for k := range cveIDAdtMap {
 | 
			
		||||
		cveIDs = append(cveIDs, k)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Info("Done")
 | 
			
		||||
 | 
			
		||||
	for _, d := range cveDetails {
 | 
			
		||||
		packs := []models.PackageInfo{}
 | 
			
		||||
		for _, r := range cveIDAdtMap[d.CveID] {
 | 
			
		||||
			packs = append(packs, r.pack)
 | 
			
		||||
	vinfos := models.VulnInfos{}
 | 
			
		||||
	for cveID := range cveIDAdtMap {
 | 
			
		||||
		packs := models.Packages{}
 | 
			
		||||
		for _, r := range cveIDAdtMap[cveID] {
 | 
			
		||||
			packs[r.pack.Name] = r.pack
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		disAdvs := []models.DistroAdvisory{}
 | 
			
		||||
		for _, r := range cveIDAdtMap[d.CveID] {
 | 
			
		||||
		for _, r := range cveIDAdtMap[cveID] {
 | 
			
		||||
			disAdvs = append(disAdvs, models.DistroAdvisory{
 | 
			
		||||
				AdvisoryID: r.vulnIDCveIDs.vulnID,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cvePacksList = append(cvePacksList, CvePacksInfo{
 | 
			
		||||
			CveID:            d.CveID,
 | 
			
		||||
			CveDetail:        d,
 | 
			
		||||
			Packs:            packs,
 | 
			
		||||
		affected := models.PackageStatuses{}
 | 
			
		||||
		for name := range packs {
 | 
			
		||||
			affected = append(affected, models.PackageStatus{
 | 
			
		||||
				Name: name,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		vinfos[cveID] = models.VulnInfo{
 | 
			
		||||
			CveID:            cveID,
 | 
			
		||||
			AffectedPackages: affected,
 | 
			
		||||
			DistroAdvisories: disAdvs,
 | 
			
		||||
		})
 | 
			
		||||
			Confidence:       models.PkgAuditMatch,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) parsePkgVersion(stdout string) (packs []models.PackageInfo) {
 | 
			
		||||
func (o *bsd) parsePkgVersion(stdout string) models.Packages {
 | 
			
		||||
	packs := models.Packages{}
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, l := range lines {
 | 
			
		||||
		fields := strings.Fields(l)
 | 
			
		||||
@@ -175,20 +221,26 @@ func (o *bsd) parsePkgVersion(stdout string) (packs []models.PackageInfo) {
 | 
			
		||||
 | 
			
		||||
		switch fields[1] {
 | 
			
		||||
		case "?", "=":
 | 
			
		||||
			packs = append(packs, models.PackageInfo{
 | 
			
		||||
			packs[name] = models.Package{
 | 
			
		||||
				Name:    name,
 | 
			
		||||
				Version: ver,
 | 
			
		||||
			})
 | 
			
		||||
			}
 | 
			
		||||
		case "<":
 | 
			
		||||
			candidate := strings.TrimSuffix(fields[6], ")")
 | 
			
		||||
			packs = append(packs, models.PackageInfo{
 | 
			
		||||
			packs[name] = models.Package{
 | 
			
		||||
				Name:       name,
 | 
			
		||||
				Version:    ver,
 | 
			
		||||
				NewVersion: candidate,
 | 
			
		||||
			})
 | 
			
		||||
			}
 | 
			
		||||
		case ">":
 | 
			
		||||
			o.log.Warn("The installed version of the %s is newer than the current version. *This situation can arise with an out of date index file, or when testing new ports.*", name)
 | 
			
		||||
			packs[name] = models.Package{
 | 
			
		||||
				Name:    name,
 | 
			
		||||
				Version: ver,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
	return packs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type vulnIDCveIDs struct {
 | 
			
		||||
@@ -197,7 +249,7 @@ type vulnIDCveIDs struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type pkgAuditResult struct {
 | 
			
		||||
	pack         models.PackageInfo
 | 
			
		||||
	pack         models.Package
 | 
			
		||||
	vulnIDCveIDs vulnIDCveIDs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ import (
 | 
			
		||||
func TestParsePkgVersion(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []models.PackageInfo
 | 
			
		||||
		expected models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			`Updating FreeBSD repository catalogue...
 | 
			
		||||
@@ -21,27 +21,32 @@ 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
 | 
			
		||||
ntp-4.2.8p8_1                      >   succeeds port (port has 4.2.8p6)
 | 
			
		||||
teTeX-base-3.0_25                  ?   orphaned: print/teTeX-base`,
 | 
			
		||||
 | 
			
		||||
			[]models.PackageInfo{
 | 
			
		||||
				{
 | 
			
		||||
			models.Packages{
 | 
			
		||||
				"bash": {
 | 
			
		||||
					Name:       "bash",
 | 
			
		||||
					Version:    "4.2.45",
 | 
			
		||||
					NewVersion: "4.3.42_1",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
				"gettext": {
 | 
			
		||||
					Name:       "gettext",
 | 
			
		||||
					Version:    "0.18.3.1",
 | 
			
		||||
					NewVersion: "0.19.7",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
				"tcl84": {
 | 
			
		||||
					Name:    "tcl84",
 | 
			
		||||
					Version: "8.4.20_2,1",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
				"teTeX-base": {
 | 
			
		||||
					Name:    "teTeX-base",
 | 
			
		||||
					Version: "3.0_25",
 | 
			
		||||
				},
 | 
			
		||||
				"ntp": {
 | 
			
		||||
					Name:    "ntp",
 | 
			
		||||
					Version: "4.2.8p8_1",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@@ -139,17 +144,17 @@ WWW: https://vuxml.FreeBSD.org/freebsd/ab3e98d9-8175-11e4-907d-d050992ecde8.html
 | 
			
		||||
 | 
			
		||||
	d := newBsd(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		aName, aCveIDs, aVunlnID := d.parseBlock(tt.in)
 | 
			
		||||
		aName, aCveIDs, aVulnID := d.parseBlock(tt.in)
 | 
			
		||||
		if tt.name != aName {
 | 
			
		||||
			t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVunlnID)
 | 
			
		||||
			t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVulnID)
 | 
			
		||||
		}
 | 
			
		||||
		for i := range tt.cveIDs {
 | 
			
		||||
			if tt.cveIDs[i] != aCveIDs[i] {
 | 
			
		||||
				t.Errorf("expected cveID: %s, actual %s", tt.cveIDs[i], aCveIDs[i])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if tt.vulnID != aVunlnID {
 | 
			
		||||
			t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVunlnID)
 | 
			
		||||
		if tt.vulnID != aVulnID {
 | 
			
		||||
			t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVulnID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										66
									
								
								scan/pseudo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,66 @@
 | 
			
		||||
/* 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 (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type pseudo struct {
 | 
			
		||||
	base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectPseudo(c config.ServerInfo) (itsMe bool, pseudo osTypeInterface, err error) {
 | 
			
		||||
	p := newPseudo(c)
 | 
			
		||||
	p.setDistro(config.ServerTypePseudo, "")
 | 
			
		||||
	return c.Type == config.ServerTypePseudo, p, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newPseudo(c config.ServerInfo) *pseudo {
 | 
			
		||||
	d := &pseudo{
 | 
			
		||||
		base: base{
 | 
			
		||||
			osPackages: osPackages{
 | 
			
		||||
				Packages:  models.Packages{},
 | 
			
		||||
				VulnInfos: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *pseudo) checkIfSudoNoPasswd() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *pseudo) checkDependencies() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *pseudo) scanPackages() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *pseudo) detectPlatform() {
 | 
			
		||||
	o.setPlatform(models.Platform{Name: "other"})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1234
									
								
								scan/redhat.go
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										1255
									
								
								scan/redhat_test.go
									
									
									
									
									
								
							
							
						
						@@ -1,37 +1,51 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/cache"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Log for localhsot
 | 
			
		||||
var Log *logrus.Entry
 | 
			
		||||
 | 
			
		||||
var servers []osTypeInterface
 | 
			
		||||
var servers, errServers []osTypeInterface
 | 
			
		||||
 | 
			
		||||
// Base Interface of redhat, debian, freebsd
 | 
			
		||||
type osTypeInterface interface {
 | 
			
		||||
	setServerInfo(config.ServerInfo)
 | 
			
		||||
	getServerInfo() config.ServerInfo
 | 
			
		||||
 | 
			
		||||
	setDistributionInfo(string, string)
 | 
			
		||||
	getDistributionInfo() string
 | 
			
		||||
 | 
			
		||||
	checkIfSudoNoPasswd() error
 | 
			
		||||
	detectPlatform() error
 | 
			
		||||
	setDistro(string, string)
 | 
			
		||||
	getDistro() config.Distro
 | 
			
		||||
	detectPlatform()
 | 
			
		||||
	getPlatform() models.Platform
 | 
			
		||||
 | 
			
		||||
	checkRequiredPackagesInstalled() error
 | 
			
		||||
	checkDependencies() error
 | 
			
		||||
	checkIfSudoNoPasswd() error
 | 
			
		||||
 | 
			
		||||
	scanPackages() error
 | 
			
		||||
	scanVulnByCpeName() error
 | 
			
		||||
	install() error
 | 
			
		||||
	convertToModel() (models.ScanResult, error)
 | 
			
		||||
	convertToModel() models.ScanResult
 | 
			
		||||
 | 
			
		||||
	runningContainers() ([]config.Container, error)
 | 
			
		||||
	exitedContainers() ([]config.Container, error)
 | 
			
		||||
@@ -41,102 +55,70 @@ type osTypeInterface interface {
 | 
			
		||||
	setErrs([]error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// osPackages included by linux struct
 | 
			
		||||
// osPackages is included by base struct
 | 
			
		||||
type osPackages struct {
 | 
			
		||||
	// installed packages
 | 
			
		||||
	Packages models.PackageInfoList
 | 
			
		||||
	Packages models.Packages
 | 
			
		||||
 | 
			
		||||
	// installed source packages (Debian based only)
 | 
			
		||||
	SrcPackages models.SrcPackages
 | 
			
		||||
 | 
			
		||||
	// unsecure packages
 | 
			
		||||
	UnsecurePackages CvePacksList
 | 
			
		||||
}
 | 
			
		||||
	VulnInfos models.VulnInfos
 | 
			
		||||
 | 
			
		||||
func (p *osPackages) setPackages(pi models.PackageInfoList) {
 | 
			
		||||
	p.Packages = pi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *osPackages) setUnsecurePackages(pi []CvePacksInfo) {
 | 
			
		||||
	p.UnsecurePackages = pi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CvePacksList have CvePacksInfo list, getter/setter, sortable methods.
 | 
			
		||||
type CvePacksList []CvePacksInfo
 | 
			
		||||
 | 
			
		||||
// CvePacksInfo hold the CVE information.
 | 
			
		||||
type CvePacksInfo struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	CveDetail        cve.CveDetail
 | 
			
		||||
	Packs            models.PackageInfoList
 | 
			
		||||
	DistroAdvisories []models.DistroAdvisory // for Aamazon, RHEL, FreeBSD
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindByCveID find by CVEID
 | 
			
		||||
func (s CvePacksList) FindByCveID(cveID string) (pi CvePacksInfo, found bool) {
 | 
			
		||||
	for _, p := range s {
 | 
			
		||||
		if cveID == p.CveID {
 | 
			
		||||
			return p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return CvePacksInfo{CveID: cveID}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// immutable
 | 
			
		||||
func (s CvePacksList) set(cveID string, cvePacksInfo CvePacksInfo) CvePacksList {
 | 
			
		||||
	for i, p := range s {
 | 
			
		||||
		if cveID == p.CveID {
 | 
			
		||||
			s[i] = cvePacksInfo
 | 
			
		||||
			return s
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return append(s, cvePacksInfo)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Len implement Sort Interface
 | 
			
		||||
func (s CvePacksList) Len() int {
 | 
			
		||||
	return len(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Swap implement Sort Interface
 | 
			
		||||
func (s CvePacksList) Swap(i, j int) {
 | 
			
		||||
	s[i], s[j] = s[j], s[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Less implement Sort Interface
 | 
			
		||||
func (s CvePacksList) Less(i, j int) bool {
 | 
			
		||||
	return s[i].CveDetail.CvssScore(config.Conf.Lang) >
 | 
			
		||||
		s[j].CveDetail.CvssScore(config.Conf.Lang)
 | 
			
		||||
	// kernel information
 | 
			
		||||
	Kernel models.Kernel
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectOS(c config.ServerInfo) (osType osTypeInterface) {
 | 
			
		||||
	var itsMe bool
 | 
			
		||||
	var fatalErr error
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType, _ = detectPseudo(c); itsMe {
 | 
			
		||||
		util.Log.Debugf("Pseudo")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	itsMe, osType, fatalErr = detectDebian(c)
 | 
			
		||||
	if fatalErr != nil {
 | 
			
		||||
		osType.setServerInfo(c)
 | 
			
		||||
		osType.setErrs([]error{fatalErr})
 | 
			
		||||
		osType.setErrs([]error{
 | 
			
		||||
			fmt.Errorf("Failed to detect OS: %s", fatalErr)})
 | 
			
		||||
		return
 | 
			
		||||
	} else if itsMe {
 | 
			
		||||
		Log.Debugf("Debian like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe {
 | 
			
		||||
		util.Log.Debugf("Debian like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType = detectRedhat(c); itsMe {
 | 
			
		||||
		Log.Debugf("Redhat like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		util.Log.Debugf("Redhat like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType = detectSUSE(c); itsMe {
 | 
			
		||||
		util.Log.Debugf("SUSE Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType = detectFreebsd(c); itsMe {
 | 
			
		||||
		Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		util.Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	osType.setServerInfo(c)
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType = detectAlpine(c); itsMe {
 | 
			
		||||
		util.Log.Debugf("Alpine. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//TODO darwin https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/darwin.rb
 | 
			
		||||
	osType.setErrs([]error{fmt.Errorf("Unknown OS Type")})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrintSSHableServerNames print SSH-able servernames
 | 
			
		||||
func PrintSSHableServerNames() {
 | 
			
		||||
	Log.Info("SSH-able servers are below...")
 | 
			
		||||
	util.Log.Info("Scannable servers are below...")
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		if s.getServerInfo().IsContainer() {
 | 
			
		||||
			fmt.Printf("%s@%s ",
 | 
			
		||||
@@ -151,58 +133,74 @@ func PrintSSHableServerNames() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InitServers detect the kind of OS distribution of target servers
 | 
			
		||||
func InitServers(localLogger *logrus.Entry) {
 | 
			
		||||
	Log = localLogger
 | 
			
		||||
	servers = detectServerOSes()
 | 
			
		||||
	containers := detectContainerOSes()
 | 
			
		||||
	servers = append(servers, containers...)
 | 
			
		||||
func InitServers(timeoutSec int) error {
 | 
			
		||||
	servers, errServers = detectServerOSes(timeoutSec)
 | 
			
		||||
	if len(servers) == 0 {
 | 
			
		||||
		return fmt.Errorf("No scannable servers")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	actives, inactives := detectContainerOSes(timeoutSec)
 | 
			
		||||
	if config.Conf.ContainersOnly {
 | 
			
		||||
		servers = actives
 | 
			
		||||
		errServers = inactives
 | 
			
		||||
	} else {
 | 
			
		||||
		servers = append(servers, actives...)
 | 
			
		||||
		errServers = append(errServers, inactives...)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectServerOSes() (sshAbleOses []osTypeInterface) {
 | 
			
		||||
	Log.Info("Detecting OS of servers... ")
 | 
			
		||||
func detectServerOSes(timeoutSec int) (servers, errServers []osTypeInterface) {
 | 
			
		||||
	util.Log.Info("Detecting OS of servers... ")
 | 
			
		||||
	osTypeChan := make(chan osTypeInterface, len(config.Conf.Servers))
 | 
			
		||||
	defer close(osTypeChan)
 | 
			
		||||
	for _, s := range config.Conf.Servers {
 | 
			
		||||
		go func(s config.ServerInfo) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if p := recover(); p != nil {
 | 
			
		||||
					Log.Debugf("Panic: %s on %s", p, s.ServerName)
 | 
			
		||||
					util.Log.Debugf("Panic: %s on %s", p, s.ServerName)
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			osTypeChan <- detectOS(s)
 | 
			
		||||
		}(s)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var oses []osTypeInterface
 | 
			
		||||
	timeout := time.After(30 * time.Second)
 | 
			
		||||
	timeout := time.After(time.Duration(timeoutSec) * time.Second)
 | 
			
		||||
	for i := 0; i < len(config.Conf.Servers); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-osTypeChan:
 | 
			
		||||
			oses = append(oses, res)
 | 
			
		||||
			if 0 < len(res.getErrs()) {
 | 
			
		||||
				Log.Errorf("(%d/%d) Failed: %s, err: %s",
 | 
			
		||||
				errServers = append(errServers, res)
 | 
			
		||||
				util.Log.Errorf("(%d/%d) Failed: %s, err: %s",
 | 
			
		||||
					i+1, len(config.Conf.Servers),
 | 
			
		||||
					res.getServerInfo().ServerName,
 | 
			
		||||
					res.getErrs())
 | 
			
		||||
			} else {
 | 
			
		||||
				Log.Infof("(%d/%d) Detected: %s: %s",
 | 
			
		||||
				servers = append(servers, res)
 | 
			
		||||
				util.Log.Infof("(%d/%d) Detected: %s: %s",
 | 
			
		||||
					i+1, len(config.Conf.Servers),
 | 
			
		||||
					res.getServerInfo().ServerName,
 | 
			
		||||
					res.getDistributionInfo())
 | 
			
		||||
					res.getDistro())
 | 
			
		||||
			}
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			msg := "Timed out while detecting servers"
 | 
			
		||||
			Log.Error(msg)
 | 
			
		||||
			for servername := range config.Conf.Servers {
 | 
			
		||||
			util.Log.Error(msg)
 | 
			
		||||
			for servername, sInfo := range config.Conf.Servers {
 | 
			
		||||
				found := false
 | 
			
		||||
				for _, o := range oses {
 | 
			
		||||
				for _, o := range append(servers, errServers...) {
 | 
			
		||||
					if servername == o.getServerInfo().ServerName {
 | 
			
		||||
						found = true
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if !found {
 | 
			
		||||
					Log.Errorf("(%d/%d) Timed out: %s",
 | 
			
		||||
					u := &unknown{}
 | 
			
		||||
					u.setServerInfo(sInfo)
 | 
			
		||||
					u.setErrs([]error{
 | 
			
		||||
						fmt.Errorf("Timed out"),
 | 
			
		||||
					})
 | 
			
		||||
					errServers = append(errServers, u)
 | 
			
		||||
					util.Log.Errorf("(%d/%d) Timed out: %s",
 | 
			
		||||
						i+1, len(config.Conf.Servers),
 | 
			
		||||
						servername)
 | 
			
		||||
					i++
 | 
			
		||||
@@ -210,75 +208,70 @@ func detectServerOSes() (sshAbleOses []osTypeInterface) {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, o := range oses {
 | 
			
		||||
		if len(o.getErrs()) == 0 {
 | 
			
		||||
			sshAbleOses = append(sshAbleOses, o)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectContainerOSes() (actives []osTypeInterface) {
 | 
			
		||||
	Log.Info("Detecting OS of containers... ")
 | 
			
		||||
func detectContainerOSes(timeoutSec int) (actives, inactives []osTypeInterface) {
 | 
			
		||||
	util.Log.Info("Detecting OS of containers... ")
 | 
			
		||||
	osTypesChan := make(chan []osTypeInterface, len(servers))
 | 
			
		||||
	defer close(osTypesChan)
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		go func(s osTypeInterface) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if p := recover(); p != nil {
 | 
			
		||||
					Log.Debugf("Panic: %s on %s",
 | 
			
		||||
						p, s.getServerInfo().ServerName)
 | 
			
		||||
					util.Log.Debugf("Panic: %s on %s",
 | 
			
		||||
						p, s.getServerInfo().GetServerName())
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			osTypesChan <- detectContainerOSesOnServer(s)
 | 
			
		||||
		}(s)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var oses []osTypeInterface
 | 
			
		||||
	timeout := time.After(30 * time.Second)
 | 
			
		||||
	timeout := time.After(time.Duration(timeoutSec) * 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())
 | 
			
		||||
					inactives = append(inactives, osi)
 | 
			
		||||
					util.Log.Errorf("Failed: %s err: %s", sinfo.ServerName, osi.getErrs())
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				oses = append(oses, res...)
 | 
			
		||||
				Log.Infof("Detected: %s@%s: %s",
 | 
			
		||||
					sinfo.Container.Name, sinfo.ServerName, osi.getDistributionInfo())
 | 
			
		||||
				actives = append(actives, osi)
 | 
			
		||||
				util.Log.Infof("Detected: %s@%s: %s",
 | 
			
		||||
					sinfo.Container.Name, sinfo.ServerName, osi.getDistro())
 | 
			
		||||
			}
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			msg := "Timed out while detecting containers"
 | 
			
		||||
			Log.Error(msg)
 | 
			
		||||
			for servername := range config.Conf.Servers {
 | 
			
		||||
			util.Log.Error(msg)
 | 
			
		||||
			for servername, sInfo := range config.Conf.Servers {
 | 
			
		||||
				found := false
 | 
			
		||||
				for _, o := range oses {
 | 
			
		||||
				for _, o := range append(actives, inactives...) {
 | 
			
		||||
					if servername == o.getServerInfo().ServerName {
 | 
			
		||||
						found = true
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if !found {
 | 
			
		||||
					Log.Errorf("Timed out: %s", servername)
 | 
			
		||||
					u := &unknown{}
 | 
			
		||||
					u.setServerInfo(sInfo)
 | 
			
		||||
					u.setErrs([]error{
 | 
			
		||||
						fmt.Errorf("Timed out"),
 | 
			
		||||
					})
 | 
			
		||||
					inactives = append(inactives)
 | 
			
		||||
					util.Log.Errorf("Timed out: %s", servername)
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, o := range oses {
 | 
			
		||||
		if len(o.getErrs()) == 0 {
 | 
			
		||||
			actives = append(actives, o)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeInterface) {
 | 
			
		||||
	containerHostInfo := containerHost.getServerInfo()
 | 
			
		||||
	if len(containerHostInfo.Containers) == 0 {
 | 
			
		||||
	if len(containerHostInfo.Containers.Includes) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -290,12 +283,24 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn
 | 
			
		||||
		return append(oses, containerHost)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if containerHostInfo.Containers[0] == "${running}" {
 | 
			
		||||
	if containerHostInfo.Containers.Includes[0] == "${running}" {
 | 
			
		||||
		for _, containerInfo := range running {
 | 
			
		||||
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, ex := range containerHost.getServerInfo().Containers.Excludes {
 | 
			
		||||
				if containerInfo.Name == ex || containerInfo.ContainerID == ex {
 | 
			
		||||
					found = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if found {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			copied := containerHostInfo
 | 
			
		||||
			copied.SetContainer(config.Container{
 | 
			
		||||
				ContainerID: containerInfo.ContainerID,
 | 
			
		||||
				Name:        containerInfo.Name,
 | 
			
		||||
				Image:       containerInfo.Image,
 | 
			
		||||
			})
 | 
			
		||||
			os := detectOS(copied)
 | 
			
		||||
			oses = append(oses, os)
 | 
			
		||||
@@ -312,7 +317,7 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var exited, unknown []string
 | 
			
		||||
	for _, container := range containerHostInfo.Containers {
 | 
			
		||||
	for _, container := range containerHostInfo.Containers.Includes {
 | 
			
		||||
		found := false
 | 
			
		||||
		for _, c := range running {
 | 
			
		||||
			if c.ContainerID == container || c.Name == container {
 | 
			
		||||
@@ -348,29 +353,28 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn
 | 
			
		||||
	return oses
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckDependencies checks dependencies are installed on target servers.
 | 
			
		||||
func CheckDependencies(timeoutSec int) {
 | 
			
		||||
	parallelExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.checkDependencies()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfSudoNoPasswd checks whether vuls can sudo with nopassword via SSH
 | 
			
		||||
func CheckIfSudoNoPasswd(localLogger *logrus.Entry) error {
 | 
			
		||||
	timeoutSec := 1 * 15
 | 
			
		||||
	errs := parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
func CheckIfSudoNoPasswd(timeoutSec int) {
 | 
			
		||||
	parallelExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.checkIfSudoNoPasswd()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		return fmt.Errorf(fmt.Sprintf("%s", errs))
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectPlatforms detects the platform of each servers.
 | 
			
		||||
func DetectPlatforms(localLogger *logrus.Entry) {
 | 
			
		||||
	errs := detectPlatforms()
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		// Only logging
 | 
			
		||||
		Log.Warnf("Failed to detect platforms. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
func DetectPlatforms(timeoutSec int) {
 | 
			
		||||
	detectPlatforms(timeoutSec)
 | 
			
		||||
	for i, s := range servers {
 | 
			
		||||
		if s.getServerInfo().IsContainer() {
 | 
			
		||||
			Log.Infof("(%d/%d) %s on %s is running on %s",
 | 
			
		||||
			util.Log.Infof("(%d/%d) %s on %s is running on %s",
 | 
			
		||||
				i+1, len(servers),
 | 
			
		||||
				s.getServerInfo().Container.Name,
 | 
			
		||||
				s.getServerInfo().ServerName,
 | 
			
		||||
@@ -378,7 +382,7 @@ func DetectPlatforms(localLogger *logrus.Entry) {
 | 
			
		||||
			)
 | 
			
		||||
 | 
			
		||||
		} else {
 | 
			
		||||
			Log.Infof("(%d/%d) %s is running on %s",
 | 
			
		||||
			util.Log.Infof("(%d/%d) %s is running on %s",
 | 
			
		||||
				i+1, len(servers),
 | 
			
		||||
				s.getServerInfo().ServerName,
 | 
			
		||||
				s.getPlatform().Name,
 | 
			
		||||
@@ -388,79 +392,117 @@ func DetectPlatforms(localLogger *logrus.Entry) {
 | 
			
		||||
	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 {
 | 
			
		||||
		if err := o.install(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
func detectPlatforms(timeoutSec int) {
 | 
			
		||||
	parallelExec(func(o osTypeInterface) error {
 | 
			
		||||
		o.detectPlatform()
 | 
			
		||||
		// Logging only if platform can not be specified
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scan scan
 | 
			
		||||
func Scan() []error {
 | 
			
		||||
func Scan(timeoutSec int) error {
 | 
			
		||||
	if len(servers) == 0 {
 | 
			
		||||
		return []error{fmt.Errorf("No server defined. Check the configuration")}
 | 
			
		||||
		return fmt.Errorf("No server defined. Check the configuration")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Check required packages for scanning...")
 | 
			
		||||
	if errs := checkRequiredPackagesInstalled(); errs != nil {
 | 
			
		||||
		Log.Error("Please execute with [prepare] subcommand to install required packages before scanning")
 | 
			
		||||
		return errs
 | 
			
		||||
	if err := setupChangelogCache(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if cache.DB != nil {
 | 
			
		||||
			cache.DB.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Scanning vulnerable OS packages...")
 | 
			
		||||
	scannedAt := time.Now()
 | 
			
		||||
	dir, err := ensureResultDir(scannedAt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := scanVulns(dir, scannedAt, timeoutSec); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerable OS packages...")
 | 
			
		||||
	if errs := scanPackages(); errs != nil {
 | 
			
		||||
		return errs
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerable software specified in the CPE...")
 | 
			
		||||
	if errs := scanVulnByCpeName(); errs != nil {
 | 
			
		||||
		return errs
 | 
			
		||||
func setupChangelogCache() error {
 | 
			
		||||
	needToSetupCache := false
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		switch s.getDistro().Family {
 | 
			
		||||
		case config.Raspbian:
 | 
			
		||||
			needToSetupCache = true
 | 
			
		||||
			break
 | 
			
		||||
		case config.Ubuntu, config.Debian:
 | 
			
		||||
			//TODO changelopg cache for RedHat, Oracle, Amazon, CentOS is not implemented yet.
 | 
			
		||||
			if config.Conf.Deep {
 | 
			
		||||
				needToSetupCache = true
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if needToSetupCache {
 | 
			
		||||
		if err := cache.SetupBolt(config.Conf.CacheDBPath, util.Log); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkRequiredPackagesInstalled() []error {
 | 
			
		||||
	timeoutSec := 30 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.checkRequiredPackagesInstalled()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanPackages() []error {
 | 
			
		||||
	timeoutSec := 120 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
func scanVulns(jsonDir string, scannedAt time.Time, timeoutSec int) error {
 | 
			
		||||
	var results models.ScanResults
 | 
			
		||||
	parallelExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.scanPackages()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
 | 
			
		||||
func scanVulnByCpeName() []error {
 | 
			
		||||
	timeoutSec := 30 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.scanVulnByCpeName()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetScanResults returns Scan Resutls
 | 
			
		||||
func GetScanResults() (results models.ScanResults, err error) {
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		r, err := s.convertToModel()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return results, fmt.Errorf("Failed converting to model: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	for _, s := range append(servers, errServers...) {
 | 
			
		||||
		r := s.convertToModel()
 | 
			
		||||
		r.ScannedAt = scannedAt
 | 
			
		||||
		r.Config.Scan = config.Conf
 | 
			
		||||
		results = append(results, r)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
 | 
			
		||||
	config.Conf.FormatJSON = true
 | 
			
		||||
	ws := []report.ResultWriter{
 | 
			
		||||
		report.LocalFileWriter{CurrentDir: jsonDir},
 | 
			
		||||
	}
 | 
			
		||||
	for _, w := range ws {
 | 
			
		||||
		if err := w.Write(results...); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to write summary report: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	report.StdoutWriter{}.WriteScanSummary(results...)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ensureResultDir(scannedAt time.Time) (currentDir string, err error) {
 | 
			
		||||
	jsonDirName := scannedAt.Format(time.RFC3339)
 | 
			
		||||
 | 
			
		||||
	resultsDir := config.Conf.ResultsDir
 | 
			
		||||
	if len(resultsDir) == 0 {
 | 
			
		||||
		wd, _ := os.Getwd()
 | 
			
		||||
		resultsDir = filepath.Join(wd, "results")
 | 
			
		||||
	}
 | 
			
		||||
	jsonDir := filepath.Join(resultsDir, jsonDirName)
 | 
			
		||||
	if err := os.MkdirAll(jsonDir, 0700); err != nil {
 | 
			
		||||
		return "", fmt.Errorf("Failed to create dir: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	symlinkPath := filepath.Join(resultsDir, "current")
 | 
			
		||||
	if _, err := os.Lstat(symlinkPath); err == nil {
 | 
			
		||||
		if err := os.Remove(symlinkPath); err != nil {
 | 
			
		||||
			return "", fmt.Errorf(
 | 
			
		||||
				"Failed to remove symlink. path: %s, err: %s", symlinkPath, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := os.Symlink(jsonDir, symlinkPath); err != nil {
 | 
			
		||||
		return "", fmt.Errorf(
 | 
			
		||||
			"Failed to create symlink: path: %s, err: %s", symlinkPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	return jsonDir, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,47 +1 @@
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestPackageCveInfosSetGet(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in  []string
 | 
			
		||||
		out []string
 | 
			
		||||
	}{
 | 
			
		||||
		[]string{
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
		},
 | 
			
		||||
		[]string{
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//  var ps packageCveInfos
 | 
			
		||||
	var ps CvePacksList
 | 
			
		||||
	for _, cid := range test.in {
 | 
			
		||||
		ps = ps.set(cid, CvePacksInfo{CveID: cid})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(test.out) != len(ps) {
 | 
			
		||||
		t.Errorf("length: expected %d, actual %d", len(test.out), len(ps))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, expectedCid := range test.out {
 | 
			
		||||
		if expectedCid != ps[i].CveID {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", expectedCid, ps[i].CveID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cid := range test.in {
 | 
			
		||||
		p, _ := ps.FindByCveID(cid)
 | 
			
		||||
		if p.CveID != cid {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", cid, p.CveID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										185
									
								
								scan/suse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,185 @@
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type suse struct {
 | 
			
		||||
	redhat
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRedhat is constructor
 | 
			
		||||
func newSUSE(c config.ServerInfo) *suse {
 | 
			
		||||
	r := &suse{
 | 
			
		||||
		redhat: redhat{
 | 
			
		||||
			base: base{
 | 
			
		||||
				osPackages: osPackages{
 | 
			
		||||
					Packages:  models.Packages{},
 | 
			
		||||
					VulnInfos: models.VulnInfos{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	r.log = util.NewCustomLogger(c)
 | 
			
		||||
	r.setServerInfo(c)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/suse.rb
 | 
			
		||||
func detectSUSE(c config.ServerInfo) (itsMe bool, suse osTypeInterface) {
 | 
			
		||||
	suse = newSUSE(c)
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "ls /etc/os-release", noSudo); r.isSuccess() {
 | 
			
		||||
		if r := exec(c, "zypper -V", noSudo); r.isSuccess() {
 | 
			
		||||
			if r := exec(c, "cat /etc/os-release", noSudo); r.isSuccess() {
 | 
			
		||||
				name := ""
 | 
			
		||||
				if strings.Contains(r.Stdout, "ID=opensuse") {
 | 
			
		||||
					//TODO check opensuse or opensuse.leap
 | 
			
		||||
					name = config.OpenSUSE
 | 
			
		||||
				} else if strings.Contains(r.Stdout, `NAME="SLES"`) {
 | 
			
		||||
					name = config.SUSEEnterpriseServer
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Warn("Failed to parse SUSE edition: %s", r)
 | 
			
		||||
					return true, suse
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				re := regexp.MustCompile(`VERSION_ID=\"(\d+\.\d+|\d+)\"`)
 | 
			
		||||
				result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
			
		||||
				if len(result) != 2 {
 | 
			
		||||
					util.Log.Warn("Failed to parse SUSE Linux version: %s", r)
 | 
			
		||||
					return true, suse
 | 
			
		||||
				}
 | 
			
		||||
				suse.setDistro(name, result[1])
 | 
			
		||||
				return true, suse
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else if r := exec(c, "ls /etc/SuSE-release", noSudo); r.isSuccess() {
 | 
			
		||||
		if r := exec(c, "zypper -V", noSudo); r.isSuccess() {
 | 
			
		||||
			if r := exec(c, "cat /etc/SuSE-release", noSudo); r.isSuccess() {
 | 
			
		||||
				re := regexp.MustCompile(`openSUSE (\d+\.\d+|\d+)`)
 | 
			
		||||
				result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
			
		||||
				if len(result) == 2 {
 | 
			
		||||
					//TODO check opensuse or opensuse.leap
 | 
			
		||||
					suse.setDistro(config.OpenSUSE, result[1])
 | 
			
		||||
					return true, suse
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				re = regexp.MustCompile(`VERSION = (\d+)`)
 | 
			
		||||
				result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
			
		||||
				if len(result) == 2 {
 | 
			
		||||
					version := result[1]
 | 
			
		||||
					re = regexp.MustCompile(`PATCHLEVEL = (\d+)`)
 | 
			
		||||
					result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
			
		||||
					if len(result) == 2 {
 | 
			
		||||
						suse.setDistro(config.SUSEEnterpriseServer,
 | 
			
		||||
							fmt.Sprintf("%s.%s", version, result[1]))
 | 
			
		||||
						return true, suse
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				util.Log.Warn("Failed to parse SUSE Linux version: %s", r)
 | 
			
		||||
				return true, suse
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("Not SUSE Linux. servername: %s", c.ServerName)
 | 
			
		||||
	return false, suse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *suse) checkDependencies() error {
 | 
			
		||||
	o.log.Infof("Dependencies... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *suse) checkIfSudoNoPasswd() error {
 | 
			
		||||
	// SUSE doesn't need root privilege
 | 
			
		||||
	o.log.Infof("sudo ... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *suse) scanPackages() error {
 | 
			
		||||
	installed, err := o.scanInstalledPackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rebootRequired, err := o.rebootRequired()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to detect the kernel reboot required: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.Kernel.RebootRequired = rebootRequired
 | 
			
		||||
 | 
			
		||||
	updatable, err := o.scanUpdatablePackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan updatable packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	installed.MergeNewVersion(updatable)
 | 
			
		||||
	o.Packages = installed
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *suse) rebootRequired() (bool, error) {
 | 
			
		||||
	r := o.exec("rpm -q --last kernel-default | head -n1", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return false, fmt.Errorf("Failed to detect the last installed kernel : %v", r)
 | 
			
		||||
	}
 | 
			
		||||
	stdout := strings.Fields(r.Stdout)[0]
 | 
			
		||||
	return !strings.Contains(stdout, strings.TrimSuffix(o.Kernel.Release, "-default")), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *suse) scanUpdatablePackages() (models.Packages, error) {
 | 
			
		||||
	cmd := ""
 | 
			
		||||
	if v, _ := o.Distro.MajorVersion(); v < 12 {
 | 
			
		||||
		cmd = "zypper -q lu"
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd = "zypper --no-color -q lu"
 | 
			
		||||
	}
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to scan updatable packages: %v", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.parseZypperLULines(r.Stdout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *suse) parseZypperLULines(stdout string) (models.Packages, error) {
 | 
			
		||||
	updatables := models.Packages{}
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		if strings.Index(line, "S | Repository") != -1 ||
 | 
			
		||||
			strings.Index(line, "--+----------------") != -1 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pack, err := o.parseZypperLUOneLine(line)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		updatables[pack.Name] = *pack
 | 
			
		||||
	}
 | 
			
		||||
	return updatables, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *suse) parseZypperLUOneLine(line string) (*models.Package, error) {
 | 
			
		||||
	fs := strings.Fields(line)
 | 
			
		||||
	if len(fs) != 11 {
 | 
			
		||||
		return nil, fmt.Errorf("zypper -q lu Unknown format: %s", line)
 | 
			
		||||
	}
 | 
			
		||||
	available := strings.Split(fs[8], "-")
 | 
			
		||||
	return &models.Package{
 | 
			
		||||
		Name:       fs[4],
 | 
			
		||||
		NewVersion: available[0],
 | 
			
		||||
		NewRelease: available[1],
 | 
			
		||||
		Arch:       fs[10],
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										106
									
								
								scan/suse_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,106 @@
 | 
			
		||||
/* 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"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestScanUpdatablePackages(t *testing.T) {
 | 
			
		||||
	r := newSUSE(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: "sles"}
 | 
			
		||||
	stdout := `S | Repository                                  | Name                          | Current Version             | Available Version           | Arch
 | 
			
		||||
--+---------------------------------------------+-------------------------------+-----------------------------+-----------------------------+-------
 | 
			
		||||
v | SLES12-SP2-Updates                          | SUSEConnect                   | 0.3.0-19.8.1                | 0.3.1-19.11.2               | x86_64
 | 
			
		||||
v | SLES12-SP2-Updates                          | SuSEfirewall2                 | 3.6.312-2.3.1               | 3.6.312-2.10.1              | noarch`
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			stdout,
 | 
			
		||||
			models.NewPackages(
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "SUSEConnect",
 | 
			
		||||
					NewVersion: "0.3.1",
 | 
			
		||||
					NewRelease: "19.11.2",
 | 
			
		||||
					Arch:       "x86_64",
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "SuSEfirewall2",
 | 
			
		||||
					NewVersion: "3.6.312",
 | 
			
		||||
					NewRelease: "2.10.1",
 | 
			
		||||
					Arch:       "noarch",
 | 
			
		||||
				},
 | 
			
		||||
			),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		packages, err := r.parseZypperLULines(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		for name, ePack := range tt.out {
 | 
			
		||||
			if !reflect.DeepEqual(ePack, packages[name]) {
 | 
			
		||||
				e := pp.Sprintf("%v", ePack)
 | 
			
		||||
				a := pp.Sprintf("%v", packages[name])
 | 
			
		||||
				t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestScanUpdatablePackage(t *testing.T) {
 | 
			
		||||
	r := newSUSE(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: "sles"}
 | 
			
		||||
	stdout := `v | SLES12-SP2-Updates                          | SUSEConnect                   | 0.3.0-19.8.1                | 0.3.1-19.11.2               | x86_64`
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out models.Package
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			stdout,
 | 
			
		||||
			models.Package{
 | 
			
		||||
				Name:       "SUSEConnect",
 | 
			
		||||
				NewVersion: "0.3.1",
 | 
			
		||||
				NewRelease: "19.11.2",
 | 
			
		||||
				Arch:       "x86_64",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		pack, err := r.parseZypperLUOneLine(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if !reflect.DeepEqual(*pack, tt.out) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.out)
 | 
			
		||||
			a := pp.Sprintf("%v", pack)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								scan/unknownDistro.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +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
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type unknown struct {
 | 
			
		||||
	base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *unknown) checkIfSudoNoPasswd() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *unknown) checkDependencies() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *unknown) scanPackages() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										69
									
								
								scan/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,69 @@
 | 
			
		||||
/* 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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func isRunningKernel(pack models.Package, family string, kernel models.Kernel) (isKernel, running bool) {
 | 
			
		||||
	switch family {
 | 
			
		||||
	case config.SUSEEnterpriseServer:
 | 
			
		||||
		if pack.Name == "kernel-default" {
 | 
			
		||||
			// Remove the last period and later because uname don't show that.
 | 
			
		||||
			ss := strings.Split(pack.Release, ".")
 | 
			
		||||
			rel := strings.Join(ss[0:len(ss)-1], ".")
 | 
			
		||||
			ver := fmt.Sprintf("%s-%s-default", pack.Version, rel)
 | 
			
		||||
			return true, kernel.Release == ver
 | 
			
		||||
		}
 | 
			
		||||
		return false, false
 | 
			
		||||
 | 
			
		||||
	case config.RedHat, config.Oracle, config.CentOS, config.Amazon:
 | 
			
		||||
		if pack.Name == "kernel" {
 | 
			
		||||
			ver := fmt.Sprintf("%s-%s.%s", pack.Version, pack.Release, pack.Arch)
 | 
			
		||||
			return true, kernel.Release == ver
 | 
			
		||||
		}
 | 
			
		||||
		return false, false
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		util.Log.Warnf("Reboot required is not implemented yet: %s, %s", family, kernel)
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func rpmQa(distro config.Distro) string {
 | 
			
		||||
	const old = "rpm -qa --queryformat '%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n'"
 | 
			
		||||
	const new = "rpm -qa --queryformat '%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n'"
 | 
			
		||||
	switch distro.Family {
 | 
			
		||||
	case config.SUSEEnterpriseServer:
 | 
			
		||||
		if v, _ := distro.MajorVersion(); v < 12 {
 | 
			
		||||
			return old
 | 
			
		||||
		}
 | 
			
		||||
		return new
 | 
			
		||||
	default:
 | 
			
		||||
		if v, _ := distro.MajorVersion(); v < 6 {
 | 
			
		||||
			return old
 | 
			
		||||
		}
 | 
			
		||||
		return new
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								scan/utils_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,117 @@
 | 
			
		||||
/* 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 (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestIsRunningKernelSUSE(t *testing.T) {
 | 
			
		||||
	r := newSUSE(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: config.SUSEEnterpriseServer}
 | 
			
		||||
 | 
			
		||||
	kernel := models.Kernel{
 | 
			
		||||
		Release: "4.4.74-92.35-default",
 | 
			
		||||
		Version: "",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		pack     models.Package
 | 
			
		||||
		family   string
 | 
			
		||||
		kernel   models.Kernel
 | 
			
		||||
		expected bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			pack: models.Package{
 | 
			
		||||
				Name:    "kernel-default",
 | 
			
		||||
				Version: "4.4.74",
 | 
			
		||||
				Release: "92.35.1",
 | 
			
		||||
				Arch:    "x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			family:   config.SUSEEnterpriseServer,
 | 
			
		||||
			kernel:   kernel,
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pack: models.Package{
 | 
			
		||||
				Name:    "kernel-default",
 | 
			
		||||
				Version: "4.4.59",
 | 
			
		||||
				Release: "92.20.2",
 | 
			
		||||
				Arch:    "x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			family:   config.SUSEEnterpriseServer,
 | 
			
		||||
			kernel:   kernel,
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		_, actual := isRunningKernel(tt.pack, tt.family, tt.kernel)
 | 
			
		||||
		if tt.expected != actual {
 | 
			
		||||
			t.Errorf("[%d] expected %t, actual %t", i, tt.expected, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsRunningKernelRedHatLikeLinux(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: config.Amazon}
 | 
			
		||||
 | 
			
		||||
	kernel := models.Kernel{
 | 
			
		||||
		Release: "4.9.43-17.38.amzn1.x86_64",
 | 
			
		||||
		Version: "",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		pack     models.Package
 | 
			
		||||
		family   string
 | 
			
		||||
		kernel   models.Kernel
 | 
			
		||||
		expected bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			pack: models.Package{
 | 
			
		||||
				Name:    "kernel",
 | 
			
		||||
				Version: "4.9.43",
 | 
			
		||||
				Release: "17.38.amzn1",
 | 
			
		||||
				Arch:    "x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			family:   config.Amazon,
 | 
			
		||||
			kernel:   kernel,
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pack: models.Package{
 | 
			
		||||
				Name:    "kernel",
 | 
			
		||||
				Version: "4.9.38",
 | 
			
		||||
				Release: "16.35.amzn1",
 | 
			
		||||
				Arch:    "x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			family:   config.Amazon,
 | 
			
		||||
			kernel:   kernel,
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		_, actual := isRunningKernel(tt.pack, tt.family, tt.kernel)
 | 
			
		||||
		if tt.expected != actual {
 | 
			
		||||
			t.Errorf("[%d] expected %t, actual %t", i, tt.expected, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||