Compare commits
	
		
			1213 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					3605645ff6 | ||
| 
						 | 
					1d97e91341 | ||
| 
						 | 
					96333f38c9 | ||
| 
						 | 
					8b5d1c8e92 | ||
| 
						 | 
					dea80f860c | ||
| 
						 | 
					6eb4c5a5fe | ||
| 
						 | 
					b219a8495e | ||
| 
						 | 
					eb87d5d4e1 | ||
| 
						 | 
					6963442a5e | ||
| 
						 | 
					f7299b9dba | ||
| 
						 | 
					379fc8a1a1 | ||
| 
						 | 
					947fbbb29e | ||
| 
						 | 
					06d2032c9c | ||
| 
						 | 
					d055c48827 | ||
| 
						 | 
					2a00339da1 | ||
| 
						 | 
					2d959b3af8 | ||
| 
						 | 
					595e26db41 | ||
| 
						 | 
					1e457320c5 | ||
| 
						 | 
					a06e689502 | ||
| 
						 | 
					ca3f6b1dbf | ||
| 
						 | 
					f1c78e42a2 | ||
| 
						 | 
					2f3b8bf3cc | ||
| 
						 | 
					ab54266f9e | ||
| 
						 | 
					d79d138440 | ||
| 
						 | 
					139f3a81b6 | ||
| 
						 | 
					d1a617cfff | ||
| 
						 | 
					48f7597bcf | ||
| 
						 | 
					93731311a1 | ||
| 
						 | 
					999529a05b | ||
| 
						 | 
					847d820af7 | ||
| 
						 | 
					5234306ded | ||
| 
						 | 
					86b60e1478 | ||
| 
						 | 
					42fdc08933 | ||
| 
						 | 
					38b1d622f6 | ||
| 
						 | 
					2477f9a8f8 | ||
| 
						 | 
					ec6e90acd3 | ||
| 
						 | 
					2aca2e4352 | ||
| 
						 | 
					14518d925e | ||
| 
						 | 
					948f8c0751 | ||
| 
						 | 
					1c1e40058e | ||
| 
						 | 
					2158fc6cb1 | ||
| 
						 | 
					91ed318c5d | ||
| 
						 | 
					bfc3828ce1 | ||
| 
						 | 
					c7eac4e7fe | ||
| 
						 | 
					cc63a0eccf | ||
| 
						 | 
					fd18df1dd4 | ||
| 
						 | 
					8775b5efdf | ||
| 
						 | 
					a9f29a6c5d | ||
| 
						 | 
					05fdde48f9 | ||
| 
						 | 
					3dfbd6b616 | ||
| 
						 | 
					04f246cf8b | ||
| 
						 | 
					7500f41655 | ||
| 
						 | 
					a1cc152e81 | ||
| 
						 | 
					1c77bc1ba3 | ||
| 
						 | 
					ec31c54caf | ||
| 
						 | 
					2f05864813 | ||
| 
						 | 
					2fbc0a001e | ||
| 
						 | 
					7d8a24ee1a | ||
| 
						 | 
					7750347010 | ||
| 
						 | 
					9bcffcd721 | ||
| 
						 | 
					787604de6a | ||
| 
						 | 
					5164fb1423 | ||
| 
						 | 
					07335617d3 | ||
| 
						 | 
					e5855922c1 | ||
| 
						 | 
					671be3f2f7 | ||
| 
						 | 
					fe8d252c51 | ||
| 
						 | 
					0cdc7a3af5 | ||
| 
						 | 
					1cfe155a3a | ||
| 
						 | 
					2923cbc645 | ||
| 
						 | 
					7c209cc9dc | ||
| 
						 | 
					84fa4ce432 | ||
| 
						 | 
					f2e9cd9668 | ||
| 
						 | 
					77049d6cbb | ||
| 
						 | 
					b4c23c158b | ||
| 
						 | 
					964b4aa389 | ||
| 
						 | 
					dc5aa35db7 | ||
| 
						 | 
					43c05d06fc | ||
| 
						 | 
					a3f7d1d7e7 | ||
| 
						 | 
					bb4a1ca6c2 | ||
| 
						 | 
					57cce640e1 | ||
| 
						 | 
					1eb5d36668 | ||
| 
						 | 
					6bc4850596 | ||
| 
						 | 
					24005ae7ae | ||
| 
						 | 
					7aa296bb57 | ||
| 
						 | 
					3829ed2f8e | ||
| 
						 | 
					2b7294a504 | ||
| 
						 | 
					0c6a892893 | ||
| 
						 | 
					89d94ad85a | ||
| 
						 | 
					ffdb78962f | ||
| 
						 | 
					321dae37ce | ||
| 
						 | 
					a31797af0b | ||
| 
						 | 
					32999cf432 | ||
| 
						 | 
					88218f5d92 | ||
| 
						 | 
					15761933ac | ||
| 
						 | 
					0b62842f0e | ||
| 
						 | 
					6bceddeeda | ||
| 
						 | 
					2dcbff8cd5 | ||
| 
						 | 
					8659668177 | ||
| 
						 | 
					e07b6a9160 | ||
| 
						 | 
					aac5ef1438 | ||
| 
						 | 
					d780a73297 | ||
| 
						 | 
					9ef8cee36e | ||
| 
						 | 
					77808a2c05 | ||
| 
						 | 
					177e553d12 | ||
| 
						 | 
					40f8272a28 | ||
| 
						 | 
					a7eb1141ae | ||
| 
						 | 
					c73ed7f32f | ||
| 
						 | 
					f047a6fe0c | ||
| 
						 | 
					7f15a86d6a | ||
| 
						 | 
					da1e515253 | ||
| 
						 | 
					591786fde6 | ||
| 
						 | 
					47e6ea249d | ||
| 
						 | 
					4a72295de7 | ||
| 
						 | 
					9ed5f2cac5 | ||
| 
						 | 
					3e67f04fe4 | ||
| 
						 | 
					b9416ae062 | ||
| 
						 | 
					b4e49e093e | ||
| 
						 | 
					020f6ac609 | ||
| 
						 | 
					7e71cbdd46 | ||
| 
						 | 
					1003f62212 | ||
| 
						 | 
					9b18e1f9f0 | ||
| 
						 | 
					24f790f474 | ||
| 
						 | 
					fb8749fc5e | ||
| 
						 | 
					96c3592db1 | ||
| 
						 | 
					d65421cf46 | ||
| 
						 | 
					c52ba448cd | ||
| 
						 | 
					21adce463b | ||
| 
						 | 
					f24240bf90 | ||
| 
						 | 
					ff83cadd6e | ||
| 
						 | 
					e8c09282d9 | ||
| 
						 | 
					5f4d68cde4 | ||
| 
						 | 
					9077a83ea8 | ||
| 
						 | 
					543dc99ecd | ||
| 
						 | 
					f0b3a8b1db | ||
| 
						 | 
					0b9ec05181 | ||
| 
						 | 
					0bf12412d6 | ||
| 
						 | 
					0ea4d58c63 | ||
| 
						 | 
					5755b00576 | ||
| 
						 | 
					1c8e074c9d | ||
| 
						 | 
					0e0e5ce4be | ||
| 
						 | 
					23dfe53885 | ||
| 
						 | 
					8e6351a9e4 | ||
| 
						 | 
					3086e2760f | ||
| 
						 | 
					b8db2e0b74 | ||
| 
						 | 
					43b46cb324 | ||
| 
						 | 
					d0559c7719 | ||
| 
						 | 
					231c63cf62 | ||
| 
						 | 
					2a9aebe059 | ||
| 
						 | 
					4e535d792f | ||
| 
						 | 
					4b487503d4 | ||
| 
						 | 
					0095c40e69 | ||
| 
						 | 
					82c1abfd3a | ||
| 
						 | 
					40988401bd | ||
| 
						 | 
					e8e3f4d138 | ||
| 
						 | 
					7eb77f5b51 | ||
| 
						 | 
					e115235299 | ||
| 
						 | 
					151d4b2d30 | ||
| 
						 | 
					e553f8b4c5 | ||
| 
						 | 
					47652ef0fb | ||
| 
						 | 
					ab0e950800 | ||
| 
						 | 
					a7b0ce1c85 | ||
| 
						 | 
					dc9c0edece | ||
| 
						 | 
					17ae386d1e | ||
| 
						 | 
					2d369d0cfe | ||
| 
						 | 
					c36e645d9b | ||
| 
						 | 
					40039c07e2 | ||
| 
						 | 
					a692cec0ef | ||
| 
						 | 
					e7ca491a94 | ||
| 
						 | 
					23f3e2fc11 | ||
| 
						 | 
					27b3e17b79 | ||
| 
						 | 
					740781af56 | ||
| 
						 | 
					36c9c229b8 | ||
| 
						 | 
					183fdcbdef | ||
| 
						 | 
					a2a697900a | ||
| 
						 | 
					6fef4db8a0 | ||
| 
						 | 
					e879ff1e9e | ||
| 
						 | 
					9bfe0627ae | ||
| 
						 | 
					0179f4299a | ||
| 
						 | 
					56017e57a0 | ||
| 
						 | 
					cda91e0906 | ||
| 
						 | 
					5d47adb5c9 | ||
| 
						 | 
					54e73c2f54 | ||
| 
						 | 
					2d075079f1 | ||
| 
						 | 
					2a8ee4b22b | ||
| 
						 | 
					1ec31d7be9 | ||
| 
						 | 
					02286b0c59 | ||
| 
						 | 
					1d0c5dea9f | ||
| 
						 | 
					1c4a12c4b7 | ||
| 
						 | 
					3f2ac45d71 | ||
| 
						 | 
					518f4dc039 | ||
| 
						 | 
					2cdeef4ffe | ||
| 
						 | 
					03579126fd | ||
| 
						 | 
					e3c27e1817 | ||
| 
						 | 
					aeaf308679 | ||
| 
						 | 
					f5e47bea40 | ||
| 
						 | 
					50cf13a7f2 | ||
| 
						 | 
					abd8041772 | ||
| 
						 | 
					847c6438e7 | ||
| 
						 | 
					ef8309df27 | ||
| 
						 | 
					0dff6cf983 | ||
| 
						 | 
					4c04acbd9e | ||
| 
						 | 
					1c4f231572 | ||
| 
						 | 
					51b8e169d2 | ||
| 
						 | 
					b4611ae9b7 | ||
| 
						 | 
					cd6722017b | ||
| 
						 | 
					290edffccf | ||
| 
						 | 
					64a6222bf9 | ||
| 
						 | 
					adb686b7c9 | ||
| 
						 | 
					d4af341b0f | ||
| 
						 | 
					fea7e93c8d | ||
| 
						 | 
					8b6b8d0f2e | ||
| 
						 | 
					4dcbd865cc | ||
| 
						 | 
					39b19444fe | ||
| 
						 | 
					644d5a5462 | ||
| 
						 | 
					8e18451e3f | ||
| 
						 | 
					3dbdd01f97 | ||
| 
						 | 
					a89079c005 | ||
| 
						 | 
					a8c0926b4f | ||
| 
						 | 
					dd2959a31b | ||
| 
						 | 
					51099f42c3 | ||
| 
						 | 
					63f170cc7a | ||
| 
						 | 
					3c1489e588 | ||
| 
						 | 
					e4f1e03f62 | ||
| 
						 | 
					83d48ec990 | ||
| 
						 | 
					b20d2b2684 | ||
| 
						 | 
					2b918c70ae | ||
| 
						 | 
					1100c133ba | ||
| 
						 | 
					88899f0e89 | ||
| 
						 | 
					59dc0059bc | ||
| 
						 | 
					986fb304c0 | ||
| 
						 | 
					d6435d2885 | ||
| 
						 | 
					affb456499 | ||
| 
						 | 
					705ed0a0ac | ||
| 
						 | 
					dfffe5b508 | ||
| 
						 | 
					fca102edba | ||
| 
						 | 
					554b6345a2 | ||
| 
						 | 
					aa954dc84c | ||
| 
						 | 
					b5506a1368 | ||
| 
						 | 
					0b55f94828 | ||
| 
						 | 
					a67052f48c | ||
| 
						 | 
					6eff6a9329 | ||
| 
						 | 
					69d32d4511 | ||
| 
						 | 
					d7a613b710 | ||
| 
						 | 
					669c019287 | ||
| 
						 | 
					fcc4901a10 | ||
| 
						 | 
					4359503484 | ||
| 
						 | 
					b13f93a2d3 | ||
| 
						 | 
					8405e0fad6 | ||
| 
						 | 
					aceb3f1826 | ||
| 
						 | 
					a206675f3e | ||
| 
						 | 
					f4253d74ae | ||
| 
						 | 
					aaea15e516 | ||
| 
						 | 
					83d1f80959 | ||
| 
						 | 
					a33cff8f13 | ||
| 
						 | 
					8679759f60 | ||
| 
						 | 
					53deaee3d7 | ||
| 
						 | 
					5a14a58fe4 | ||
| 
						 | 
					fb1fbf8f95 | ||
| 
						 | 
					cfbf779f9b | ||
| 
						 | 
					d576b6c6c1 | ||
| 
						 | 
					514eb71482 | ||
| 
						 | 
					43ed904db1 | ||
| 
						 | 
					0a440ca629 | ||
| 
						 | 
					eff1dbf95b | ||
| 
						 | 
					9a32a94806 | ||
| 
						 | 
					2534098509 | ||
| 
						 | 
					9497365758 | ||
| 
						 | 
					101c44c9c0 | ||
| 
						 | 
					ffd745c004 | ||
| 
						 | 
					5fea4eaef8 | ||
| 
						 | 
					1f610043cf | ||
| 
						 | 
					3f8de02683 | ||
| 
						 | 
					d02535d053 | ||
| 
						 | 
					75fceff5f7 | ||
| 
						 | 
					ebd3834a35 | ||
| 
						 | 
					93059b74c3 | ||
| 
						 | 
					2fc3462d35 | ||
| 
						 | 
					f78dab50cb | ||
| 
						 | 
					edb324c3d9 | ||
| 
						 | 
					83bcca6e66 | ||
| 
						 | 
					a124518d78 | ||
| 
						 | 
					94bf630e29 | ||
| 
						 | 
					31bb33fd90 | ||
| 
						 | 
					4b680b9960 | ||
| 
						 | 
					8a8ab8cb18 | ||
| 
						 | 
					8146f5fd1b | ||
| 
						 | 
					425c585e47 | ||
| 
						 | 
					4f1578b2d6 | ||
| 
						 | 
					7969b343b0 | ||
| 
						 | 
					58cf1f4c8e | ||
| 
						 | 
					a5b87af862 | ||
| 
						 | 
					a0e592b934 | ||
| 
						 | 
					7eccc538bb | ||
| 
						 | 
					59daa8570a | ||
| 
						 | 
					3f52d318bc | ||
| 
						 | 
					11a7a0c934 | ||
| 
						 | 
					89f49b0e29 | ||
| 
						 | 
					72457cbf8e | ||
| 
						 | 
					c11ba27509 | ||
| 
						 | 
					8a611f9ba6 | ||
| 
						 | 
					4a73875e4d | ||
| 
						 | 
					d9d5e612ff | ||
| 
						 | 
					4d8599e4fc | ||
| 
						 | 
					59c7061d29 | ||
| 
						 | 
					996557c667 | ||
| 
						 | 
					519fb19a77 | ||
| 
						 | 
					36456cb151 | ||
| 
						 | 
					4ae87cc36c | ||
| 
						 | 
					b37df89fb1 | ||
| 
						 | 
					d18e7a751d | ||
| 
						 | 
					8d5ea98e50 | ||
| 
						 | 
					835dc08049 | ||
| 
						 | 
					62c9409fe9 | ||
| 
						 | 
					2374f578ed | ||
| 
						 | 
					34e2f033d8 | ||
| 
						 | 
					420825cacc | ||
| 
						 | 
					466ec93d8e | ||
| 
						 | 
					3f5bb6ab29 | ||
| 
						 | 
					ebe5f858c8 | ||
| 
						 | 
					9dd025437b | ||
| 
						 | 
					c0ebac305a | ||
| 
						 | 
					1f23ab7ba4 | ||
| 
						 | 
					ea3b63998d | ||
| 
						 | 
					3093426458 | ||
| 
						 | 
					37716feac7 | ||
| 
						 | 
					56b12c38d2 | ||
| 
						 | 
					749ead5d4a | ||
| 
						 | 
					3be50ab8da | ||
| 
						 | 
					649f4a6991 | ||
| 
						 | 
					0ff7641471 | ||
| 
						 | 
					1679bfae20 | ||
| 
						 | 
					45aa364436 | ||
| 
						 | 
					778516c4d9 | ||
| 
						 | 
					464d523c42 | ||
| 
						 | 
					0f6a1987d4 | ||
| 
						 | 
					20c6247ce5 | ||
| 
						 | 
					a10dd67e0f | ||
| 
						 | 
					5729ad6026 | ||
| 
						 | 
					9aa0d87a21 | ||
| 
						 | 
					fe3f1b9924 | ||
| 
						 | 
					00e52a88fa | ||
| 
						 | 
					5811dffe7a | ||
| 
						 | 
					7278982af4 | ||
| 
						 | 
					c17b4154ec | ||
| 
						 | 
					d6e74cce08 | ||
| 
						 | 
					3f80749241 | ||
| 
						 | 
					7f72b6ac69 | ||
| 
						 | 
					03e7b90b9f | ||
| 
						 | 
					7936b3533b | ||
| 
						 | 
					bd7e61d7cc | ||
| 
						 | 
					69214e0c22 | ||
| 
						 | 
					45bff26558 | ||
| 
						 | 
					b2e429ccc6 | ||
| 
						 | 
					76363c227b | ||
| 
						 | 
					d5a3e5c2c5 | ||
| 
						 | 
					2b02807ef0 | ||
| 
						 | 
					be659ae094 | ||
| 
						 | 
					b2c105adbc | ||
| 
						 | 
					c61f462948 | ||
| 
						 | 
					3ffed18e02 | ||
| 
						 | 
					f54e7257d1 | ||
| 
						 | 
					cc13b6a27c | ||
| 
						 | 
					8877db1979 | ||
| 
						 | 
					af58122c91 | ||
| 
						 | 
					b7ca5e5590 | ||
| 
						 | 
					69b6d875e6 | ||
| 
						 | 
					1fbd516b83 | ||
| 
						 | 
					dec5d3b165 | ||
| 
						 | 
					d5e2040cef | ||
| 
						 | 
					4326befdec | ||
| 
						 | 
					3d4a5d9917 | ||
| 
						 | 
					d770034788 | ||
| 
						 | 
					a977533c78 | ||
| 
						 | 
					c5e13dd5e4 | ||
| 
						 | 
					a8040fe4d2 | ||
| 
						 | 
					9e066008c3 | ||
| 
						 | 
					22c6601526 | ||
| 
						 | 
					425464fd76 | ||
| 
						 | 
					ccb0751ffd | ||
| 
						 | 
					f832de81b7 | ||
| 
						 | 
					8a37de0686 | ||
| 
						 | 
					836e4704f8 | ||
| 
						 | 
					3e5390309c | ||
| 
						 | 
					f8c0b38716 | ||
| 
						 | 
					65e6070e5f | ||
| 
						 | 
					7b78ebbc42 | ||
| 
						 | 
					03c3189c02 | ||
| 
						 | 
					4a34dfe0e9 | ||
| 
						 | 
					4cf9a723fe | ||
| 
						 | 
					bd1b135db3 | ||
| 
						 | 
					8c3b305149 | ||
| 
						 | 
					a3719038b8 | ||
| 
						 | 
					c68a261c0b | ||
| 
						 | 
					75fea79ac1 | ||
| 
						 | 
					eb9f9680ec | ||
| 
						 | 
					3634afdb81 | ||
| 
						 | 
					77b5df896a | ||
| 
						 | 
					b81f64058c | ||
| 
						 | 
					a8a90d7c63 | ||
| 
						 | 
					17bb575002 | ||
| 
						 | 
					abcea1a14d | ||
| 
						 | 
					10942f7c08 | ||
| 
						 | 
					87ee829e80 | ||
| 
						 | 
					fcc2c1e4c7 | ||
| 
						 | 
					269095d034 | ||
| 
						 | 
					40492ee00a | ||
| 
						 | 
					64cdd5aedc | ||
| 
						 | 
					3bb650cb77 | ||
| 
						 | 
					774544c975 | ||
| 
						 | 
					299805a726 | ||
| 
						 | 
					276363e793 | ||
| 
						 | 
					e750bd53fc | ||
| 
						 | 
					98fee7b5d2 | ||
| 
						 | 
					53aaea9fe2 | ||
| 
						 | 
					824fbb6368 | ||
| 
						 | 
					80566b91ab | ||
| 
						 | 
					533d05a1b5 | ||
| 
						 | 
					6a1fc4fade | ||
| 
						 | 
					9008d0ddf0 | ||
| 
						 | 
					583f4577bc | ||
| 
						 | 
					e5716d5092 | ||
| 
						 | 
					7192ae1287 | ||
| 
						 | 
					99c65eff48 | ||
| 
						 | 
					91df593566 | ||
| 
						 | 
					07aeaeb989 | ||
| 
						 | 
					cfeecdacd0 | ||
| 
						 | 
					564dfa8b62 | ||
| 
						 | 
					75dd6f2010 | ||
| 
						 | 
					e26fd0b759 | ||
| 
						 | 
					d630680a51 | ||
| 
						 | 
					1723c3f6a0 | ||
| 
						 | 
					53dd90302e | ||
| 
						 | 
					5c6e06b05e | ||
| 
						 | 
					cf6fb0c8a5 | ||
| 
						 | 
					e0e71b2eae | ||
| 
						 | 
					53f4a29fb1 | ||
| 
						 | 
					89d58d1abc | ||
| 
						 | 
					d6b6969cb3 | ||
| 
						 | 
					e7bf6fa69d | ||
| 
						 | 
					6e51970b91 | ||
| 
						 | 
					56d7d43768 | ||
| 
						 | 
					256c99ffa2 | ||
| 
						 | 
					9c0bc3b13b | ||
| 
						 | 
					9b8a323d85 | ||
| 
						 | 
					3178c1e326 | ||
| 
						 | 
					321d68e03a | ||
| 
						 | 
					3d8753c621 | ||
| 
						 | 
					967c56909d | ||
| 
						 | 
					7c4831d2d1 | ||
| 
						 | 
					4b49e11a33 | ||
| 
						 | 
					d84a6a8627 | ||
| 
						 | 
					63b7f4a8db | ||
| 
						 | 
					ca2160264a | ||
| 
						 | 
					7842594f53 | ||
| 
						 | 
					7db056102c | ||
| 
						 | 
					a5a800fa0a | ||
| 
						 | 
					9147ec148d | ||
| 
						 | 
					b3260588c6 | ||
| 
						 | 
					7d31328271 | ||
| 
						 | 
					6e82981ee3 | ||
| 
						 | 
					9d7b115bb5 | ||
| 
						 | 
					8eae5002a3 | ||
| 
						 | 
					31bd6c0371 | ||
| 
						 | 
					7585f9d537 | ||
| 
						 | 
					76037cdf72 | ||
| 
						 | 
					98c5421edc | ||
| 
						 | 
					e63fc7e3f5 | ||
| 
						 | 
					6ed9cf3fb4 | ||
| 
						 | 
					9865eab2c0 | ||
| 
						 | 
					678e72a8b6 | ||
| 
						 | 
					ec41899089 | ||
| 
						 | 
					b2d913cc21 | ||
| 
						 | 
					bc86c24e6a | ||
| 
						 | 
					87a77dd95c | ||
| 
						 | 
					e8188f3432 | ||
| 
						 | 
					50506be546 | ||
| 
						 | 
					4ded028258 | ||
| 
						 | 
					6da8b3c4a1 | ||
| 
						 | 
					d5c92cbcb3 | ||
| 
						 | 
					ed5f98d6f0 | ||
| 
						 | 
					f854b8f908 | ||
| 
						 | 
					de7a6159d4 | ||
| 
						 | 
					6090a34037 | ||
| 
						 | 
					f566745479 | ||
| 
						 | 
					153234b623 | ||
| 
						 | 
					ac510d21ff | ||
| 
						 | 
					44fa2c5800 | ||
| 
						 | 
					d785fc2a54 | ||
| 
						 | 
					ea800e04bc | ||
| 
						 | 
					fe582ac635 | ||
| 
						 | 
					330edb3bce | ||
| 
						 | 
					212fec7115 | ||
| 
						 | 
					24d7021c47 | ||
| 
						 | 
					e3a01ff6a8 | ||
| 
						 | 
					81f2ba8a46 | ||
| 
						 | 
					9e9370b178 | ||
| 
						 | 
					ced6114a95 | ||
| 
						 | 
					3144faae5d | ||
| 
						 | 
					8960c67a82 | ||
| 
						 | 
					f8ca924434 | ||
| 
						 | 
					399a08775e | ||
| 
						 | 
					92f36ca558 | ||
| 
						 | 
					3dcc58205a | ||
| 
						 | 
					09779962cf | ||
| 
						 | 
					9cc78770a3 | ||
| 
						 | 
					f653ca9131 | ||
| 
						 | 
					6f9fd91849 | ||
| 
						 | 
					cb1aec4fc0 | ||
| 
						 | 
					7cebaf8a76 | ||
| 
						 | 
					241c943424 | ||
| 
						 | 
					d5d88d8cf0 | ||
| 
						 | 
					cf9d26068c | ||
| 
						 | 
					308a93dc72 | ||
| 
						 | 
					d6a7e65e4c | ||
| 
						 | 
					e0a5c5d3b8 | ||
| 
						 | 
					314f775243 | ||
| 
						 | 
					7a1644135a | ||
| 
						 | 
					5076326589 | ||
| 
						 | 
					ce56261b52 | ||
| 
						 | 
					baa0e897b2 | ||
| 
						 | 
					1d49c0e1ce | ||
| 
						 | 
					08755e446e | ||
| 
						 | 
					bb12d9dadb | ||
| 
						 | 
					fd1429fef0 | ||
| 
						 | 
					d3c421a4a8 | ||
| 
						 | 
					0c919da4b1 | ||
| 
						 | 
					9afbf1255f | ||
| 
						 | 
					50b105c4af | ||
| 
						 | 
					028508c1f7 | ||
| 
						 | 
					f0137a3695 | ||
| 
						 | 
					e6d3a1718c | ||
| 
						 | 
					86ba551e07 | ||
| 
						 | 
					26418be937 | ||
| 
						 | 
					092a19bdc1 | ||
| 
						 | 
					6d3398574c | ||
| 
						 | 
					b08969ad89 | ||
| 
						 | 
					0653656526 | ||
| 
						 | 
					7a5793c562 | ||
| 
						 | 
					562ff7807d | ||
| 
						 | 
					7971bdf7f7 | ||
| 
						 | 
					d926b7fd6d | ||
| 
						 | 
					c00404793a | ||
| 
						 | 
					a0e0ee6c1e | ||
| 
						 | 
					4ccbee705b | ||
| 
						 | 
					db43d55b2c | ||
| 
						 | 
					5a3a333eec | ||
| 
						 | 
					039edf1616 | ||
| 
						 | 
					47498bbf23 | ||
| 
						 | 
					cc28bf4ae2 | ||
| 
						 | 
					0e8736045e | ||
| 
						 | 
					19b581edef | ||
| 
						 | 
					295f6656d9 | ||
| 
						 | 
					1214d8c14d | ||
| 
						 | 
					b4cd96fc9a | ||
| 
						 | 
					3238a9b898 | ||
| 
						 | 
					c0f66320f6 | ||
| 
						 | 
					383220f384 | ||
| 
						 | 
					76a9c37e6b | ||
| 
						 | 
					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 | ||
| 
						 | 
					8b264a564a | ||
| 
						 | 
					227da93c13 | ||
| 
						 | 
					f939041606 | ||
| 
						 | 
					e5b1a0bef8 | ||
| 
						 | 
					b9404d0880 | ||
| 
						 | 
					d6f12868be | ||
| 
						 | 
					b79e96f6cf | ||
| 
						 | 
					b066cc819e | ||
| 
						 | 
					4b669a0d49 | ||
| 
						 | 
					5e9de5d91a | ||
| 
						 | 
					da68b061e3 | ||
| 
						 | 
					6c3802071f | ||
| 
						 | 
					ad84f09bce | ||
| 
						 | 
					04166632d3 | ||
| 
						 | 
					376238b1ad | ||
| 
						 | 
					4f0dbff059 | ||
| 
						 | 
					f506e2b50a | ||
| 
						 | 
					88d2fbf5e2 | ||
| 
						 | 
					7fd8cc5449 | ||
| 
						 | 
					d033463b34 | ||
| 
						 | 
					740208cf74 | ||
| 
						 | 
					0036c0b10e | ||
| 
						 | 
					834c832390 | ||
| 
						 | 
					5bc99dfd25 | ||
| 
						 | 
					c92d2d064a | ||
| 
						 | 
					a60c21323c | ||
| 
						 | 
					34d6d6e709 | ||
| 
						 | 
					f2ddafc718 | ||
| 
						 | 
					267afdd15d | ||
| 
						 | 
					48b7b82e33 | ||
| 
						 | 
					84e5e5432e | ||
| 
						 | 
					201e18eac2 | ||
| 
						 | 
					3f3f0b1fec | ||
| 
						 | 
					ca697c5038 | ||
| 
						 | 
					5aeeb4e8b4 | ||
| 
						 | 
					c285f9f587 | ||
| 
						 | 
					d046608426 | ||
| 
						 | 
					b91ed9cff5 | ||
| 
						 | 
					185d85bfdd | ||
| 
						 | 
					44b2c1464a | ||
| 
						 | 
					a0762a0a6c | ||
| 
						 | 
					2ad7660c09 | ||
| 
						 | 
					d8b8c38182 | ||
| 
						 | 
					1d50e5126a | ||
| 
						 | 
					aa55e30358 | ||
| 
						 | 
					f662de50db | ||
| 
						 | 
					24c798ad3a | ||
| 
						 | 
					0e304ae546 | ||
| 
						 | 
					cd604cbfe7 | ||
| 
						 | 
					b8e66d9df0 | ||
| 
						 | 
					a2c738e57b | ||
| 
						 | 
					ae16cd708c | ||
| 
						 | 
					2ed0443f88 | ||
| 
						 | 
					38f1c5075d | ||
| 
						 | 
					55043a6348 | ||
| 
						 | 
					1f6eb55b86 | ||
| 
						 | 
					d9d8500484 | ||
| 
						 | 
					0fca75c2db | ||
| 
						 | 
					a7dcccbdf9 | ||
| 
						 | 
					396eb5aec2 | ||
| 
						 | 
					79d2076e09 | ||
| 
						 | 
					693dca4ca2 | ||
| 
						 | 
					4047076033 | ||
| 
						 | 
					acb0b71f1b | ||
| 
						 | 
					32d9352048 | ||
| 
						 | 
					0246556f7c | ||
| 
						 | 
					a17284681f | ||
| 
						 | 
					adb66e3298 | ||
| 
						 | 
					ad062d777d | ||
| 
						 | 
					ffe1ff73a5 | ||
| 
						 | 
					54f9202d74 | ||
| 
						 | 
					ef3e173fb2 | ||
| 
						 | 
					1aeec2ae51 | ||
| 
						 | 
					1f50bfd801 | ||
| 
						 | 
					d3466eabe5 | ||
| 
						 | 
					8aff1af939 | ||
| 
						 | 
					af35303432 | ||
| 
						 | 
					0ef1a5a3ce | ||
| 
						 | 
					e958bc8212 | ||
| 
						 | 
					e0ca6e89d1 | ||
| 
						 | 
					55d8ae124a | ||
| 
						 | 
					5e28ec22e1 | ||
| 
						 | 
					c3deb93489 | ||
| 
						 | 
					a9aca94848 | ||
| 
						 | 
					f3c06890dd | ||
| 
						 | 
					d9d0e629fd | ||
| 
						 | 
					17181405e3 | ||
| 
						 | 
					c209564945 | ||
| 
						 | 
					2da01db438 | ||
| 
						 | 
					8c4913d411 | ||
| 
						 | 
					e7ffc24844 | ||
| 
						 | 
					259f23f6ee | ||
| 
						 | 
					0de38b99c2 | ||
| 
						 | 
					1044fb8574 | ||
| 
						 | 
					e5bfa1bd6f | ||
| 
						 | 
					a29b2a2ad9 | ||
| 
						 | 
					b6899ce461 | ||
| 
						 | 
					32c11af07c | ||
| 
						 | 
					6ff55d24d0 | ||
| 
						 | 
					055aacd7f6 | ||
| 
						 | 
					5ecf58fd56 | ||
| 
						 | 
					8a9106052f | ||
| 
						 | 
					91264547c9 | ||
| 
						 | 
					3190b877ae | ||
| 
						 | 
					f8a8cc4676 | ||
| 
						 | 
					93ee329315 | ||
| 
						 | 
					b45163388d | ||
| 
						 | 
					6029784f76 | ||
| 
						 | 
					058ab55a6f | ||
| 
						 | 
					1005d241b8 | ||
| 
						 | 
					33b1ccba67 | ||
| 
						 | 
					a5549fb500 | ||
| 
						 | 
					b057ed3e77 | ||
| 
						 | 
					1e88cc10e7 | ||
| 
						 | 
					2f8634383e | ||
| 
						 | 
					86f9e5ce96 | ||
| 
						 | 
					9ae42d647c | ||
| 
						 | 
					54d6217b93 | ||
| 
						 | 
					150b1c2406 | ||
| 
						 | 
					51b6f1b5f3 | ||
| 
						 | 
					3eae14cef6 | ||
| 
						 | 
					cc6dc1ca69 | ||
| 
						 | 
					7f2361f58c | ||
| 
						 | 
					7cb02d77ae | ||
| 
						 | 
					52cc9b0cc0 | ||
| 
						 | 
					d91bf61038 | ||
| 
						 | 
					d5f81674f8 | ||
| 
						 | 
					9381883835 | ||
| 
						 | 
					f82e5a281d | ||
| 
						 | 
					904e6241e4 | ||
| 
						 | 
					ce39a3daf9 | ||
| 
						 | 
					f2c7f74beb | ||
| 
						 | 
					20db997fc2 | ||
| 
						 | 
					7188e97444 | ||
| 
						 | 
					6d528e741d | ||
| 
						 | 
					d356e8370d | ||
| 
						 | 
					5e336b5928 | ||
| 
						 | 
					787ad0629b | ||
| 
						 | 
					53e4adf24e | ||
| 
						 | 
					6af811d63e | ||
| 
						 | 
					359dab3380 | ||
| 
						 | 
					97a8e6e965 | ||
| 
						 | 
					8ea699aa08 | ||
| 
						 | 
					7d924d2b0c | ||
| 
						 | 
					3c85613ada | ||
| 
						 | 
					c536d26db3 | ||
| 
						 | 
					4350ff2692 | ||
| 
						 | 
					0b9a1e7bb4 | ||
| 
						 | 
					714ad18fa0 | ||
| 
						 | 
					f81f785813 | ||
| 
						 | 
					76c32af46f | ||
| 
						 | 
					cd108263e1 | ||
| 
						 | 
					093c47b59c | ||
| 
						 | 
					56a40ec51a | ||
| 
						 | 
					1337be2b84 | ||
| 
						 | 
					eecd2c60f5 | ||
| 
						 | 
					da071cb120 | ||
| 
						 | 
					012cfa3cbe | ||
| 
						 | 
					21180847dc | ||
| 
						 | 
					9e9e538846 | ||
| 
						 | 
					66025b1ae2 | ||
| 
						 | 
					5999361358 | ||
| 
						 | 
					e8699d1cb7 | ||
| 
						 | 
					9292448e73 | ||
| 
						 | 
					d7e156613d | ||
| 
						 | 
					c3604aa66d | ||
| 
						 | 
					5e037b1743 | ||
| 
						 | 
					ebc79805ed | ||
| 
						 | 
					c37e56e51d | ||
| 
						 | 
					28a93c02e6 | ||
| 
						 | 
					da16f9673e | 
							
								
								
									
										26
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,26 @@
 | 
			
		||||
name: Test
 | 
			
		||||
 | 
			
		||||
on: 
 | 
			
		||||
  pull_request:
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  test:
 | 
			
		||||
    name: Test
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Check out code into the Go module directory
 | 
			
		||||
      uses: actions/checkout@v3
 | 
			
		||||
 | 
			
		||||
    - name: Set up Go 1.x
 | 
			
		||||
      uses: actions/setup-go@v3
 | 
			
		||||
      with:
 | 
			
		||||
        go-version-file: 'go.mod'
 | 
			
		||||
 | 
			
		||||
    - name: golangci-lint
 | 
			
		||||
      uses: golangci/golangci-lint-action@v3
 | 
			
		||||
      with:
 | 
			
		||||
        version: latest
 | 
			
		||||
        args: --timeout=10m
 | 
			
		||||
        
 | 
			
		||||
    - name: testing
 | 
			
		||||
      run: go test -race ./...
 | 
			
		||||
							
								
								
									
										29
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,9 +1,24 @@
 | 
			
		||||
# Binaries for programs and plugins
 | 
			
		||||
*.exe
 | 
			
		||||
*.exe~
 | 
			
		||||
*.dll
 | 
			
		||||
*.so
 | 
			
		||||
*.dylib
 | 
			
		||||
 | 
			
		||||
# Test binary, built with `go test -c`
 | 
			
		||||
*.test
 | 
			
		||||
 | 
			
		||||
# Output of the go coverage tool, specifically when used with LiteIDE
 | 
			
		||||
*.out
 | 
			
		||||
 | 
			
		||||
# Dependency directories (remove the comment below to include it)
 | 
			
		||||
# vendor/
 | 
			
		||||
 | 
			
		||||
.vscode
 | 
			
		||||
coverage.out
 | 
			
		||||
issues/
 | 
			
		||||
*.txt
 | 
			
		||||
vendor/
 | 
			
		||||
log/
 | 
			
		||||
.gitmodules
 | 
			
		||||
 | 
			
		||||
# Vuls
 | 
			
		||||
vuls
 | 
			
		||||
*.sqlite3
 | 
			
		||||
!cmd/vuls
 | 
			
		||||
vuls.db
 | 
			
		||||
config.json
 | 
			
		||||
results
 | 
			
		||||
							
								
								
									
										36
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						@@ -1,36 +0,0 @@
 | 
			
		||||
# Change Log
 | 
			
		||||
 | 
			
		||||
## [v0.1.2](https://github.com/future-architect/vuls/tree/v0.1.2) (2016-04-12)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.1...v0.1.2)
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Maximum 6 nodes available to scan [\#12](https://github.com/future-architect/vuls/issues/12)
 | 
			
		||||
- panic: runtime error: index out of range [\#5](https://github.com/future-architect/vuls/issues/5)
 | 
			
		||||
- Fix sudo option on RedHat like Linux and change some messages. [\#20](https://github.com/future-architect/vuls/pull/20) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Typo fix and updated readme [\#19](https://github.com/future-architect/vuls/pull/19) ([Euan-Kerr](https://github.com/Euan-Kerr))
 | 
			
		||||
- remove a period at the end of error messages. [\#18](https://github.com/future-architect/vuls/pull/18) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix error while yum updateinfo --security update on rhel@aws [\#17](https://github.com/future-architect/vuls/pull/17) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fixed typos [\#15](https://github.com/future-architect/vuls/pull/15) ([radarhere](https://github.com/radarhere))
 | 
			
		||||
- Typo fix in error messages [\#14](https://github.com/future-architect/vuls/pull/14) ([Bregor](https://github.com/Bregor))
 | 
			
		||||
- Fix index out of range error when the number of servers is over 6. \#12 [\#13](https://github.com/future-architect/vuls/pull/13) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Revise small grammar mistakes in serverapi.go [\#9](https://github.com/future-architect/vuls/pull/9) ([cpobrien](https://github.com/cpobrien))
 | 
			
		||||
- Fix error handling in HTTP backoff function [\#7](https://github.com/future-architect/vuls/pull/7) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
## [v0.1.1](https://github.com/future-architect/vuls/tree/v0.1.1) (2016-04-06)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.0...v0.1.1)
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Typo in Exapmle [\#6](https://github.com/future-architect/vuls/pull/6) ([toli](https://github.com/toli))
 | 
			
		||||
 | 
			
		||||
## [v0.1.0](https://github.com/future-architect/vuls/tree/v0.1.0) (2016-04-04)
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- English translation [\#4](https://github.com/future-architect/vuls/pull/4) ([hikachan](https://github.com/hikachan))
 | 
			
		||||
- English translation [\#3](https://github.com/future-architect/vuls/pull/3) ([chewyinping](https://github.com/chewyinping))
 | 
			
		||||
- Add a Bitdeli Badge to README [\#2](https://github.com/future-architect/vuls/pull/2) ([bitdeli-chef](https://github.com/bitdeli-chef))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
 | 
			
		||||
							
								
								
									
										42
									
								
								GNUmakefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,42 @@
 | 
			
		||||
VERSION := $(shell git describe --tags --abbrev=0)
 | 
			
		||||
ifeq ($(VERSION), )
 | 
			
		||||
	VERSION := $(shell git rev-parse --abbrev-ref HEAD)
 | 
			
		||||
endif
 | 
			
		||||
ifeq ($(shell git rev-parse --abbrev-ref HEAD), nightly)
 | 
			
		||||
	VERSION := nightly
 | 
			
		||||
endif
 | 
			
		||||
REVISION := $(shell git rev-parse --short HEAD)
 | 
			
		||||
LDFLAGS := -ldflags "-s -w -X=github.com/future-architect/vuls/pkg/cmd/version.Version=$(VERSION) -X=github.com/future-architect/vuls/pkg/cmd/version.Revision=$(REVISION)"
 | 
			
		||||
 | 
			
		||||
GOPATH := $(shell go env GOPATH)
 | 
			
		||||
GOBIN := $(GOPATH)/bin
 | 
			
		||||
 | 
			
		||||
$(GOBIN)/golangci-lint:
 | 
			
		||||
	go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
 | 
			
		||||
 | 
			
		||||
.PHONY: build
 | 
			
		||||
build: 
 | 
			
		||||
	go build $(LDFLAGS) ./cmd/vuls
 | 
			
		||||
 | 
			
		||||
.PHONY: install
 | 
			
		||||
install: 
 | 
			
		||||
	go install $(LDFLAGS) ./cmd/vuls
 | 
			
		||||
 | 
			
		||||
.PHONY: test
 | 
			
		||||
test: pretest
 | 
			
		||||
	go test -race ./...
 | 
			
		||||
 | 
			
		||||
.PHONY: pretest
 | 
			
		||||
pretest: lint vet fmtcheck
 | 
			
		||||
 | 
			
		||||
.PHONY: lint
 | 
			
		||||
lint: $(GOBIN)/golangci-lint
 | 
			
		||||
	golangci-lint run
 | 
			
		||||
 | 
			
		||||
.PHONY: vet
 | 
			
		||||
vet:
 | 
			
		||||
	go vet ./...
 | 
			
		||||
 | 
			
		||||
.PHONY: fmtcheck
 | 
			
		||||
fmtcheck:
 | 
			
		||||
	gofmt -s -d .
 | 
			
		||||
							
								
								
									
										674
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						@@ -1,674 +0,0 @@
 | 
			
		||||
                    GNU GENERAL PUBLIC LICENSE
 | 
			
		||||
                       Version 3, 29 June 2007
 | 
			
		||||
 | 
			
		||||
 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 | 
			
		||||
 Everyone is permitted to copy and distribute verbatim copies
 | 
			
		||||
 of this license document, but changing it is not allowed.
 | 
			
		||||
 | 
			
		||||
                            Preamble
 | 
			
		||||
 | 
			
		||||
  The GNU General Public License is a free, copyleft license for
 | 
			
		||||
software and other kinds of works.
 | 
			
		||||
 | 
			
		||||
  The licenses for most software and other practical works are designed
 | 
			
		||||
to take away your freedom to share and change the works.  By contrast,
 | 
			
		||||
the GNU General Public License is intended to guarantee your freedom to
 | 
			
		||||
share and change all versions of a program--to make sure it remains free
 | 
			
		||||
software for all its users.  We, the Free Software Foundation, use the
 | 
			
		||||
GNU General Public License for most of our software; it applies also to
 | 
			
		||||
any other work released this way by its authors.  You can apply it to
 | 
			
		||||
your programs, too.
 | 
			
		||||
 | 
			
		||||
  When we speak of free software, we are referring to freedom, not
 | 
			
		||||
price.  Our General Public Licenses are designed to make sure that you
 | 
			
		||||
have the freedom to distribute copies of free software (and charge for
 | 
			
		||||
them if you wish), that you receive source code or can get it if you
 | 
			
		||||
want it, that you can change the software or use pieces of it in new
 | 
			
		||||
free programs, and that you know you can do these things.
 | 
			
		||||
 | 
			
		||||
  To protect your rights, we need to prevent others from denying you
 | 
			
		||||
these rights or asking you to surrender the rights.  Therefore, you have
 | 
			
		||||
certain responsibilities if you distribute copies of the software, or if
 | 
			
		||||
you modify it: responsibilities to respect the freedom of others.
 | 
			
		||||
 | 
			
		||||
  For example, if you distribute copies of such a program, whether
 | 
			
		||||
gratis or for a fee, you must pass on to the recipients the same
 | 
			
		||||
freedoms that you received.  You must make sure that they, too, receive
 | 
			
		||||
or can get the source code.  And you must show them these terms so they
 | 
			
		||||
know their rights.
 | 
			
		||||
 | 
			
		||||
  Developers that use the GNU GPL protect your rights with two steps:
 | 
			
		||||
(1) assert copyright on the software, and (2) offer you this License
 | 
			
		||||
giving you legal permission to copy, distribute and/or modify it.
 | 
			
		||||
 | 
			
		||||
  For the developers' and authors' protection, the GPL clearly explains
 | 
			
		||||
that there is no warranty for this free software.  For both users' and
 | 
			
		||||
authors' sake, the GPL requires that modified versions be marked as
 | 
			
		||||
changed, so that their problems will not be attributed erroneously to
 | 
			
		||||
authors of previous versions.
 | 
			
		||||
 | 
			
		||||
  Some devices are designed to deny users access to install or run
 | 
			
		||||
modified versions of the software inside them, although the manufacturer
 | 
			
		||||
can do so.  This is fundamentally incompatible with the aim of
 | 
			
		||||
protecting users' freedom to change the software.  The systematic
 | 
			
		||||
pattern of such abuse occurs in the area of products for individuals to
 | 
			
		||||
use, which is precisely where it is most unacceptable.  Therefore, we
 | 
			
		||||
have designed this version of the GPL to prohibit the practice for those
 | 
			
		||||
products.  If such problems arise substantially in other domains, we
 | 
			
		||||
stand ready to extend this provision to those domains in future versions
 | 
			
		||||
of the GPL, as needed to protect the freedom of users.
 | 
			
		||||
 | 
			
		||||
  Finally, every program is threatened constantly by software patents.
 | 
			
		||||
States should not allow patents to restrict development and use of
 | 
			
		||||
software on general-purpose computers, but in those that do, we wish to
 | 
			
		||||
avoid the special danger that patents applied to a free program could
 | 
			
		||||
make it effectively proprietary.  To prevent this, the GPL assures that
 | 
			
		||||
patents cannot be used to render the program non-free.
 | 
			
		||||
 | 
			
		||||
  The precise terms and conditions for copying, distribution and
 | 
			
		||||
modification follow.
 | 
			
		||||
 | 
			
		||||
                       TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
  0. Definitions.
 | 
			
		||||
 | 
			
		||||
  "This License" refers to version 3 of the GNU General Public License.
 | 
			
		||||
 | 
			
		||||
  "Copyright" also means copyright-like laws that apply to other kinds of
 | 
			
		||||
works, such as semiconductor masks.
 | 
			
		||||
 | 
			
		||||
  "The Program" refers to any copyrightable work licensed under this
 | 
			
		||||
License.  Each licensee is addressed as "you".  "Licensees" and
 | 
			
		||||
"recipients" may be individuals or organizations.
 | 
			
		||||
 | 
			
		||||
  To "modify" a work means to copy from or adapt all or part of the work
 | 
			
		||||
in a fashion requiring copyright permission, other than the making of an
 | 
			
		||||
exact copy.  The resulting work is called a "modified version" of the
 | 
			
		||||
earlier work or a work "based on" the earlier work.
 | 
			
		||||
 | 
			
		||||
  A "covered work" means either the unmodified Program or a work based
 | 
			
		||||
on the Program.
 | 
			
		||||
 | 
			
		||||
  To "propagate" a work means to do anything with it that, without
 | 
			
		||||
permission, would make you directly or secondarily liable for
 | 
			
		||||
infringement under applicable copyright law, except executing it on a
 | 
			
		||||
computer or modifying a private copy.  Propagation includes copying,
 | 
			
		||||
distribution (with or without modification), making available to the
 | 
			
		||||
public, and in some countries other activities as well.
 | 
			
		||||
 | 
			
		||||
  To "convey" a work means any kind of propagation that enables other
 | 
			
		||||
parties to make or receive copies.  Mere interaction with a user through
 | 
			
		||||
a computer network, with no transfer of a copy, is not conveying.
 | 
			
		||||
 | 
			
		||||
  An interactive user interface displays "Appropriate Legal Notices"
 | 
			
		||||
to the extent that it includes a convenient and prominently visible
 | 
			
		||||
feature that (1) displays an appropriate copyright notice, and (2)
 | 
			
		||||
tells the user that there is no warranty for the work (except to the
 | 
			
		||||
extent that warranties are provided), that licensees may convey the
 | 
			
		||||
work under this License, and how to view a copy of this License.  If
 | 
			
		||||
the interface presents a list of user commands or options, such as a
 | 
			
		||||
menu, a prominent item in the list meets this criterion.
 | 
			
		||||
 | 
			
		||||
  1. Source Code.
 | 
			
		||||
 | 
			
		||||
  The "source code" for a work means the preferred form of the work
 | 
			
		||||
for making modifications to it.  "Object code" means any non-source
 | 
			
		||||
form of a work.
 | 
			
		||||
 | 
			
		||||
  A "Standard Interface" means an interface that either is an official
 | 
			
		||||
standard defined by a recognized standards body, or, in the case of
 | 
			
		||||
interfaces specified for a particular programming language, one that
 | 
			
		||||
is widely used among developers working in that language.
 | 
			
		||||
 | 
			
		||||
  The "System Libraries" of an executable work include anything, other
 | 
			
		||||
than the work as a whole, that (a) is included in the normal form of
 | 
			
		||||
packaging a Major Component, but which is not part of that Major
 | 
			
		||||
Component, and (b) serves only to enable use of the work with that
 | 
			
		||||
Major Component, or to implement a Standard Interface for which an
 | 
			
		||||
implementation is available to the public in source code form.  A
 | 
			
		||||
"Major Component", in this context, means a major essential component
 | 
			
		||||
(kernel, window system, and so on) of the specific operating system
 | 
			
		||||
(if any) on which the executable work runs, or a compiler used to
 | 
			
		||||
produce the work, or an object code interpreter used to run it.
 | 
			
		||||
 | 
			
		||||
  The "Corresponding Source" for a work in object code form means all
 | 
			
		||||
the source code needed to generate, install, and (for an executable
 | 
			
		||||
work) run the object code and to modify the work, including scripts to
 | 
			
		||||
control those activities.  However, it does not include the work's
 | 
			
		||||
System Libraries, or general-purpose tools or generally available free
 | 
			
		||||
programs which are used unmodified in performing those activities but
 | 
			
		||||
which are not part of the work.  For example, Corresponding Source
 | 
			
		||||
includes interface definition files associated with source files for
 | 
			
		||||
the work, and the source code for shared libraries and dynamically
 | 
			
		||||
linked subprograms that the work is specifically designed to require,
 | 
			
		||||
such as by intimate data communication or control flow between those
 | 
			
		||||
subprograms and other parts of the work.
 | 
			
		||||
 | 
			
		||||
  The Corresponding Source need not include anything that users
 | 
			
		||||
can regenerate automatically from other parts of the Corresponding
 | 
			
		||||
Source.
 | 
			
		||||
 | 
			
		||||
  The Corresponding Source for a work in source code form is that
 | 
			
		||||
same work.
 | 
			
		||||
 | 
			
		||||
  2. Basic Permissions.
 | 
			
		||||
 | 
			
		||||
  All rights granted under this License are granted for the term of
 | 
			
		||||
copyright on the Program, and are irrevocable provided the stated
 | 
			
		||||
conditions are met.  This License explicitly affirms your unlimited
 | 
			
		||||
permission to run the unmodified Program.  The output from running a
 | 
			
		||||
covered work is covered by this License only if the output, given its
 | 
			
		||||
content, constitutes a covered work.  This License acknowledges your
 | 
			
		||||
rights of fair use or other equivalent, as provided by copyright law.
 | 
			
		||||
 | 
			
		||||
  You may make, run and propagate covered works that you do not
 | 
			
		||||
convey, without conditions so long as your license otherwise remains
 | 
			
		||||
in force.  You may convey covered works to others for the sole purpose
 | 
			
		||||
of having them make modifications exclusively for you, or provide you
 | 
			
		||||
with facilities for running those works, provided that you comply with
 | 
			
		||||
the terms of this License in conveying all material for which you do
 | 
			
		||||
not control copyright.  Those thus making or running the covered works
 | 
			
		||||
for you must do so exclusively on your behalf, under your direction
 | 
			
		||||
and control, on terms that prohibit them from making any copies of
 | 
			
		||||
your copyrighted material outside their relationship with you.
 | 
			
		||||
 | 
			
		||||
  Conveying under any other circumstances is permitted solely under
 | 
			
		||||
the conditions stated below.  Sublicensing is not allowed; section 10
 | 
			
		||||
makes it unnecessary.
 | 
			
		||||
 | 
			
		||||
  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
 | 
			
		||||
 | 
			
		||||
  No covered work shall be deemed part of an effective technological
 | 
			
		||||
measure under any applicable law fulfilling obligations under article
 | 
			
		||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
 | 
			
		||||
similar laws prohibiting or restricting circumvention of such
 | 
			
		||||
measures.
 | 
			
		||||
 | 
			
		||||
  When you convey a covered work, you waive any legal power to forbid
 | 
			
		||||
circumvention of technological measures to the extent such circumvention
 | 
			
		||||
is effected by exercising rights under this License with respect to
 | 
			
		||||
the covered work, and you disclaim any intention to limit operation or
 | 
			
		||||
modification of the work as a means of enforcing, against the work's
 | 
			
		||||
users, your or third parties' legal rights to forbid circumvention of
 | 
			
		||||
technological measures.
 | 
			
		||||
 | 
			
		||||
  4. Conveying Verbatim Copies.
 | 
			
		||||
 | 
			
		||||
  You may convey verbatim copies of the Program's source code as you
 | 
			
		||||
receive it, in any medium, provided that you conspicuously and
 | 
			
		||||
appropriately publish on each copy an appropriate copyright notice;
 | 
			
		||||
keep intact all notices stating that this License and any
 | 
			
		||||
non-permissive terms added in accord with section 7 apply to the code;
 | 
			
		||||
keep intact all notices of the absence of any warranty; and give all
 | 
			
		||||
recipients a copy of this License along with the Program.
 | 
			
		||||
 | 
			
		||||
  You may charge any price or no price for each copy that you convey,
 | 
			
		||||
and you may offer support or warranty protection for a fee.
 | 
			
		||||
 | 
			
		||||
  5. Conveying Modified Source Versions.
 | 
			
		||||
 | 
			
		||||
  You may convey a work based on the Program, or the modifications to
 | 
			
		||||
produce it from the Program, in the form of source code under the
 | 
			
		||||
terms of section 4, provided that you also meet all of these conditions:
 | 
			
		||||
 | 
			
		||||
    a) The work must carry prominent notices stating that you modified
 | 
			
		||||
    it, and giving a relevant date.
 | 
			
		||||
 | 
			
		||||
    b) The work must carry prominent notices stating that it is
 | 
			
		||||
    released under this License and any conditions added under section
 | 
			
		||||
    7.  This requirement modifies the requirement in section 4 to
 | 
			
		||||
    "keep intact all notices".
 | 
			
		||||
 | 
			
		||||
    c) You must license the entire work, as a whole, under this
 | 
			
		||||
    License to anyone who comes into possession of a copy.  This
 | 
			
		||||
    License will therefore apply, along with any applicable section 7
 | 
			
		||||
    additional terms, to the whole of the work, and all its parts,
 | 
			
		||||
    regardless of how they are packaged.  This License gives no
 | 
			
		||||
    permission to license the work in any other way, but it does not
 | 
			
		||||
    invalidate such permission if you have separately received it.
 | 
			
		||||
 | 
			
		||||
    d) If the work has interactive user interfaces, each must display
 | 
			
		||||
    Appropriate Legal Notices; however, if the Program has interactive
 | 
			
		||||
    interfaces that do not display Appropriate Legal Notices, your
 | 
			
		||||
    work need not make them do so.
 | 
			
		||||
 | 
			
		||||
  A compilation of a covered work with other separate and independent
 | 
			
		||||
works, which are not by their nature extensions of the covered work,
 | 
			
		||||
and which are not combined with it such as to form a larger program,
 | 
			
		||||
in or on a volume of a storage or distribution medium, is called an
 | 
			
		||||
"aggregate" if the compilation and its resulting copyright are not
 | 
			
		||||
used to limit the access or legal rights of the compilation's users
 | 
			
		||||
beyond what the individual works permit.  Inclusion of a covered work
 | 
			
		||||
in an aggregate does not cause this License to apply to the other
 | 
			
		||||
parts of the aggregate.
 | 
			
		||||
 | 
			
		||||
  6. Conveying Non-Source Forms.
 | 
			
		||||
 | 
			
		||||
  You may convey a covered work in object code form under the terms
 | 
			
		||||
of sections 4 and 5, provided that you also convey the
 | 
			
		||||
machine-readable Corresponding Source under the terms of this License,
 | 
			
		||||
in one of these ways:
 | 
			
		||||
 | 
			
		||||
    a) Convey the object code in, or embodied in, a physical product
 | 
			
		||||
    (including a physical distribution medium), accompanied by the
 | 
			
		||||
    Corresponding Source fixed on a durable physical medium
 | 
			
		||||
    customarily used for software interchange.
 | 
			
		||||
 | 
			
		||||
    b) Convey the object code in, or embodied in, a physical product
 | 
			
		||||
    (including a physical distribution medium), accompanied by a
 | 
			
		||||
    written offer, valid for at least three years and valid for as
 | 
			
		||||
    long as you offer spare parts or customer support for that product
 | 
			
		||||
    model, to give anyone who possesses the object code either (1) a
 | 
			
		||||
    copy of the Corresponding Source for all the software in the
 | 
			
		||||
    product that is covered by this License, on a durable physical
 | 
			
		||||
    medium customarily used for software interchange, for a price no
 | 
			
		||||
    more than your reasonable cost of physically performing this
 | 
			
		||||
    conveying of source, or (2) access to copy the
 | 
			
		||||
    Corresponding Source from a network server at no charge.
 | 
			
		||||
 | 
			
		||||
    c) Convey individual copies of the object code with a copy of the
 | 
			
		||||
    written offer to provide the Corresponding Source.  This
 | 
			
		||||
    alternative is allowed only occasionally and noncommercially, and
 | 
			
		||||
    only if you received the object code with such an offer, in accord
 | 
			
		||||
    with subsection 6b.
 | 
			
		||||
 | 
			
		||||
    d) Convey the object code by offering access from a designated
 | 
			
		||||
    place (gratis or for a charge), and offer equivalent access to the
 | 
			
		||||
    Corresponding Source in the same way through the same place at no
 | 
			
		||||
    further charge.  You need not require recipients to copy the
 | 
			
		||||
    Corresponding Source along with the object code.  If the place to
 | 
			
		||||
    copy the object code is a network server, the Corresponding Source
 | 
			
		||||
    may be on a different server (operated by you or a third party)
 | 
			
		||||
    that supports equivalent copying facilities, provided you maintain
 | 
			
		||||
    clear directions next to the object code saying where to find the
 | 
			
		||||
    Corresponding Source.  Regardless of what server hosts the
 | 
			
		||||
    Corresponding Source, you remain obligated to ensure that it is
 | 
			
		||||
    available for as long as needed to satisfy these requirements.
 | 
			
		||||
 | 
			
		||||
    e) Convey the object code using peer-to-peer transmission, provided
 | 
			
		||||
    you inform other peers where the object code and Corresponding
 | 
			
		||||
    Source of the work are being offered to the general public at no
 | 
			
		||||
    charge under subsection 6d.
 | 
			
		||||
 | 
			
		||||
  A separable portion of the object code, whose source code is excluded
 | 
			
		||||
from the Corresponding Source as a System Library, need not be
 | 
			
		||||
included in conveying the object code work.
 | 
			
		||||
 | 
			
		||||
  A "User Product" is either (1) a "consumer product", which means any
 | 
			
		||||
tangible personal property which is normally used for personal, family,
 | 
			
		||||
or household purposes, or (2) anything designed or sold for incorporation
 | 
			
		||||
into a dwelling.  In determining whether a product is a consumer product,
 | 
			
		||||
doubtful cases shall be resolved in favor of coverage.  For a particular
 | 
			
		||||
product received by a particular user, "normally used" refers to a
 | 
			
		||||
typical or common use of that class of product, regardless of the status
 | 
			
		||||
of the particular user or of the way in which the particular user
 | 
			
		||||
actually uses, or expects or is expected to use, the product.  A product
 | 
			
		||||
is a consumer product regardless of whether the product has substantial
 | 
			
		||||
commercial, industrial or non-consumer uses, unless such uses represent
 | 
			
		||||
the only significant mode of use of the product.
 | 
			
		||||
 | 
			
		||||
  "Installation Information" for a User Product means any methods,
 | 
			
		||||
procedures, authorization keys, or other information required to install
 | 
			
		||||
and execute modified versions of a covered work in that User Product from
 | 
			
		||||
a modified version of its Corresponding Source.  The information must
 | 
			
		||||
suffice to ensure that the continued functioning of the modified object
 | 
			
		||||
code is in no case prevented or interfered with solely because
 | 
			
		||||
modification has been made.
 | 
			
		||||
 | 
			
		||||
  If you convey an object code work under this section in, or with, or
 | 
			
		||||
specifically for use in, a User Product, and the conveying occurs as
 | 
			
		||||
part of a transaction in which the right of possession and use of the
 | 
			
		||||
User Product is transferred to the recipient in perpetuity or for a
 | 
			
		||||
fixed term (regardless of how the transaction is characterized), the
 | 
			
		||||
Corresponding Source conveyed under this section must be accompanied
 | 
			
		||||
by the Installation Information.  But this requirement does not apply
 | 
			
		||||
if neither you nor any third party retains the ability to install
 | 
			
		||||
modified object code on the User Product (for example, the work has
 | 
			
		||||
been installed in ROM).
 | 
			
		||||
 | 
			
		||||
  The requirement to provide Installation Information does not include a
 | 
			
		||||
requirement to continue to provide support service, warranty, or updates
 | 
			
		||||
for a work that has been modified or installed by the recipient, or for
 | 
			
		||||
the User Product in which it has been modified or installed.  Access to a
 | 
			
		||||
network may be denied when the modification itself materially and
 | 
			
		||||
adversely affects the operation of the network or violates the rules and
 | 
			
		||||
protocols for communication across the network.
 | 
			
		||||
 | 
			
		||||
  Corresponding Source conveyed, and Installation Information provided,
 | 
			
		||||
in accord with this section must be in a format that is publicly
 | 
			
		||||
documented (and with an implementation available to the public in
 | 
			
		||||
source code form), and must require no special password or key for
 | 
			
		||||
unpacking, reading or copying.
 | 
			
		||||
 | 
			
		||||
  7. Additional Terms.
 | 
			
		||||
 | 
			
		||||
  "Additional permissions" are terms that supplement the terms of this
 | 
			
		||||
License by making exceptions from one or more of its conditions.
 | 
			
		||||
Additional permissions that are applicable to the entire Program shall
 | 
			
		||||
be treated as though they were included in this License, to the extent
 | 
			
		||||
that they are valid under applicable law.  If additional permissions
 | 
			
		||||
apply only to part of the Program, that part may be used separately
 | 
			
		||||
under those permissions, but the entire Program remains governed by
 | 
			
		||||
this License without regard to the additional permissions.
 | 
			
		||||
 | 
			
		||||
  When you convey a copy of a covered work, you may at your option
 | 
			
		||||
remove any additional permissions from that copy, or from any part of
 | 
			
		||||
it.  (Additional permissions may be written to require their own
 | 
			
		||||
removal in certain cases when you modify the work.)  You may place
 | 
			
		||||
additional permissions on material, added by you to a covered work,
 | 
			
		||||
for which you have or can give appropriate copyright permission.
 | 
			
		||||
 | 
			
		||||
  Notwithstanding any other provision of this License, for material you
 | 
			
		||||
add to a covered work, you may (if authorized by the copyright holders of
 | 
			
		||||
that material) supplement the terms of this License with terms:
 | 
			
		||||
 | 
			
		||||
    a) Disclaiming warranty or limiting liability differently from the
 | 
			
		||||
    terms of sections 15 and 16 of this License; or
 | 
			
		||||
 | 
			
		||||
    b) Requiring preservation of specified reasonable legal notices or
 | 
			
		||||
    author attributions in that material or in the Appropriate Legal
 | 
			
		||||
    Notices displayed by works containing it; or
 | 
			
		||||
 | 
			
		||||
    c) Prohibiting misrepresentation of the origin of that material, or
 | 
			
		||||
    requiring that modified versions of such material be marked in
 | 
			
		||||
    reasonable ways as different from the original version; or
 | 
			
		||||
 | 
			
		||||
    d) Limiting the use for publicity purposes of names of licensors or
 | 
			
		||||
    authors of the material; or
 | 
			
		||||
 | 
			
		||||
    e) Declining to grant rights under trademark law for use of some
 | 
			
		||||
    trade names, trademarks, or service marks; or
 | 
			
		||||
 | 
			
		||||
    f) Requiring indemnification of licensors and authors of that
 | 
			
		||||
    material by anyone who conveys the material (or modified versions of
 | 
			
		||||
    it) with contractual assumptions of liability to the recipient, for
 | 
			
		||||
    any liability that these contractual assumptions directly impose on
 | 
			
		||||
    those licensors and authors.
 | 
			
		||||
 | 
			
		||||
  All other non-permissive additional terms are considered "further
 | 
			
		||||
restrictions" within the meaning of section 10.  If the Program as you
 | 
			
		||||
received it, or any part of it, contains a notice stating that it is
 | 
			
		||||
governed by this License along with a term that is a further
 | 
			
		||||
restriction, you may remove that term.  If a license document contains
 | 
			
		||||
a further restriction but permits relicensing or conveying under this
 | 
			
		||||
License, you may add to a covered work material governed by the terms
 | 
			
		||||
of that license document, provided that the further restriction does
 | 
			
		||||
not survive such relicensing or conveying.
 | 
			
		||||
 | 
			
		||||
  If you add terms to a covered work in accord with this section, you
 | 
			
		||||
must place, in the relevant source files, a statement of the
 | 
			
		||||
additional terms that apply to those files, or a notice indicating
 | 
			
		||||
where to find the applicable terms.
 | 
			
		||||
 | 
			
		||||
  Additional terms, permissive or non-permissive, may be stated in the
 | 
			
		||||
form of a separately written license, or stated as exceptions;
 | 
			
		||||
the above requirements apply either way.
 | 
			
		||||
 | 
			
		||||
  8. Termination.
 | 
			
		||||
 | 
			
		||||
  You may not propagate or modify a covered work except as expressly
 | 
			
		||||
provided under this License.  Any attempt otherwise to propagate or
 | 
			
		||||
modify it is void, and will automatically terminate your rights under
 | 
			
		||||
this License (including any patent licenses granted under the third
 | 
			
		||||
paragraph of section 11).
 | 
			
		||||
 | 
			
		||||
  However, if you cease all violation of this License, then your
 | 
			
		||||
license from a particular copyright holder is reinstated (a)
 | 
			
		||||
provisionally, unless and until the copyright holder explicitly and
 | 
			
		||||
finally terminates your license, and (b) permanently, if the copyright
 | 
			
		||||
holder fails to notify you of the violation by some reasonable means
 | 
			
		||||
prior to 60 days after the cessation.
 | 
			
		||||
 | 
			
		||||
  Moreover, your license from a particular copyright holder is
 | 
			
		||||
reinstated permanently if the copyright holder notifies you of the
 | 
			
		||||
violation by some reasonable means, this is the first time you have
 | 
			
		||||
received notice of violation of this License (for any work) from that
 | 
			
		||||
copyright holder, and you cure the violation prior to 30 days after
 | 
			
		||||
your receipt of the notice.
 | 
			
		||||
 | 
			
		||||
  Termination of your rights under this section does not terminate the
 | 
			
		||||
licenses of parties who have received copies or rights from you under
 | 
			
		||||
this License.  If your rights have been terminated and not permanently
 | 
			
		||||
reinstated, you do not qualify to receive new licenses for the same
 | 
			
		||||
material under section 10.
 | 
			
		||||
 | 
			
		||||
  9. Acceptance Not Required for Having Copies.
 | 
			
		||||
 | 
			
		||||
  You are not required to accept this License in order to receive or
 | 
			
		||||
run a copy of the Program.  Ancillary propagation of a covered work
 | 
			
		||||
occurring solely as a consequence of using peer-to-peer transmission
 | 
			
		||||
to receive a copy likewise does not require acceptance.  However,
 | 
			
		||||
nothing other than this License grants you permission to propagate or
 | 
			
		||||
modify any covered work.  These actions infringe copyright if you do
 | 
			
		||||
not accept this License.  Therefore, by modifying or propagating a
 | 
			
		||||
covered work, you indicate your acceptance of this License to do so.
 | 
			
		||||
 | 
			
		||||
  10. Automatic Licensing of Downstream Recipients.
 | 
			
		||||
 | 
			
		||||
  Each time you convey a covered work, the recipient automatically
 | 
			
		||||
receives a license from the original licensors, to run, modify and
 | 
			
		||||
propagate that work, subject to this License.  You are not responsible
 | 
			
		||||
for enforcing compliance by third parties with this License.
 | 
			
		||||
 | 
			
		||||
  An "entity transaction" is a transaction transferring control of an
 | 
			
		||||
organization, or substantially all assets of one, or subdividing an
 | 
			
		||||
organization, or merging organizations.  If propagation of a covered
 | 
			
		||||
work results from an entity transaction, each party to that
 | 
			
		||||
transaction who receives a copy of the work also receives whatever
 | 
			
		||||
licenses to the work the party's predecessor in interest had or could
 | 
			
		||||
give under the previous paragraph, plus a right to possession of the
 | 
			
		||||
Corresponding Source of the work from the predecessor in interest, if
 | 
			
		||||
the predecessor has it or can get it with reasonable efforts.
 | 
			
		||||
 | 
			
		||||
  You may not impose any further restrictions on the exercise of the
 | 
			
		||||
rights granted or affirmed under this License.  For example, you may
 | 
			
		||||
not impose a license fee, royalty, or other charge for exercise of
 | 
			
		||||
rights granted under this License, and you may not initiate litigation
 | 
			
		||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
 | 
			
		||||
any patent claim is infringed by making, using, selling, offering for
 | 
			
		||||
sale, or importing the Program or any portion of it.
 | 
			
		||||
 | 
			
		||||
  11. Patents.
 | 
			
		||||
 | 
			
		||||
  A "contributor" is a copyright holder who authorizes use under this
 | 
			
		||||
License of the Program or a work on which the Program is based.  The
 | 
			
		||||
work thus licensed is called the contributor's "contributor version".
 | 
			
		||||
 | 
			
		||||
  A contributor's "essential patent claims" are all patent claims
 | 
			
		||||
owned or controlled by the contributor, whether already acquired or
 | 
			
		||||
hereafter acquired, that would be infringed by some manner, permitted
 | 
			
		||||
by this License, of making, using, or selling its contributor version,
 | 
			
		||||
but do not include claims that would be infringed only as a
 | 
			
		||||
consequence of further modification of the contributor version.  For
 | 
			
		||||
purposes of this definition, "control" includes the right to grant
 | 
			
		||||
patent sublicenses in a manner consistent with the requirements of
 | 
			
		||||
this License.
 | 
			
		||||
 | 
			
		||||
  Each contributor grants you a non-exclusive, worldwide, royalty-free
 | 
			
		||||
patent license under the contributor's essential patent claims, to
 | 
			
		||||
make, use, sell, offer for sale, import and otherwise run, modify and
 | 
			
		||||
propagate the contents of its contributor version.
 | 
			
		||||
 | 
			
		||||
  In the following three paragraphs, a "patent license" is any express
 | 
			
		||||
agreement or commitment, however denominated, not to enforce a patent
 | 
			
		||||
(such as an express permission to practice a patent or covenant not to
 | 
			
		||||
sue for patent infringement).  To "grant" such a patent license to a
 | 
			
		||||
party means to make such an agreement or commitment not to enforce a
 | 
			
		||||
patent against the party.
 | 
			
		||||
 | 
			
		||||
  If you convey a covered work, knowingly relying on a patent license,
 | 
			
		||||
and the Corresponding Source of the work is not available for anyone
 | 
			
		||||
to copy, free of charge and under the terms of this License, through a
 | 
			
		||||
publicly available network server or other readily accessible means,
 | 
			
		||||
then you must either (1) cause the Corresponding Source to be so
 | 
			
		||||
available, or (2) arrange to deprive yourself of the benefit of the
 | 
			
		||||
patent license for this particular work, or (3) arrange, in a manner
 | 
			
		||||
consistent with the requirements of this License, to extend the patent
 | 
			
		||||
license to downstream recipients.  "Knowingly relying" means you have
 | 
			
		||||
actual knowledge that, but for the patent license, your conveying the
 | 
			
		||||
covered work in a country, or your recipient's use of the covered work
 | 
			
		||||
in a country, would infringe one or more identifiable patents in that
 | 
			
		||||
country that you have reason to believe are valid.
 | 
			
		||||
 | 
			
		||||
  If, pursuant to or in connection with a single transaction or
 | 
			
		||||
arrangement, you convey, or propagate by procuring conveyance of, a
 | 
			
		||||
covered work, and grant a patent license to some of the parties
 | 
			
		||||
receiving the covered work authorizing them to use, propagate, modify
 | 
			
		||||
or convey a specific copy of the covered work, then the patent license
 | 
			
		||||
you grant is automatically extended to all recipients of the covered
 | 
			
		||||
work and works based on it.
 | 
			
		||||
 | 
			
		||||
  A patent license is "discriminatory" if it does not include within
 | 
			
		||||
the scope of its coverage, prohibits the exercise of, or is
 | 
			
		||||
conditioned on the non-exercise of one or more of the rights that are
 | 
			
		||||
specifically granted under this License.  You may not convey a covered
 | 
			
		||||
work if you are a party to an arrangement with a third party that is
 | 
			
		||||
in the business of distributing software, under which you make payment
 | 
			
		||||
to the third party based on the extent of your activity of conveying
 | 
			
		||||
the work, and under which the third party grants, to any of the
 | 
			
		||||
parties who would receive the covered work from you, a discriminatory
 | 
			
		||||
patent license (a) in connection with copies of the covered work
 | 
			
		||||
conveyed by you (or copies made from those copies), or (b) primarily
 | 
			
		||||
for and in connection with specific products or compilations that
 | 
			
		||||
contain the covered work, unless you entered into that arrangement,
 | 
			
		||||
or that patent license was granted, prior to 28 March 2007.
 | 
			
		||||
 | 
			
		||||
  Nothing in this License shall be construed as excluding or limiting
 | 
			
		||||
any implied license or other defenses to infringement that may
 | 
			
		||||
otherwise be available to you under applicable patent law.
 | 
			
		||||
 | 
			
		||||
  12. No Surrender of Others' Freedom.
 | 
			
		||||
 | 
			
		||||
  If conditions are imposed on you (whether by court order, agreement or
 | 
			
		||||
otherwise) that contradict the conditions of this License, they do not
 | 
			
		||||
excuse you from the conditions of this License.  If you cannot convey a
 | 
			
		||||
covered work so as to satisfy simultaneously your obligations under this
 | 
			
		||||
License and any other pertinent obligations, then as a consequence you may
 | 
			
		||||
not convey it at all.  For example, if you agree to terms that obligate you
 | 
			
		||||
to collect a royalty for further conveying from those to whom you convey
 | 
			
		||||
the Program, the only way you could satisfy both those terms and this
 | 
			
		||||
License would be to refrain entirely from conveying the Program.
 | 
			
		||||
 | 
			
		||||
  13. Use with the GNU Affero General Public License.
 | 
			
		||||
 | 
			
		||||
  Notwithstanding any other provision of this License, you have
 | 
			
		||||
permission to link or combine any covered work with a work licensed
 | 
			
		||||
under version 3 of the GNU Affero General Public License into a single
 | 
			
		||||
combined work, and to convey the resulting work.  The terms of this
 | 
			
		||||
License will continue to apply to the part which is the covered work,
 | 
			
		||||
but the special requirements of the GNU Affero General Public License,
 | 
			
		||||
section 13, concerning interaction through a network will apply to the
 | 
			
		||||
combination as such.
 | 
			
		||||
 | 
			
		||||
  14. Revised Versions of this License.
 | 
			
		||||
 | 
			
		||||
  The Free Software Foundation may publish revised and/or new versions of
 | 
			
		||||
the GNU General Public License from time to time.  Such new versions will
 | 
			
		||||
be similar in spirit to the present version, but may differ in detail to
 | 
			
		||||
address new problems or concerns.
 | 
			
		||||
 | 
			
		||||
  Each version is given a distinguishing version number.  If the
 | 
			
		||||
Program specifies that a certain numbered version of the GNU General
 | 
			
		||||
Public License "or any later version" applies to it, you have the
 | 
			
		||||
option of following the terms and conditions either of that numbered
 | 
			
		||||
version or of any later version published by the Free Software
 | 
			
		||||
Foundation.  If the Program does not specify a version number of the
 | 
			
		||||
GNU General Public License, you may choose any version ever published
 | 
			
		||||
by the Free Software Foundation.
 | 
			
		||||
 | 
			
		||||
  If the Program specifies that a proxy can decide which future
 | 
			
		||||
versions of the GNU General Public License can be used, that proxy's
 | 
			
		||||
public statement of acceptance of a version permanently authorizes you
 | 
			
		||||
to choose that version for the Program.
 | 
			
		||||
 | 
			
		||||
  Later license versions may give you additional or different
 | 
			
		||||
permissions.  However, no additional obligations are imposed on any
 | 
			
		||||
author or copyright holder as a result of your choosing to follow a
 | 
			
		||||
later version.
 | 
			
		||||
 | 
			
		||||
  15. Disclaimer of Warranty.
 | 
			
		||||
 | 
			
		||||
  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
 | 
			
		||||
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
 | 
			
		||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
 | 
			
		||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
 | 
			
		||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 | 
			
		||||
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
 | 
			
		||||
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
 | 
			
		||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
 | 
			
		||||
 | 
			
		||||
  16. Limitation of Liability.
 | 
			
		||||
 | 
			
		||||
  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 | 
			
		||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
 | 
			
		||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
 | 
			
		||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
 | 
			
		||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
 | 
			
		||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
 | 
			
		||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
 | 
			
		||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
 | 
			
		||||
SUCH DAMAGES.
 | 
			
		||||
 | 
			
		||||
  17. Interpretation of Sections 15 and 16.
 | 
			
		||||
 | 
			
		||||
  If the disclaimer of warranty and limitation of liability provided
 | 
			
		||||
above cannot be given local legal effect according to their terms,
 | 
			
		||||
reviewing courts shall apply local law that most closely approximates
 | 
			
		||||
an absolute waiver of all civil liability in connection with the
 | 
			
		||||
Program, unless a warranty or assumption of liability accompanies a
 | 
			
		||||
copy of the Program in return for a fee.
 | 
			
		||||
 | 
			
		||||
                     END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
            How to Apply These Terms to Your New Programs
 | 
			
		||||
 | 
			
		||||
  If you develop a new program, and you want it to be of the greatest
 | 
			
		||||
possible use to the public, the best way to achieve this is to make it
 | 
			
		||||
free software which everyone can redistribute and change under these terms.
 | 
			
		||||
 | 
			
		||||
  To do so, attach the following notices to the program.  It is safest
 | 
			
		||||
to attach them to the start of each source file to most effectively
 | 
			
		||||
state the exclusion of warranty; and each file should have at least
 | 
			
		||||
the "copyright" line and a pointer to where the full notice is found.
 | 
			
		||||
 | 
			
		||||
    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/>.
 | 
			
		||||
 | 
			
		||||
Also add information on how to contact you by electronic and paper mail.
 | 
			
		||||
 | 
			
		||||
  If the program does terminal interaction, make it output a short
 | 
			
		||||
notice like this when it starts in an interactive mode:
 | 
			
		||||
 | 
			
		||||
    Vuls  Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
 | 
			
		||||
    This is free software, and you are welcome to redistribute it
 | 
			
		||||
    under certain conditions; type `show c' for details.
 | 
			
		||||
 | 
			
		||||
The hypothetical commands `show w' and `show c' should show the appropriate
 | 
			
		||||
parts of the General Public License.  Of course, your program's commands
 | 
			
		||||
might be different; for a GUI interface, you would use an "about box".
 | 
			
		||||
 | 
			
		||||
  You should also get your employer (if you work as a programmer) or school,
 | 
			
		||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
 | 
			
		||||
For more information on this, and how to apply and follow the GNU GPL, see
 | 
			
		||||
<http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
  The GNU General Public License does not permit incorporating your program
 | 
			
		||||
into proprietary programs.  If your program is a subroutine library, you
 | 
			
		||||
may consider it more useful to permit linking proprietary applications with
 | 
			
		||||
the library.  If this is what you want to do, use the GNU Lesser General
 | 
			
		||||
Public License instead of this License.  But first, please read
 | 
			
		||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
 | 
			
		||||
							
								
								
									
										52
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						@@ -1,52 +0,0 @@
 | 
			
		||||
.PHONY: \
 | 
			
		||||
	all \
 | 
			
		||||
	vendor \
 | 
			
		||||
	lint \
 | 
			
		||||
	vet \
 | 
			
		||||
	fmt \
 | 
			
		||||
	fmtcheck \
 | 
			
		||||
	pretest \
 | 
			
		||||
	test \
 | 
			
		||||
	integration \
 | 
			
		||||
	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;)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										652
									
								
								README.fr.md
									
									
									
									
									
								
							
							
						
						@@ -1,652 +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 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).  
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
$ go-cve-dictionary server
 | 
			
		||||
... Fetching ...
 | 
			
		||||
$ ls -alh cve.sqlite3
 | 
			
		||||
-rw-r--r-- 1 ec2-user ec2-user 7.0M Mar 24 13:20 cve.sqlite3
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Une fois les informations de vulnérabilités collectées redémarrez le mode serveur.
 | 
			
		||||
```bash
 | 
			
		||||
$ go-cve-dictionary server
 | 
			
		||||
[Mar 24 15:21:55]  INFO Opening DB. datafile: /home/ec2-user/cve.sqlite3
 | 
			
		||||
[Mar 24 15:21:55]  INFO Migrating DB
 | 
			
		||||
[Mar 24 15:21:56]  INFO Starting HTTP Sever...
 | 
			
		||||
[Mar 24 15:21:56]  INFO Listening on 127.0.0.1:1323
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step5. Déploiement de Vuls
 | 
			
		||||
 | 
			
		||||
Ouvrez un second terminal, connectez vous à l'instance ec2 via SSH
 | 
			
		||||
 | 
			
		||||
go get
 | 
			
		||||
```
 | 
			
		||||
$ go get github.com/future-architect/vuls
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step6. Configuration
 | 
			
		||||
 | 
			
		||||
Créez un fichier de configuration (TOML format).
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ cat config.toml
 | 
			
		||||
[servers]
 | 
			
		||||
 | 
			
		||||
[servers.172-31-4-82]
 | 
			
		||||
host         = "172.31.4.82"
 | 
			
		||||
port        = "22"
 | 
			
		||||
user        = "ec2-user"
 | 
			
		||||
keyPath     = "/home/ec2-user/.ssh/id_rsa"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step7. Configuration des serveurs cibles vuls  
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls prepare
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step8. Scan
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls scan
 | 
			
		||||
INFO[0000] Begin scanning (config: /home/ec2-user/config.toml)
 | 
			
		||||
 | 
			
		||||
... snip ...
 | 
			
		||||
 | 
			
		||||
172-31-4-82 (amazon 2015.09)
 | 
			
		||||
============================
 | 
			
		||||
CVE-2016-0494   10.0    Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle
 | 
			
		||||
                        Java SE 6u105, 7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to
 | 
			
		||||
                        affect confidentiality, integrity, and availability via unknown vectors related to
 | 
			
		||||
                        2D.
 | 
			
		||||
... snip ...
 | 
			
		||||
 | 
			
		||||
CVE-2016-0494
 | 
			
		||||
-------------
 | 
			
		||||
Score           10.0 (High)
 | 
			
		||||
Vector          (AV:N/AC:L/Au:N/C:C/I:C/A:C)
 | 
			
		||||
Summary         Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle Java SE 6u105,
 | 
			
		||||
                7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to affect confidentiality,
 | 
			
		||||
                integrity, and availability via unknown vectors related to 2D.
 | 
			
		||||
NVD             https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0494
 | 
			
		||||
MITRE           https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0494
 | 
			
		||||
CVE Details     http://www.cvedetails.com/cve/CVE-2016-0494
 | 
			
		||||
CVSS Claculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0494&vector=(AV:N/AC:L/Au:N/C:C/I:C/A:C)
 | 
			
		||||
RHEL-CVE        https://access.redhat.com/security/cve/CVE-2016-0494
 | 
			
		||||
ALAS-2016-643   https://alas.aws.amazon.com/ALAS-2016-643.html
 | 
			
		||||
Package/CPE     java-1.7.0-openjdk-1.7.0.91-2.6.2.2.63.amzn1 -> java-1.7.0-openjdk-1:1.7.0.95-2.6.4.0.65.amzn1
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step9. TUI
 | 
			
		||||
 | 
			
		||||
Les résultats de Vuls peuvent etre affichés dans un Shell via TUI (Terminal-Based User Interface).
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls tui
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Architecture
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## go-cve-dictinary  
 | 
			
		||||
- Collecte les informations de vulnérabilités depuis NVD, JVN(Japonais), et les envoie dans SQLite.
 | 
			
		||||
 | 
			
		||||
## Vuls
 | 
			
		||||
- Scan de vulnérabilités sur serveurs et création d'une liste contenant les CVE ID
 | 
			
		||||
- Pour des informations plus détaillés sur une CVE, envoie une requete HTTP à go-cve-dictinary 
 | 
			
		||||
- Rapport à Slack et par Email
 | 
			
		||||
- L'administrateur système peut voir les résultats du dernier rapport dans le terminal
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Exemples d'utilisation
 | 
			
		||||
 | 
			
		||||
## Scan de tous les serverus
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Scan d'un seul serveur
 | 
			
		||||
 | 
			
		||||
web/app server in the same configuration under the load balancer
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# OS supportés 
 | 
			
		||||
 | 
			
		||||
| Distribution|            Release |
 | 
			
		||||
|:------------|-------------------:|
 | 
			
		||||
| Ubuntu      |          12, 14, 16|
 | 
			
		||||
| Debian      |                7, 8|
 | 
			
		||||
| RHEL        |          4, 5, 6, 7|
 | 
			
		||||
| CentOS      |             5, 6, 7|
 | 
			
		||||
| Amazon Linux|                All |
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Usage: Détection Automatique de Serveurs 
 | 
			
		||||
 | 
			
		||||
La sous-commande Discovery permet de détecter les serveurs actifs dans un range d'IP CIDR, les résultas sont directement affichés dans le terminal en respectant le format du fichier de configuration (TOML format).
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls discover -help
 | 
			
		||||
discover:
 | 
			
		||||
        discover 192.168.0.0/24
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Exemple
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls discover 172.31.4.0/24
 | 
			
		||||
# Create config.toml using below and then ./vuls --config=/path/to/config.toml
 | 
			
		||||
 | 
			
		||||
[slack]
 | 
			
		||||
hookURL      = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
 | 
			
		||||
channel      = "#channel-name"
 | 
			
		||||
#channel      = "#{servername}"
 | 
			
		||||
iconEmoji    = ":ghost:"
 | 
			
		||||
authUser     = "username"
 | 
			
		||||
notifyUsers  = ["@username"]
 | 
			
		||||
 | 
			
		||||
[mail]
 | 
			
		||||
smtpAddr      = "smtp.gmail.com"
 | 
			
		||||
smtpPort      = 465
 | 
			
		||||
user          = "username"
 | 
			
		||||
password      = "password"
 | 
			
		||||
from          = "from@address.com"
 | 
			
		||||
to            = ["to@address.com"]
 | 
			
		||||
cc            = ["cc@address.com"]
 | 
			
		||||
subjectPrefix = "[vuls]"
 | 
			
		||||
 | 
			
		||||
[default]
 | 
			
		||||
#port        = "22"
 | 
			
		||||
#user        = "username"
 | 
			
		||||
#password    = "password"
 | 
			
		||||
#keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
#keyPassword = "password"
 | 
			
		||||
 | 
			
		||||
[servers]
 | 
			
		||||
 | 
			
		||||
[servers.172-31-4-82]
 | 
			
		||||
host         = "172.31.4.82"
 | 
			
		||||
#port        = "22"
 | 
			
		||||
#user        = "root"
 | 
			
		||||
#password    = "password"
 | 
			
		||||
#keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
#keyPassword = "password"
 | 
			
		||||
#cpeNames = [
 | 
			
		||||
#  "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
#]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Vous pouvez customiser votre configuration en utilisant ce modèle.
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Configuration
 | 
			
		||||
 | 
			
		||||
- Slack section
 | 
			
		||||
    ```
 | 
			
		||||
    [slack]
 | 
			
		||||
    hookURL      = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
 | 
			
		||||
    channel      = "#channel-name"
 | 
			
		||||
    #channel      = "#{servername}"
 | 
			
		||||
    iconEmoji    = ":ghost:"
 | 
			
		||||
    authUser     = "username"
 | 
			
		||||
    notifyUsers  = ["@username"]
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    - hookURL : Incomming webhook's URL  
 | 
			
		||||
    - channel : channel name.  
 | 
			
		||||
    If you set #{servername} to channel, the report will be sent to #servername channel.  
 | 
			
		||||
    In the following example, the report will be sent to the #server1 and #server2.  
 | 
			
		||||
    Be sure to create these channels before scanning.
 | 
			
		||||
      ```
 | 
			
		||||
      [slack]
 | 
			
		||||
      channel      = "#{servername}"
 | 
			
		||||
      ...snip...
 | 
			
		||||
 | 
			
		||||
      [servers]
 | 
			
		||||
 | 
			
		||||
      [servers.server1]
 | 
			
		||||
      host         = "172.31.4.82"
 | 
			
		||||
      ...snip...
 | 
			
		||||
 | 
			
		||||
      [servers.server2]
 | 
			
		||||
      host         = "172.31.4.83"
 | 
			
		||||
      ...snip...
 | 
			
		||||
      ```
 | 
			
		||||
 | 
			
		||||
    - iconEmoji: emoji
 | 
			
		||||
    - authUser: username of the slack team
 | 
			
		||||
    - notifyUsers: a list of Slack usernames to send Slack notifications.
 | 
			
		||||
      If you set ["@foo", "@bar"] to notifyUsers, @foo @bar will be included in text.  
 | 
			
		||||
      So @foo, @bar can receive mobile push notifications on their smartphone.  
 | 
			
		||||
 | 
			
		||||
- Mail section
 | 
			
		||||
    ```
 | 
			
		||||
    [mail]
 | 
			
		||||
    smtpAddr      = "smtp.gmail.com"
 | 
			
		||||
    smtpPort      = 465
 | 
			
		||||
    user          = "username"
 | 
			
		||||
    password      = "password"
 | 
			
		||||
    from          = "from@address.com"
 | 
			
		||||
    to            = ["to@address.com"]
 | 
			
		||||
    cc            = ["cc@address.com"]
 | 
			
		||||
    subjectPrefix = "[vuls]"
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
- Default section
 | 
			
		||||
    ```
 | 
			
		||||
    [default]
 | 
			
		||||
    #port        = "22"
 | 
			
		||||
    #user        = "username"
 | 
			
		||||
    #password    = "password"
 | 
			
		||||
    #keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
    #keyPassword = "password"
 | 
			
		||||
    ```
 | 
			
		||||
    Items of the default section will be used if not specified.
 | 
			
		||||
 | 
			
		||||
- servers section
 | 
			
		||||
    ```
 | 
			
		||||
    [servers]
 | 
			
		||||
 | 
			
		||||
    [servers.172-31-4-82]
 | 
			
		||||
    host         = "172.31.4.82"
 | 
			
		||||
    #port        = "22"
 | 
			
		||||
    #user        = "root"
 | 
			
		||||
    #password    = "password"
 | 
			
		||||
    #keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
    #keyPassword = "password"
 | 
			
		||||
    #cpeNames = [
 | 
			
		||||
    #  "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
    #]
 | 
			
		||||
    ```
 | 
			
		||||
    Vous pouvez remplacer les valeurs par défaut indiquées en modifiant la section default
 | 
			
		||||
    Vuls supporte plusieurs méthodes d'authentification SSH :
 | 
			
		||||
    - SSH agent
 | 
			
		||||
    - SSH authentication par clés (avec mot de passe ou sans mot de passe)
 | 
			
		||||
    - Authentification par mot de passe
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Utilisation : Prepare
 | 
			
		||||
 | 
			
		||||
La sous-commande prepare installe tous les paquets nécessaires sur chaque serveur.
 | 
			
		||||
 | 
			
		||||
| Distribution|            Release | Requirements |
 | 
			
		||||
|:------------|-------------------:|:-------------|
 | 
			
		||||
| Ubuntu      |          12, 14, 16| -            |
 | 
			
		||||
| Debian      |                7, 8| apptitude    |
 | 
			
		||||
| CentOS      |                   5| yum-plugin-security, yum-changelog |
 | 
			
		||||
| CentOS      |                6, 7| yum-plugin-security, yum-plugin-changelog |
 | 
			
		||||
| Amazon      |                All | -            |
 | 
			
		||||
| RHEL        |         4, 5, 6, 7 | -            |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls prepare -help
 | 
			
		||||
prepare:
 | 
			
		||||
        prepare [-config=/path/to/config.toml] [-debug]
 | 
			
		||||
 | 
			
		||||
  -config string
 | 
			
		||||
        /path/to/toml (default "$PWD/config.toml")
 | 
			
		||||
  -debug
 | 
			
		||||
        debug mode
 | 
			
		||||
  -use-unattended-upgrades
 | 
			
		||||
        [Deprecated] For Ubuntu, install unattended-upgrades
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Utilisation : Scan
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls scan -help
 | 
			
		||||
scan:
 | 
			
		||||
        scan
 | 
			
		||||
                [-lang=en|ja]
 | 
			
		||||
                [-config=/path/to/config.toml]
 | 
			
		||||
                [-dbpath=/path/to/vuls.sqlite3]
 | 
			
		||||
                [-cve-dictionary-url=http://127.0.0.1:1323]
 | 
			
		||||
                [-cvss-over=7]
 | 
			
		||||
                [-report-slack]
 | 
			
		||||
                [-report-mail]
 | 
			
		||||
                [-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
                [-debug]
 | 
			
		||||
                [-debug-sql]
 | 
			
		||||
  -config string
 | 
			
		||||
        /path/to/toml (default "$PWD/config.toml")
 | 
			
		||||
  -cve-dictionary-url string
 | 
			
		||||
        http://CVE.Dictionary (default "http://127.0.0.1:1323")
 | 
			
		||||
  -cvss-over float
 | 
			
		||||
        -cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))
 | 
			
		||||
  -dbpath string
 | 
			
		||||
        /path/to/sqlite3 (default "$PWD/vuls.sqlite3")
 | 
			
		||||
  -debug
 | 
			
		||||
        debug mode
 | 
			
		||||
  -debug-sql
 | 
			
		||||
        SQL debug mode
 | 
			
		||||
  -http-proxy string
 | 
			
		||||
        http://proxy-url:port (default: empty)
 | 
			
		||||
  -lang string
 | 
			
		||||
        [en|ja] (default "en")
 | 
			
		||||
  -report-mail
 | 
			
		||||
        Email report
 | 
			
		||||
  -report-slack
 | 
			
		||||
        Slack report
 | 
			
		||||
  -use-unattended-upgrades
 | 
			
		||||
        [Deprecated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)
 | 
			
		||||
  -use-yum-plugin-security
 | 
			
		||||
        [Deprecated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## exemple
 | 
			
		||||
 | 
			
		||||
Lancez go-cve-dictionary en mode serveur avant de lancer un scan
 | 
			
		||||
```
 | 
			
		||||
$ go-cve-dictionary server
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Scan tous les serveurs identifiés dans le fichier de configuration
 | 
			
		||||
```
 | 
			
		||||
$ vuls scan --report-slack --report-mail --cvss-over=7
 | 
			
		||||
```
 | 
			
		||||
Via cette simple commande Vuls va : ..
 | 
			
		||||
- Scanner tous les serveurs identifiés dans le fichier de configuration
 | 
			
		||||
- Envoyer les résultas du scan à slack et par email
 | 
			
		||||
- Ne rapporter que les CVE dont la note CVSS est au dessus de 7
 | 
			
		||||
- Afficher les résultats du scan dans le terminal
 | 
			
		||||
 | 
			
		||||
### Scan de serveurs spécifiques
 | 
			
		||||
```
 | 
			
		||||
$ vuls scan server1 server2
 | 
			
		||||
```
 | 
			
		||||
Via cette simple commande Vuls va : ..
 | 
			
		||||
- Scanner seulement 2 serveurs. (server1, server2)
 | 
			
		||||
- Afficher les résultats du scan dans le terminal
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Utilisation : Recherche de vulnérabilités sur des paquets non compris dans l'OS
 | 
			
		||||
 | 
			
		||||
Il est possible de détecter des vulnérabilités sur des programmes que vous avez compilés, des lors que les libraries et frameworks ont été enregistré dans [CPE](https://nvd.nist.gov/cpe.cfm).
 | 
			
		||||
 | 
			
		||||
-  Comment rechercher dans CPE via le nom du programme
 | 
			
		||||
    - [NVD: Search Common Platform Enumerations (CPE)](https://web.nvd.nist.gov/view/cpe/search)  
 | 
			
		||||
    **Check CPE Naming Format: 2.2**
 | 
			
		||||
 | 
			
		||||
- Configuration  
 | 
			
		||||
Pour détecter des vulnérabilités sur Ruby on Rails v4.2.1, cpeNames doit etre déclaré dans la section servers.
 | 
			
		||||
    ```
 | 
			
		||||
    [servers]
 | 
			
		||||
 | 
			
		||||
    [servers.172-31-4-82]
 | 
			
		||||
    host         = "172.31.4.82"
 | 
			
		||||
    user        = "ec2-user"
 | 
			
		||||
    keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
    cpeNames = [
 | 
			
		||||
      "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
    ]
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
# Utilisation : Mise à jour des données NVD.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ go-cve-dictionary fetchnvd -h
 | 
			
		||||
fetchnvd:
 | 
			
		||||
        fetchnvd
 | 
			
		||||
                [-last2y]
 | 
			
		||||
                [-dbpath=/path/to/cve.sqlite3]
 | 
			
		||||
                [-debug]
 | 
			
		||||
                [-debug-sql]
 | 
			
		||||
 | 
			
		||||
  -dbpath string
 | 
			
		||||
        /path/to/sqlite3 (default "$PWD/cve.sqlite3")
 | 
			
		||||
  -debug
 | 
			
		||||
        debug mode
 | 
			
		||||
  -debug-sql
 | 
			
		||||
        SQL debug mode
 | 
			
		||||
  -last2y
 | 
			
		||||
        Refresh NVD data in the last two years.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- Récupérer toutes les données jusqu'à aujourd'hui
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ go-cve-dictionary fetchnvd -entire
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- Reçupérer les données des 2 denières années
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ go-cve-dictionary fetchnvd -last2y
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Misc
 | 
			
		||||
 | 
			
		||||
- HTTP Proxy Support  
 | 
			
		||||
If your system is behind HTTP proxy, you have to specify --http-proxy option.
 | 
			
		||||
 | 
			
		||||
- How to Daemonize go-cve-dictionary  
 | 
			
		||||
Use Systemd, Upstart or supervisord, daemontools...
 | 
			
		||||
 | 
			
		||||
- How to Enable Automatic-Update of Vunerability Data.  
 | 
			
		||||
Use job scheduler like Cron (with -last2y option).
 | 
			
		||||
 | 
			
		||||
- How to cross compile
 | 
			
		||||
    ```bash
 | 
			
		||||
    $ cd /path/to/your/local-git-reporsitory/vuls
 | 
			
		||||
    $ GOOS=linux GOARCH=amd64 go build -o vuls.amd64
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
- Logging  
 | 
			
		||||
Log wrote to under /var/log/vuls/
 | 
			
		||||
 | 
			
		||||
- Debug  
 | 
			
		||||
Run with --debug, --sql-debug option.
 | 
			
		||||
 | 
			
		||||
- Ajusting Open File Limit  
 | 
			
		||||
[Riak docs](http://docs.basho.com/riak/latest/ops/tuning/open-files-limit/) is awesome.
 | 
			
		||||
 | 
			
		||||
- Does Vuls accept ssh connections with fish-shell or old zsh as the login shell?  
 | 
			
		||||
No, Vuls needs a user on the server for bash login. see also [#8](/../../issues/8)
 | 
			
		||||
 | 
			
		||||
- Windows  
 | 
			
		||||
Use Microsoft Baseline Security Analyzer. [MBSA](https://technet.microsoft.com/en-us/security/cc184924.aspx)
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Data Source
 | 
			
		||||
 | 
			
		||||
- [NVD](https://nvd.nist.gov/)
 | 
			
		||||
- [JVN(Japanese)](http://jvndb.jvn.jp/apis/myjvn/)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Authors
 | 
			
		||||
 | 
			
		||||
kotakanbe ([@kotakanbe](https://twitter.com/kotakanbe)) created vuls and [these fine people](https://github.com/future-architect/vuls/graphs/contributors) have contributed.
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Contribute
 | 
			
		||||
 | 
			
		||||
1. Fork it
 | 
			
		||||
2. Create your feature branch (`git checkout -b my-new-feature`)
 | 
			
		||||
3. Commit your changes (`git commit -am 'Add some feature'`)
 | 
			
		||||
4. Push to the branch (`git push origin my-new-feature`)
 | 
			
		||||
5. Create new Pull Request
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Change Log
 | 
			
		||||
 | 
			
		||||
Please see [CHANGELOG](https://github.com/future-architect/vuls/blob/master/CHANGELOG.md).
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Licence
 | 
			
		||||
 | 
			
		||||
Please see [LICENSE](https://github.com/future-architect/vuls/blob/master/LICENSE).
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[](https://bitdeli.com/free "Bitdeli Badge")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										109
									
								
								README.ja.md
									
									
									
									
									
								
							
							
						
						@@ -1,109 +0,0 @@
 | 
			
		||||
 | 
			
		||||
# Vuls: VULnerability Scanner
 | 
			
		||||
 | 
			
		||||
[](http://goo.gl/forms/xm5KFo35tu)
 | 
			
		||||
 | 
			
		||||
Vulnerability scanner for Linux, agentless, written in golang.
 | 
			
		||||
 | 
			
		||||
[README in English](https://github.com/future-architect/vuls/blob/master/README.md)  
 | 
			
		||||
Slackチームは[こちらから](http://goo.gl/forms/xm5KFo35tu)参加できます。(日本語でオッケーです)
 | 
			
		||||
 | 
			
		||||
[](https://asciinema.org/a/bazozlxrw1wtxfu9yojyihick)
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Abstract
 | 
			
		||||
 | 
			
		||||
毎日のように発見される脆弱性の調査やソフトウェアアップデート作業は、システム管理者にとって負荷の高いタスクである。
 | 
			
		||||
プロダクション環境ではサービス停止リスクを避けるために、パッケージマネージャの自動更新機能を使わずに手動更新で運用するケースも多い。
 | 
			
		||||
だが、手動更新での運用には以下の問題がある。
 | 
			
		||||
- システム管理者がNVDなどで新着の脆弱性をウォッチし続けなければならない
 | 
			
		||||
- サーバにインストールされているソフトウェアは膨大であり、システム管理者が全てを把握するのは困難
 | 
			
		||||
- 新着の脆弱性がどのサーバに該当するのかといった調査コストが大きく、漏れる可能性がある
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Vulsは上に挙げた手動運用での課題を解決するツールであり、以下の特徴がある。
 | 
			
		||||
- システムに関係ある脆弱性のみ教えてくれる
 | 
			
		||||
- その脆弱性に該当するサーバを教えてくれる
 | 
			
		||||
- 自動スキャンのため脆弱性検知の漏れを防ぐことができる
 | 
			
		||||
- CRONなどで定期実行、レポートすることで脆弱性の放置を防ぐことできる
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Main Features
 | 
			
		||||
 | 
			
		||||
- Linuxサーバに存在する脆弱性をスキャン
 | 
			
		||||
    - Ubuntu, Debian, CentOS, Amazon Linux, RHELに対応
 | 
			
		||||
    - クラウド、オンプレミス、Docker
 | 
			
		||||
- OSパッケージ管理対象外のミドルウェアをスキャン
 | 
			
		||||
    - プログラミング言語のライブラリやフレームワーク、ミドルウェアの脆弱性スキャン
 | 
			
		||||
    - CPEに登録されているソフトウェアが対象
 | 
			
		||||
- エージェントレスアーキテクチャ
 | 
			
		||||
    - スキャン対象サーバにSSH接続可能なマシン1台にセットアップするだけで動作
 | 
			
		||||
- 設定ファイルのテンプレート自動生成
 | 
			
		||||
    - CIDRを指定してサーバを自動検出、設定ファイルのテンプレートを生成
 | 
			
		||||
- EmailやSlackで通知可能(日本語でのレポートも可能)
 | 
			
		||||
- 付属するTerminal-Based User Interfaceビューアでは、Vim風キーバインドでスキャン結果を参照可能
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
詳細は[README in English](https://github.com/future-architect/vuls/blob/master/README.md) を参照
 | 
			
		||||
 | 
			
		||||
# レポートの日本語化
 | 
			
		||||
 | 
			
		||||
- JVNから日本語の脆弱性情報を取得
 | 
			
		||||
    ```
 | 
			
		||||
    $ go-cve-dictionary fetchjvn -help
 | 
			
		||||
    fetchjvn:
 | 
			
		||||
            fetchjvn [-dump-path=$PWD/cve] [-dpath=$PWD/vuls.sqlite3] [-week] [-month] [-entire]
 | 
			
		||||
 | 
			
		||||
      -dbpath string
 | 
			
		||||
            /path/to/sqlite3/DBfile (default "$PWD/cve.sqlite3")
 | 
			
		||||
      -debug
 | 
			
		||||
            debug mode
 | 
			
		||||
      -debug-sql
 | 
			
		||||
            SQL debug mode
 | 
			
		||||
      -dump-path string
 | 
			
		||||
            /path/to/dump.json (default "$PWD/cve.json")
 | 
			
		||||
      -entire
 | 
			
		||||
            Fetch data for entire period.(This operation is time-consuming) (default: false)
 | 
			
		||||
      -month
 | 
			
		||||
            Fetch data in the last month (default: false)
 | 
			
		||||
      -week
 | 
			
		||||
            Fetch data in the last week. (default: false)
 | 
			
		||||
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
- すべての期間の脆弱性情報を取得(1時間以上かかる)
 | 
			
		||||
    ```
 | 
			
		||||
    $ go-cve-dictionary fetchjvn -entire
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
- 直近1ヶ月間に更新された脆弱性情報を取得(1分未満)
 | 
			
		||||
    ```
 | 
			
		||||
    $ go-cve-dictionary fetchjvn -month
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
- 直近1週間に更新された脆弱性情報を取得(1分未満)
 | 
			
		||||
    ```
 | 
			
		||||
    $ go-cve-dictionary fetchjvn -week
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
- 脆弱性情報の自動アップデート  
 | 
			
		||||
Cronなどのジョブスケジューラを用いて実現可能。  
 | 
			
		||||
-week オプションを指定して夜間の日次実行を推奨。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## スキャン実行
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls scan -lang=ja
 | 
			
		||||
```
 | 
			
		||||
Scan時にlang=jaを指定すると脆弱性レポートが日本語になる  
 | 
			
		||||
slack, emailは日本語対応済み TUIは日本語表示未対応
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										15
									
								
								cmd/vuls/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,15 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/cmd/root"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	if err := root.NewCmdRoot().Execute(); err != nil {
 | 
			
		||||
		fmt.Fprintf(os.Stderr, "failed to exec vuls: %s\n", fmt.Sprintf("%+v", err))
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,154 +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"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"text/template"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	ps "github.com/kotakanbe/go-pingscanner"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DiscoverCmd is Subcommand of host discovery mode
 | 
			
		||||
type DiscoverCmd struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*DiscoverCmd) Name() string { return "discover" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*DiscoverCmd) Synopsis() string { return "Host discovery in the CIDR" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*DiscoverCmd) Usage() string {
 | 
			
		||||
	return `discover:
 | 
			
		||||
	discover 192.168.0.0/24
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *DiscoverCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	// validate
 | 
			
		||||
	if len(f.Args()) == 0 {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, cidr := range f.Args() {
 | 
			
		||||
		scanner := ps.PingScanner{
 | 
			
		||||
			CIDR: cidr,
 | 
			
		||||
			PingOptions: []string{
 | 
			
		||||
				"-c1",
 | 
			
		||||
				"-t1",
 | 
			
		||||
			},
 | 
			
		||||
			NumOfConcurrency: 100,
 | 
			
		||||
		}
 | 
			
		||||
		hosts, err := scanner.Scan()
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Errorf("Host Discovery failed. err: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(hosts) < 1 {
 | 
			
		||||
			logrus.Errorf("Active hosts not found in %s", cidr)
 | 
			
		||||
			return subcommands.ExitSuccess
 | 
			
		||||
		} else if err := printConfigToml(hosts); err != nil {
 | 
			
		||||
			logrus.Errorf("Failed to parse template. err: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Output the tmeplate of config.toml
 | 
			
		||||
func printConfigToml(ips []string) (err error) {
 | 
			
		||||
	const tomlTempale = `
 | 
			
		||||
[slack]
 | 
			
		||||
hookURL      = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
 | 
			
		||||
channel      = "#channel-name"
 | 
			
		||||
#channel      = "#{servername}"
 | 
			
		||||
iconEmoji    = ":ghost:"
 | 
			
		||||
authUser     = "username"
 | 
			
		||||
notifyUsers  = ["@username"]
 | 
			
		||||
 | 
			
		||||
[mail]
 | 
			
		||||
smtpAddr      = "smtp.gmail.com"
 | 
			
		||||
smtpPort      = 465
 | 
			
		||||
user          = "username"
 | 
			
		||||
password      = "password"
 | 
			
		||||
from          = "from@address.com"
 | 
			
		||||
to            = ["to@address.com"]
 | 
			
		||||
cc            = ["cc@address.com"]
 | 
			
		||||
subjectPrefix = "[vuls]"
 | 
			
		||||
 | 
			
		||||
[default]
 | 
			
		||||
#port        = "22"
 | 
			
		||||
#user        = "username"
 | 
			
		||||
#keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
 | 
			
		||||
[servers]
 | 
			
		||||
{{- $names:=  .Names}}
 | 
			
		||||
{{range $i, $ip := .IPs}}
 | 
			
		||||
[servers.{{index $names $i}}]
 | 
			
		||||
host         = "{{$ip}}"
 | 
			
		||||
#port        = "22"
 | 
			
		||||
#user        = "root"
 | 
			
		||||
#keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
#cpeNames = [
 | 
			
		||||
#  "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
#]
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
	var tpl *template.Template
 | 
			
		||||
	if tpl, err = template.New("tempalte").Parse(tomlTempale); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type activeHosts struct {
 | 
			
		||||
		IPs   []string
 | 
			
		||||
		Names []string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a := activeHosts{IPs: ips}
 | 
			
		||||
	names := []string{}
 | 
			
		||||
	for _, ip := range ips {
 | 
			
		||||
		// TOML section header must not contain "."
 | 
			
		||||
		name := strings.Replace(ip, ".", "-", -1)
 | 
			
		||||
		names = append(names, name)
 | 
			
		||||
	}
 | 
			
		||||
	a.Names = names
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
@@ -1,168 +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"
 | 
			
		||||
 | 
			
		||||
	"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-sudo-password]
 | 
			
		||||
			[-ask-key-password]
 | 
			
		||||
			[-debug]
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *PrepareCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := os.Getenv("PWD") + "/config.toml"
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askSudoPassword,
 | 
			
		||||
		"ask-sudo-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"Ask sudo password of target servers before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useUnattendedUpgrades,
 | 
			
		||||
		"use-unattended-upgrades",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Depricated] For Ubuntu, install unattended-upgrades",
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	var keyPass, sudoPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if p.askSudoPassword {
 | 
			
		||||
		prompt := "sudo password: "
 | 
			
		||||
		if sudoPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass, sudoPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Infof("Start Preparing (config: %s)", p.configPath)
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
	for _, arg := range f.Args() {
 | 
			
		||||
		found := false
 | 
			
		||||
		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... ")
 | 
			
		||||
	err = scan.InitServers(logger)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Errorf("Failed to init servers. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										276
									
								
								commands/scan.go
									
									
									
									
									
								
							
							
						
						@@ -1,276 +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"
 | 
			
		||||
 | 
			
		||||
	"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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanCmd is Subcommand of host discovery mode
 | 
			
		||||
type ScanCmd struct {
 | 
			
		||||
	lang     string
 | 
			
		||||
	debug    bool
 | 
			
		||||
	debugSQL bool
 | 
			
		||||
 | 
			
		||||
	configPath string
 | 
			
		||||
 | 
			
		||||
	dbpath           string
 | 
			
		||||
	cveDictionaryURL string
 | 
			
		||||
	cvssScoreOver    float64
 | 
			
		||||
	httpProxy        string
 | 
			
		||||
 | 
			
		||||
	// reporting
 | 
			
		||||
	reportSlack bool
 | 
			
		||||
	reportMail  bool
 | 
			
		||||
 | 
			
		||||
	askSudoPassword bool
 | 
			
		||||
	askKeyPassword  bool
 | 
			
		||||
 | 
			
		||||
	useYumPluginSecurity  bool
 | 
			
		||||
	useUnattendedUpgrades bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*ScanCmd) Name() string { return "scan" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*ScanCmd) Usage() string {
 | 
			
		||||
	return `scan:
 | 
			
		||||
	scan
 | 
			
		||||
		[-lang=en|ja]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-dbpath=/path/to/vuls.sqlite3]
 | 
			
		||||
		[-cve-dictionary-url=http://127.0.0.1:1323]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-report-slack]
 | 
			
		||||
		[-report-mail]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-ask-sudo-password]
 | 
			
		||||
		[-ask-key-password]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := os.Getenv("PWD") + "/config.toml"
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultDBPath := os.Getenv("PWD") + "/vuls.sqlite3"
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3")
 | 
			
		||||
 | 
			
		||||
	defaultURL := "http://127.0.0.1:1323"
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDictionaryURL,
 | 
			
		||||
		"cve-dictionary-url",
 | 
			
		||||
		defaultURL,
 | 
			
		||||
		"http://CVE.Dictionary")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(
 | 
			
		||||
		&p.cvssScoreOver,
 | 
			
		||||
		"cvss-over",
 | 
			
		||||
		0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.httpProxy,
 | 
			
		||||
		"http-proxy",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://proxy-url:port (default: empty)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.reportSlack, "report-slack", false, "Slack report")
 | 
			
		||||
	f.BoolVar(&p.reportMail, "report-mail", false, "Email report")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askSudoPassword,
 | 
			
		||||
		"ask-sudo-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"Ask sudo password of target servers before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useYumPluginSecurity,
 | 
			
		||||
		"use-yum-plugin-security",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Depricated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useUnattendedUpgrades,
 | 
			
		||||
		"use-unattended-upgrades",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Depricated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	var keyPass, sudoPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if p.askSudoPassword {
 | 
			
		||||
		prompt := "sudo password: "
 | 
			
		||||
		if sudoPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass, sudoPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Infof("Start scanning (config: %s)", p.configPath)
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
	for _, arg := range f.Args() {
 | 
			
		||||
		found := false
 | 
			
		||||
		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.Lang = p.lang
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
 | 
			
		||||
	// logger
 | 
			
		||||
	Log := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	// report
 | 
			
		||||
	reports := []report.ResultWriter{
 | 
			
		||||
		report.TextWriter{},
 | 
			
		||||
		report.LogrusWriter{},
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportSlack {
 | 
			
		||||
		reports = append(reports, report.SlackWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportMail {
 | 
			
		||||
		reports = append(reports, report.MailWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.DBPath = p.dbpath
 | 
			
		||||
	c.Conf.CveDictionaryURL = p.cveDictionaryURL
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
	c.Conf.UseYumPluginSecurity = p.useYumPluginSecurity
 | 
			
		||||
	c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
 | 
			
		||||
 | 
			
		||||
	Log.Info("Validating Config...")
 | 
			
		||||
	if !c.Conf.Validate() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ok, err := cveapi.CveClient.CheckHealth(); !ok {
 | 
			
		||||
		Log.Errorf("CVE HTTP server is not running. %#v", cveapi.CveClient)
 | 
			
		||||
		Log.Fatal(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting the type of OS... ")
 | 
			
		||||
	err = scan.InitServers(Log)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		Log.Errorf("Failed to init servers. Check the configuration. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerabilities... ")
 | 
			
		||||
	if errs := scan.Scan(); 0 < len(errs) {
 | 
			
		||||
		for _, e := range errs {
 | 
			
		||||
			Log.Errorf("Failed to scan. err: %s", e)
 | 
			
		||||
		}
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scanResults, err := scan.GetScanResults()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		Log.Fatal(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Reporting...")
 | 
			
		||||
	filtered := scanResults.FilterByCvssOver()
 | 
			
		||||
	for _, w := range reports {
 | 
			
		||||
		if err := w.Write(filtered); err != nil {
 | 
			
		||||
			Log.Fatalf("Failed to output report, err: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Insert to DB...")
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.MigrateDB(); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to migrate. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := db.Insert(scanResults); err != nil {
 | 
			
		||||
		Log.Fatalf("Failed to insert. dbpath: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
@@ -1,68 +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"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TuiCmd is Subcommand of host discovery mode
 | 
			
		||||
type TuiCmd struct {
 | 
			
		||||
	lang     string
 | 
			
		||||
	debugSQL bool
 | 
			
		||||
	dbpath   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*TuiCmd) Name() string { return "tui" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*TuiCmd) Synopsis() string { return "Run Tui view to anayze vulnerabilites" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*TuiCmd) Usage() string {
 | 
			
		||||
	return `tui:
 | 
			
		||||
	tui [-dbpath=/path/to/vuls.sqlite3]
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	//  f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "debug SQL")
 | 
			
		||||
 | 
			
		||||
	defaultDBPath := os.Getenv("PWD") + "/vuls.sqlite3"
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath,
 | 
			
		||||
		fmt.Sprintf("/path/to/sqlite3 (default: %s)", defaultDBPath))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	c.Conf.Lang = "en"
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.DBPath = p.dbpath
 | 
			
		||||
	return report.RunTui()
 | 
			
		||||
}
 | 
			
		||||
@@ -1,32 +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 config
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// Colors has ansi color list
 | 
			
		||||
	Colors = []string{
 | 
			
		||||
		"\033[32m", // green
 | 
			
		||||
		"\033[33m", // yellow
 | 
			
		||||
		"\033[36m", // cyan
 | 
			
		||||
		"\033[35m", // magenta
 | 
			
		||||
		"\033[31m", // red
 | 
			
		||||
		"\033[34m", // blue
 | 
			
		||||
	}
 | 
			
		||||
	// ResetColor is reset color
 | 
			
		||||
	ResetColor = "\033[0m"
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										221
									
								
								config/config.go
									
									
									
									
									
								
							
							
						
						@@ -1,221 +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 config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	valid "github.com/asaskevich/govalidator"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Conf has Configuration
 | 
			
		||||
var Conf Config
 | 
			
		||||
 | 
			
		||||
//Config is struct of Configuration
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Debug    bool
 | 
			
		||||
	DebugSQL bool
 | 
			
		||||
	Lang     string
 | 
			
		||||
 | 
			
		||||
	Mail    smtpConf
 | 
			
		||||
	Slack   SlackConf
 | 
			
		||||
	Default ServerInfo
 | 
			
		||||
	Servers map[string]ServerInfo
 | 
			
		||||
 | 
			
		||||
	CveDictionaryURL string `valid:"url"`
 | 
			
		||||
 | 
			
		||||
	CvssScoreOver float64
 | 
			
		||||
	HTTPProxy     string `valid:"url"`
 | 
			
		||||
	DBPath        string
 | 
			
		||||
	//  CpeNames      []string
 | 
			
		||||
	//  SummaryMode          bool
 | 
			
		||||
	UseYumPluginSecurity  bool
 | 
			
		||||
	UseUnattendedUpgrades bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate configuration
 | 
			
		||||
func (c Config) Validate() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.DBPath) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.DBPath); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB path must be a *Absolute* file path. dbpath: %s", c.DBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := valid.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mailerrs := c.Mail.Validate(); 0 < len(mailerrs) {
 | 
			
		||||
		errs = append(errs, mailerrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if slackerrs := c.Slack.Validate(); 0 < len(slackerrs) {
 | 
			
		||||
		errs = append(errs, slackerrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// smtpConf is smtp config
 | 
			
		||||
type smtpConf struct {
 | 
			
		||||
	SMTPAddr string
 | 
			
		||||
	SMTPPort string `valid:"port"`
 | 
			
		||||
 | 
			
		||||
	User          string
 | 
			
		||||
	Password      string
 | 
			
		||||
	From          string
 | 
			
		||||
	To            []string
 | 
			
		||||
	Cc            []string
 | 
			
		||||
	SubjectPrefix string
 | 
			
		||||
 | 
			
		||||
	UseThisTime bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkEmails(emails []string) (errs []error) {
 | 
			
		||||
	for _, addr := range emails {
 | 
			
		||||
		if len(addr) == 0 {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if ok := valid.IsEmail(addr); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf("Invalid email address. email: %s", addr))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate SMTP configuration
 | 
			
		||||
func (c *smtpConf) Validate() (errs []error) {
 | 
			
		||||
 | 
			
		||||
	if !c.UseThisTime {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check Emails fromat
 | 
			
		||||
	emails := []string{}
 | 
			
		||||
	emails = append(emails, c.From)
 | 
			
		||||
	emails = append(emails, c.To...)
 | 
			
		||||
	emails = append(emails, c.Cc...)
 | 
			
		||||
 | 
			
		||||
	if emailErrs := checkEmails(emails); 0 < len(emailErrs) {
 | 
			
		||||
		errs = append(errs, emailErrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.SMTPAddr) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("smtpAddr must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.SMTPPort) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("smtpPort must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.To) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("To required at least one address"))
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.From) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("From required at least one address"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := valid.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SlackConf is slack config
 | 
			
		||||
type SlackConf struct {
 | 
			
		||||
	HookURL   string `valid:"url"`
 | 
			
		||||
	Channel   string `json:"channel"`
 | 
			
		||||
	IconEmoji string `json:"icon_emoji"`
 | 
			
		||||
	AuthUser  string `json:"username"`
 | 
			
		||||
 | 
			
		||||
	NotifyUsers []string
 | 
			
		||||
	Text        string `json:"text"`
 | 
			
		||||
 | 
			
		||||
	UseThisTime bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *SlackConf) Validate() (errs []error) {
 | 
			
		||||
 | 
			
		||||
	if !c.UseThisTime {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.HookURL) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("hookURL must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.Channel) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("channel must not be empty"))
 | 
			
		||||
	} else {
 | 
			
		||||
		if !(strings.HasPrefix(c.Channel, "#") ||
 | 
			
		||||
			c.Channel == "${servername}") {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"channel's prefix must be '#', channel: %s", c.Channel))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.AuthUser) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("authUser must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := valid.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO check if slack configration is valid
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfo has SSH Info, additional CPE packages to scan.
 | 
			
		||||
type ServerInfo struct {
 | 
			
		||||
	ServerName  string
 | 
			
		||||
	User        string
 | 
			
		||||
	Password    string
 | 
			
		||||
	Host        string
 | 
			
		||||
	Port        string
 | 
			
		||||
	KeyPath     string
 | 
			
		||||
	KeyPassword string
 | 
			
		||||
	SudoOpt     SudoOption
 | 
			
		||||
 | 
			
		||||
	CpeNames []string
 | 
			
		||||
 | 
			
		||||
	// DebugLog Color
 | 
			
		||||
	LogMsgAnsiColor string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SudoOption is flag of sudo option.
 | 
			
		||||
type SudoOption struct {
 | 
			
		||||
 | 
			
		||||
	// echo pass | sudo -S ls
 | 
			
		||||
	ExecBySudo bool
 | 
			
		||||
 | 
			
		||||
	// echo pass | sudo sh -C 'ls'
 | 
			
		||||
	ExecBySudoSh bool
 | 
			
		||||
}
 | 
			
		||||
@@ -1,29 +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 config
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
// JSONLoader loads configuration
 | 
			
		||||
type JSONLoader struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load load the configuraiton JSON file specified by path arg.
 | 
			
		||||
func (c JSONLoader) Load(path, sudoPass, keyPass string) (err error) {
 | 
			
		||||
	return fmt.Errorf("Not implement yet")
 | 
			
		||||
}
 | 
			
		||||
@@ -1,31 +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 config
 | 
			
		||||
 | 
			
		||||
// Load loads configuration
 | 
			
		||||
func Load(path, keyPass, sudoPass string) error {
 | 
			
		||||
	var loader Loader
 | 
			
		||||
	loader = TOMLLoader{}
 | 
			
		||||
 | 
			
		||||
	return loader.Load(path, keyPass, sudoPass)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Loader is interface of concrete loader
 | 
			
		||||
type Loader interface {
 | 
			
		||||
	Load(string, string, string) error
 | 
			
		||||
}
 | 
			
		||||
@@ -1,113 +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 config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/BurntSushi/toml"
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TOMLLoader loads config
 | 
			
		||||
type TOMLLoader struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load load the configuraiton TOML file specified by path arg.
 | 
			
		||||
func (c TOMLLoader) Load(pathToToml, keyPass, sudoPass string) (err error) {
 | 
			
		||||
	var conf Config
 | 
			
		||||
	if _, err := toml.DecodeFile(pathToToml, &conf); err != nil {
 | 
			
		||||
		log.Error("Load config failed", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Conf.Mail = conf.Mail
 | 
			
		||||
	Conf.Slack = conf.Slack
 | 
			
		||||
 | 
			
		||||
	d := conf.Default
 | 
			
		||||
	Conf.Default = d
 | 
			
		||||
	servers := make(map[string]ServerInfo)
 | 
			
		||||
 | 
			
		||||
	if keyPass != "" {
 | 
			
		||||
		d.KeyPassword = keyPass
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if sudoPass != "" {
 | 
			
		||||
		d.Password = sudoPass
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	i := 0
 | 
			
		||||
	for name, v := range conf.Servers {
 | 
			
		||||
 | 
			
		||||
		if 0 < len(v.KeyPassword) || 0 < len(v.Password) {
 | 
			
		||||
			log.Warn("[Depricated] password and keypassword in config file are unsecure. Remove them immediately for a security reason. They will be removed in a future release.")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s := ServerInfo{ServerName: name}
 | 
			
		||||
		s.User = v.User
 | 
			
		||||
		if s.User == "" {
 | 
			
		||||
			s.User = d.User
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//  s.Password = sudoPass
 | 
			
		||||
		s.Password = v.Password
 | 
			
		||||
		if s.Password == "" {
 | 
			
		||||
			s.Password = d.Password
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Host = v.Host
 | 
			
		||||
 | 
			
		||||
		s.Port = v.Port
 | 
			
		||||
		if s.Port == "" {
 | 
			
		||||
			s.Port = d.Port
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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(
 | 
			
		||||
					"config.toml is invalid. keypath: %s not exists", s.KeyPath)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//  s.KeyPassword = keyPass
 | 
			
		||||
		s.KeyPassword = v.KeyPassword
 | 
			
		||||
		if s.KeyPassword == "" {
 | 
			
		||||
			s.KeyPassword = d.KeyPassword
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.CpeNames = v.CpeNames
 | 
			
		||||
		if len(s.CpeNames) == 0 {
 | 
			
		||||
			s.CpeNames = d.CpeNames
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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
 | 
			
		||||
}
 | 
			
		||||
@@ -1,240 +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 cveapi
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CveClient is api client of CVE disctionary service.
 | 
			
		||||
var CveClient cvedictClient
 | 
			
		||||
 | 
			
		||||
type cvedictClient struct {
 | 
			
		||||
	//  httpProxy string
 | 
			
		||||
	baseURL string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api *cvedictClient) initialize() {
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) CheckHealth() (ok bool, err error) {
 | 
			
		||||
	api.initialize()
 | 
			
		||||
	url := fmt.Sprintf("%s/health", api.baseURL)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if len(errs) > 0 || resp.StatusCode != 200 {
 | 
			
		||||
		return false, fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v",
 | 
			
		||||
			url,
 | 
			
		||||
			errs,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type response struct {
 | 
			
		||||
	Key       string
 | 
			
		||||
	CveDetail cve.CveDetail
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDetails, err error) {
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
	reqChan := make(chan string, len(cveIDs))
 | 
			
		||||
	resChan := make(chan response, len(cveIDs))
 | 
			
		||||
	errChan := make(chan error, len(cveIDs))
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, cveID := range cveIDs {
 | 
			
		||||
			reqChan <- cveID
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for range cveIDs {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case cveID := <-reqChan:
 | 
			
		||||
				url, err := util.URLPathJoin(api.baseURL, "cves", cveID)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					api.httpGet(cveID, url, resChan, errChan)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(2 * 60 * time.Second)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	for range cveIDs {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-resChan:
 | 
			
		||||
			if len(res.CveDetail.CveID) == 0 {
 | 
			
		||||
				cveDetails = append(cveDetails, cve.CveDetail{
 | 
			
		||||
					CveID: res.Key,
 | 
			
		||||
				})
 | 
			
		||||
			} else {
 | 
			
		||||
				cveDetails = append(cveDetails, res.CveDetail)
 | 
			
		||||
			}
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return []cve.CveDetail{}, fmt.Errorf("Timeout Fetching CVE")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to fetch CVE. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// order by CVE ID desc
 | 
			
		||||
	sort.Sort(cveDetails)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
	var body string
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		//  resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Get(url).End()
 | 
			
		||||
		if len(errs) > 0 || resp.StatusCode != 200 {
 | 
			
		||||
			return fmt.Errorf("HTTP GET error: %v, code: %d, url: %s", errs, resp.StatusCode, url)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		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)
 | 
			
		||||
	}
 | 
			
		||||
	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,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  func (api cvedictClient) httpGet(key, url string, query map[string]string, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
 | 
			
		||||
//      var body string
 | 
			
		||||
//      var errs []error
 | 
			
		||||
//      var resp *http.Response
 | 
			
		||||
//      f := func() (err error) {
 | 
			
		||||
//          req := gorequest.New().SetDebug(true).Proxy(api.httpProxy).Get(url)
 | 
			
		||||
//          for key := range query {
 | 
			
		||||
//              req = req.Query(fmt.Sprintf("%s=%s", key, query[key])).Set("Content-Type", "application/x-www-form-urlencoded")
 | 
			
		||||
//          }
 | 
			
		||||
//          pp.Println(req)
 | 
			
		||||
//          resp, body, errs = req.End()
 | 
			
		||||
//          if len(errs) > 0 || resp.StatusCode != 200 {
 | 
			
		||||
//              errChan <- fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
 | 
			
		||||
//          }
 | 
			
		||||
//          return nil
 | 
			
		||||
//      }
 | 
			
		||||
//      notify := func(err error, t time.Duration) {
 | 
			
		||||
//          log.Warnf("Failed to get. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
//      }
 | 
			
		||||
//      err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
//      if err != nil {
 | 
			
		||||
//          errChan <- fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
//      }
 | 
			
		||||
//      //  resChan <- body
 | 
			
		||||
//      cveDetail := cve.CveDetail{}
 | 
			
		||||
//      if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
 | 
			
		||||
//          errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
 | 
			
		||||
//      }
 | 
			
		||||
//      resChan <- response{
 | 
			
		||||
//          key,
 | 
			
		||||
//          cveDetail,
 | 
			
		||||
//      }
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
type responseGetCveDetailByCpeName struct {
 | 
			
		||||
	CpeName    string
 | 
			
		||||
	CveDetails []cve.CveDetail
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeName(cpeName string) ([]cve.CveDetail, error) {
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
		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.StatusCode != 200 {
 | 
			
		||||
			return fmt.Errorf("HTTP POST errors: %v, code: %d, url: %s", errs, resp.StatusCode, url)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		log.Warnf("Failed to 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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveDetails := []cve.CveDetail{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &cveDetails); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
 | 
			
		||||
	}
 | 
			
		||||
	return cveDetails, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										272
									
								
								db/db.go
									
									
									
									
									
								
							
							
						
						@@ -1,272 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	m "github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/jinzhu/gorm"
 | 
			
		||||
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var db *gorm.DB
 | 
			
		||||
 | 
			
		||||
// OpenDB opens Database
 | 
			
		||||
func OpenDB() (err error) {
 | 
			
		||||
	db, err = gorm.Open("sqlite3", config.Conf.DBPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
 | 
			
		||||
		return
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	db.LogMode(config.Conf.DebugSQL)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MigrateDB migrates Database
 | 
			
		||||
func MigrateDB() error {
 | 
			
		||||
	if err := db.AutoMigrate(
 | 
			
		||||
		&m.ScanHistory{},
 | 
			
		||||
		&m.ScanResult{},
 | 
			
		||||
		//  &m.NWLink{},
 | 
			
		||||
		&m.CveInfo{},
 | 
			
		||||
		&m.CpeName{},
 | 
			
		||||
		&m.PackageInfo{},
 | 
			
		||||
		&m.DistroAdvisory{},
 | 
			
		||||
		&cve.CveDetail{},
 | 
			
		||||
		&cve.Jvn{},
 | 
			
		||||
		&cve.Nvd{},
 | 
			
		||||
		&cve.Reference{},
 | 
			
		||||
		&cve.Cpe{},
 | 
			
		||||
	).Error; err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to migrate. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errMsg := "Failed to create index. err: %s"
 | 
			
		||||
	//  if err := db.Model(&m.NWLink{}).
 | 
			
		||||
	//      AddIndex("idx_n_w_links_scan_result_id", "scan_result_id").Error; err != nil {
 | 
			
		||||
	//      return fmt.Errorf(errMsg, err)
 | 
			
		||||
	//  }
 | 
			
		||||
	if err := db.Model(&m.CveInfo{}).
 | 
			
		||||
		AddIndex("idx_cve_infos_scan_result_id", "scan_result_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.CpeName{}).
 | 
			
		||||
		AddIndex("idx_cpe_names_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.PackageInfo{}).
 | 
			
		||||
		AddIndex("idx_package_infos_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.DistroAdvisory{}).
 | 
			
		||||
		//TODO check table name
 | 
			
		||||
		AddIndex("idx_distro_advisories_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.CveDetail{}).
 | 
			
		||||
		AddIndex("idx_cve_detail_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.CveDetail{}).
 | 
			
		||||
		AddIndex("idx_cve_detail_cveid", "cve_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Nvd{}).
 | 
			
		||||
		AddIndex("idx_nvds_cve_detail_id", "cve_detail_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Jvn{}).
 | 
			
		||||
		AddIndex("idx_jvns_cve_detail_id", "cve_detail_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Cpe{}).
 | 
			
		||||
		AddIndex("idx_cpes_jvn_id", "jvn_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Reference{}).
 | 
			
		||||
		AddIndex("idx_references_jvn_id", "jvn_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Cpe{}).
 | 
			
		||||
		AddIndex("idx_cpes_nvd_id", "nvd_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Reference{}).
 | 
			
		||||
		AddIndex("idx_references_nvd_id", "nvd_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Insert inserts scan results into DB
 | 
			
		||||
func Insert(results []m.ScanResult) error {
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		r.KnownCves = resetGormIDs(r.KnownCves)
 | 
			
		||||
		r.UnknownCves = resetGormIDs(r.UnknownCves)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	history := m.ScanHistory{
 | 
			
		||||
		ScanResults: results,
 | 
			
		||||
		ScannedAt:   time.Now(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db = db.Set("gorm:save_associations", false)
 | 
			
		||||
	if err := db.Create(&history).Error; err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, scanResult := range history.ScanResults {
 | 
			
		||||
		scanResult.ScanHistoryID = history.ID
 | 
			
		||||
		if err := db.Create(&scanResult).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := insertCveInfos(scanResult.ID, scanResult.KnownCves); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := insertCveInfos(scanResult.ID, scanResult.UnknownCves); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func insertCveInfos(scanResultID uint, infos []m.CveInfo) error {
 | 
			
		||||
	for _, cveInfo := range infos {
 | 
			
		||||
		cveInfo.ScanResultID = scanResultID
 | 
			
		||||
		if err := db.Create(&cveInfo).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, pack := range cveInfo.Packages {
 | 
			
		||||
			pack.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&pack).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, distroAdvisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			distroAdvisory.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&distroAdvisory).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, cpeName := range cveInfo.CpeNames {
 | 
			
		||||
			cpeName.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&cpeName).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		db = db.Set("gorm:save_associations", true)
 | 
			
		||||
		cveDetail := cveInfo.CveDetail
 | 
			
		||||
		cveDetail.CveInfoID = cveInfo.ID
 | 
			
		||||
		if err := db.Create(&cveDetail).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		db = db.Set("gorm:save_associations", false)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func resetGormIDs(infos []m.CveInfo) []m.CveInfo {
 | 
			
		||||
	for i := range infos {
 | 
			
		||||
		infos[i].CveDetail.ID = 0
 | 
			
		||||
		// NVD
 | 
			
		||||
		infos[i].CveDetail.Nvd.ID = 0
 | 
			
		||||
		for j := range infos[i].CveDetail.Nvd.Cpes {
 | 
			
		||||
			infos[i].CveDetail.Nvd.Cpes[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
		for j := range infos[i].CveDetail.Nvd.References {
 | 
			
		||||
			infos[i].CveDetail.Nvd.References[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// JVN
 | 
			
		||||
		infos[i].CveDetail.Jvn.ID = 0
 | 
			
		||||
		for j := range infos[i].CveDetail.Jvn.Cpes {
 | 
			
		||||
			infos[i].CveDetail.Jvn.Cpes[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
		for j := range infos[i].CveDetail.Jvn.References {
 | 
			
		||||
			infos[i].CveDetail.Jvn.References[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//Packages
 | 
			
		||||
		for j := range infos[i].Packages {
 | 
			
		||||
			infos[i].Packages[j].ID = 0
 | 
			
		||||
			infos[i].Packages[j].CveInfoID = 0
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return infos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SelectLatestScanHistory select latest scan history from DB
 | 
			
		||||
func SelectLatestScanHistory() (m.ScanHistory, error) {
 | 
			
		||||
	scanHistory := m.ScanHistory{}
 | 
			
		||||
	db.Order("scanned_at desc").First(&scanHistory)
 | 
			
		||||
 | 
			
		||||
	if scanHistory.ID == 0 {
 | 
			
		||||
		return m.ScanHistory{}, fmt.Errorf("No scanHistory records")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	results := []m.ScanResult{}
 | 
			
		||||
	db.Model(&scanHistory).Related(&results, "ScanResults")
 | 
			
		||||
	scanHistory.ScanResults = results
 | 
			
		||||
 | 
			
		||||
	for i, r := range results {
 | 
			
		||||
		//  nw := []m.NWLink{}
 | 
			
		||||
		//  db.Model(&r).Related(&nw, "NWLinks")
 | 
			
		||||
		//  scanHistory.ScanResults[i].NWLinks = nw
 | 
			
		||||
 | 
			
		||||
		knownCves := selectCveInfos(&r, "KnownCves")
 | 
			
		||||
		sort.Sort(m.CveInfos(knownCves))
 | 
			
		||||
		scanHistory.ScanResults[i].KnownCves = knownCves
 | 
			
		||||
	}
 | 
			
		||||
	return scanHistory, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func selectCveInfos(result *m.ScanResult, fieldName string) []m.CveInfo {
 | 
			
		||||
	cveInfos := []m.CveInfo{}
 | 
			
		||||
	db.Model(&result).Related(&cveInfos, fieldName)
 | 
			
		||||
 | 
			
		||||
	for i, cveInfo := range cveInfos {
 | 
			
		||||
		cveDetail := cve.CveDetail{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&cveDetail, "CveDetail")
 | 
			
		||||
		id := cveDetail.CveID
 | 
			
		||||
		filledCveDetail := cvedb.Get(id, db)
 | 
			
		||||
		cveInfos[i].CveDetail = filledCveDetail
 | 
			
		||||
 | 
			
		||||
		packs := []m.PackageInfo{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&packs, "Packages")
 | 
			
		||||
		cveInfos[i].Packages = packs
 | 
			
		||||
 | 
			
		||||
		advisories := []m.DistroAdvisory{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&advisories, "DistroAdvisories")
 | 
			
		||||
		cveInfos[i].DistroAdvisories = advisories
 | 
			
		||||
 | 
			
		||||
		names := []m.CpeName{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&names, "CpeNames")
 | 
			
		||||
		cveInfos[i].CpeNames = names
 | 
			
		||||
	}
 | 
			
		||||
	return cveInfos
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +0,0 @@
 | 
			
		||||
FROM golang:1.6
 | 
			
		||||
RUN apt-get update \
 | 
			
		||||
    && apt-get upgrade -y \
 | 
			
		||||
    && apt-get install -y git openssh-client gcc
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
RUN go get github.com/kotakanbe/go-cve-dictionary
 | 
			
		||||
RUN go get github.com/future-architect/vuls
 | 
			
		||||
COPY fetch.sh .
 | 
			
		||||
RUN /bin/bash /app/fetch.sh
 | 
			
		||||
COPY config.toml .
 | 
			
		||||
COPY run.sh .
 | 
			
		||||
ENTRYPOINT ["/bin/bash", "/app/run.sh"]
 | 
			
		||||
COPY id_rsa .
 | 
			
		||||
COPY id_rsa.pub .
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
# Must do
 | 
			
		||||
* Edit your config.toml to match your infrastructure
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 for i in {2002..2016}; do go-cve-dictionary fetchnvd -years $i ; done
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
 | 
			
		||||
@@ -1,4 +0,0 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
go-cve-dictionary server &
 | 
			
		||||
sleep 2
 | 
			
		||||
vuls scan -config /app/config.toml -report-slack
 | 
			
		||||
							
								
								
									
										75
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,75 @@
 | 
			
		||||
module github.com/future-architect/vuls
 | 
			
		||||
 | 
			
		||||
go 1.19
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/MakeNowJust/heredoc v1.0.0
 | 
			
		||||
	github.com/go-redis/redis/v9 v9.0.0-rc.1
 | 
			
		||||
	github.com/google/uuid v1.3.0
 | 
			
		||||
	github.com/hashicorp/go-version v1.6.0
 | 
			
		||||
	github.com/knqyf263/go-cpe v0.0.0-20201213041631-54f6ab28673f
 | 
			
		||||
	github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
 | 
			
		||||
	github.com/labstack/echo/v4 v4.9.1
 | 
			
		||||
	github.com/olekukonko/tablewriter v0.0.5
 | 
			
		||||
	github.com/opencontainers/image-spec v1.1.0-rc2
 | 
			
		||||
	github.com/pkg/errors v0.9.1
 | 
			
		||||
	github.com/spf13/cobra v1.6.1
 | 
			
		||||
	github.com/ulikunitz/xz v0.5.10
 | 
			
		||||
	go.etcd.io/bbolt v1.3.6
 | 
			
		||||
	go.uber.org/zap v1.13.0
 | 
			
		||||
	golang.org/x/exp v0.0.0-20221106115401-f9659909a136
 | 
			
		||||
	gorm.io/driver/mysql v1.4.3
 | 
			
		||||
	gorm.io/driver/postgres v1.4.5
 | 
			
		||||
	gorm.io/gorm v1.24.1
 | 
			
		||||
	modernc.org/sqlite v1.19.4
 | 
			
		||||
	oras.land/oras-go/v2 v2.0.0-rc.4
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 | 
			
		||||
	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 | 
			
		||||
	github.com/fsnotify/fsnotify v1.5.4 // indirect
 | 
			
		||||
	github.com/go-sql-driver/mysql v1.6.0 // indirect
 | 
			
		||||
	github.com/gofrs/uuid v4.2.0+incompatible // indirect
 | 
			
		||||
	github.com/inconshreveable/mousetrap v1.0.1 // indirect
 | 
			
		||||
	github.com/jackc/chunkreader/v2 v2.0.1 // indirect
 | 
			
		||||
	github.com/jackc/pgconn v1.13.0 // indirect
 | 
			
		||||
	github.com/jackc/pgio v1.0.0 // indirect
 | 
			
		||||
	github.com/jackc/pgpassfile v1.0.0 // indirect
 | 
			
		||||
	github.com/jackc/pgproto3/v2 v2.3.1 // indirect
 | 
			
		||||
	github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
 | 
			
		||||
	github.com/jackc/pgtype v1.12.0 // indirect
 | 
			
		||||
	github.com/jackc/pgx/v4 v4.17.2 // indirect
 | 
			
		||||
	github.com/jinzhu/inflection v1.0.0 // indirect
 | 
			
		||||
	github.com/jinzhu/now v1.1.5 // indirect
 | 
			
		||||
	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
 | 
			
		||||
	github.com/labstack/gommon v0.4.0 // indirect
 | 
			
		||||
	github.com/lib/pq v1.10.6 // indirect
 | 
			
		||||
	github.com/mattn/go-colorable v0.1.11 // indirect
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.16 // indirect
 | 
			
		||||
	github.com/mattn/go-runewidth v0.0.9 // indirect
 | 
			
		||||
	github.com/opencontainers/go-digest v1.0.0 // indirect
 | 
			
		||||
	github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
 | 
			
		||||
	github.com/spf13/pflag v1.0.5 // indirect
 | 
			
		||||
	github.com/valyala/bytebufferpool v1.0.0 // indirect
 | 
			
		||||
	github.com/valyala/fasttemplate v1.2.1 // indirect
 | 
			
		||||
	go.uber.org/atomic v1.6.0 // indirect
 | 
			
		||||
	go.uber.org/multierr v1.5.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.1.0 // indirect
 | 
			
		||||
	golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect
 | 
			
		||||
	golang.org/x/mod v0.6.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.1.0 // indirect
 | 
			
		||||
	golang.org/x/sync v0.1.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.1.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.4.0 // indirect
 | 
			
		||||
	golang.org/x/tools v0.2.0 // indirect
 | 
			
		||||
	lukechampine.com/uint128 v1.2.0 // indirect
 | 
			
		||||
	modernc.org/cc/v3 v3.40.0 // indirect
 | 
			
		||||
	modernc.org/ccgo/v3 v3.16.13 // indirect
 | 
			
		||||
	modernc.org/libc v1.21.4 // indirect
 | 
			
		||||
	modernc.org/mathutil v1.5.0 // indirect
 | 
			
		||||
	modernc.org/memory v1.4.0 // indirect
 | 
			
		||||
	modernc.org/opt v0.1.3 // indirect
 | 
			
		||||
	modernc.org/strutil v1.1.3 // indirect
 | 
			
		||||
	modernc.org/token v1.0.1 // indirect
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										318
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,318 @@
 | 
			
		||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 | 
			
		||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
			
		||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
 | 
			
		||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
 | 
			
		||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
 | 
			
		||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 | 
			
		||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
 | 
			
		||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
			
		||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
 | 
			
		||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 | 
			
		||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 | 
			
		||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 | 
			
		||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 | 
			
		||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 | 
			
		||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
 | 
			
		||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 | 
			
		||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 | 
			
		||||
github.com/go-redis/redis/v9 v9.0.0-rc.1 h1:/+bS+yeUnanqAbuD3QwlejzQZ+4eqgfUtFTG4b+QnXs=
 | 
			
		||||
github.com/go-redis/redis/v9 v9.0.0-rc.1/go.mod h1:8et+z03j0l8N+DvsVnclzjf3Dl/pFHgRk+2Ct1qw66A=
 | 
			
		||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 | 
			
		||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 | 
			
		||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 | 
			
		||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 | 
			
		||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
 | 
			
		||||
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 | 
			
		||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 | 
			
		||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 | 
			
		||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 | 
			
		||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
 | 
			
		||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 | 
			
		||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
 | 
			
		||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 | 
			
		||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
 | 
			
		||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
 | 
			
		||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
 | 
			
		||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
 | 
			
		||||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
 | 
			
		||||
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
 | 
			
		||||
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
 | 
			
		||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
 | 
			
		||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
 | 
			
		||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
 | 
			
		||||
github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
 | 
			
		||||
github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
 | 
			
		||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
 | 
			
		||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
 | 
			
		||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
 | 
			
		||||
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
 | 
			
		||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
 | 
			
		||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
 | 
			
		||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 | 
			
		||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
 | 
			
		||||
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
 | 
			
		||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
 | 
			
		||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
 | 
			
		||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
 | 
			
		||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
 | 
			
		||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 | 
			
		||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 | 
			
		||||
github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
 | 
			
		||||
github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 | 
			
		||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
 | 
			
		||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
 | 
			
		||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
 | 
			
		||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
 | 
			
		||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
 | 
			
		||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
 | 
			
		||||
github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
 | 
			
		||||
github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 | 
			
		||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
 | 
			
		||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
 | 
			
		||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
 | 
			
		||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
 | 
			
		||||
github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
 | 
			
		||||
github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
 | 
			
		||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 | 
			
		||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 | 
			
		||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 | 
			
		||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 | 
			
		||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 | 
			
		||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 | 
			
		||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 | 
			
		||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
 | 
			
		||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 | 
			
		||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 | 
			
		||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 | 
			
		||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 | 
			
		||||
github.com/knqyf263/go-cpe v0.0.0-20201213041631-54f6ab28673f h1:vZP1dTKPOR7zSAbgqNbnTnYX77+gj3eu0QK+UmANZqE=
 | 
			
		||||
github.com/knqyf263/go-cpe v0.0.0-20201213041631-54f6ab28673f/go.mod h1:4cVhzV/TndScEg4xMtSo3TTz3cMFhEAvhAA4igAyXZY=
 | 
			
		||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d h1:X4cedH4Kn3JPupAwwWuo4AzYp16P0OyLO9d7OnMZc/c=
 | 
			
		||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
			
		||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y=
 | 
			
		||||
github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo=
 | 
			
		||||
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
 | 
			
		||||
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
 | 
			
		||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
			
		||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
			
		||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
			
		||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
			
		||||
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
 | 
			
		||||
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 | 
			
		||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
 | 
			
		||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 | 
			
		||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 | 
			
		||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 | 
			
		||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 | 
			
		||||
github.com/onsi/gomega v1.21.1 h1:OB/euWYIExnPBohllTicTHmGTrMaqJ67nIu80j0/uEM=
 | 
			
		||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 | 
			
		||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 | 
			
		||||
github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034=
 | 
			
		||||
github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
 | 
			
		||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 | 
			
		||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
 | 
			
		||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 | 
			
		||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
 | 
			
		||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
 | 
			
		||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
			
		||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 | 
			
		||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
 | 
			
		||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
 | 
			
		||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 | 
			
		||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 | 
			
		||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 | 
			
		||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
 | 
			
		||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
 | 
			
		||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 | 
			
		||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 | 
			
		||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 | 
			
		||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 | 
			
		||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
			
		||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
			
		||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 | 
			
		||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 | 
			
		||||
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
 | 
			
		||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 | 
			
		||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 | 
			
		||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 | 
			
		||||
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
 | 
			
		||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 | 
			
		||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 | 
			
		||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
 | 
			
		||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
 | 
			
		||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 | 
			
		||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 | 
			
		||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 | 
			
		||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
 | 
			
		||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 | 
			
		||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 | 
			
		||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 | 
			
		||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
 | 
			
		||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
 | 
			
		||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
 | 
			
		||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 | 
			
		||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 | 
			
		||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 | 
			
		||||
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
 | 
			
		||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 | 
			
		||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
 | 
			
		||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
 | 
			
		||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw=
 | 
			
		||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
 | 
			
		||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
 | 
			
		||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 | 
			
		||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 | 
			
		||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 | 
			
		||||
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
 | 
			
		||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
 | 
			
		||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
 | 
			
		||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 | 
			
		||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
 | 
			
		||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
			
		||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
 | 
			
		||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
 | 
			
		||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 | 
			
		||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k=
 | 
			
		||||
gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
 | 
			
		||||
gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=
 | 
			
		||||
gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg=
 | 
			
		||||
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
 | 
			
		||||
gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
 | 
			
		||||
gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs=
 | 
			
		||||
gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
 | 
			
		||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
 | 
			
		||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 | 
			
		||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
 | 
			
		||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
 | 
			
		||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
 | 
			
		||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
 | 
			
		||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
 | 
			
		||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
 | 
			
		||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
 | 
			
		||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
 | 
			
		||||
modernc.org/libc v1.21.4 h1:CzTlumWeIbPV5/HVIMzYHNPCRP8uiU/CWiN2gtd/Qu8=
 | 
			
		||||
modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
 | 
			
		||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
 | 
			
		||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
 | 
			
		||||
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
 | 
			
		||||
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
 | 
			
		||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
 | 
			
		||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
 | 
			
		||||
modernc.org/sqlite v1.19.4 h1:nlPIDqumn6/mSvs7T5C8MNYEuN73sISzPdKtMdURpUI=
 | 
			
		||||
modernc.org/sqlite v1.19.4/go.mod h1:x/yZNb3h5+I3zGQSlwIv4REL5eJhiRkUH5MReogAeIc=
 | 
			
		||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
 | 
			
		||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
 | 
			
		||||
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
 | 
			
		||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
 | 
			
		||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
 | 
			
		||||
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
 | 
			
		||||
oras.land/oras-go/v2 v2.0.0-rc.4 h1:hg/R2znUQ1+qd43gRmL16VeX1GIZ8hQlLalBjYhhKSk=
 | 
			
		||||
oras.land/oras-go/v2 v2.0.0-rc.4/go.mod h1:YGHvWBGuqRlZgUyXUIoKsR3lcuCOb3DAtG0SEsEw1iY=
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 247 KiB  | 
@@ -1,979 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
 | 
			
		||||
  <!--Created by yEd 3.14.2-->
 | 
			
		||||
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
 | 
			
		||||
  <key for="port" id="d1" yfiles.type="portgraphics"/>
 | 
			
		||||
  <key for="port" id="d2" yfiles.type="portgeometry"/>
 | 
			
		||||
  <key for="port" id="d3" yfiles.type="portuserdata"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="node" id="d4"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="node" id="d5"/>
 | 
			
		||||
  <key for="node" id="d6" yfiles.type="nodegraphics"/>
 | 
			
		||||
  <key for="graphml" id="d7" yfiles.type="resources"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
 | 
			
		||||
  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
 | 
			
		||||
  <graph edgedefault="directed" id="G">
 | 
			
		||||
    <data key="d0"/>
 | 
			
		||||
    <node id="n0">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.cloud">
 | 
			
		||||
          <y:Geometry height="50.0" width="80.0" x="269.4041252136233" y="446.4841308593749"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="38.0" y="23.0">
 | 
			
		||||
            <y: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="n1" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="285.54366048177087" width="173.0" x="66.40412521362327" y="347.9090576171874"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="173.0" x="0.0" y="0.0">Vulnerbility Database</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="54" bottomF="53.63557942708337" left="30" leftF="29.90412521362373" right="28" rightF="28.09587478637627" top="27" topF="27.242065429687557"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 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="n1:">
 | 
			
		||||
        <node id="n1::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="70.0" width="85.0" x="111.308250427247" y="494.8171386718749"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="63.279296875" x="10.8603515625" y="18.8671875">JVN
 | 
			
		||||
(Japanese)<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n1::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="70.0" width="85.0" x="111.308250427247" y="411.81713867187494"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="29.69921875" x="27.650390625" y="25.93359375">NVD<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n2" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="285.54366048177087" width="137.0" x="1209.345874786376" y="347.9090576171874"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="137.0" x="0.0" y="0.0">Linux Support</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="4" bottomF="4.059529622395871" left="5" leftF="4.85411262512207" right="8" rightF="8.14588737487793" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 2</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="n2:">
 | 
			
		||||
        <node id="n2::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="71.484130859375" width="94.0" x="1229.1999874114981" y="463.7420654296874"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="64.158203125" x="14.9208984375" y="19.6092529296875">apptitude
 | 
			
		||||
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:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n2::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="71.484130859375" width="94.0" x="1229.1999874114981" y="384.5750732421874"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="64.158203125" x="14.9208984375" y="19.6092529296875">yum
 | 
			
		||||
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:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n2::n2">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="71.484130859375" width="94.0" x="1229.1999874114981" y="542.9090576171874"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="92.828125" x="0.5859375" y="19.6092529296875">RHSA (RedHat)
 | 
			
		||||
ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n3">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.cloud">
 | 
			
		||||
          <y:Geometry height="50.0" width="80.0" x="1109.272931098937" y="399.1136678059895"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="38.0" y="23.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n4">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:SVGNode>
 | 
			
		||||
          <y:Geometry height="121.666015625" width="137.0" x="942.2729310989371" y="363.2806599934895"/>
 | 
			
		||||
          <y:Fill color="#CCCCFF" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="45.63671875" x="45.681640625" y="-30.475463867187386">servers<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.5" labelRatioY="0.5" nodeRatioX="0.16655736770072993" nodeRatioY="-0.5" offsetX="0.0" offsetY="-12.342651367187386" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="66.5" y="58.8330078125">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:SVGNodeProperties usingVisualBounds="false"/>
 | 
			
		||||
          <y:SVGModel svgBoundsPolicy="0">
 | 
			
		||||
            <y:SVGContent refid="1"/>
 | 
			
		||||
          </y:SVGModel>
 | 
			
		||||
        </y:SVGNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n5" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="285.54366048177087" width="220.24999999999977" x="662.2499999999998" y="347.9090576171874"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="220.24999999999977" x="0.0" y="0.0">Vuls</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="12" bottomF="11.710652669270871" left="10" leftF="9.999999999999773" right="0" rightF="0.0" top="31" topF="30.57621256510413"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 3</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="n5:">
 | 
			
		||||
        <node id="n5::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="80.0" x="787.4999999999995" y="495.1512858072915"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="42.595703125" x="18.7021484375" y="15.93359375">Report<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n5::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="80.0" x="787.4999999999995" y="556.7420654296874"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="54.40234375" x="12.798828125" y="15.93359375">TUI View<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n5::n2">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="180.25" x="687.2499999999995" y="415.1512858072915"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="30.68359375" x="74.783203125" y="15.93359375">Scan<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n6">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:SVGNode>
 | 
			
		||||
          <y:Geometry height="64.96826171875" width="56.554100036621094" x="1083.5729436874383" y="568.4844563802083"/>
 | 
			
		||||
          <y:Fill color="#CCCCFF" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="100.890625" x="-22.168262481689453" y="68.96826171875">System Operator<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:SVGNodeProperties usingVisualBounds="true"/>
 | 
			
		||||
          <y:SVGModel svgBoundsPolicy="0">
 | 
			
		||||
            <y:SVGContent refid="2"/>
 | 
			
		||||
          </y:SVGModel>
 | 
			
		||||
        </y:SVGNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n7">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
          <y:Geometry height="70.0" width="60.5" x="696.9999999999995" y="546.7420654296874"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="28.25" y="33.0">
 | 
			
		||||
            <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: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.4140625" x="6.04296875" y="25.93359375">SQLite3<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:SVGNode>
 | 
			
		||||
          <y:Geometry height="37.0" width="109.57881927490234" x="991.1335277557366" y="532.4466756184895"/>
 | 
			
		||||
          <y:Fill color="#CCCCFF" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="sandwich" modelPosition="s" textColor="#000000" visible="true" width="4.0" x="52.78940963745117" y="41.0"/>
 | 
			
		||||
          <y:SVGNodeProperties usingVisualBounds="true"/>
 | 
			
		||||
          <y:SVGModel svgBoundsPolicy="0">
 | 
			
		||||
            <y:SVGContent refid="3"/>
 | 
			
		||||
          </y:SVGModel>
 | 
			
		||||
        </y:SVGNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n9">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.bpmn.Artifact.withShadow">
 | 
			
		||||
          <y:Geometry height="24.0" width="35.0" x="943.5205974578851" y="538.9466756184895"/>
 | 
			
		||||
          <y:Fill color="#FFFFFFE6" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="15.5" y="28.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:StyleProperties>
 | 
			
		||||
            <y:Property class="java.awt.Color" name="com.yworks.bpmn.icon.line.color" value="#000000"/>
 | 
			
		||||
            <y:Property class="java.awt.Color" name="com.yworks.bpmn.icon.fill2" value="#d4d4d4cc"/>
 | 
			
		||||
            <y:Property class="java.awt.Color" name="com.yworks.bpmn.icon.fill" value="#ffffffe6"/>
 | 
			
		||||
            <y:Property class="com.yworks.yfiles.bpmn.view.BPMNTypeEnum" name="com.yworks.bpmn.type" value="ARTIFACT_TYPE_REQUEST_MESSAGE"/>
 | 
			
		||||
          </y:StyleProperties>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n10" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="285.54366048177087" width="233.0" x="379.4041252136233" y="347.9090576171874"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="233.0" x="0.0" y="0.0">go-cve-dictionary</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="2" leftF="1.5" right="5" rightF="5.0" top="62" topF="61.9090576171875"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 4</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="n10:">
 | 
			
		||||
        <node id="n10::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="70.0" width="60.5" x="447.15412521362305" y="548.4527180989583"/>
 | 
			
		||||
              <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="28.25" y="33.0">
 | 
			
		||||
                <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: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.4140625" x="6.04296875" y="25.93359375">SQLite3<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::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="101.0" x="491.4041252136233" y="446.4841308593749"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="73.943359375" x="13.5283203125" y="15.93359375">HTTP server<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="rectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n10::n2">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="80.0" x="395.9041252136233" y="446.4841308593749"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="46.796875" x="16.6015625" y="15.93359375">Fetcher<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="rectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="n10::e0" source="n10::n1" target="n10::n0">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e0" source="n10::n2" target="n0">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="106.240234375" x="-141.12006313536395" y="46.068162980709076">Fetch 
 | 
			
		||||
Vulnerability data<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="62.2009754807092" distanceToCenter="true" position="left" ratio="41.499957391955974" segment="-1"/>
 | 
			
		||||
            </y: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="e1" source="n3" target="n2">
 | 
			
		||||
      <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="e2" source="n5::n2" target="n10::n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="34.626953125" x="-64.74898719787609" y="-33.01884181664411">HTTP<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e3" source="n5::n1" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e4" source="n0" target="n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e5" source="n5::n0" target="n6">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e6" source="n4" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="n5::e0" source="n5::n2" target="n5::n0">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e7" source="n5::n2" target="n4">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.744140625" x="44.03690814971833" y="17.093924778052497">SSH<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="1.0" segment="-1"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e8" source="n6" target="n5::n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="n10::e1" source="n10::n2" target="n10::n0">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e9" source="n5::n2" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
  </graph>
 | 
			
		||||
  <data key="d7">
 | 
			
		||||
    <y:Resources>
 | 
			
		||||
      <y:Resource id="1"><?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<svg version="1.1"
 | 
			
		||||
	 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
 | 
			
		||||
	 x="0px" y="0px" width="68px" height="60px" viewBox="-0.435 -0.869 68 60" enable-background="new -0.435 -0.869 68 60"
 | 
			
		||||
	 xml:space="preserve">
 | 
			
		||||
<defs>
 | 
			
		||||
</defs>
 | 
			
		||||
<path fill="#666666" d="M52.462,30.881c-0.021,0-0.037,0.01-0.059,0.012c-0.021-0.002-0.037-0.012-0.059-0.012h-18.5v-7.555
 | 
			
		||||
	c0-0.414-0.335-0.75-0.75-0.75c-0.414,0-0.75,0.336-0.75,0.75v7.555h-18.5c-0.02,0-0.037,0.01-0.057,0.012
 | 
			
		||||
	c-0.02-0.002-0.037-0.012-0.057-0.012c-0.414,0-0.75,0.336-0.75,0.75v3.834c0,0.414,0.336,0.75,0.75,0.75s0.75-0.336,0.75-0.75
 | 
			
		||||
	v-3.084H51.71v3.084c0,0.414,0.336,0.75,0.75,0.75s0.75-0.336,0.75-0.75v-3.834C53.212,31.217,52.876,30.881,52.462,30.881z"/>
 | 
			
		||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="130.7236" y1="-184.1631" x2="130.7236" y2="-191.9565" gradientTransform="matrix(1 0 0 -1 -97.6001 -158.6377)">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#9CD7FF"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#3C89C9"/>
 | 
			
		||||
</linearGradient>
 | 
			
		||||
<path fill="url(#SVGID_1_)" d="M36.296,29.976c-0.832,0-1.513-0.681-1.513-1.513v-1.424c0-0.832-0.681-1.513-1.513-1.513h-0.214
 | 
			
		||||
	c-0.832,0-1.513,0.681-1.513,1.513v1.424c0,0.832-0.681,1.513-1.513,1.513h-2.499c-0.832,0-1.513,0.681-1.513,1.513v0.317
 | 
			
		||||
	c0,0.832,0.681,1.513,1.513,1.513h11.187c0.832,0,1.513-0.681,1.513-1.513v-0.317c0-0.833-0.681-1.513-1.513-1.513H36.296z"/>
 | 
			
		||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="605.8877" y1="2040.6665" x2="593.1709" y2="2040.6665" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#4D4D4D"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#999999"/>
 | 
			
		||||
</linearGradient>
 | 
			
		||||
<path fill="url(#SVGID_2_)" d="M20.205,57.452c0,0.519-3.619,0.752-6.627,0.752c-2.083,0-5.846-0.186-6.089-0.678
 | 
			
		||||
	c0,0.238,0,0.806,0,0.89c0,0.389,2.573,0.661,6.084,0.661c3.511,0,6.632-0.344,6.632-0.729C20.205,58.264,20.205,57.7,20.205,57.452
 | 
			
		||||
	z"/>
 | 
			
		||||
<path fill="#808080" d="M13.846,56.806c3.512,0,6.358,0.313,6.358,0.699s-2.846,0.763-6.358,0.763c-3.59,0-6.358-0.375-6.358-0.763
 | 
			
		||||
	S10.335,56.806,13.846,56.806z"/>
 | 
			
		||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="600.833" y1="2037.4702" x2="598.1563" y2="2037.4702" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#999999"/>
 | 
			
		||||
	<stop  offset="0.0417" style="stop-color:#8D8D8D"/>
 | 
			
		||||
	<stop  offset="0.1617" style="stop-color:#717171"/>
 | 
			
		||||
	<stop  offset="0.2821" style="stop-color:#5D5D5D"/>
 | 
			
		||||
	<stop  offset="0.4021" style="stop-color:#515151"/>
 | 
			
		||||
	<stop  offset="0.5212" style="stop-color:#4D4D4D"/>
 | 
			
		||||
	<stop  offset="0.6202" style="stop-color:#565656"/>
 | 
			
		||||
	<stop  offset="0.7817" style="stop-color:#6E6E6E"/>
 | 
			
		||||
	<stop  offset="0.9844" style="stop-color:#969696"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#999999"/>
 | 
			
		||||
</linearGradient>
 | 
			
		||||
<path fill="url(#SVGID_3_)" d="M15.215,57.657c0,0-0.792,0.053-1.339,0.053s-1.338-0.053-1.338-0.053v-5.231h2.677V57.657z"/>
 | 
			
		||||
<radialGradient id="SVGID_4_" cx="465.1113" cy="2023.4497" r="12.8975" gradientTransform="matrix(1.15 0 0 1 -526.6041 -1982.4023)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#F2F2F2"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#666666"/>
 | 
			
		||||
</radialGradient>
 | 
			
		||||
<path fill="url(#SVGID_4_)" d="M0.065,36.888c0-0.59,0.482-1.071,1.072-1.071H26.98c0.589,0,1.071,0.481,1.071,1.071v16.108
 | 
			
		||||
	c0,0.589-0.482,1.07-1.071,1.07H1.137c-0.59,0.002-1.072-0.481-1.072-1.07V36.888z"/>
 | 
			
		||||
<path fill="none" stroke="#666666" stroke-width="0.1305" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
 | 
			
		||||
	M0.065,36.888c0-0.59,0.482-1.071,1.072-1.071H26.98c0.589,0,1.071,0.481,1.071,1.071v16.108c0,0.589-0.482,1.07-1.071,1.07H1.137
 | 
			
		||||
	c-0.59,0.002-1.072-0.481-1.072-1.07V36.888z"/>
 | 
			
		||||
<radialGradient id="SVGID_5_" cx="439.1309" cy="2019.0845" r="28.5715" fx="461.6079" fy="2015.234" gradientTransform="matrix(1.1935 0 0 1 -509.6013 -1982.4023)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#4D4D4D"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#999999"/>
 | 
			
		||||
</radialGradient>
 | 
			
		||||
<path fill="url(#SVGID_5_)" d="M0.613,37.436c0-0.591,0.482-1.072,1.071-1.072h24.871c0.589,0,1.071,0.481,1.071,1.072v14.893
 | 
			
		||||
	c0,0.59-0.482,1.072-1.071,1.072H1.685c-0.589,0-1.071-0.482-1.071-1.072V37.436z"/>
 | 
			
		||||
<radialGradient id="SVGID_6_" cx="440.0439" cy="2019.1304" r="18.3134" gradientTransform="matrix(1.1923 0 0 1 -510.0601 -1982.4023)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#9CD7FF"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#3C89C9"/>
 | 
			
		||||
</radialGradient>
 | 
			
		||||
<path fill="url(#SVGID_6_)" d="M0.917,37.679c0-0.59,0.482-1.071,1.072-1.071h24.262c0.589,0,1.071,0.481,1.071,1.071v14.406
 | 
			
		||||
	c0,0.588-0.482,1.069-1.071,1.069H1.989c-0.59,0-1.072-0.481-1.072-1.069V37.679z"/>
 | 
			
		||||
<path opacity="0.24" fill="#F2F2F2" d="M0.917,49.11V37.679c0-0.59,0.482-1.071,1.072-1.071h24.262c0.589,0,1.071,0.481,1.071,1.071
 | 
			
		||||
	v7.252l-12.407,2.646c-0.57,0.146-1.52,0.293-2.107,0.326L0.917,49.11z"/>
 | 
			
		||||
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="644.3887" y1="2040.6665" x2="631.6719" y2="2040.6665" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#4D4D4D"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#999999"/>
 | 
			
		||||
</linearGradient>
 | 
			
		||||
<path fill="url(#SVGID_7_)" d="M58.706,57.452c0,0.518-3.621,0.752-6.627,0.752c-2.084,0-5.848-0.186-6.09-0.678
 | 
			
		||||
	c0,0.237,0,0.805,0,0.889c0,0.389,2.572,0.662,6.084,0.662s6.633-0.344,6.633-0.729C58.706,58.263,58.706,57.7,58.706,57.452z"/>
 | 
			
		||||
<path fill="#808080" d="M52.347,56.805c3.512,0,6.357,0.313,6.357,0.699s-2.847,0.762-6.357,0.762c-3.59,0-6.357-0.373-6.357-0.762
 | 
			
		||||
	C45.989,57.118,48.837,56.805,52.347,56.805z"/>
 | 
			
		||||
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="639.333" y1="2037.4683" x2="636.6553" y2="2037.4683" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#999999"/>
 | 
			
		||||
	<stop  offset="0.0417" style="stop-color:#8D8D8D"/>
 | 
			
		||||
	<stop  offset="0.1617" style="stop-color:#717171"/>
 | 
			
		||||
	<stop  offset="0.2821" style="stop-color:#5D5D5D"/>
 | 
			
		||||
	<stop  offset="0.4021" style="stop-color:#515151"/>
 | 
			
		||||
	<stop  offset="0.5212" style="stop-color:#4D4D4D"/>
 | 
			
		||||
	<stop  offset="0.6202" style="stop-color:#565656"/>
 | 
			
		||||
	<stop  offset="0.7817" style="stop-color:#6E6E6E"/>
 | 
			
		||||
	<stop  offset="0.9844" style="stop-color:#969696"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#999999"/>
 | 
			
		||||
</linearGradient>
 | 
			
		||||
<path fill="url(#SVGID_8_)" d="M53.716,57.657c0,0-0.791,0.052-1.34,0.052c-0.547,0-1.338-0.052-1.338-0.052v-5.232h2.678V57.657z"
 | 
			
		||||
	/>
 | 
			
		||||
<radialGradient id="SVGID_9_" cx="498.5898" cy="2023.4487" r="12.8975" gradientTransform="matrix(1.15 0 0 1 -526.6041 -1982.4023)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#F2F2F2"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#666666"/>
 | 
			
		||||
</radialGradient>
 | 
			
		||||
<path fill="url(#SVGID_9_)" d="M38.566,36.887c0-0.59,0.481-1.072,1.071-1.072h25.844c0.589,0,1.07,0.482,1.07,1.072v16.107
 | 
			
		||||
	c0,0.59-0.481,1.072-1.07,1.072H39.638c-0.59,0-1.071-0.482-1.071-1.072V36.887z"/>
 | 
			
		||||
<path fill="none" stroke="#666666" stroke-width="0.1305" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
 | 
			
		||||
	M38.566,36.887c0-0.59,0.481-1.072,1.071-1.072h25.844c0.589,0,1.07,0.482,1.07,1.072v16.107c0,0.59-0.481,1.072-1.07,1.072H39.638
 | 
			
		||||
	c-0.59,0-1.071-0.482-1.071-1.072V36.887z"/>
 | 
			
		||||
<radialGradient id="SVGID_10_" cx="471.3896" cy="2019.0845" r="28.5697" fx="493.8652" fy="2015.2343" gradientTransform="matrix(1.1935 0 0 1 -509.6013 -1982.4023)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#4D4D4D"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#999999"/>
 | 
			
		||||
</radialGradient>
 | 
			
		||||
<path fill="url(#SVGID_10_)" d="M39.114,37.434c0-0.59,0.482-1.072,1.071-1.072h24.87c0.589,0,1.07,0.482,1.07,1.072v14.895
 | 
			
		||||
	c0,0.589-0.481,1.07-1.07,1.07h-24.87c-0.589,0-1.071-0.481-1.071-1.07V37.434z"/>
 | 
			
		||||
<radialGradient id="SVGID_11_" cx="472.334" cy="2019.1294" r="18.3139" gradientTransform="matrix(1.1923 0 0 1 -510.0601 -1982.4023)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#9CD7FF"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#3C89C9"/>
 | 
			
		||||
</radialGradient>
 | 
			
		||||
<path fill="url(#SVGID_11_)" d="M39.419,37.678c0-0.59,0.481-1.072,1.07-1.072h24.264c0.588,0,1.07,0.482,1.07,1.072v14.406
 | 
			
		||||
	c0,0.588-0.482,1.07-1.07,1.07H40.489c-0.589,0-1.07-0.482-1.07-1.07V37.678z"/>
 | 
			
		||||
<path opacity="0.24" fill="#F2F2F2" d="M39.419,49.108v-11.43c0-0.59,0.481-1.072,1.07-1.072h24.264c0.588,0,1.07,0.482,1.07,1.072
 | 
			
		||||
	v7.252l-12.408,2.645c-0.57,0.146-1.52,0.295-2.106,0.326L39.419,49.108z"/>
 | 
			
		||||
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="624.8936" y1="2004.9155" x2="612.1787" y2="2004.9155" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#4D4D4D"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#999999"/>
 | 
			
		||||
</linearGradient>
 | 
			
		||||
<path fill="url(#SVGID_12_)" d="M39.212,21.701c0,0.518-3.621,0.752-6.626,0.752c-2.083,0-5.847-0.186-6.089-0.678
 | 
			
		||||
	c0,0.238,0,0.805,0,0.889c0,0.389,2.573,0.662,6.084,0.662c3.51,0,6.631-0.344,6.631-0.729
 | 
			
		||||
	C39.212,22.513,39.212,21.949,39.212,21.701z"/>
 | 
			
		||||
<path fill="#808080" d="M32.854,21.055c3.511,0,6.358,0.313,6.358,0.699c0,0.386-2.848,0.762-6.358,0.762
 | 
			
		||||
	c-3.589,0-6.358-0.374-6.358-0.762C26.496,21.367,29.342,21.055,32.854,21.055z"/>
 | 
			
		||||
<linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="619.8379" y1="2001.7183" x2="617.1611" y2="2001.7183" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#999999"/>
 | 
			
		||||
	<stop  offset="0.0417" style="stop-color:#8D8D8D"/>
 | 
			
		||||
	<stop  offset="0.1617" style="stop-color:#717171"/>
 | 
			
		||||
	<stop  offset="0.2821" style="stop-color:#5D5D5D"/>
 | 
			
		||||
	<stop  offset="0.4021" style="stop-color:#515151"/>
 | 
			
		||||
	<stop  offset="0.5212" style="stop-color:#4D4D4D"/>
 | 
			
		||||
	<stop  offset="0.6202" style="stop-color:#565656"/>
 | 
			
		||||
	<stop  offset="0.7817" style="stop-color:#6E6E6E"/>
 | 
			
		||||
	<stop  offset="0.9844" style="stop-color:#969696"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#999999"/>
 | 
			
		||||
</linearGradient>
 | 
			
		||||
<path fill="url(#SVGID_13_)" d="M34.222,21.906c0,0-0.791,0.052-1.338,0.052c-0.547,0-1.338-0.052-1.338-0.052v-5.232h2.677
 | 
			
		||||
	L34.222,21.906L34.222,21.906z"/>
 | 
			
		||||
<radialGradient id="SVGID_14_" cx="481.6387" cy="1987.6978" r="12.8975" gradientTransform="matrix(1.15 0 0 1 -526.6041 -1982.4023)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#F2F2F2"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#666666"/>
 | 
			
		||||
</radialGradient>
 | 
			
		||||
<path fill="url(#SVGID_14_)" d="M19.072,1.137c0-0.59,0.482-1.072,1.071-1.072h25.843c0.589,0,1.071,0.482,1.071,1.072v16.108
 | 
			
		||||
	c0,0.589-0.482,1.071-1.071,1.071H20.145c-0.589,0-1.071-0.482-1.071-1.071L19.072,1.137L19.072,1.137z"/>
 | 
			
		||||
<path fill="none" stroke="#666666" stroke-width="0.1305" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
 | 
			
		||||
	M19.072,1.137c0-0.59,0.482-1.072,1.071-1.072h25.843c0.589,0,1.071,0.482,1.071,1.072v16.108c0,0.589-0.482,1.071-1.071,1.071
 | 
			
		||||
	H20.145c-0.589,0-1.071-0.482-1.071-1.071L19.072,1.137L19.072,1.137z"/>
 | 
			
		||||
<radialGradient id="SVGID_15_" cx="455.0566" cy="1983.3345" r="28.5689" fx="477.5316" fy="1979.4844" gradientTransform="matrix(1.1935 0 0 1 -509.6013 -1982.4023)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#4D4D4D"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#999999"/>
 | 
			
		||||
</radialGradient>
 | 
			
		||||
<path fill="url(#SVGID_15_)" d="M19.621,1.685c0-0.59,0.482-1.072,1.072-1.072h24.87c0.589,0,1.071,0.482,1.071,1.072v14.894
 | 
			
		||||
	c0,0.589-0.482,1.071-1.071,1.071h-24.87c-0.589,0-1.072-0.482-1.072-1.071V1.685z"/>
 | 
			
		||||
<radialGradient id="SVGID_16_" cx="455.9854" cy="1983.3784" r="18.3134" gradientTransform="matrix(1.1923 0 0 1 -510.0601 -1982.4023)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
	<stop  offset="0" style="stop-color:#9CD7FF"/>
 | 
			
		||||
	<stop  offset="1" style="stop-color:#3C89C9"/>
 | 
			
		||||
</radialGradient>
 | 
			
		||||
<path fill="url(#SVGID_16_)" d="M19.924,1.928c0-0.59,0.482-1.072,1.072-1.072h24.262c0.589,0,1.07,0.482,1.07,1.072v14.406
 | 
			
		||||
	c0,0.588-0.481,1.07-1.07,1.07H20.997c-0.589,0-1.072-0.482-1.072-1.07V1.928z"/>
 | 
			
		||||
<path opacity="0.24" fill="#F2F2F2" d="M19.924,13.358V1.928c0-0.59,0.482-1.072,1.072-1.072h24.262c0.589,0,1.07,0.482,1.07,1.072
 | 
			
		||||
	V9.18l-12.408,2.646c-0.569,0.146-1.519,0.294-2.106,0.326L19.924,13.358z"/>
 | 
			
		||||
</svg>
 | 
			
		||||
</y:Resource>
 | 
			
		||||
      <y:Resource id="2"><?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 | 
			
		||||
	 width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve">
 | 
			
		||||
<g>
 | 
			
		||||
	
 | 
			
		||||
		<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)">
 | 
			
		||||
		<stop  offset="0.2711" style="stop-color:#FFAB4F"/>
 | 
			
		||||
		<stop  offset="1" style="stop-color:#FFD28F"/>
 | 
			
		||||
	</linearGradient>
 | 
			
		||||
	<path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109
 | 
			
		||||
		V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77
 | 
			
		||||
		c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/>
 | 
			
		||||
	
 | 
			
		||||
		<radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
		<stop  offset="0" style="stop-color:#FFD28F"/>
 | 
			
		||||
		<stop  offset="1" style="stop-color:#FFAB4F"/>
 | 
			
		||||
	</radialGradient>
 | 
			
		||||
	<path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357
 | 
			
		||||
		c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012
 | 
			
		||||
		C36.627,4.945,43.59,13.158,43.676,23.357z"/>
 | 
			
		||||
	
 | 
			
		||||
		<linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)">
 | 
			
		||||
		<stop  offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/>
 | 
			
		||||
		<stop  offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/>
 | 
			
		||||
	</linearGradient>
 | 
			
		||||
	<path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386
 | 
			
		||||
		c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247
 | 
			
		||||
		c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/>
 | 
			
		||||
	<path fill="#CC9869" stroke="#99724F" stroke-width="0.9271" stroke-linecap="round" stroke-linejoin="round" d="M28.02,31.921
 | 
			
		||||
		c-6.78,0-6.717,3.708-6.717,3.708c0,8.133,2.985,8.788,6.955,8.788c4.243,0,6.792-0.926,6.792-8.595
 | 
			
		||||
		C35.051,35.822,35.881,31.921,28.02,31.921z M23.989,35.678c0-0.556,1.838-1.005,4.107-1.005c2.27,0,4.107,0.449,4.107,1.005
 | 
			
		||||
		C32.204,36.232,23.989,36.232,23.989,35.678z"/>
 | 
			
		||||
	<path id="hair_x5F_gray_2_" fill="#CC9869" stroke="#99724F" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25
 | 
			
		||||
		c0,0,5.321,7.25,15,3.75c2.729-0.563,9.058,1.035,9.058,1.035S40.68,1.865,27.289,2.744C9.403,4.125,12.058,25.678,12.058,25.678
 | 
			
		||||
		s2.768-0.684,5.036-4.802C18.068,19.106,20.278,13.25,20.278,13.25z"/>
 | 
			
		||||
	
 | 
			
		||||
		<radialGradient id="collar_x5F_body_1_" cx="14.9609" cy="3148.9336" r="32.4004" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
		<stop  offset="0" style="stop-color:#B0E8FF"/>
 | 
			
		||||
		<stop  offset="1" style="stop-color:#74AEEE"/>
 | 
			
		||||
	</radialGradient>
 | 
			
		||||
	<path id="collar_x5F_body_3_" fill="url(#collar_x5F_body_1_)" stroke="#5491CF" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494
 | 
			
		||||
		h48.51c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146
 | 
			
		||||
		c-1.771,1.655-5.61,2.802-10.063,2.802c-4.453,0-8.292-1.146-10.063-2.802c0,0-5.755,0.586-11.189,6.021
 | 
			
		||||
		C1.378,56.689,0.5,62.768,0.5,62.768z"/>
 | 
			
		||||
	
 | 
			
		||||
		<radialGradient id="collar_x5F_r_1_" cx="31.2998" cy="3139.0605" r="9.2823" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
		<stop  offset="0" style="stop-color:#80CCFF"/>
 | 
			
		||||
		<stop  offset="1" style="stop-color:#74AEEE"/>
 | 
			
		||||
	</radialGradient>
 | 
			
		||||
	<path id="collar_x5F_r_3_" fill="url(#collar_x5F_r_1_)" stroke="#5491CF" d="M38.159,41.381c0,0-0.574,2.369-3.013,4.441
 | 
			
		||||
		c-2.108,1.795-5.783,2.072-5.783,2.072l3.974,6.217c0,0,2.957-1.637,5.009-3.848c1.922-2.072,1.37-5.479,1.37-5.479L38.159,41.381z
 | 
			
		||||
		"/>
 | 
			
		||||
	
 | 
			
		||||
		<radialGradient id="collar_x5F_l_1_" cx="18.9375" cy="3139.1016" r="9.2843" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
		<stop  offset="0" style="stop-color:#80CCFF"/>
 | 
			
		||||
		<stop  offset="1" style="stop-color:#74AEEE"/>
 | 
			
		||||
	</radialGradient>
 | 
			
		||||
	<path id="collar_x5F_l_3_" fill="url(#collar_x5F_l_1_)" stroke="#5491CF" d="M18.63,41.422c0,0,0.576,2.369,3.012,4.441
 | 
			
		||||
		c2.109,1.793,5.785,2.072,5.785,2.072l-3.974,6.217c0,0-2.957-1.637-5.007-3.85c-1.922-2.072-1.37-5.48-1.37-5.48L18.63,41.422z"/>
 | 
			
		||||
	
 | 
			
		||||
		<radialGradient id="Knob2_1_" cx="27.6895" cy="2375.2871" r="0.9669" gradientTransform="matrix(1 0 0 1 0.2402 -2319.0742)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
		<stop  offset="0" style="stop-color:#80CCFF"/>
 | 
			
		||||
		<stop  offset="1" style="stop-color:#74AEEE"/>
 | 
			
		||||
	</radialGradient>
 | 
			
		||||
	<circle id="Knob2_3_" fill="url(#Knob2_1_)" stroke="#5491CF" cx="28.258" cy="56.254" r="0.584"/>
 | 
			
		||||
	
 | 
			
		||||
		<radialGradient id="Knob1_1_" cx="27.7275" cy="2381.5283" r="0.9669" gradientTransform="matrix(1 0 0 1 0.2402 -2319.0742)" gradientUnits="userSpaceOnUse">
 | 
			
		||||
		<stop  offset="0" style="stop-color:#80CCFF"/>
 | 
			
		||||
		<stop  offset="1" style="stop-color:#74AEEE"/>
 | 
			
		||||
	</radialGradient>
 | 
			
		||||
	<circle id="Knob1_3_" fill="url(#Knob1_1_)" stroke="#5491CF" cx="28.297" cy="62.499" r="0.584"/>
 | 
			
		||||
	<path id="path5135_5_" fill="#D54A30" stroke="#B51A19" d="M27.442,55.23c0,0-1.852,2.057-2.082,6.543c-0.23,4.488,0,4.488,0,4.488
 | 
			
		||||
		h6.546c0,0,0.23,0.063-0.154-4.367c-0.4-4.604-2.389-6.668-2.389-6.668L27.442,55.23L27.442,55.23z"/>
 | 
			
		||||
	<path id="path5131_5_" fill="#D54A30" stroke="#B51A19" d="M28.325,48.688h0.125L31,52.691c0.516,0.953-1.207,1.797-1.457,2.547
 | 
			
		||||
		l-2.277-0.018c-0.242-0.761-2.26-1.369-1.477-2.584L28.325,48.688z"/>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
</y:Resource>
 | 
			
		||||
      <y:Resource id="3"><?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg
 | 
			
		||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
			
		||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
			
		||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
			
		||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
			
		||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
			
		||||
   viewBox="0 0 585.16241 167.58249"
 | 
			
		||||
   height="167.58249"
 | 
			
		||||
   width="585.16241"
 | 
			
		||||
   xml:space="preserve"
 | 
			
		||||
   id="svg2"
 | 
			
		||||
   version="1.1"
 | 
			
		||||
   inkscape:version="0.91 r13725"
 | 
			
		||||
   sodipodi:docname="Slack CMYK.svg"><sodipodi:namedview
 | 
			
		||||
     pagecolor="#ffffff"
 | 
			
		||||
     bordercolor="#666666"
 | 
			
		||||
     borderopacity="1"
 | 
			
		||||
     objecttolerance="10"
 | 
			
		||||
     gridtolerance="10"
 | 
			
		||||
     guidetolerance="10"
 | 
			
		||||
     inkscape:pageopacity="0"
 | 
			
		||||
     inkscape:pageshadow="2"
 | 
			
		||||
     inkscape:window-width="1366"
 | 
			
		||||
     inkscape:window-height="705"
 | 
			
		||||
     id="namedview3358"
 | 
			
		||||
     showgrid="false"
 | 
			
		||||
     fit-margin-top="0"
 | 
			
		||||
     fit-margin-left="0"
 | 
			
		||||
     fit-margin-right="0"
 | 
			
		||||
     fit-margin-bottom="0"
 | 
			
		||||
     inkscape:zoom="1.4633713"
 | 
			
		||||
     inkscape:cx="271.33569"
 | 
			
		||||
     inkscape:cy="125.32114"
 | 
			
		||||
     inkscape:window-x="-8"
 | 
			
		||||
     inkscape:window-y="-8"
 | 
			
		||||
     inkscape:window-maximized="1"
 | 
			
		||||
     inkscape:current-layer="svg2" /><metadata
 | 
			
		||||
     id="metadata8"><rdf:RDF><cc:Work
 | 
			
		||||
         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
 | 
			
		||||
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
 | 
			
		||||
     id="defs6"><clipPath
 | 
			
		||||
       id="clipPath20"
 | 
			
		||||
       clipPathUnits="userSpaceOnUse"><path
 | 
			
		||||
         id="path18"
 | 
			
		||||
         d="M 0,1256.87 0,0 l 4388.72,0 0,1256.87 z"
 | 
			
		||||
         inkscape:connector-curvature="0" /></clipPath></defs><g
 | 
			
		||||
     id="g3411"
 | 
			
		||||
     transform="translate(12.322913,-242.28632)"><path
 | 
			
		||||
       d="m 93.133967,257.26632 c -2.4724,-7.60934 -10.644271,-11.77334 -18.253204,-9.30001 -7.608267,2.472 -11.7724,10.64401 -9.300533,18.252 l 37.45734,115.24281 c 2.57133,7.10786 10.24946,11.10266 17.62079,8.98133 7.68814,-2.2136 12.3748,-10.37867 9.92147,-17.93027 -0.0933,-0.2864 -37.445863,-115.24586 -37.445863,-115.24586"
 | 
			
		||||
       style="fill:#e7a213;fill-opacity:1;fill-rule:nonzero;stroke:none"
 | 
			
		||||
       id="path22-7"
 | 
			
		||||
       inkscape:connector-curvature="0" /><path
 | 
			
		||||
       d="m 35.095431,276.12365 c -2.4724,-7.608 -10.644267,-11.772 -18.2532,-9.3 -7.6082657,2.472 -11.7723997,10.64426 -9.3005327,18.2532 L 44.99903,400.3195 c 2.571334,7.10834 10.249467,11.1026 17.620267,8.98021 7.688133,-2.21198 12.375599,-10.37761 9.921866,-17.92913 -0.0932,-0.28706 -37.445732,-115.24693 -37.445732,-115.24693"
 | 
			
		||||
       style="fill:#4dc088;fill-opacity:1;fill-rule:nonzero;stroke:none"
 | 
			
		||||
       id="path24-8"
 | 
			
		||||
       inkscape:connector-curvature="0" /><path
 | 
			
		||||
       d="m 140.27983,352.71018 c 7.60933,-2.4724 11.772,-10.64427 9.3,-18.2532 -2.472,-7.60827 -10.644,-11.7724 -18.252,-9.30053 L 16.085031,362.61378 c -7.1083997,2.57134 -11.1026657,10.24947 -8.9813327,17.62027 2.213067,7.688 10.3781327,12.37507 17.9302657,9.92133 0.2864,-0.0932 115.245866,-37.4452 115.245866,-37.4452"
 | 
			
		||||
       style="fill:#e10d63;fill-opacity:1;fill-rule:nonzero;stroke:none"
 | 
			
		||||
       id="path26-3"
 | 
			
		||||
       inkscape:connector-curvature="0" /><path
 | 
			
		||||
       d="m 40.103697,385.25965 c 7.5016,-2.4376 17.169866,-5.57867 27.543733,-8.94947 -2.436934,-7.50106 -5.579067,-17.17093 -8.950533,-27.5464 l -27.5452,8.95254 8.952,27.54333"
 | 
			
		||||
       style="fill:#3f2543;fill-opacity:1;fill-rule:nonzero;stroke:none"
 | 
			
		||||
       id="path28-4"
 | 
			
		||||
       inkscape:connector-curvature="0" /><path
 | 
			
		||||
       d="m 98.142767,366.40125 c 10.413603,-3.38333 20.091733,-6.52813 27.543733,-8.94947 -2.43734,-7.50213 -5.58014,-17.174 -8.95254,-27.5516 l -27.545333,8.95267 8.95414,27.5484"
 | 
			
		||||
       style="fill:#d01e25;fill-opacity:1;fill-rule:nonzero;stroke:none"
 | 
			
		||||
       id="path30-9"
 | 
			
		||||
       inkscape:connector-curvature="0" /><path
 | 
			
		||||
       d="m 121.4225,294.67165 c 7.608,-2.4724 11.772,-10.64427 9.3,-18.25334 -2.472,-7.60933 -10.64427,-11.772 -18.2532,-9.3 L -2.7733549,304.57525 c -7.1078131,2.57133 -11.1026001,10.25 -8.9807301,17.62026 2.2130236,7.688 10.3781303,12.3756 17.9296503,9.92187 C 6.4626313,332.02418 121.4225,294.67165 121.4225,294.67165"
 | 
			
		||||
       style="fill:#7cd3dc;fill-opacity:1;fill-rule:nonzero;stroke:none"
 | 
			
		||||
       id="path32-2"
 | 
			
		||||
       inkscape:connector-curvature="0" /><path
 | 
			
		||||
       d="m 21.240764,327.22258 c 7.501467,-2.438 17.1724,-5.5796 27.548933,-8.95147 -3.383867,-10.41413 -6.528667,-20.0928 -8.950533,-27.5464 l -27.550533,8.95467 8.952133,27.5432"
 | 
			
		||||
       style="fill:#36987b;fill-opacity:1;fill-rule:nonzero;stroke:none"
 | 
			
		||||
       id="path34-0"
 | 
			
		||||
       inkscape:connector-curvature="0" /><path
 | 
			
		||||
       d="m 79.279296,308.36431 c 10.415601,-3.38386 20.095731,-6.5292 27.548934,-8.95106 -3.3844,-10.41667 -6.53026,-20.09787 -8.952133,-27.55227 l -27.551067,8.95533 8.954266,27.548"
 | 
			
		||||
       style="fill:#5a872d;fill-opacity:1;fill-rule:nonzero;stroke:none"
 | 
			
		||||
       id="path36-4"
 | 
			
		||||
       inkscape:connector-curvature="0" /><path
 | 
			
		||||
       d="m 257.17716,287.60071 c 5.09733,2.224 5.468,3.89227 1.48267,11.49067 -4.076,7.78387 -5.096,8.24733 -10.1,6.20827 -6.30133,-2.68694 -14.364,-4.726 -19.55333,-4.726 -8.524,0 -14.17734,3.0584 -14.17734,7.69173 0,15.29 48.836,7.04213 48.836,39.66094 0,16.40253 -14.08667,27.33746 -35.21333,27.33746 -11.12,0 -24.836,-3.7068 -34.288,-8.526 -4.72533,-2.40893 -5.00267,-3.79906 -0.92667,-11.5828 3.52267,-6.85786 4.63334,-7.59906 9.73067,-5.46773 8.06133,3.52133 18.256,6.2088 25.11333,6.2088 7.784,0 12.97334,-3.15107 12.97334,-7.78387 0,-14.82707 -49.66934,-7.7844 -49.66934,-39.38387 0,-16.7724 13.992,-27.98533 34.93467,-27.98533 9.82267,0 22.24,2.96507 30.85733,6.85773"
 | 
			
		||||
       style="fill:#373d47;fill-opacity:1;fill-rule:nonzero;stroke:none"
 | 
			
		||||
       id="path38-9"
 | 
			
		||||
       inkscape:connector-curvature="0" /><path
 | 
			
		||||
       d="m 296.56516,248.86498 0,121.57947 c 0,1.85373 -1.94667,3.79947 -4.72533,3.79947 l -12.78934,0 c -2.78,0 -4.72666,-1.94574 -4.72666,-3.79947 l 0,-121.57947 c 0,-6.02266 1.66933,-6.57866 11.12133,-6.57866 10.74933,0 11.12,0.74133 11.12,6.57866"
 | 
			
		||||
       style="fill:#373d47;fill-opacity:1;fill-rule:nonzero;stroke:none"
 | 
			
		||||
       id="path40-2"
 | 
			
		||||
       inkscape:connector-curvature="0" /><path
 | 
			
		||||
       d="m 328.53449,347.55592 c 0,6.20893 5.28267,10.37866 13.252,10.37866 9.63734,0 18.34667,-4.63333 23.444,-12.50986 l 0,-8.15467 c -5.09733,-1.9464 -11.30533,-3.0584 -16.86533,-3.0584 -11.86133,0 -19.83067,5.6532 -19.83067,13.34427 z m 58.936,-30.30201 0,52.35667 c 0,2.77974 -1.94533,4.72614 -4.72533,4.72614 l -12.604,0 c -2.872,0 -4.91067,-2.13134 -4.72533,-5.00374 l 0.18533,-5.65253 c -6.85733,7.59787 -16.68,11.58333 -26.50267,11.58333 -19.08933,0 -31.87733,-11.02813 -31.87733,-27.5224 0,-17.514 14.456,-29.2828 36.14,-29.37547 8.248,0 15.75333,1.4828 21.86933,3.98427 l 0,-6.02333 c 0,-9.63707 -7.59866,-15.3824 -20.47866,-15.3824 -6.024,0 -13.43734,2.41 -19.368,5.83813 -4.54134,2.59467 -5.65334,2.40933 -10.564,-4.91093 -4.81867,-7.32147 -4.63334,-8.71094 0,-11.67667 8.896,-5.74533 20.94266,-9.452 32.06266,-9.452 24.92667,0 40.588,13.5296 40.588,36.51093"
 | 
			
		||||
       style="fill:#373d47;fill-opacity:1;fill-rule:nonzero;stroke:none"
 | 
			
		||||
       id="path42-7"
 | 
			
		||||
       inkscape:connector-curvature="0" /><path
 | 
			
		||||
       d="m 474.21182,287.78618 c 4.632,2.68693 4.81867,4.16973 -0.27866,12.0464 -4.81734,7.41347 -5.652,7.87707 -10.748,5.28227 -3.89334,-2.03907 -10.10134,-3.79947 -15.19867,-3.79947 -16.03067,0 -26.688,10.56347 -26.688,26.50253 0,16.58747 10.65733,27.70734 26.688,27.70734 5.56133,0 12.51067,-2.13173 17.05067,-4.63333 4.63333,-2.68747 5.65333,-2.50214 10.564,4.63333 4.448,6.6724 4.356,8.2468 0.37066,11.0276 -7.13466,4.91147 -18.44,8.71093 -28.35466,8.71093 -29.65334,0 -49.48534,-18.99693 -49.48534,-47.44587 0,-28.26293 19.832,-47.07493 49.66934,-47.07493 9.08133,0 19.73866,3.05827 26.41066,7.0432"
 | 
			
		||||
       style="fill:#373d47;fill-opacity:1;fill-rule:nonzero;stroke:none"
 | 
			
		||||
       id="path44-1"
 | 
			
		||||
       inkscape:connector-curvature="0" /><path
 | 
			
		||||
       d="m 570.95849,362.75338 c 3.70666,4.72547 2.224,6.39427 -7.04267,9.73014 -9.452,3.42813 -10.74933,3.2428 -14.084,-1.11187 l -26.504,-35.39907 -11.86133,11.49014 0,22.98173 c 0,1.85373 -1.94533,3.79947 -4.72533,3.79947 l -12.788,0 c -2.78,0 -4.72667,-1.94574 -4.72667,-3.79947 l 0,-121.57947 c 0,-6.02266 1.668,-6.57866 11.12,-6.57866 10.74933,0 11.12,0.74133 11.12,6.57866 l 0,69.13 36.32533,-34.84213 c 3.98533,-3.8 6.20933,-3.52134 13.344,1.20466 7.87733,5.0964 8.43333,6.4864 4.63333,10.19267 l -27.05866,26.31773 32.248,41.88547"
 | 
			
		||||
       style="fill:#373d47;fill-opacity:1;fill-rule:nonzero;stroke:none"
 | 
			
		||||
       id="path46-3"
 | 
			
		||||
       inkscape:connector-curvature="0" /></g></svg></y:Resource>
 | 
			
		||||
    </y:Resources>
 | 
			
		||||
  </data>
 | 
			
		||||
</graphml>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 60 KiB  | 
| 
		 Before Width: | Height: | Size: 36 KiB  | 
| 
		 Before Width: | Height: | Size: 198 KiB  | 
| 
		 Before Width: | Height: | Size: 179 KiB  | 
@@ -1,265 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
 | 
			
		||||
  <!--Created by yEd 3.14.2-->
 | 
			
		||||
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
 | 
			
		||||
  <key for="port" id="d1" yfiles.type="portgraphics"/>
 | 
			
		||||
  <key for="port" id="d2" yfiles.type="portgeometry"/>
 | 
			
		||||
  <key for="port" id="d3" yfiles.type="portuserdata"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="node" id="d4"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="node" id="d5"/>
 | 
			
		||||
  <key for="node" id="d6" yfiles.type="nodegraphics"/>
 | 
			
		||||
  <key for="graphml" id="d7" yfiles.type="resources"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
 | 
			
		||||
  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
 | 
			
		||||
  <graph edgedefault="directed" id="G">
 | 
			
		||||
    <data key="d0"/>
 | 
			
		||||
    <node id="n0">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="478.6165008544913" y="1358.206868489578"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="28.87890625" x="22.185546875" y="15.93359375">Vuls<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n1">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="711.9623756408686" y="1043.7241210937468"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="38.623046875" x="17.3134765625" y="15.93359375">Nginx<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n2">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="711.9623756408686" y="1287.206868489578"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="42.7890625" x="15.23046875" y="15.93359375">MySQL<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n3" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="101.666015625" width="291.7208747863772" x="602.72693824768" y="1146.2994791666624"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="291.7208747863772" x="0.0" y="0.0">Web/App</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="23" leftF="23.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" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 5</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
          </y:Realizers>
 | 
			
		||||
        </y:ProxyAutoBoundsNode>
 | 
			
		||||
      </data>
 | 
			
		||||
      <graph edgedefault="directed" id="n3:">
 | 
			
		||||
        <node id="n3::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="73.25" x="640.72693824768" y="1182.9654947916624"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n3::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="73.25" x="723.4623756408686" y="1182.9654947916624"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n3::n2">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="73.25" x="806.1978130340572" y="1182.9654947916624"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n4">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="821.1978130340572" y="1287.206868489578"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="35.412109375" x="18.9189453125" y="15.93359375">Redis<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n3" target="n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e1" source="n3" target="n2">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e2" source="n0" target="n3::n0">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e3" source="n0" target="n3::n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e4" source="n0" target="n3::n2">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e5" source="n3" target="n4">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e6" source="n0" target="n4">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e7" source="n0" target="n1">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e8" source="n0" target="n2">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="dashed" 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>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 14 KiB  | 
@@ -1,194 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
 | 
			
		||||
  <!--Created by yEd 3.14.2-->
 | 
			
		||||
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
 | 
			
		||||
  <key for="port" id="d1" yfiles.type="portgraphics"/>
 | 
			
		||||
  <key for="port" id="d2" yfiles.type="portgeometry"/>
 | 
			
		||||
  <key for="port" id="d3" yfiles.type="portuserdata"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="node" id="d4"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="node" id="d5"/>
 | 
			
		||||
  <key for="node" id="d6" yfiles.type="nodegraphics"/>
 | 
			
		||||
  <key for="graphml" id="d7" yfiles.type="resources"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
 | 
			
		||||
  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
 | 
			
		||||
  <graph edgedefault="directed" id="G">
 | 
			
		||||
    <data key="d0"/>
 | 
			
		||||
    <node id="n0">
 | 
			
		||||
      <data key="d5"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="508.30825042724564" y="1132.4827473958312"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="28.87890625" x="22.185546875" y="15.93359375">Vuls<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n1">
 | 
			
		||||
      <data key="d5"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="749.6541252136229" y="993.2413736979156"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="23.8046875" x="24.72265625" y="15.93359375">ELB<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n2">
 | 
			
		||||
      <data key="d5"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="749.6541252136229" y="1236.7241210937468"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="27.0390625" x="23.10546875" y="15.93359375">RDS<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n3" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d5"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="101.666015625" width="291.7208747863772" x="640.4186878204343" y="1095.8167317708312"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="291.7208747863772" x="0.0" y="0.0">Web/App</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="23" leftF="23.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" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 5</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
          </y:Realizers>
 | 
			
		||||
        </y:ProxyAutoBoundsNode>
 | 
			
		||||
      </data>
 | 
			
		||||
      <graph edgedefault="directed" id="n3:">
 | 
			
		||||
        <node id="n3::n0">
 | 
			
		||||
          <data key="d5"/>
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="73.25" x="678.4186878204343" y="1132.4827473958312"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n3::n1">
 | 
			
		||||
          <data key="d5"/>
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="73.25" x="761.1541252136229" y="1132.4827473958312"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n3::n2">
 | 
			
		||||
          <data key="d5"/>
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="73.25" x="843.8895626068115" y="1132.4827473958312"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n3" target="n1">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e1" source="n3" target="n2">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e2" source="n0" target="n3::n0">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
  </graph>
 | 
			
		||||
  <data key="d7">
 | 
			
		||||
    <y:Resources/>
 | 
			
		||||
  </data>
 | 
			
		||||
</graphml>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 5.6 KiB  | 
							
								
								
									
										44
									
								
								main.go
									
									
									
									
									
								
							
							
						
						@@ -1,44 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/commands"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
 | 
			
		||||
	_ "github.com/mattn/go-sqlite3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	subcommands.Register(subcommands.HelpCommand(), "")
 | 
			
		||||
	subcommands.Register(subcommands.FlagsCommand(), "")
 | 
			
		||||
	subcommands.Register(subcommands.CommandsCommand(), "")
 | 
			
		||||
	subcommands.Register(&commands.DiscoverCmd{}, "discover")
 | 
			
		||||
	subcommands.Register(&commands.TuiCmd{}, "tui")
 | 
			
		||||
	subcommands.Register(&commands.ScanCmd{}, "scan")
 | 
			
		||||
	subcommands.Register(&commands.PrepareCmd{}, "prepare")
 | 
			
		||||
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	os.Exit(int(subcommands.Execute(ctx)))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										244
									
								
								models/models.go
									
									
									
									
									
								
							
							
						
						@@ -1,244 +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 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 []ScanResult
 | 
			
		||||
	ScannedAt   time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResults is slice of ScanResult.
 | 
			
		||||
type ScanResults []ScanResult
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver is filter function.
 | 
			
		||||
func (results ScanResults) FilterByCvssOver() (filtered ScanResults) {
 | 
			
		||||
	for _, result := range results {
 | 
			
		||||
		cveInfos := []CveInfo{}
 | 
			
		||||
		for _, cveInfo := range result.KnownCves {
 | 
			
		||||
			if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
 | 
			
		||||
				cveInfos = append(cveInfos, cveInfo)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		result.KnownCves = cveInfos
 | 
			
		||||
		filtered = append(filtered, result)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResult has the result of scanned CVE information.
 | 
			
		||||
type ScanResult struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	ScanHistoryID uint
 | 
			
		||||
 | 
			
		||||
	ServerName string // TOML Section key
 | 
			
		||||
	//  Hostname    string
 | 
			
		||||
	Family  string
 | 
			
		||||
	Release string
 | 
			
		||||
	//  Fqdn        string
 | 
			
		||||
	//  NWLinks     []NWLink
 | 
			
		||||
	KnownCves   []CveInfo
 | 
			
		||||
	UnknownCves []CveInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
	ScanResultID uint
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	return c[i].CveDetail.CvssScore(lang) > c[j].CveDetail.CvssScore(lang)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveInfo has Cve Information.
 | 
			
		||||
type CveInfo struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	ScanResultID uint
 | 
			
		||||
 | 
			
		||||
	CveDetail        cve.CveDetail
 | 
			
		||||
	Packages         []PackageInfo
 | 
			
		||||
	DistroAdvisories []DistroAdvisory
 | 
			
		||||
	CpeNames         []CpeName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CpeName has CPE name
 | 
			
		||||
type CpeName struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	CveInfoID uint
 | 
			
		||||
 | 
			
		||||
	Name string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
	CveInfoID uint
 | 
			
		||||
 | 
			
		||||
	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 AMI Security Advisory information.
 | 
			
		||||
//TODO Rename to DistroAdvisory
 | 
			
		||||
type DistroAdvisory struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	CveInfoID uint
 | 
			
		||||
 | 
			
		||||
	AdvisoryID string
 | 
			
		||||
	Severity   string
 | 
			
		||||
	Issued     time.Time
 | 
			
		||||
	Updated    time.Time
 | 
			
		||||
}
 | 
			
		||||
@@ -1,54 +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 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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								pkg/cmd/config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,22 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	configInitCmd "github.com/future-architect/vuls/pkg/cmd/config/init"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdConfig() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "config <subcommand>",
 | 
			
		||||
		Short: "Vuls Config Operation",
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls config init > config.json
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.AddCommand(configInitCmd.NewCmdInit())
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										101
									
								
								pkg/cmd/config/init/init.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,101 @@
 | 
			
		||||
package init
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"text/template"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdInit() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "init",
 | 
			
		||||
		Short: "generate vuls config template",
 | 
			
		||||
		Args:  cobra.NoArgs,
 | 
			
		||||
		RunE: func(_ *cobra.Command, _ []string) error {
 | 
			
		||||
			return generateConfigTemplate()
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls config init > config.json
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func generateConfigTemplate() error {
 | 
			
		||||
	pwd, err := os.Getwd()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		pwd = os.TempDir()
 | 
			
		||||
	}
 | 
			
		||||
	home, err := os.UserHomeDir()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		home = "/home/vuls"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	create := func(name, t string) *template.Template {
 | 
			
		||||
		return template.Must(template.New(name).Parse(t))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t := create("config template",
 | 
			
		||||
		`{
 | 
			
		||||
	"server": {
 | 
			
		||||
		"listen": "127.0.0.1:5515",
 | 
			
		||||
		"path": "{{.dbpath}}"
 | 
			
		||||
	},
 | 
			
		||||
	"hosts": {
 | 
			
		||||
		"local": {
 | 
			
		||||
			"type": "local",
 | 
			
		||||
			"scan": {
 | 
			
		||||
				"ospkg": {
 | 
			
		||||
					"root": false
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			"detect": {
 | 
			
		||||
				"path": "{{.dbpath}}",
 | 
			
		||||
				"result_dir": "{{.results}}"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"remote": {
 | 
			
		||||
			"type": "remote",
 | 
			
		||||
			"host": "127.0.0.1",
 | 
			
		||||
			"port": "22",
 | 
			
		||||
			"user": "vuls",
 | 
			
		||||
			"ssh_config": "{{.sshconfig}}",
 | 
			
		||||
			"ssh_key": "{{.sshkey}}",
 | 
			
		||||
			"scan": {
 | 
			
		||||
				"ospkg": {
 | 
			
		||||
					"root": false
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			"detect": {
 | 
			
		||||
				"path": "{{.dbpath}}",
 | 
			
		||||
				"result_dir": "{{.results}}"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"cpe": {
 | 
			
		||||
			"type": "local",
 | 
			
		||||
			"scan": {
 | 
			
		||||
				"cpe": [
 | 
			
		||||
					{
 | 
			
		||||
						"cpe": "cpe:2.3:a:apache:log4j:2.3:*:*:*:*:*:*:*"
 | 
			
		||||
					}
 | 
			
		||||
				]
 | 
			
		||||
			},
 | 
			
		||||
			"detect": {
 | 
			
		||||
				"path": "{{.dbpath}}",
 | 
			
		||||
				"result_dir": "{{.results}}"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
`)
 | 
			
		||||
 | 
			
		||||
	if err := t.Execute(os.Stdout, map[string]string{"dbpath": filepath.Join(pwd, "vuls.db"), "results": filepath.Join(pwd, "results"), "sshconfig": filepath.Join(home, ".ssh", "config"), "sshkey": filepath.Join(home, ".ssh", "id_rsa")}); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "output config template")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										413
									
								
								pkg/cmd/db/create/create.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,413 @@
 | 
			
		||||
package create
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/cmd/db/create/vulnsrc"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DBCreateOption struct {
 | 
			
		||||
	Path string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCmdCreate() *cobra.Command {
 | 
			
		||||
	opts := &DBCreateOption{
 | 
			
		||||
		Path: "vuls.db",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "create",
 | 
			
		||||
		Short: "Create Vuls DB",
 | 
			
		||||
		Args:  cobra.ExactArgs(1),
 | 
			
		||||
		RunE: func(_ *cobra.Command, args []string) error {
 | 
			
		||||
			return create(args[0], opts.Path)
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls db create https://github.com/vulsio/vuls-data.git
 | 
			
		||||
			$ vuls db create /home/MaineK00n/.cache/vuls
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Path, "path", "p", "vuls.db", "path to create Vuls DB")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func create(src, dbpath string) error {
 | 
			
		||||
	datapath := src
 | 
			
		||||
	if u, err := url.Parse(src); err == nil && u.Scheme != "" {
 | 
			
		||||
		cloneDir := filepath.Join(util.CacheDir(), "clone")
 | 
			
		||||
		if err := exec.Command("git", "clone", "--depth", "1", src, cloneDir).Run(); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "git clone --depth 1 %s %s", src, cloneDir)
 | 
			
		||||
		}
 | 
			
		||||
		datapath = cloneDir
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := os.Stat(datapath); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "%s not found", datapath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db, err := db.Open("boltdb", dbpath, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "open db")
 | 
			
		||||
	}
 | 
			
		||||
	defer db.Close()
 | 
			
		||||
 | 
			
		||||
	if err := filepath.WalkDir(datapath, func(path string, d fs.DirEntry, err error) error {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if d.IsDir() {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		p := strings.TrimPrefix(strings.TrimPrefix(path, datapath), string(os.PathSeparator))
 | 
			
		||||
		srcType, p, found := strings.Cut(p, string(os.PathSeparator))
 | 
			
		||||
		if !found {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		advType, p, found := strings.Cut(p, string(os.PathSeparator))
 | 
			
		||||
		if !found {
 | 
			
		||||
			return errors.Errorf(`unexpected filepath. expected: "%s/["official", ...]/["vulnerability", "os", "library", "cpe"]/...", actual: "%s"`, datapath, path)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bs, err := util.Read(path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "read %s", path)
 | 
			
		||||
		}
 | 
			
		||||
		if len(bs) == 0 {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch advType {
 | 
			
		||||
		case "vulnerability":
 | 
			
		||||
			var src vulnsrc.Vulnerability
 | 
			
		||||
			if err := json.Unmarshal(bs, &src); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "unmarshal json. path: %s", path)
 | 
			
		||||
			}
 | 
			
		||||
			if err := db.PutVulnerability(srcType, fmt.Sprintf("vulnerability:%s", src.ID), vulnsrc.ToVulsVulnerability(src)); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "put vulnerability")
 | 
			
		||||
			}
 | 
			
		||||
		case "os":
 | 
			
		||||
			advType, err := toAdvType(p)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "path to adv type")
 | 
			
		||||
			}
 | 
			
		||||
			bucket, err := toAdvBucket(p)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "path to adv bucket")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			switch advType {
 | 
			
		||||
			case "redhat_oval":
 | 
			
		||||
				if strings.Contains(p, "repository_to_cpe.json") {
 | 
			
		||||
					var src vulnsrc.RepositoryToCPE
 | 
			
		||||
					if err := json.Unmarshal(bs, &src); err != nil {
 | 
			
		||||
						return errors.Wrapf(err, "unmarshal json. path: %s", path)
 | 
			
		||||
					}
 | 
			
		||||
					if err := db.PutRedHatRepoToCPE(srcType, bucket, vulnsrc.ToVulsRepositoryToCPE(src)); err != nil {
 | 
			
		||||
						return errors.Wrap(err, "put repository to cpe")
 | 
			
		||||
					}
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				var src vulnsrc.DetectPackage
 | 
			
		||||
				if err := json.Unmarshal(bs, &src); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "unmarshal json. path: %s", path)
 | 
			
		||||
				}
 | 
			
		||||
				pkgs, err := vulnsrc.ToVulsPackage(src, advType)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return errors.Wrap(err, "to vuls package")
 | 
			
		||||
				}
 | 
			
		||||
				if err := db.PutPackage(srcType, bucket, pkgs); err != nil {
 | 
			
		||||
					return errors.Wrap(err, "put package")
 | 
			
		||||
				}
 | 
			
		||||
			case "windows":
 | 
			
		||||
				if strings.Contains(p, "supercedence.json") {
 | 
			
		||||
					var supercedences []vulnsrc.Supercedence
 | 
			
		||||
					if err := json.Unmarshal(bs, &supercedences); err != nil {
 | 
			
		||||
						return errors.Wrapf(err, "unnmarshal json. path: %s", path)
 | 
			
		||||
					}
 | 
			
		||||
					if err := db.PutWindowsSupercedence(srcType, bucket, vulnsrc.ToVulsSupercedences(supercedences)); err != nil {
 | 
			
		||||
						return errors.Wrap(err, "put supercedence")
 | 
			
		||||
					}
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				var src vulnsrc.DetectPackage
 | 
			
		||||
				if err := json.Unmarshal(bs, &src); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "unmarshal json. path: %s", path)
 | 
			
		||||
				}
 | 
			
		||||
				pkgs, err := vulnsrc.ToVulsPackage(src, advType)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return errors.Wrap(err, "to vuls package")
 | 
			
		||||
				}
 | 
			
		||||
				if err := db.PutPackage(srcType, bucket, pkgs); err != nil {
 | 
			
		||||
					return errors.Wrap(err, "put package")
 | 
			
		||||
				}
 | 
			
		||||
			default:
 | 
			
		||||
				var src vulnsrc.DetectPackage
 | 
			
		||||
				if err := json.Unmarshal(bs, &src); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "unmarshal json. path: %s", path)
 | 
			
		||||
				}
 | 
			
		||||
				pkgs, err := vulnsrc.ToVulsPackage(src, advType)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return errors.Wrap(err, "to vuls package")
 | 
			
		||||
				}
 | 
			
		||||
				if err := db.PutPackage(srcType, bucket, pkgs); err != nil {
 | 
			
		||||
					return errors.Wrap(err, "put package")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		case "library":
 | 
			
		||||
		case "cpe":
 | 
			
		||||
			var src vulnsrc.DetectCPE
 | 
			
		||||
			if err := json.Unmarshal(bs, &src); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "unmarshal json. path: %s", path)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			advType, err := toAdvType(p)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "path to adv type")
 | 
			
		||||
			}
 | 
			
		||||
			cs, err := vulnsrc.ToVulsCPEConfiguration(src, advType)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "to vuls cpe configuration")
 | 
			
		||||
			}
 | 
			
		||||
			bucket, err := toAdvBucket(p)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "path to adv bucket")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := db.PutCPEConfiguration(srcType, bucket, cs); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "put cpe configuration")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toAdvType(path string) (string, error) {
 | 
			
		||||
	ss := strings.Split(path, string(os.PathSeparator))
 | 
			
		||||
	if len(ss) < 3 && ss[0] != "windows" {
 | 
			
		||||
		return "", errors.Errorf(`unexpected path. accepts: "[<os name>, <library name>, "nvd", "jvn"]/**/*.json*", received: "%s"`, path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch ss[0] {
 | 
			
		||||
	case "alma", "alpine", "amazon", "epel", "fedora", "oracle", "rocky":
 | 
			
		||||
		return fmt.Sprintf("%s:%s", ss[0], ss[1]), nil
 | 
			
		||||
	case "arch", "freebsd", "gentoo", "windows", "conan", "erlang", "nvd", "jvn":
 | 
			
		||||
		return ss[0], nil
 | 
			
		||||
	case "debian":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "oval":
 | 
			
		||||
			return "debian_oval", nil
 | 
			
		||||
		case "tracker":
 | 
			
		||||
			return "debian_security_tracker", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected debian advisory type. accepts: ["oval", "tracker"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "redhat":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "api":
 | 
			
		||||
			return "redhat_security_api", nil
 | 
			
		||||
		case "oval":
 | 
			
		||||
			return "redhat_oval", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected redhat advisory type. accepts: ["api", "oval"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "suse":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "cvrf":
 | 
			
		||||
			return "suse_cvrf", nil
 | 
			
		||||
		case "oval":
 | 
			
		||||
			return "suse_oval", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected suse advisory type. accepts: ["cvrf", "oval"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "oval":
 | 
			
		||||
			return "ubuntu_oval", nil
 | 
			
		||||
		case "tracker":
 | 
			
		||||
			return "ubuntu_security_tracker", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected debian advisory type. accepts: ["oval", "tracker"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "cargo":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "db":
 | 
			
		||||
			return "cargo_db", nil
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "cargo_ghsa", nil
 | 
			
		||||
		case "osv":
 | 
			
		||||
			return "cargo_osv", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected cargo advisory type. accepts: ["db", "ghsa", "osv"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "composer":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "db":
 | 
			
		||||
			return "composer_db", nil
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "composer_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "composer_glsa", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected composer advisory type. accepts: ["db", "ghsa", "glsa"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "golang":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "db":
 | 
			
		||||
			return "golang_db", nil
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "golang_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "golang_glsa", nil
 | 
			
		||||
		case "govulndb":
 | 
			
		||||
			return "golang_govulndb", nil
 | 
			
		||||
		case "osv":
 | 
			
		||||
			return "golang_osv", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected golang advisory type. accepts: ["db", "ghsa", "glsa", "govulndb", "osv"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "maven":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "maven_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "maven_glsa", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected maven advisory type. accepts: ["ghsa", "glsa"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "npm":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "db":
 | 
			
		||||
			return "npm_db", nil
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "npm_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "npm_glsa", nil
 | 
			
		||||
		case "osv":
 | 
			
		||||
			return "npm_osv", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected npm advisory type. accepts: ["db", "ghsa", "glsa", "osv"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "nuget":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "nuget_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "nuget_glsa", nil
 | 
			
		||||
		case "osv":
 | 
			
		||||
			return "nuget_osv", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected nuget advisory type. accepts: ["ghsa", "glsa", "osv"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "pip":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "db":
 | 
			
		||||
			return "pip_db", nil
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "pip_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "pip_glsa", nil
 | 
			
		||||
		case "osv":
 | 
			
		||||
			return "pip_osv", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected pip advisory type. accepts: ["db", "ghsa", "glsa", "osv"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "rubygems":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "db":
 | 
			
		||||
			return "rubygems_db", nil
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "rubygems_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "rubygems_glsa", nil
 | 
			
		||||
		case "osv":
 | 
			
		||||
			return "rubygems_osv", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected rubygems advisory type. accepts: ["db", "ghsa", "glsa", "osv"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return "", errors.Errorf(`unexpected os or library or cpe. accepts: ["alma", "alpine", "amazon", "arch", "debian", "epel", "fedora", "freebsd", "gentoo", "oracle", "redhat", "rocky", "suse", "ubuntu", "windows", "cargo", "composer", "conan", "erlang", "golang", "maven", "npm", "nuget", "pip", "rubygems", "nvd", "jvn"], received: "%s"`, ss[0])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toAdvBucket(path string) (string, error) {
 | 
			
		||||
	ss := strings.Split(path, string(os.PathSeparator))
 | 
			
		||||
	if len(ss) < 3 && ss[0] != "windows" {
 | 
			
		||||
		return "", errors.Errorf(`unexpected path. accepts: "[<os name>, <library name>, "nvd", "jvn"]/**/*.json*", received: "%s"`, path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch ss[0] {
 | 
			
		||||
	case "alma", "alpine", "amazon", "epel", "fedora", "oracle", "rocky":
 | 
			
		||||
		return fmt.Sprintf("%s:%s", ss[0], ss[1]), nil
 | 
			
		||||
	case "arch", "freebsd", "gentoo", "cargo", "composer", "conan", "erlang", "golang", "maven", "npm", "nuget", "pip", "rubygems":
 | 
			
		||||
		return ss[0], nil
 | 
			
		||||
	case "debian":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "oval", "tracker":
 | 
			
		||||
			return fmt.Sprintf("%s:%s", ss[0], ss[2]), nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected debian advisory type. accepts: ["oval", "tracker"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "redhat":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "api":
 | 
			
		||||
			return fmt.Sprintf("%s:%s", ss[0], ss[2]), nil
 | 
			
		||||
		case "oval":
 | 
			
		||||
			if len(ss) < 4 {
 | 
			
		||||
				return "", errors.Errorf(`unexpected path. accepts: "redhat/oval/<os version>/<stream>/yyyy/*.json*", received: "%s"`, path)
 | 
			
		||||
			}
 | 
			
		||||
			if strings.Contains(path, "repository_to_cpe.json") {
 | 
			
		||||
				return fmt.Sprintf("%s_cpe:%s", ss[0], ss[3]), nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("%s:%s", ss[0], ss[3]), nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected redhat advisory type. accepts: ["api", "oval"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "suse":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "cvrf", "oval":
 | 
			
		||||
			if len(ss) < 4 {
 | 
			
		||||
				return "", errors.Errorf(`unexpected path. accepts: "suse/[cvrf, oval]/<os>/<version>/yyyy/*.json*", received: "%s"`, path)
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("%s:%s", ss[2], ss[3]), nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected suse advisory type. accepts: ["cvrf", "oval"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "oval", "tracker":
 | 
			
		||||
			return fmt.Sprintf("%s:%s", ss[0], ss[2]), nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected debian advisory type. accepts: ["oval", "tracker"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "windows":
 | 
			
		||||
		if strings.Contains(path, "supercedence.json") {
 | 
			
		||||
			return "windows_supercedence", nil
 | 
			
		||||
		}
 | 
			
		||||
		return fmt.Sprintf("%s:%s", ss[0], ss[1]), nil
 | 
			
		||||
	case "nvd", "jvn":
 | 
			
		||||
		return "cpe", nil
 | 
			
		||||
	default:
 | 
			
		||||
		return "", errors.Errorf(`unexpected os or library or cpe. accepts: ["alma", "alpine", "amazon", "arch", "debian", "epel", "fedora", "freebsd", "gentoo", "oracle", "redhat", "rocky", "suse", "ubuntu", "windows", "cargo", "composer", "conan", "erlang", "golang", "maven", "npm", "nuget", "pip", "rubygems", "nvd", "jvn"], received: "%s"`, ss[0])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										269
									
								
								pkg/cmd/db/create/vulnsrc/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,269 @@
 | 
			
		||||
package vulnsrc
 | 
			
		||||
 | 
			
		||||
// https://github.com/MaineK00n/vuls-data-update/blob/main/pkg/build/types.go
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
type Vulnerability struct {
 | 
			
		||||
	ID          string        `json:"id,omitempty"`
 | 
			
		||||
	Advisory    *Advisories   `json:"advisory,omitempty"`
 | 
			
		||||
	Title       *Titles       `json:"title,omitempty"`
 | 
			
		||||
	Description *Descriptions `json:"description,omitempty"`
 | 
			
		||||
	CVSS        *CVSSes       `json:"cvss,omitempty"`
 | 
			
		||||
	EPSS        *EPSS         `json:"epss,omitempty"`
 | 
			
		||||
	CWE         *CWEs         `json:"cwe,omitempty"`
 | 
			
		||||
	Metasploit  []Metasploit  `json:"metasploit,omitempty"`
 | 
			
		||||
	Exploit     *Exploit      `json:"exploit,omitempty"`
 | 
			
		||||
	KEV         *KEV          `json:"kev,omitempty"`
 | 
			
		||||
	Mitigation  *Mitigation   `json:"mitigation,omitempty"`
 | 
			
		||||
	Published   *Publisheds   `json:"published,omitempty"`
 | 
			
		||||
	Modified    *Modifieds    `json:"modified,omitempty"`
 | 
			
		||||
	References  *References   `json:"references,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Advisories struct {
 | 
			
		||||
	MITRE                 *Advisory             `json:"mitre,omitempty"`
 | 
			
		||||
	NVD                   *Advisory             `json:"nvd,omitempty"`
 | 
			
		||||
	JVN                   []Advisory            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma                  map[string][]Advisory `json:"alma,omitempty"`
 | 
			
		||||
	Alpine                map[string]Advisory   `json:"alpine,omitempty"`
 | 
			
		||||
	Amazon                map[string][]Advisory `json:"amazon,omitempty"`
 | 
			
		||||
	Arch                  []Advisory            `json:"arch,omitempty"`
 | 
			
		||||
	DebianOVAL            map[string][]Advisory `json:"debian_oval,omitempty"`
 | 
			
		||||
	DebianSecurityTracker map[string]Advisory   `json:"debian_security_tracker,omitempty"`
 | 
			
		||||
	FreeBSD               []Advisory            `json:"freebsd,omitempty"`
 | 
			
		||||
	Oracle                map[string][]Advisory `json:"oracle,omitempty"`
 | 
			
		||||
	RedHatOVAL            map[string][]Advisory `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL              map[string][]Advisory `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF              *Advisory             `json:"suse_cvrf,omitempty"`
 | 
			
		||||
	UbuntuOVAL            map[string][]Advisory `json:"ubuntu_oval,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker *Advisory             `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
type Advisory struct {
 | 
			
		||||
	ID  string `json:"id,omitempty"`
 | 
			
		||||
	URL string `json:"url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Titles struct {
 | 
			
		||||
	MITRE                 string                       `json:"mitre,omitempty"`
 | 
			
		||||
	NVD                   string                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN                   map[string]string            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma                  map[string]map[string]string `json:"alma,omitempty"`
 | 
			
		||||
	Alpine                map[string]string            `json:"alpine,omitempty"`
 | 
			
		||||
	Amazon                map[string]map[string]string `json:"amazon,omitempty"`
 | 
			
		||||
	Arch                  map[string]string            `json:"arch,omitempty"`
 | 
			
		||||
	DebianOVAL            map[string]map[string]string `json:"debian_oval,omitempty"`
 | 
			
		||||
	DebianSecurityTracker map[string]string            `json:"debian_security_tracker,omitempty"`
 | 
			
		||||
	FreeBSD               map[string]string            `json:"freebsd,omitempty"`
 | 
			
		||||
	Oracle                map[string]map[string]string `json:"oracle,omitempty"`
 | 
			
		||||
	RedHatOVAL            map[string]map[string]string `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL              map[string]map[string]string `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF              string                       `json:"suse_cvrf,omitempty"`
 | 
			
		||||
	UbuntuOVAL            map[string]map[string]string `json:"ubuntu_oval,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker string                       `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Descriptions struct {
 | 
			
		||||
	MITRE                 string                       `json:"mitre,omitempty"`
 | 
			
		||||
	NVD                   string                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN                   map[string]string            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma                  map[string]map[string]string `json:"alma,omitempty"`
 | 
			
		||||
	Amazon                map[string]map[string]string `json:"amazon,omitempty"`
 | 
			
		||||
	DebianOVAL            map[string]map[string]string `json:"debian_oval,omitempty"`
 | 
			
		||||
	DebianSecurityTracker map[string]string            `json:"debian_security_tracker,omitempty"`
 | 
			
		||||
	FreeBSD               map[string]string            `json:"freebsd,omitempty"`
 | 
			
		||||
	Oracle                map[string]map[string]string `json:"oracle,omitempty"`
 | 
			
		||||
	RedHatOVAL            map[string]map[string]string `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL              map[string]map[string]string `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF              string                       `json:"suse_cvrf,omitempty"`
 | 
			
		||||
	UbuntuOVAL            map[string]map[string]string `json:"ubuntu_oval,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker string                       `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CVSSes struct {
 | 
			
		||||
	NVD                   []CVSS                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN                   map[string][]CVSS            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma                  map[string]map[string][]CVSS `json:"alma,omitempty"`
 | 
			
		||||
	Amazon                map[string]map[string][]CVSS `json:"amazon,omitempty"`
 | 
			
		||||
	Arch                  map[string][]CVSS            `json:"arch,omitempty"`
 | 
			
		||||
	Oracle                map[string]map[string][]CVSS `json:"oracle,omitempty"`
 | 
			
		||||
	RedHatOVAL            map[string]map[string][]CVSS `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL              map[string]map[string][]CVSS `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF              []CVSS                       `json:"suse_cvrf,omitempty"`
 | 
			
		||||
	UbuntuOVAL            map[string]map[string][]CVSS `json:"ubuntu_oval,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker []CVSS                       `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CVSS struct {
 | 
			
		||||
	Version  string   `json:"version,omitempty"`
 | 
			
		||||
	Source   string   `json:"source,omitempty"`
 | 
			
		||||
	Vector   string   `json:"vector,omitempty"`
 | 
			
		||||
	Score    *float64 `json:"score,omitempty"`
 | 
			
		||||
	Severity string   `json:"severity,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EPSS struct {
 | 
			
		||||
	EPSS       *float64 `json:"epss,omitempty"`
 | 
			
		||||
	Percentile *float64 `json:"percentile,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CWEs struct {
 | 
			
		||||
	NVD        []string                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN        map[string][]string            `json:"jvn,omitempty"`
 | 
			
		||||
	RedHatOVAL map[string]map[string][]string `json:"redhat_oval,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Metasploit struct {
 | 
			
		||||
	Name        string   `json:"name,omitempty"`
 | 
			
		||||
	Title       string   `json:"title,omitempty"`
 | 
			
		||||
	Description string   `json:"description,omitempty"`
 | 
			
		||||
	URLs        []string `json:"urls,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Exploit struct {
 | 
			
		||||
	NVD       []string    `json:"nvd,omitempty"`
 | 
			
		||||
	ExploitDB []ExploitDB `json:"exploit_db,omitempty"`
 | 
			
		||||
	GitHub    []GitHub    `json:"github,omitempty"`
 | 
			
		||||
	InTheWild []InTheWild `json:"inthewild,omitempty"`
 | 
			
		||||
	Trickest  *Trickest   `json:"trickest,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ExploitDB struct {
 | 
			
		||||
	ID          string `json:"name,omitempty"`
 | 
			
		||||
	Type        string `json:"type,omitempty"`
 | 
			
		||||
	Description string `json:"description,omitempty"`
 | 
			
		||||
	URL         string `json:"url,omitempty"`
 | 
			
		||||
	FileURL     string `json:"file_url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type GitHub struct {
 | 
			
		||||
	Name    string `json:"name,omitempty"`
 | 
			
		||||
	Stars   int    `json:"stars"`
 | 
			
		||||
	Forks   int    `json:"forks"`
 | 
			
		||||
	Watches int    `json:"watches"`
 | 
			
		||||
	URL     string `json:"url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InTheWild struct {
 | 
			
		||||
	Source string `json:"source,omitempty"`
 | 
			
		||||
	URL    string `json:"url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Trickest struct {
 | 
			
		||||
	Description string       `json:"description,omitempty"`
 | 
			
		||||
	PoC         *TrickestPoc `json:"poc,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TrickestPoc struct {
 | 
			
		||||
	Reference []string `json:"reference,omitempty"`
 | 
			
		||||
	GitHub    []string `json:"github,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type KEV struct {
 | 
			
		||||
	Title          string     `json:"title,omitempty"`
 | 
			
		||||
	Description    string     `json:"description,omitempty"`
 | 
			
		||||
	RequiredAction string     `json:"required_action,omitempty"`
 | 
			
		||||
	DueDate        *time.Time `json:"due_date,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Mitigation struct {
 | 
			
		||||
	NVD                   []string `json:"nvd,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker string   `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Publisheds struct {
 | 
			
		||||
	MITRE                 *time.Time                       `json:"mitre,omitempty"`
 | 
			
		||||
	NVD                   *time.Time                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN                   map[string]*time.Time            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma                  map[string]map[string]*time.Time `json:"alma,omitempty"`
 | 
			
		||||
	Amazon                map[string]map[string]*time.Time `json:"amazon,omitempty"`
 | 
			
		||||
	FreeBSD               map[string]*time.Time            `json:"freebsd,omitempty"`
 | 
			
		||||
	Oracle                map[string]map[string]*time.Time `json:"oracle,omitempty"`
 | 
			
		||||
	RedHatOVAL            map[string]map[string]*time.Time `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL              map[string]map[string]*time.Time `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF              *time.Time                       `json:"suse_cvrf,omitempty"`
 | 
			
		||||
	UbuntuOVAL            map[string]map[string]*time.Time `json:"ubuntu_oval,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker *time.Time                       `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Modifieds struct {
 | 
			
		||||
	MITRE      *time.Time                       `json:"mitre,omitempty"`
 | 
			
		||||
	NVD        *time.Time                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN        map[string]*time.Time            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma       map[string]map[string]*time.Time `json:"alma,omitempty"`
 | 
			
		||||
	Amazon     map[string]map[string]*time.Time `json:"amazon,omitempty"`
 | 
			
		||||
	FreeBSD    map[string]*time.Time            `json:"freebsd,omitempty"`
 | 
			
		||||
	RedHatOVAL map[string]map[string]*time.Time `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL   map[string]map[string]*time.Time `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF   *time.Time                       `json:"suse_cvrf,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type References struct {
 | 
			
		||||
	MITRE                 []Reference                       `json:"mitre,omitempty"`
 | 
			
		||||
	NVD                   []Reference                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN                   map[string][]Reference            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma                  map[string]map[string][]Reference `json:"alma,omitempty"`
 | 
			
		||||
	Amazon                map[string]map[string][]Reference `json:"amazon,omitempty"`
 | 
			
		||||
	Arch                  map[string][]Reference            `json:"arch,omitempty"`
 | 
			
		||||
	DebianOVAL            map[string]map[string][]Reference `json:"debian_oval,omitempty"`
 | 
			
		||||
	DebianSecurityTracker map[string][]Reference            `json:"debian_security_tracker,omitempty"`
 | 
			
		||||
	FreeBSD               map[string][]Reference            `json:"freebsd,omitempty"`
 | 
			
		||||
	Oracle                map[string]map[string][]Reference `json:"oracle,omitempty"`
 | 
			
		||||
	RedHatOVAL            map[string]map[string][]Reference `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL              map[string]map[string][]Reference `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF              []Reference                       `json:"suse_cvrf,omitempty"`
 | 
			
		||||
	UbuntuOVAL            map[string]map[string][]Reference `json:"ubuntu_oval,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker []Reference                       `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Reference struct {
 | 
			
		||||
	Source string   `json:"source,omitempty"`
 | 
			
		||||
	Name   string   `json:"name,omitempty"`
 | 
			
		||||
	Tags   []string `json:"tags,omitempty"`
 | 
			
		||||
	URL    string   `json:"url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DetectCPE struct {
 | 
			
		||||
	ID             string                        `json:"id,omitempty"`
 | 
			
		||||
	Configurations map[string][]CPEConfiguration `json:"configurations,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CPEConfiguration struct {
 | 
			
		||||
	Vulnerable []CPE `json:"vulnerable,omitempty"`
 | 
			
		||||
	RunningOn  []CPE `json:"running_on,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CPE struct {
 | 
			
		||||
	CPEVersion string    `json:"cpe_version,omitempty"`
 | 
			
		||||
	CPE        string    `json:"cpe,omitempty"`
 | 
			
		||||
	Version    []Version `json:"version,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DetectPackage struct {
 | 
			
		||||
	ID       string               `json:"id,omitempty"`
 | 
			
		||||
	Packages map[string][]Package `json:"packages,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Package struct {
 | 
			
		||||
	Name            string      `json:"name,omitempty"`
 | 
			
		||||
	Status          string      `json:"status,omitempty"`
 | 
			
		||||
	Version         [][]Version `json:"version,omitempty"`
 | 
			
		||||
	ModularityLabel string      `json:"modularity_label,omitempty"`
 | 
			
		||||
	Arch            []string    `json:"arch,omitempty"`
 | 
			
		||||
	Repository      string      `json:"repository,omitempty"`
 | 
			
		||||
	CPE             []string    `json:"cpe,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Version struct {
 | 
			
		||||
	Operator string `json:"operator,omitempty"`
 | 
			
		||||
	Version  string `json:"version,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RepositoryToCPE map[string][]string
 | 
			
		||||
 | 
			
		||||
type Supercedence struct {
 | 
			
		||||
	KBID         string `json:"KBID,omitempty"`
 | 
			
		||||
	Supersededby struct {
 | 
			
		||||
		KBIDs []string `json:"KBIDs,omitempty"`
 | 
			
		||||
	} `json:"Supersededby,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										619
									
								
								pkg/cmd/db/create/vulnsrc/vulnsrc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,619 @@
 | 
			
		||||
package vulnsrc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
 | 
			
		||||
	"github.com/knqyf263/go-cpe/common"
 | 
			
		||||
	"github.com/knqyf263/go-cpe/naming"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ToVulsVulnerability(src Vulnerability) types.Vulnerability {
 | 
			
		||||
	return types.Vulnerability{
 | 
			
		||||
		ID:          src.ID,
 | 
			
		||||
		Advisory:    toVulsAdvisory(src.Advisory),
 | 
			
		||||
		Title:       toVulsTitle(src.Title),
 | 
			
		||||
		Description: toVulsDescription(src.Description),
 | 
			
		||||
		CVSS:        toVulsCVSS(src.CVSS),
 | 
			
		||||
		EPSS:        toVulsEPSS(src.EPSS),
 | 
			
		||||
		CWE:         toVulsCWE(src.CWE),
 | 
			
		||||
		Metasploit:  toVulsMetasploit(src.Metasploit),
 | 
			
		||||
		Exploit:     toVulsExploit(src.Exploit),
 | 
			
		||||
		KEV:         src.KEV != nil,
 | 
			
		||||
		Published:   toVulsPublished(src.Published),
 | 
			
		||||
		Modified:    toVulsModified(src.Modified),
 | 
			
		||||
		Reference:   toVulsReference(src.References),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsAdvisory(src *Advisories) []string {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var advs []string
 | 
			
		||||
	if src.MITRE != nil {
 | 
			
		||||
		advs = append(advs, "mitre")
 | 
			
		||||
	}
 | 
			
		||||
	if src.NVD != nil {
 | 
			
		||||
		advs = append(advs, "nvd")
 | 
			
		||||
	}
 | 
			
		||||
	for _, a := range src.JVN {
 | 
			
		||||
		advs = append(advs, fmt.Sprintf("jvn:%s", a.ID))
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.Alma {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("alma:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, a := range src.Alpine {
 | 
			
		||||
		advs = append(advs, fmt.Sprintf("alpine:%s:%s", v, a.ID))
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.Amazon {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("amazon:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, a := range src.Arch {
 | 
			
		||||
		advs = append(advs, fmt.Sprintf("arch:%s", a.ID))
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.DebianOVAL {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("debian_oval:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, a := range src.DebianSecurityTracker {
 | 
			
		||||
		advs = append(advs, fmt.Sprintf("debian_security_tracker:%s:%s", v, a.ID))
 | 
			
		||||
	}
 | 
			
		||||
	for _, a := range src.FreeBSD {
 | 
			
		||||
		advs = append(advs, fmt.Sprintf("freebsd:%s", a.ID))
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.Oracle {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("oracle:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.RedHatOVAL {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("redhat_oval:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.SUSEOVAL {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("suse_oval:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if src.SUSECVRF != nil {
 | 
			
		||||
		advs = append(advs, "suse_cvrf")
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.UbuntuOVAL {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("ubuntu_oval:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if src.UbuntuSecurityTracker != nil {
 | 
			
		||||
		advs = append(advs, "ubuntu_security_tracker")
 | 
			
		||||
	}
 | 
			
		||||
	return advs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsTitle(src *Titles) string {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if src.NVD != "" {
 | 
			
		||||
		return src.NVD
 | 
			
		||||
	}
 | 
			
		||||
	if src.MITRE != "" {
 | 
			
		||||
		return src.MITRE
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsDescription(src *Descriptions) string {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if src.NVD != "" {
 | 
			
		||||
		return src.NVD
 | 
			
		||||
	}
 | 
			
		||||
	if src.MITRE != "" {
 | 
			
		||||
		return src.MITRE
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsCVSS(src *CVSSes) []types.CVSS {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cvsses []types.CVSS
 | 
			
		||||
	for _, c := range src.NVD {
 | 
			
		||||
		cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
			Source:   "nvd",
 | 
			
		||||
			Version:  c.Version,
 | 
			
		||||
			Vector:   c.Vector,
 | 
			
		||||
			Score:    c.Score,
 | 
			
		||||
			Severity: c.Severity,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	for id, cs := range src.JVN {
 | 
			
		||||
		for _, c := range cs {
 | 
			
		||||
			cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
				Source:   fmt.Sprintf("jvn:%s", id),
 | 
			
		||||
				Version:  c.Version,
 | 
			
		||||
				Vector:   c.Vector,
 | 
			
		||||
				Score:    c.Score,
 | 
			
		||||
				Severity: c.Severity,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.Alma {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
					Source:   fmt.Sprintf("alma:%s:%s", v, id),
 | 
			
		||||
					Version:  c.Version,
 | 
			
		||||
					Vector:   c.Vector,
 | 
			
		||||
					Score:    c.Score,
 | 
			
		||||
					Severity: c.Severity,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.Amazon {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
					Source:   fmt.Sprintf("amazon:%s:%s", v, id),
 | 
			
		||||
					Version:  c.Version,
 | 
			
		||||
					Vector:   c.Vector,
 | 
			
		||||
					Score:    c.Score,
 | 
			
		||||
					Severity: c.Severity,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for id, cs := range src.Arch {
 | 
			
		||||
		for _, c := range cs {
 | 
			
		||||
			cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
				Source:   fmt.Sprintf("arch:%s", id),
 | 
			
		||||
				Version:  c.Version,
 | 
			
		||||
				Vector:   c.Vector,
 | 
			
		||||
				Score:    c.Score,
 | 
			
		||||
				Severity: c.Severity,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.Oracle {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
					Source:   fmt.Sprintf("oracle:%s:%s", v, id),
 | 
			
		||||
					Version:  c.Version,
 | 
			
		||||
					Vector:   c.Vector,
 | 
			
		||||
					Score:    c.Score,
 | 
			
		||||
					Severity: c.Severity,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.RedHatOVAL {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
					Source:   fmt.Sprintf("redhat_oval:%s:%s", v, id),
 | 
			
		||||
					Version:  c.Version,
 | 
			
		||||
					Vector:   c.Vector,
 | 
			
		||||
					Score:    c.Score,
 | 
			
		||||
					Severity: c.Severity,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.SUSEOVAL {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
					Source:   fmt.Sprintf("suse_oval:%s:%s", v, id),
 | 
			
		||||
					Version:  c.Version,
 | 
			
		||||
					Vector:   c.Vector,
 | 
			
		||||
					Score:    c.Score,
 | 
			
		||||
					Severity: c.Severity,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, c := range src.SUSECVRF {
 | 
			
		||||
		cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
			Source:   "suse_cvrf",
 | 
			
		||||
			Version:  c.Version,
 | 
			
		||||
			Vector:   c.Vector,
 | 
			
		||||
			Score:    c.Score,
 | 
			
		||||
			Severity: c.Severity,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.UbuntuOVAL {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
					Source:   fmt.Sprintf("ubuntu_oval:%s:%s", v, id),
 | 
			
		||||
					Version:  c.Version,
 | 
			
		||||
					Vector:   c.Vector,
 | 
			
		||||
					Score:    c.Score,
 | 
			
		||||
					Severity: c.Severity,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, c := range src.UbuntuSecurityTracker {
 | 
			
		||||
		cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
			Source:   "ubuntu_security_tracker",
 | 
			
		||||
			Version:  c.Version,
 | 
			
		||||
			Vector:   c.Vector,
 | 
			
		||||
			Score:    c.Score,
 | 
			
		||||
			Severity: c.Severity,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cvsses
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsEPSS(src *EPSS) *types.EPSS {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return &types.EPSS{EPSS: src.EPSS, Percentile: src.Percentile}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsCWE(src *CWEs) []types.CWE {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m := map[string][]string{}
 | 
			
		||||
	for _, c := range src.NVD {
 | 
			
		||||
		m[c] = append(m[c], "nvd")
 | 
			
		||||
	}
 | 
			
		||||
	for id, cs := range src.JVN {
 | 
			
		||||
		for _, c := range cs {
 | 
			
		||||
			m[c] = append(m[c], fmt.Sprintf("jvn:%s", id))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.RedHatOVAL {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				m[c] = append(m[c], fmt.Sprintf("redhat_oval:%s:%s", v, id))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cwes []types.CWE
 | 
			
		||||
	for id, srcs := range m {
 | 
			
		||||
		cwes = append(cwes, types.CWE{
 | 
			
		||||
			Source: srcs,
 | 
			
		||||
			ID:     id,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return cwes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsMetasploit(src []Metasploit) []types.Metasploit {
 | 
			
		||||
	ms := make([]types.Metasploit, 0, len(src))
 | 
			
		||||
	for _, m := range src {
 | 
			
		||||
		ms = append(ms, types.Metasploit{
 | 
			
		||||
			Title: m.Title,
 | 
			
		||||
			URL:   m.URLs[0],
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return ms
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsExploit(src *Exploit) []types.Exploit {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m := map[string][]string{}
 | 
			
		||||
	for _, e := range src.NVD {
 | 
			
		||||
		m[e] = append(m[e], "nvd")
 | 
			
		||||
	}
 | 
			
		||||
	for _, e := range src.ExploitDB {
 | 
			
		||||
		m[e.URL] = append(m[e.URL], "exploit-db")
 | 
			
		||||
	}
 | 
			
		||||
	for _, e := range src.GitHub {
 | 
			
		||||
		m[e.URL] = append(m[e.URL], "github")
 | 
			
		||||
	}
 | 
			
		||||
	for _, e := range src.InTheWild {
 | 
			
		||||
		m[e.URL] = append(m[e.URL], "inthewild")
 | 
			
		||||
	}
 | 
			
		||||
	if src.Trickest != nil {
 | 
			
		||||
		if src.Trickest.PoC != nil {
 | 
			
		||||
			for _, e := range src.Trickest.PoC.Reference {
 | 
			
		||||
				m[e] = append(m[e], "trickest")
 | 
			
		||||
			}
 | 
			
		||||
			for _, e := range src.Trickest.PoC.GitHub {
 | 
			
		||||
				m[e] = append(m[e], "trickest")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var es []types.Exploit
 | 
			
		||||
	for u, srcs := range m {
 | 
			
		||||
		es = append(es, types.Exploit{
 | 
			
		||||
			Source: srcs,
 | 
			
		||||
			URL:    u,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return es
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsPublished(src *Publisheds) *time.Time {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if src.NVD != nil {
 | 
			
		||||
		return src.NVD
 | 
			
		||||
	}
 | 
			
		||||
	if src.MITRE != nil {
 | 
			
		||||
		return src.MITRE
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsModified(src *Modifieds) *time.Time {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if src.NVD != nil {
 | 
			
		||||
		return src.NVD
 | 
			
		||||
	}
 | 
			
		||||
	if src.MITRE != nil {
 | 
			
		||||
		return src.MITRE
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsReference(src *References) []string {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m := map[string]struct{}{}
 | 
			
		||||
	for _, r := range src.MITRE {
 | 
			
		||||
		m[r.URL] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range src.NVD {
 | 
			
		||||
		m[r.URL] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	for _, rs := range src.JVN {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			m[r.URL] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.Alma {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.Amazon {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, rs := range src.Arch {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			m[r.URL] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.DebianOVAL {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, rs := range src.DebianSecurityTracker {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			m[r.URL] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, rs := range src.FreeBSD {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			m[r.URL] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.Oracle {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.RedHatOVAL {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.SUSEOVAL {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range src.SUSECVRF {
 | 
			
		||||
		m[r.URL] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.UbuntuOVAL {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range src.UbuntuSecurityTracker {
 | 
			
		||||
		m[r.URL] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return maps.Keys(m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToVulsPackage(src DetectPackage, advType string) (map[string]types.Packages, error) {
 | 
			
		||||
	m := map[string]types.Packages{}
 | 
			
		||||
	for id, ps := range src.Packages {
 | 
			
		||||
		id = fmt.Sprintf("%s:%s", advType, id)
 | 
			
		||||
		for _, p := range ps {
 | 
			
		||||
			name, err := toVulsPackageName(p.Name, p.ModularityLabel)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, errors.Wrap(err, "to vuls package name")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			base, ok := m[name]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				base = types.Packages{
 | 
			
		||||
					ID:      src.ID,
 | 
			
		||||
					Package: map[string]types.Package{},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			vers := make([][]types.Version, 0, len(p.Version))
 | 
			
		||||
			for _, vs := range p.Version {
 | 
			
		||||
				vss := make([]types.Version, 0, len(vs))
 | 
			
		||||
				for _, v := range vs {
 | 
			
		||||
					vss = append(vss, types.Version{
 | 
			
		||||
						Operator: v.Operator,
 | 
			
		||||
						Version:  v.Version,
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
				vers = append(vers, vss)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			base.Package[id] = types.Package{
 | 
			
		||||
				Status:     p.Status,
 | 
			
		||||
				Version:    vers,
 | 
			
		||||
				Arch:       p.Arch,
 | 
			
		||||
				Repository: p.Repository,
 | 
			
		||||
				CPE:        p.CPE,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			m[name] = base
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return m, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsPackageName(name, modularitylabel string) (string, error) {
 | 
			
		||||
	if modularitylabel == "" {
 | 
			
		||||
		return name, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ss := strings.Split(modularitylabel, ":")
 | 
			
		||||
	if len(ss) < 2 {
 | 
			
		||||
		return name, errors.Errorf(`[WARN] unexpected modularitylabel. accepts: "<module name>:<stream>(:<version>:<context>:<arch>)", received: "%s"`, modularitylabel)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s:%s::%s", ss[0], ss[1], name), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToVulsCPEConfiguration(src DetectCPE, advType string) (map[string]types.CPEConfigurations, error) {
 | 
			
		||||
	m := map[string][]types.CPEConfiguration{}
 | 
			
		||||
	for id, cs := range src.Configurations {
 | 
			
		||||
		id = fmt.Sprintf("%s:%s", advType, id)
 | 
			
		||||
		for _, c := range cs {
 | 
			
		||||
			rs := make([]types.CPE, 0, len(c.RunningOn))
 | 
			
		||||
			for _, r := range c.RunningOn {
 | 
			
		||||
				rs = append(rs, toVulnsrcCPEtoVulsCPE(r))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, v := range c.Vulnerable {
 | 
			
		||||
				m[id] = append(m[id], types.CPEConfiguration{
 | 
			
		||||
					Vulnerable: toVulnsrcCPEtoVulsCPE(v),
 | 
			
		||||
					RunningOn:  rs,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m2 := map[string]types.CPEConfigurations{}
 | 
			
		||||
	for id, cs := range m {
 | 
			
		||||
		for _, c := range cs {
 | 
			
		||||
			pvp, err := toVulsCPEConfigurationName(c.Vulnerable.CPEVersion, c.Vulnerable.CPE)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, errors.Wrap(err, "to vuls cpe configuration name")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			base, ok := m2[pvp]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				base = types.CPEConfigurations{
 | 
			
		||||
					ID:            src.ID,
 | 
			
		||||
					Configuration: map[string][]types.CPEConfiguration{},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			base.Configuration[id] = append(base.Configuration[id], c)
 | 
			
		||||
 | 
			
		||||
			m2[pvp] = base
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return m2, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsCPEConfigurationName(version string, cpe string) (string, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		wfn common.WellFormedName
 | 
			
		||||
		err error
 | 
			
		||||
	)
 | 
			
		||||
	switch version {
 | 
			
		||||
	case "2.3":
 | 
			
		||||
		wfn, err = naming.UnbindFS(cpe)
 | 
			
		||||
	default:
 | 
			
		||||
		wfn, err = naming.UnbindURI(cpe)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", errors.Wrapf(err, "unbind %s", cpe)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s:%s:%s", wfn.GetString(common.AttributePart), wfn.GetString(common.AttributeVendor), wfn.GetString(common.AttributeProduct)), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulnsrcCPEtoVulsCPE(s CPE) types.CPE {
 | 
			
		||||
	d := types.CPE{
 | 
			
		||||
		CPEVersion: s.CPEVersion,
 | 
			
		||||
		CPE:        s.CPE,
 | 
			
		||||
	}
 | 
			
		||||
	for _, v := range s.Version {
 | 
			
		||||
		d.Version = append(d.Version, types.Version{
 | 
			
		||||
			Operator: v.Operator,
 | 
			
		||||
			Version:  v.Version,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToVulsRepositoryToCPE(src RepositoryToCPE) types.RepositoryToCPE {
 | 
			
		||||
	return types.RepositoryToCPE(src)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToVulsSupercedences(src []Supercedence) types.Supercedence {
 | 
			
		||||
	ss := types.Supercedence{}
 | 
			
		||||
	for _, s := range src {
 | 
			
		||||
		ss[s.KBID] = s.Supersededby.KBIDs
 | 
			
		||||
	}
 | 
			
		||||
	return ss
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								pkg/cmd/db/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,37 @@
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	dbCreateCmd "github.com/future-architect/vuls/pkg/cmd/db/create"
 | 
			
		||||
	dbEditCmd "github.com/future-architect/vuls/pkg/cmd/db/edit"
 | 
			
		||||
	dbFetchCmd "github.com/future-architect/vuls/pkg/cmd/db/fetch"
 | 
			
		||||
	dbSearchCmd "github.com/future-architect/vuls/pkg/cmd/db/search"
 | 
			
		||||
	dbUploadCmd "github.com/future-architect/vuls/pkg/cmd/db/upload"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdDB() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "db <subcommand>",
 | 
			
		||||
		Short: "Vuls DB Operation",
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls db create https://github.com/vulsio/vuls-data.git
 | 
			
		||||
			$ vuls db create /home/MaineK00n/.cache/vuls
 | 
			
		||||
			$ vuls db edit ubuntu 22.04 openssl
 | 
			
		||||
			$ vuls db edit vulnerability CVE-2022-3602
 | 
			
		||||
			$ vuls db fetch
 | 
			
		||||
			$ vuls db fetch ghcr.io/vuls/db
 | 
			
		||||
			$ vuls db search ubuntu 22.04 openssl
 | 
			
		||||
			$ vuls db search vulnerability CVE-2022-3602
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.AddCommand(dbCreateCmd.NewCmdCreate())
 | 
			
		||||
	cmd.AddCommand(dbEditCmd.NewCmdEdit())
 | 
			
		||||
	cmd.AddCommand(dbFetchCmd.NewCmdFetch())
 | 
			
		||||
	cmd.AddCommand(dbSearchCmd.NewCmdSearch())
 | 
			
		||||
	cmd.AddCommand(dbUploadCmd.NewCmdUpload())
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								pkg/cmd/db/edit/edit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,23 @@
 | 
			
		||||
package edit
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdEdit() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "edit",
 | 
			
		||||
		Short: "Edit Data in Vuls DB",
 | 
			
		||||
		Args:  cobra.RangeArgs(2, 3),
 | 
			
		||||
		RunE: func(_ *cobra.Command, _ []string) error {
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls db edit ubuntu 22.04 openssl
 | 
			
		||||
			$ vuls db edit vulnerability CVE-2022-3602
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										152
									
								
								pkg/cmd/db/fetch/fetch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,152 @@
 | 
			
		||||
package fetch
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"archive/tar"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"compress/gzip"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"oras.land/oras-go/v2/content"
 | 
			
		||||
	"oras.land/oras-go/v2/registry/remote"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DBFetchOption struct {
 | 
			
		||||
	Path      string
 | 
			
		||||
	PlainHTTP bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	defaultVulsDBRepository = "ghcr.io/mainek00n/vuls-data/vuls-db"
 | 
			
		||||
	defaultTag              = "latest"
 | 
			
		||||
	vulsDBConfigMediaType   = "application/vnd.vuls.vuls.db"
 | 
			
		||||
	vulsDBLayerMediaType    = "application/vnd.vuls.vuls.db.layer.v1.tar+gzip"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdFetch() *cobra.Command {
 | 
			
		||||
	opts := &DBFetchOption{
 | 
			
		||||
		Path: "vuls.db",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "fetch",
 | 
			
		||||
		Short: "Fetch Vuls DB",
 | 
			
		||||
		Args:  cobra.MaximumNArgs(1),
 | 
			
		||||
		RunE: func(_ *cobra.Command, args []string) error {
 | 
			
		||||
			p := defaultVulsDBRepository
 | 
			
		||||
			if len(args) > 0 {
 | 
			
		||||
				p = args[0]
 | 
			
		||||
			}
 | 
			
		||||
			return fetch(context.Background(), p, opts.Path, opts.PlainHTTP)
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls db fetch
 | 
			
		||||
			$ vuls db fetch ghcr.io/vuls/db
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Path, "path", "p", "vuls.db", "path to fetch Vuls DB")
 | 
			
		||||
	cmd.Flags().BoolVarP(&opts.PlainHTTP, "plain-http", "", false, "container registry is provided with plain http")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fetch(ctx context.Context, ref, dbpath string, plainHTTP bool) error {
 | 
			
		||||
	repo, err := remote.NewRepository(ref)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.WithStack(err)
 | 
			
		||||
	}
 | 
			
		||||
	if plainHTTP {
 | 
			
		||||
		repo.PlainHTTP = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	desc, err := repo.Resolve(ctx, defaultTag)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.WithStack(err)
 | 
			
		||||
	}
 | 
			
		||||
	pulledBlob, err := content.FetchAll(ctx, repo, desc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.WithStack(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var manifest ocispec.Manifest
 | 
			
		||||
	if err := json.Unmarshal(pulledBlob, &manifest); err != nil {
 | 
			
		||||
		return errors.WithStack(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if manifest.Config.MediaType != vulsDBConfigMediaType {
 | 
			
		||||
		return errors.New("not vuls repository")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, l := range manifest.Layers {
 | 
			
		||||
		if l.MediaType != vulsDBLayerMediaType {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		desc, err := repo.Blobs().Resolve(ctx, l.Digest.String())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.WithStack(err)
 | 
			
		||||
		}
 | 
			
		||||
		rc, err := repo.Fetch(ctx, desc)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.WithStack(err)
 | 
			
		||||
		}
 | 
			
		||||
		defer rc.Close()
 | 
			
		||||
 | 
			
		||||
		bs, err := content.ReadAll(rc, desc)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.WithStack(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		gr, err := gzip.NewReader(bytes.NewReader(bs))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.WithStack(err)
 | 
			
		||||
		}
 | 
			
		||||
		defer gr.Close()
 | 
			
		||||
 | 
			
		||||
		tr := tar.NewReader(gr)
 | 
			
		||||
		for {
 | 
			
		||||
			header, err := tr.Next()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if err == io.EOF {
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				return errors.Wrap(err, "Next()")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			switch header.Typeflag {
 | 
			
		||||
			case tar.TypeDir:
 | 
			
		||||
				if err := os.MkdirAll(filepath.Join(dbpath, header.Name), header.FileInfo().Mode()); err != nil {
 | 
			
		||||
					return errors.WithStack(err)
 | 
			
		||||
				}
 | 
			
		||||
			case tar.TypeReg:
 | 
			
		||||
				if err := func() error {
 | 
			
		||||
					f, err := os.OpenFile(dbpath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(header.Mode))
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return errors.WithStack(err)
 | 
			
		||||
					}
 | 
			
		||||
					defer f.Close()
 | 
			
		||||
 | 
			
		||||
					if _, err := io.Copy(f, tr); err != nil {
 | 
			
		||||
						return errors.WithStack(err)
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					return nil
 | 
			
		||||
				}(); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			default:
 | 
			
		||||
				return errors.Errorf("unknown type: %s in %s", header.Typeflag, header.Name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								pkg/cmd/db/search/search.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,23 @@
 | 
			
		||||
package search
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdSearch() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "search",
 | 
			
		||||
		Short: "Search Vulnerabilty/Package in Vuls DB",
 | 
			
		||||
		Args:  cobra.RangeArgs(2, 3),
 | 
			
		||||
		RunE: func(_ *cobra.Command, _ []string) error {
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls db search ubuntu 22.04 openssl
 | 
			
		||||
			$ vuls db search vulnerability CVE-2022-3602
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								pkg/cmd/db/upload/upload.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,23 @@
 | 
			
		||||
package upload
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdUpload() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "upload",
 | 
			
		||||
		Short: "Upload Vuls DB",
 | 
			
		||||
		Args:  cobra.MaximumNArgs(1),
 | 
			
		||||
		RunE: func(_ *cobra.Command, _ []string) error {
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls db upload
 | 
			
		||||
			$ vuls db upload ghcr.io/vuls/db
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										183
									
								
								pkg/cmd/detect/detect.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,183 @@
 | 
			
		||||
package detect
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
	"golang.org/x/exp/slices"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/config"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/detect"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/log"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DetectOptions struct {
 | 
			
		||||
	Config string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCmdDetect() *cobra.Command {
 | 
			
		||||
	opts := &DetectOptions{
 | 
			
		||||
		Config: "config.json",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "detect ([\"host\"])",
 | 
			
		||||
		Short: "Vuls detect vulnerabilities",
 | 
			
		||||
		RunE: func(_ *cobra.Command, args []string) error {
 | 
			
		||||
			if err := exec(context.Background(), opts.Config, args); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "failed to detect")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls detect
 | 
			
		||||
			$ vuls detect results/**/host.json
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Config, "config", "c", "config.json", "vuls config file path")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func exec(ctx context.Context, path string, args []string) error {
 | 
			
		||||
	logger, err := zap.NewProduction()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "create logger")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = log.ContextWithLogger(ctx, logger)
 | 
			
		||||
 | 
			
		||||
	c, err := config.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s as config", path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(args) == 0 {
 | 
			
		||||
		pwd, err := os.Getwd()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get working direcotry")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fs, err := os.ReadDir(filepath.Join(pwd, "results"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "read %s", filepath.Join(pwd, "results"))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var ds []time.Time
 | 
			
		||||
		for _, f := range fs {
 | 
			
		||||
			if !f.IsDir() {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			t, err := time.Parse("2006-01-02T150405-0700", f.Name())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			ds = append(ds, t)
 | 
			
		||||
		}
 | 
			
		||||
		if len(ds) == 0 {
 | 
			
		||||
			return errors.Wrapf(err, "result dir not found")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		slices.SortFunc(ds, func(e1, e2 time.Time) bool {
 | 
			
		||||
			return e1.After(e2)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		args = append(args, filepath.Join(pwd, "results", ds[0].Format("2006-01-02T150405-0700")))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type result struct {
 | 
			
		||||
		error string
 | 
			
		||||
		nCVEs int
 | 
			
		||||
	}
 | 
			
		||||
	detectCVEs := map[string]result{}
 | 
			
		||||
	for _, arg := range args {
 | 
			
		||||
		if err := filepath.WalkDir(arg, func(p string, d fs.DirEntry, err error) error {
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if d.IsDir() {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			f, err := os.OpenFile(p, os.O_RDWR, 0644)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "open %s", p)
 | 
			
		||||
			}
 | 
			
		||||
			defer f.Close()
 | 
			
		||||
 | 
			
		||||
			var host types.Host
 | 
			
		||||
			if err := json.NewDecoder(f).Decode(&host); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "decode %s", p)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			hc, ok := c.Hosts[host.Name]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return errors.Wrapf(err, "not found %s in %s", host.Name, path)
 | 
			
		||||
			}
 | 
			
		||||
			host.Config.Detect = &hc.Detect
 | 
			
		||||
 | 
			
		||||
			host.ScannedCves = nil
 | 
			
		||||
			host.DetectError = ""
 | 
			
		||||
 | 
			
		||||
			if err := detect.Detect(ctx, &host); err != nil {
 | 
			
		||||
				host.DetectError = err.Error()
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			name := host.Name
 | 
			
		||||
			if host.Family != "" && host.Release != "" {
 | 
			
		||||
				name = fmt.Sprintf("%s (%s %s)", host.Name, host.Family, host.Release)
 | 
			
		||||
			}
 | 
			
		||||
			errstr := host.DetectError
 | 
			
		||||
			if host.ScanError != "" {
 | 
			
		||||
				errstr = fmt.Sprintf("scan error: %s", host.ScanError)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			detectCVEs[name] = result{
 | 
			
		||||
				error: errstr,
 | 
			
		||||
				nCVEs: len(host.ScannedCves),
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := f.Truncate(0); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "truncate file")
 | 
			
		||||
			}
 | 
			
		||||
			if _, err := f.Seek(0, 0); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "set offset")
 | 
			
		||||
			}
 | 
			
		||||
			enc := json.NewEncoder(f)
 | 
			
		||||
			enc.SetIndent("", "  ")
 | 
			
		||||
			if err := enc.Encode(host); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "encode json")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "walk %s", arg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Detect Summary")
 | 
			
		||||
	fmt.Println("==============")
 | 
			
		||||
	for name, r := range detectCVEs {
 | 
			
		||||
		if r.error != "" {
 | 
			
		||||
			fmt.Printf("%s : error msg: %s\n", name, r.error)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Printf("%s : success %d CVEs detected\n", name, r.nCVEs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										245
									
								
								pkg/cmd/report/report.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,245 @@
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/olekukonko/tablewriter"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
	"golang.org/x/exp/slices"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/log"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ReportOptions struct {
 | 
			
		||||
	Format string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCmdReport() *cobra.Command {
 | 
			
		||||
	opts := &ReportOptions{
 | 
			
		||||
		Format: "oneline",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "report (<result path>)",
 | 
			
		||||
		Short: "Vuls report vulnerabilities",
 | 
			
		||||
		RunE: func(_ *cobra.Command, args []string) error {
 | 
			
		||||
			if err := exec(context.Background(), opts.Format, args); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "failed to report")
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls report
 | 
			
		||||
			$ vuls report results
 | 
			
		||||
			$ vuls report resutls/2022-11-05T01:08:44+09:00/local/localhost.json
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Format, "format", "f", "oneline", "stdout format")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type affectedPackage struct {
 | 
			
		||||
	name   string
 | 
			
		||||
	status string
 | 
			
		||||
	source string
 | 
			
		||||
}
 | 
			
		||||
type result struct {
 | 
			
		||||
	cveid      string
 | 
			
		||||
	cvssVector string
 | 
			
		||||
	cvssScore  *float64
 | 
			
		||||
	epss       *float64
 | 
			
		||||
	kev        bool
 | 
			
		||||
	packages   []affectedPackage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func exec(ctx context.Context, format string, args []string) error {
 | 
			
		||||
	logger, err := zap.NewProduction()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "create logger")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = log.ContextWithLogger(ctx, logger)
 | 
			
		||||
 | 
			
		||||
	if len(args) == 0 {
 | 
			
		||||
		pwd, err := os.Getwd()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get working direcotry")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fs, err := os.ReadDir(filepath.Join(pwd, "results"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "read %s", filepath.Join(pwd, "results"))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var ds []time.Time
 | 
			
		||||
		for _, f := range fs {
 | 
			
		||||
			if !f.IsDir() {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			t, err := time.Parse("2006-01-02T150405-0700", f.Name())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			ds = append(ds, t)
 | 
			
		||||
		}
 | 
			
		||||
		if len(ds) == 0 {
 | 
			
		||||
			return errors.Wrapf(err, "result dir not found")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		slices.SortFunc(ds, func(e1, e2 time.Time) bool {
 | 
			
		||||
			return e1.After(e2)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		args = append(args, filepath.Join(pwd, "results", ds[0].Format("2006-01-02T150405-0700")))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rs := map[string][]result{}
 | 
			
		||||
	for _, arg := range args {
 | 
			
		||||
		if err := filepath.WalkDir(arg, func(path string, d fs.DirEntry, err error) error {
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if d.IsDir() {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			f, err := os.Open(path)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "open %s", path)
 | 
			
		||||
			}
 | 
			
		||||
			defer f.Close()
 | 
			
		||||
 | 
			
		||||
			var host types.Host
 | 
			
		||||
			if err := json.NewDecoder(f).Decode(&host); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "decode %s", path)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			name := host.Name
 | 
			
		||||
			if host.Family != "" && host.Release != "" {
 | 
			
		||||
				name = fmt.Sprintf("%s (%s %s)", host.Name, host.Family, host.Release)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for id, vinfo := range host.ScannedCves {
 | 
			
		||||
				r := result{
 | 
			
		||||
					cveid: id,
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if officialCont, ok := vinfo.Content["official"]; ok {
 | 
			
		||||
					for _, c := range officialCont.CVSS {
 | 
			
		||||
						if c.Source != "nvd" || strings.HasPrefix(c.Version, "3") {
 | 
			
		||||
							continue
 | 
			
		||||
						}
 | 
			
		||||
						r.cvssVector = c.Vector
 | 
			
		||||
						r.cvssScore = c.Score
 | 
			
		||||
					}
 | 
			
		||||
					if officialCont.EPSS != nil {
 | 
			
		||||
						r.epss = officialCont.EPSS.EPSS
 | 
			
		||||
					}
 | 
			
		||||
					r.kev = officialCont.KEV
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				for _, p := range vinfo.AffectedPackages {
 | 
			
		||||
					r.packages = append(r.packages, affectedPackage{
 | 
			
		||||
						name:   p.Name,
 | 
			
		||||
						status: p.Status,
 | 
			
		||||
						source: p.Source,
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				rs[name] = append(rs[name], r)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "walk %s", arg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch format {
 | 
			
		||||
	case "oneline":
 | 
			
		||||
		formatOneline(rs)
 | 
			
		||||
	case "list":
 | 
			
		||||
		formatList(rs)
 | 
			
		||||
	default:
 | 
			
		||||
		return errors.Errorf("%s is not implemented format", format)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatOneline(rs map[string][]result) {
 | 
			
		||||
	for name, lines := range rs {
 | 
			
		||||
		fmt.Println(name)
 | 
			
		||||
		fmt.Println(strings.Repeat("=", len(name)))
 | 
			
		||||
 | 
			
		||||
		status := map[string]int{}
 | 
			
		||||
		for _, l := range lines {
 | 
			
		||||
			for _, p := range l.packages {
 | 
			
		||||
				s := p.status
 | 
			
		||||
				if p.status == "" {
 | 
			
		||||
					s = "(none)"
 | 
			
		||||
				}
 | 
			
		||||
				status[s]++
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var ss []string
 | 
			
		||||
		for s, num := range status {
 | 
			
		||||
			ss = append(ss, fmt.Sprintf("%s: %d", s, num))
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Printf("%d CVEs detected. package status: %s\n\n", len(lines), strings.Join(ss, ", "))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatList(rs map[string][]result) {
 | 
			
		||||
	for name, lines := range rs {
 | 
			
		||||
		slices.SortFunc(lines, func(l1, l2 result) bool {
 | 
			
		||||
			s1, s2 := 0.0, 0.0
 | 
			
		||||
			if l1.cvssScore != nil {
 | 
			
		||||
				s1 = *l1.cvssScore
 | 
			
		||||
			}
 | 
			
		||||
			if l2.cvssScore != nil {
 | 
			
		||||
				s2 = *l2.cvssScore
 | 
			
		||||
			}
 | 
			
		||||
			return s1 > s2
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		fmt.Println(name)
 | 
			
		||||
		fmt.Println(strings.Repeat("=", len(name)))
 | 
			
		||||
		table := tablewriter.NewWriter(os.Stdout)
 | 
			
		||||
		table.SetHeader([]string{"CVEID", "Vector", "CVSS", "EPSS", "KEV", "Package", "Status", "Source"})
 | 
			
		||||
		table.SetAutoMergeCells(true)
 | 
			
		||||
		table.SetRowLine(true)
 | 
			
		||||
		for _, l := range lines {
 | 
			
		||||
			for _, p := range l.packages {
 | 
			
		||||
				var score string
 | 
			
		||||
				if l.cvssScore != nil {
 | 
			
		||||
					score = fmt.Sprintf("%.1f", *l.cvssScore)
 | 
			
		||||
				}
 | 
			
		||||
				var epss string
 | 
			
		||||
				if l.epss != nil {
 | 
			
		||||
					epss = fmt.Sprintf("%f", *l.epss)
 | 
			
		||||
				}
 | 
			
		||||
				source, _, _ := strings.Cut(p.source, ":")
 | 
			
		||||
				table.Append([]string{l.cveid, l.cvssVector, score, epss, fmt.Sprintf("%v", l.kev), p.name, p.status, source})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		table.Render()
 | 
			
		||||
		fmt.Println()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								pkg/cmd/root/root.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,44 @@
 | 
			
		||||
package root
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	configCmd "github.com/future-architect/vuls/pkg/cmd/config"
 | 
			
		||||
	dbCmd "github.com/future-architect/vuls/pkg/cmd/db"
 | 
			
		||||
	detectCmd "github.com/future-architect/vuls/pkg/cmd/detect"
 | 
			
		||||
	reportCmd "github.com/future-architect/vuls/pkg/cmd/report"
 | 
			
		||||
	scanCmd "github.com/future-architect/vuls/pkg/cmd/scan"
 | 
			
		||||
	serverCmd "github.com/future-architect/vuls/pkg/cmd/server"
 | 
			
		||||
	tuiCmd "github.com/future-architect/vuls/pkg/cmd/tui"
 | 
			
		||||
	versionCmd "github.com/future-architect/vuls/pkg/cmd/version"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdRoot() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:           "vuls <command>",
 | 
			
		||||
		Short:         "Vuls",
 | 
			
		||||
		Long:          "Vulnerability Scanner: Vuls",
 | 
			
		||||
		SilenceErrors: true,
 | 
			
		||||
		SilenceUsage:  true,
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls config init
 | 
			
		||||
			$ vuls db fetch
 | 
			
		||||
			$ vuls scan
 | 
			
		||||
			$ vuls detect
 | 
			
		||||
			$ vuls report
 | 
			
		||||
			$ vuls tui
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.AddCommand(configCmd.NewCmdConfig())
 | 
			
		||||
	cmd.AddCommand(dbCmd.NewCmdDB())
 | 
			
		||||
	cmd.AddCommand(detectCmd.NewCmdDetect())
 | 
			
		||||
	cmd.AddCommand(reportCmd.NewCmdReport())
 | 
			
		||||
	cmd.AddCommand(scanCmd.NewCmdScan())
 | 
			
		||||
	cmd.AddCommand(serverCmd.NewCmdServer())
 | 
			
		||||
	cmd.AddCommand(tuiCmd.NewCmdTUI())
 | 
			
		||||
	cmd.AddCommand(versionCmd.NewCmdVersion())
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										136
									
								
								pkg/cmd/scan/scan.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,136 @@
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/config"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/log"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ScanOptions struct {
 | 
			
		||||
	Config string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCmdScan() *cobra.Command {
 | 
			
		||||
	opts := &ScanOptions{
 | 
			
		||||
		Config: "config.json",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "scan ([\"host\"])",
 | 
			
		||||
		Short: "Vuls scan your machine information",
 | 
			
		||||
		RunE: func(_ *cobra.Command, args []string) error {
 | 
			
		||||
			if err := exec(context.Background(), opts.Config, args); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "failed to scan")
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls scan
 | 
			
		||||
			$ vuls scan host
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Config, "config", "c", "config.json", "vuls config file path")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func exec(ctx context.Context, path string, args []string) error {
 | 
			
		||||
	logger, err := zap.NewProduction()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "create logger")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = log.ContextWithLogger(ctx, logger)
 | 
			
		||||
 | 
			
		||||
	c, err := config.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s as config", path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hosts := []types.Host{}
 | 
			
		||||
	targets := args
 | 
			
		||||
	if len(args) == 0 {
 | 
			
		||||
		targets = maps.Keys(c.Hosts)
 | 
			
		||||
	}
 | 
			
		||||
	for _, t := range targets {
 | 
			
		||||
		h, ok := c.Hosts[t]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return errors.Errorf("host %s is not defined in config %s", t, path)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		hosts = append(hosts, types.Host{
 | 
			
		||||
			Name: t,
 | 
			
		||||
			Config: types.Config{
 | 
			
		||||
				Type:      h.Type,
 | 
			
		||||
				Host:      h.Host,
 | 
			
		||||
				Port:      h.Port,
 | 
			
		||||
				User:      h.User,
 | 
			
		||||
				SSHConfig: h.SSHConfig,
 | 
			
		||||
				SSHKey:    h.SSHKey,
 | 
			
		||||
				Scan:      &h.Scan,
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := range hosts {
 | 
			
		||||
		if err := scan.Scan(ctx, &hosts[i]); err != nil {
 | 
			
		||||
			hosts[i].ScanError = err.Error()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	for _, h := range hosts {
 | 
			
		||||
		if err := func() error {
 | 
			
		||||
			resultDir := filepath.Join(h.Config.Scan.ResultDir, now.Format("2006-01-02T150405-0700"))
 | 
			
		||||
			if err := os.MkdirAll(resultDir, os.ModePerm); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "mkdir %s", resultDir)
 | 
			
		||||
			}
 | 
			
		||||
			f, err := os.Create(filepath.Join(resultDir, fmt.Sprintf("%s.json", h.Name)))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s", filepath.Join(resultDir, fmt.Sprintf("%s.json", h.Name)))
 | 
			
		||||
			}
 | 
			
		||||
			defer f.Close()
 | 
			
		||||
 | 
			
		||||
			enc := json.NewEncoder(f)
 | 
			
		||||
			enc.SetIndent("", "  ")
 | 
			
		||||
			if err := enc.Encode(h); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "encode %s result", h.Name)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		}(); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "write %s result", h.Name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Scan Summary")
 | 
			
		||||
	fmt.Println("============")
 | 
			
		||||
	for _, h := range hosts {
 | 
			
		||||
		name := h.Name
 | 
			
		||||
		if h.Family != "" && h.Release != "" {
 | 
			
		||||
			name = fmt.Sprintf("%s (%s %s)", h.Name, h.Family, h.Release)
 | 
			
		||||
		}
 | 
			
		||||
		if h.ScanError != "" {
 | 
			
		||||
			fmt.Printf("%s : error msg: %s\n", name, h.ScanError)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Printf("%s: success ospkg: %d, cpe: %d, KB %d installed\n", name, len(h.Packages.OSPkg), len(h.Packages.CPE), len(h.Packages.KB))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										78
									
								
								pkg/cmd/server/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,78 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/labstack/echo/v4"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/config"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/log"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/server"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Serveroptions struct {
 | 
			
		||||
	Config string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCmdServer() *cobra.Command {
 | 
			
		||||
	opts := &Serveroptions{
 | 
			
		||||
		Config: "config.json",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "server",
 | 
			
		||||
		Short: "Vuls start server mode",
 | 
			
		||||
		Args:  cobra.NoArgs,
 | 
			
		||||
		RunE: func(_ *cobra.Command, _ []string) error {
 | 
			
		||||
			if err := exec(context.Background(), opts.Config); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "failed to server")
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls server
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Config, "config", "c", "config.json", "vuls config file path")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func exec(ctx context.Context, path string) error {
 | 
			
		||||
	logger, err := zap.NewProduction()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "create logger")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = log.ContextWithLogger(ctx, logger)
 | 
			
		||||
 | 
			
		||||
	c, err := config.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s as config", path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Server == nil {
 | 
			
		||||
		pwd, err := os.Getwd()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get working directory")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Server = &config.Server{
 | 
			
		||||
			Listen: "127.0.0.1:5515",
 | 
			
		||||
			Path:   filepath.Join(pwd, "vuls.db"),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	e := echo.New()
 | 
			
		||||
	e.POST("/scan", server.Scan())
 | 
			
		||||
	e.POST("/detect", server.Detect(c.Server.Path))
 | 
			
		||||
 | 
			
		||||
	return e.Start(c.Server.Listen)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								pkg/cmd/tui/tui.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,33 @@
 | 
			
		||||
package tui
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TUIOptions struct {
 | 
			
		||||
	Config string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCmdTUI() *cobra.Command {
 | 
			
		||||
	opts := &TUIOptions{
 | 
			
		||||
		Config: "config.json",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "tui (<result path>)",
 | 
			
		||||
		Short: "View vulnerabilities detected by TUI",
 | 
			
		||||
		RunE: func(_ *cobra.Command, _ []string) error {
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls tui
 | 
			
		||||
			$ vuls tui results
 | 
			
		||||
			$ vuls tui resutls/2022-11-05T01:08:44+09:00/local/localhost.json
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Config, "config", "c", "config.json", "vuls config file path")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								pkg/cmd/version/version.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,25 @@
 | 
			
		||||
package version
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	Version  string
 | 
			
		||||
	Revision string
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdVersion() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "version",
 | 
			
		||||
		Short: "Print the version",
 | 
			
		||||
		Args:  cobra.NoArgs,
 | 
			
		||||
		Run: func(_ *cobra.Command, _ []string) {
 | 
			
		||||
			fmt.Fprintf(os.Stdout, "vuls %s %s\n", Version, Revision)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										59
									
								
								pkg/config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,59 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/user"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Open(path string) (Config, error) {
 | 
			
		||||
	f, err := os.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Config{}, errors.Wrapf(err, "open %s", path)
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
 | 
			
		||||
	var src Config
 | 
			
		||||
	if err := json.NewDecoder(f).Decode(&src); err != nil {
 | 
			
		||||
		return Config{}, errors.Wrap(err, "decode json")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	u, err := user.Current()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Config{}, errors.Wrap(err, "get current user")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pwd, err := os.Getwd()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Config{}, errors.Wrap(err, "get working directory")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	config := Config{Server: src.Server, Hosts: map[string]Host{}}
 | 
			
		||||
	for n, h := range src.Hosts {
 | 
			
		||||
		c := Host{
 | 
			
		||||
			Type:      h.Type,
 | 
			
		||||
			Host:      h.Host,
 | 
			
		||||
			Port:      h.Port,
 | 
			
		||||
			User:      h.User,
 | 
			
		||||
			SSHConfig: h.SSHConfig,
 | 
			
		||||
			SSHKey:    h.SSHKey,
 | 
			
		||||
			Scan:      h.Scan,
 | 
			
		||||
			Detect:    h.Detect,
 | 
			
		||||
		}
 | 
			
		||||
		if c.User == nil {
 | 
			
		||||
			c.User = &u.Name
 | 
			
		||||
		}
 | 
			
		||||
		if c.Scan.ResultDir == "" {
 | 
			
		||||
			c.Scan.ResultDir = filepath.Join(pwd, "results")
 | 
			
		||||
		}
 | 
			
		||||
		if c.Detect.ResultDir == "" {
 | 
			
		||||
			c.Detect.ResultDir = filepath.Join(pwd, "results")
 | 
			
		||||
		}
 | 
			
		||||
		config.Hosts[n] = c
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return config, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								pkg/config/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,41 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Server *Server         `json:"server"`
 | 
			
		||||
	Hosts  map[string]Host `json:"hosts"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Scan struct {
 | 
			
		||||
	OSPkg     *scanOSPkg `json:"ospkg,omitempty"`
 | 
			
		||||
	CPE       []scanCPE  `json:"cpe,omitempty"`
 | 
			
		||||
	ResultDir string     `json:"result_dir,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type scanOSPkg struct {
 | 
			
		||||
	Root bool `json:"root"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type scanCPE struct {
 | 
			
		||||
	CPE       string `json:"cpe,omitempty"`
 | 
			
		||||
	RunningOn string `json:"running_on,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Detect struct {
 | 
			
		||||
	Path      string `json:"path"`
 | 
			
		||||
	ResultDir string `json:"result_dir"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Server struct {
 | 
			
		||||
	Listen string `json:"listen"`
 | 
			
		||||
	Path   string `json:"path"`
 | 
			
		||||
}
 | 
			
		||||
type Host struct {
 | 
			
		||||
	Type      string  `json:"type"`
 | 
			
		||||
	Host      *string `json:"host"`
 | 
			
		||||
	Port      *string `json:"port"`
 | 
			
		||||
	User      *string `json:"user"`
 | 
			
		||||
	SSHConfig *string `json:"ssh_config"`
 | 
			
		||||
	SSHKey    *string `json:"ssh_key"`
 | 
			
		||||
	Scan      Scan    `json:"scan"`
 | 
			
		||||
	Detect    Detect  `json:"detect"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										490
									
								
								pkg/db/boltdb/boltdb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,490 @@
 | 
			
		||||
package boltdb
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	bolt "go.etcd.io/bbolt"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type options struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Option interface {
 | 
			
		||||
	apply(*options)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DB struct {
 | 
			
		||||
	conn *bolt.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Open(dbPath string, debug bool, opts ...Option) (*DB, error) {
 | 
			
		||||
	db, err := bolt.Open(dbPath, 0666, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrap(err, "open boltdb")
 | 
			
		||||
	}
 | 
			
		||||
	return &DB{conn: db}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) Close() error {
 | 
			
		||||
	if db.conn == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.conn.Close(); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "close boltdb")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutVulnerability(src, key string, value types.Vulnerability) error {
 | 
			
		||||
	bucket, id, found := strings.Cut(key, ":")
 | 
			
		||||
	if !found {
 | 
			
		||||
		return errors.Errorf(`unexpected key. accepts: "vulnerability:<Vulnerability ID>, received: "%s"`, key)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.conn.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		b, err := tx.CreateBucketIfNotExists([]byte(bucket))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s bucket", bucket)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		vb, err := b.CreateBucketIfNotExists([]byte(id))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s/%s bucket", bucket, id)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bs, err := json.MarshalIndent(value, "", "  ")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "marshal json")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := vb.Put([]byte(src), bs); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "put %%s/%s/%s", bucket, id, src)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "update db")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutPackage(src, key string, value map[string]types.Packages) error {
 | 
			
		||||
	if err := db.conn.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		name, version, found := strings.Cut(key, ":")
 | 
			
		||||
		if !found && name == "" {
 | 
			
		||||
			return errors.Errorf(`unexpected key. accepts: "<osname>(:<version>)", received: "%s"`, key)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bucket := name
 | 
			
		||||
		b, err := tx.CreateBucketIfNotExists([]byte(name))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s bucket", name)
 | 
			
		||||
		}
 | 
			
		||||
		switch name {
 | 
			
		||||
		case "arch", "freebsd", "gentoo":
 | 
			
		||||
		case "redhat":
 | 
			
		||||
			if version == "" {
 | 
			
		||||
				return errors.Errorf(`unexpected key. accepts: "<osname>:<version>", received: "%s"`, key)
 | 
			
		||||
			}
 | 
			
		||||
			b, err = b.CreateBucketIfNotExists([]byte(version[:1]))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s bucket", name, version[:1])
 | 
			
		||||
			}
 | 
			
		||||
			b, err = b.CreateBucketIfNotExists([]byte(version))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s/%s bucket", name, version[:1], version)
 | 
			
		||||
			}
 | 
			
		||||
			bucket = fmt.Sprintf("%s/%s/%s", name, version[:1], version)
 | 
			
		||||
		default:
 | 
			
		||||
			if version == "" {
 | 
			
		||||
				return errors.Errorf(`unexpected key. accepts: "<osname>:<version>", received: "%s"`, key)
 | 
			
		||||
			}
 | 
			
		||||
			b, err = b.CreateBucketIfNotExists([]byte(version))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "crate %s/%s bucket", name, version)
 | 
			
		||||
			}
 | 
			
		||||
			bucket = fmt.Sprintf("%s/%s", name, version)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for n, v := range value {
 | 
			
		||||
			pb, err := b.CreateBucketIfNotExists([]byte(n))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s bucket", bucket, n)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			vb, err := pb.CreateBucketIfNotExists([]byte(v.ID))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s/%s bucket", bucket, n, v.ID)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var p map[string]types.Package
 | 
			
		||||
			bs := vb.Get([]byte(src))
 | 
			
		||||
			if len(bs) > 0 {
 | 
			
		||||
				if err := json.Unmarshal(bs, &p); err != nil {
 | 
			
		||||
					return errors.Wrap(err, "unmarshal json")
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				p = map[string]types.Package{}
 | 
			
		||||
			}
 | 
			
		||||
			maps.Copy(p, v.Package)
 | 
			
		||||
			bs, err = json.MarshalIndent(p, "", "  ")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "marshal json")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := vb.Put([]byte(src), bs); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "put %s", key)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if name == "windows" {
 | 
			
		||||
			kbToProduct := map[string][]string{}
 | 
			
		||||
			for n, ps := range value {
 | 
			
		||||
				for _, p := range ps.Package {
 | 
			
		||||
					for _, v := range p.Version {
 | 
			
		||||
						if _, err := strconv.Atoi(v[0].Version); err != nil {
 | 
			
		||||
							continue
 | 
			
		||||
						}
 | 
			
		||||
						kbToProduct[v[0].Version] = append(kbToProduct[v[0].Version], n)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			b, err := tx.CreateBucketIfNotExists([]byte("windows_kb_to_product"))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "create windows_kb_to_product bucket")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if version == "" {
 | 
			
		||||
				return errors.Errorf(`unexpected key. accepts: "<osname>:<version>", received: "%s"`, key)
 | 
			
		||||
			}
 | 
			
		||||
			b, err = b.CreateBucketIfNotExists([]byte(version))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s bucket", name, version)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for kb, ps := range kbToProduct {
 | 
			
		||||
				bs, err := json.Marshal(ps)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return errors.Wrap(err, "marshal json")
 | 
			
		||||
				}
 | 
			
		||||
				b.Put([]byte(kb), bs)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "update db")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutCPEConfiguration(src, key string, value map[string]types.CPEConfigurations) error {
 | 
			
		||||
	if err := db.conn.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		b, err := tx.CreateBucketIfNotExists([]byte(key))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s bucket", key)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for pvp, c := range value {
 | 
			
		||||
			pvpb, err := b.CreateBucketIfNotExists([]byte(pvp))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s bucket", key, pvp)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			vb, err := pvpb.CreateBucketIfNotExists([]byte(c.ID))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s/%s bucket", key, pvp, c.ID)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var v map[string][]types.CPEConfiguration
 | 
			
		||||
			bs := vb.Get([]byte(src))
 | 
			
		||||
			if len(bs) > 0 {
 | 
			
		||||
				if err := json.Unmarshal(bs, &v); err != nil {
 | 
			
		||||
					return errors.Wrap(err, "unmarshal json")
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				v = map[string][]types.CPEConfiguration{}
 | 
			
		||||
			}
 | 
			
		||||
			maps.Copy(v, c.Configuration)
 | 
			
		||||
			bs, err = json.MarshalIndent(v, "", "  ")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "marshal json")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := vb.Put([]byte(src), bs); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "put %s", key)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "update db")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutRedHatRepoToCPE(src, key string, value types.RepositoryToCPE) error {
 | 
			
		||||
	if err := db.conn.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		name, version, found := strings.Cut(key, ":")
 | 
			
		||||
		if !found && name == "" {
 | 
			
		||||
			return errors.Errorf(`unexpected key. accepts: "redhat_cpe:<version>", received: "%s"`, key)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		b, err := tx.CreateBucketIfNotExists([]byte(name))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s bucket", name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		b, err = b.CreateBucketIfNotExists([]byte(version[:1]))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s/%s bucket", name, version[:1])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		b, err = b.CreateBucketIfNotExists([]byte(version))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s/%s/%s bucket", name, version[:1], version)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for repo, cpes := range value {
 | 
			
		||||
			rb, err := b.CreateBucketIfNotExists([]byte(repo))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s/%s/%s bucket", name, version[:1], version, repo)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			bs, err := json.MarshalIndent(cpes, "", "  ")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "marshal json")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := rb.Put([]byte(src), bs); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "put %s", key)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "update db")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutWindowsSupercedence(src, key string, value types.Supercedence) error {
 | 
			
		||||
	if err := db.conn.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		b, err := tx.CreateBucketIfNotExists([]byte(key))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s bucket", key)
 | 
			
		||||
		}
 | 
			
		||||
		for kb, supercedences := range value {
 | 
			
		||||
			kbb, err := b.CreateBucketIfNotExists([]byte(kb))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s bucket", key, kb)
 | 
			
		||||
			}
 | 
			
		||||
			bs, err := json.Marshal(supercedences)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "marshal json")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := kbb.Put([]byte(src), bs); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "put %s", key)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "update db")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetVulnerability(ids []string) (map[string]map[string]types.Vulnerability, error) {
 | 
			
		||||
	r := map[string]map[string]types.Vulnerability{}
 | 
			
		||||
	if err := db.conn.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		b := tx.Bucket([]byte("vulnerability"))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, id := range ids {
 | 
			
		||||
			vb := b.Bucket([]byte(id))
 | 
			
		||||
			if vb == nil {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			r[string(id)] = map[string]types.Vulnerability{}
 | 
			
		||||
			if err := vb.ForEach(func(src, bs []byte) error {
 | 
			
		||||
				var v types.Vulnerability
 | 
			
		||||
				if err := json.Unmarshal(bs, &v); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "decode %s/%s", string(id), string(src))
 | 
			
		||||
				}
 | 
			
		||||
				r[string(id)][string(src)] = v
 | 
			
		||||
 | 
			
		||||
				return nil
 | 
			
		||||
			}); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetPackage(family, release string, name string) (map[string]map[string]map[string]types.Package, error) {
 | 
			
		||||
	r := map[string]map[string]map[string]types.Package{}
 | 
			
		||||
	if err := db.conn.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		b := tx.Bucket([]byte(family))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		switch family {
 | 
			
		||||
		case "debian", "ubuntu", "windows":
 | 
			
		||||
			b = b.Bucket([]byte(release))
 | 
			
		||||
			if b == nil {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			b = b.Bucket([]byte(name))
 | 
			
		||||
			if b == nil {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := b.ForEach(func(cveid, _ []byte) error {
 | 
			
		||||
				vb := b.Bucket(cveid)
 | 
			
		||||
				r[string(cveid)] = map[string]map[string]types.Package{}
 | 
			
		||||
				if err := vb.ForEach(func(src, bs []byte) error {
 | 
			
		||||
					var v map[string]types.Package
 | 
			
		||||
					if err := json.Unmarshal(bs, &v); err != nil {
 | 
			
		||||
						return errors.Wrapf(err, "decode %s/%s", string(cveid), string(src))
 | 
			
		||||
					}
 | 
			
		||||
					r[string(cveid)][string(src)] = v
 | 
			
		||||
 | 
			
		||||
					return nil
 | 
			
		||||
				}); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				return nil
 | 
			
		||||
			}); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			return errors.New("not implemented")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetCPEConfiguration(partvendorproduct string) (map[string]map[string]map[string][]types.CPEConfiguration, error) {
 | 
			
		||||
	r := map[string]map[string]map[string][]types.CPEConfiguration{}
 | 
			
		||||
	if err := db.conn.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		b := tx.Bucket([]byte("cpe"))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		b = b.Bucket([]byte(partvendorproduct))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := b.ForEach(func(cveid, _ []byte) error {
 | 
			
		||||
			vb := b.Bucket(cveid)
 | 
			
		||||
			r[string(cveid)] = map[string]map[string][]types.CPEConfiguration{}
 | 
			
		||||
			if err := vb.ForEach(func(src, bs []byte) error {
 | 
			
		||||
				var v map[string][]types.CPEConfiguration
 | 
			
		||||
				if err := json.Unmarshal(bs, &v); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "decode cpe/%s/%s/%s", partvendorproduct, string(cveid), string(src))
 | 
			
		||||
				}
 | 
			
		||||
				r[string(cveid)][string(src)] = v
 | 
			
		||||
 | 
			
		||||
				return nil
 | 
			
		||||
			}); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetSupercedence(kbs []string) (map[string][]string, error) {
 | 
			
		||||
	r := map[string][]string{}
 | 
			
		||||
	if err := db.conn.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		b := tx.Bucket([]byte("windows_supercedence"))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, kb := range kbs {
 | 
			
		||||
			kbb := b.Bucket([]byte(kb))
 | 
			
		||||
			if kbb == nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if err := kbb.ForEach(func(_, v []byte) error {
 | 
			
		||||
				var ss []string
 | 
			
		||||
				if err := json.Unmarshal(v, &ss); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "decode windows_supercedence/%s", kb)
 | 
			
		||||
				}
 | 
			
		||||
				r[kb] = append(r[kb], ss...)
 | 
			
		||||
				return nil
 | 
			
		||||
			}); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetKBtoProduct(release string, kbs []string) ([]string, error) {
 | 
			
		||||
	var r []string
 | 
			
		||||
	if err := db.conn.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		b := tx.Bucket([]byte("windows_kb_to_product"))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		b = b.Bucket([]byte(release))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, kb := range kbs {
 | 
			
		||||
			if bs := b.Get([]byte(kb)); len(bs) > 0 {
 | 
			
		||||
				var ps []string
 | 
			
		||||
				if err := json.Unmarshal(bs, &ps); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "decode windows_kb_to_product/%s/%s", release, kb)
 | 
			
		||||
				}
 | 
			
		||||
				r = append(r, ps...)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										149
									
								
								pkg/db/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,149 @@
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/boltdb"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/rdb"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/redis"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type options struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Option interface {
 | 
			
		||||
	apply(*options)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DB struct {
 | 
			
		||||
	name   string
 | 
			
		||||
	driver Driver
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Driver interface {
 | 
			
		||||
	Close() error
 | 
			
		||||
 | 
			
		||||
	PutVulnerability(string, string, types.Vulnerability) error
 | 
			
		||||
	PutPackage(string, string, map[string]types.Packages) error
 | 
			
		||||
	PutCPEConfiguration(string, string, map[string]types.CPEConfigurations) error
 | 
			
		||||
	PutRedHatRepoToCPE(string, string, types.RepositoryToCPE) error
 | 
			
		||||
	PutWindowsSupercedence(string, string, types.Supercedence) error
 | 
			
		||||
 | 
			
		||||
	GetVulnerability([]string) (map[string]map[string]types.Vulnerability, error)
 | 
			
		||||
	GetPackage(string, string, string) (map[string]map[string]map[string]types.Package, error)
 | 
			
		||||
	GetCPEConfiguration(string) (map[string]map[string]map[string][]types.CPEConfiguration, error)
 | 
			
		||||
	GetSupercedence([]string) (map[string][]string, error)
 | 
			
		||||
	GetKBtoProduct(string, []string) ([]string, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) Name() string {
 | 
			
		||||
	return db.name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Open(dbType, dbPath string, debug bool, opts ...Option) (*DB, error) {
 | 
			
		||||
	switch dbType {
 | 
			
		||||
	case "boltdb":
 | 
			
		||||
		d, err := boltdb.Open(dbPath, debug)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "open boltdb")
 | 
			
		||||
		}
 | 
			
		||||
		return &DB{name: dbType, driver: d}, nil
 | 
			
		||||
	case "sqlite3", "mysql", "postgres":
 | 
			
		||||
		d, err := rdb.Open(dbType, dbPath, debug)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "open rdb")
 | 
			
		||||
		}
 | 
			
		||||
		return &DB{name: dbType, driver: d}, nil
 | 
			
		||||
	case "redis":
 | 
			
		||||
		d, err := redis.Open(dbPath, debug)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "open rdb")
 | 
			
		||||
		}
 | 
			
		||||
		return &DB{name: dbType, driver: d}, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, errors.Errorf(`unexpected dbType. accepts: ["boltdb", "sqlite3", "mysql", "postgres", "redis"], received: "%s"`, dbType)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) Close() error {
 | 
			
		||||
	if err := db.driver.Close(); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "close %s", db.name)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutVulnerability(src, key string, value types.Vulnerability) error {
 | 
			
		||||
	if err := db.driver.PutVulnerability(src, key, value); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "put vulnerability")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutPackage(src, key string, value map[string]types.Packages) error {
 | 
			
		||||
	if err := db.driver.PutPackage(src, key, value); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "put package")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutCPEConfiguration(src, key string, value map[string]types.CPEConfigurations) error {
 | 
			
		||||
	if err := db.driver.PutCPEConfiguration(src, key, value); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "put cpe configuration")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutRedHatRepoToCPE(src, key string, value types.RepositoryToCPE) error {
 | 
			
		||||
	if err := db.driver.PutRedHatRepoToCPE(src, key, value); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "put repository to cpe")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutWindowsSupercedence(src, key string, value types.Supercedence) error {
 | 
			
		||||
	if err := db.driver.PutWindowsSupercedence(src, key, value); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "put supercedence")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetVulnerability(ids []string) (map[string]map[string]types.Vulnerability, error) {
 | 
			
		||||
	rs, err := db.driver.GetVulnerability(ids)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrapf(err, "get vulnerability")
 | 
			
		||||
	}
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetPackage(family, release string, name string) (map[string]map[string]map[string]types.Package, error) {
 | 
			
		||||
	rs, err := db.driver.GetPackage(family, release, name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrapf(err, "get package")
 | 
			
		||||
	}
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetCPEConfiguration(partvendorproduct string) (map[string]map[string]map[string][]types.CPEConfiguration, error) {
 | 
			
		||||
	rs, err := db.driver.GetCPEConfiguration(partvendorproduct)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrapf(err, "get cpe configuration")
 | 
			
		||||
	}
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetSupercedence(kb []string) (map[string][]string, error) {
 | 
			
		||||
	rs, err := db.driver.GetSupercedence(kb)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrap(err, "get supercedence")
 | 
			
		||||
	}
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetKBtoProduct(release string, kb []string) ([]string, error) {
 | 
			
		||||
	rs, err := db.driver.GetKBtoProduct(release, kb)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrap(err, "get product from kb")
 | 
			
		||||
	}
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										110
									
								
								pkg/db/rdb/rdb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,110 @@
 | 
			
		||||
package rdb
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"database/sql"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"gorm.io/driver/mysql"
 | 
			
		||||
	"gorm.io/driver/postgres"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
	_ "modernc.org/sqlite"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type options struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Option interface {
 | 
			
		||||
	apply(*options)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DB struct {
 | 
			
		||||
	conn *gorm.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Open(dbType, dbPath string, debug bool, opts ...Option) (*DB, error) {
 | 
			
		||||
	switch dbType {
 | 
			
		||||
	case "sqlite3":
 | 
			
		||||
		// db, err := gorm.Open(sqlite.Open(dbPath))
 | 
			
		||||
		db := &gorm.DB{}
 | 
			
		||||
		conn, err := sql.Open("sqlite", dbPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "open sqlite3")
 | 
			
		||||
		}
 | 
			
		||||
		db.ConnPool = conn
 | 
			
		||||
		return &DB{conn: db}, nil
 | 
			
		||||
	case "mysql":
 | 
			
		||||
		db, err := gorm.Open(mysql.Open(dbPath))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "open mysql")
 | 
			
		||||
		}
 | 
			
		||||
		return &DB{conn: db}, nil
 | 
			
		||||
	case "postgres":
 | 
			
		||||
		db, err := gorm.Open(postgres.Open(dbPath))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "open postgres")
 | 
			
		||||
		}
 | 
			
		||||
		return &DB{conn: db}, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, errors.Errorf(`unexpected dbType. accepts: ["sqlite3", "mysql", "postgres"], received: "%s"`, dbType)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) Close() error {
 | 
			
		||||
	if db.conn == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		sqlDB *sql.DB
 | 
			
		||||
		err   error
 | 
			
		||||
	)
 | 
			
		||||
	if sqlDB, err = db.conn.DB(); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get *sql.DB")
 | 
			
		||||
	}
 | 
			
		||||
	if err := sqlDB.Close(); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "close *sql.DB")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutVulnerability(src, key string, value types.Vulnerability) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutPackage(src, key string, value map[string]types.Packages) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutCPEConfiguration(src, key string, value map[string]types.CPEConfigurations) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutRedHatRepoToCPE(src, key string, value types.RepositoryToCPE) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutWindowsSupercedence(src, key string, value types.Supercedence) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetVulnerability(ids []string) (map[string]map[string]types.Vulnerability, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetPackage(family, release string, name string) (map[string]map[string]map[string]types.Package, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetCPEConfiguration(partvendorproduct string) (map[string]map[string]map[string][]types.CPEConfiguration, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetSupercedence(kb []string) (map[string][]string, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetKBtoProduct(elease string, kb []string) ([]string, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										77
									
								
								pkg/db/redis/redis.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,77 @@
 | 
			
		||||
package redis
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/go-redis/redis/v9"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type options struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Option interface {
 | 
			
		||||
	apply(*options)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DB struct {
 | 
			
		||||
	conn *redis.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Open(dbPath string, debug bool, opts ...Option) (*DB, error) {
 | 
			
		||||
	redisOpts, err := redis.ParseURL(dbPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrap(err, "parse redis URL")
 | 
			
		||||
	}
 | 
			
		||||
	return &DB{conn: redis.NewClient(redisOpts)}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) Close() error {
 | 
			
		||||
	if db.conn == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.conn.Close(); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "close redis")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutVulnerability(src, key string, value types.Vulnerability) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutPackage(src, key string, value map[string]types.Packages) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutCPEConfiguration(src, key string, value map[string]types.CPEConfigurations) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutRedHatRepoToCPE(src, key string, value types.RepositoryToCPE) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutWindowsSupercedence(src, key string, value types.Supercedence) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetVulnerability(ids []string) (map[string]map[string]types.Vulnerability, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetPackage(family, release string, name string) (map[string]map[string]map[string]types.Package, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetCPEConfiguration(partvendorproduct string) (map[string]map[string]map[string][]types.CPEConfiguration, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetSupercedence(kb []string) (map[string][]string, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetKBtoProduct(release string, kb []string) ([]string, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										85
									
								
								pkg/db/types/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,85 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
type Vulnerability struct {
 | 
			
		||||
	ID          string       `json:"id,omitempty"`
 | 
			
		||||
	Advisory    []string     `json:"advisory,omitempty"`
 | 
			
		||||
	Title       string       `json:"title,omitempty"`
 | 
			
		||||
	Description string       `json:"description,omitempty"`
 | 
			
		||||
	CVSS        []CVSS       `json:"cvss,omitempty"`
 | 
			
		||||
	EPSS        *EPSS        `json:"epss,omitempty"`
 | 
			
		||||
	CWE         []CWE        `json:"cwe,omitempty"`
 | 
			
		||||
	Metasploit  []Metasploit `json:"metasploit,omitempty"`
 | 
			
		||||
	Exploit     []Exploit    `json:"exploit,omitempty"`
 | 
			
		||||
	KEV         bool         `json:"kev,omitempty"`
 | 
			
		||||
	Published   *time.Time   `json:"published,omitempty"`
 | 
			
		||||
	Modified    *time.Time   `json:"modified,omitempty"`
 | 
			
		||||
	Reference   []string     `json:"reference,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CVSS struct {
 | 
			
		||||
	Source   string   `json:"source,omitempty"`
 | 
			
		||||
	Version  string   `json:"version,omitempty"`
 | 
			
		||||
	Vector   string   `json:"vector,omitempty"`
 | 
			
		||||
	Score    *float64 `json:"score,omitempty"`
 | 
			
		||||
	Severity string   `json:"severity,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EPSS struct {
 | 
			
		||||
	EPSS       *float64 `json:"epss,omitempty"`
 | 
			
		||||
	Percentile *float64 `json:"percentile,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CWE struct {
 | 
			
		||||
	Source []string `json:"source,omitempty"`
 | 
			
		||||
	ID     string   `json:"id,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Metasploit struct {
 | 
			
		||||
	Title string `json:"title,omitempty"`
 | 
			
		||||
	URL   string `json:"url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Exploit struct {
 | 
			
		||||
	Source []string `json:"source,omitempty"`
 | 
			
		||||
	URL    string   `json:"url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CPEConfigurations struct {
 | 
			
		||||
	ID            string                        `json:"-,omitempty"`
 | 
			
		||||
	Configuration map[string][]CPEConfiguration `json:"configuration,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CPEConfiguration struct {
 | 
			
		||||
	Vulnerable CPE   `json:"vulnerable,omitempty"`
 | 
			
		||||
	RunningOn  []CPE `json:"running_on,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CPE struct {
 | 
			
		||||
	CPEVersion string    `json:"cpe_version,omitempty"`
 | 
			
		||||
	CPE        string    `json:"cpe,omitempty"`
 | 
			
		||||
	Version    []Version `json:"version,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Packages struct {
 | 
			
		||||
	ID      string             `json:"-,omitempty"`
 | 
			
		||||
	Package map[string]Package `json:"package,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Package struct {
 | 
			
		||||
	Status     string      `json:"status,omitempty"`
 | 
			
		||||
	Version    [][]Version `json:"version,omitempty"`
 | 
			
		||||
	Arch       []string    `json:"arch,omitempty"`
 | 
			
		||||
	Repository string      `json:"repository,omitempty"`
 | 
			
		||||
	CPE        []string    `json:"cpe,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Version struct {
 | 
			
		||||
	Operator string `json:"operator,omitempty"`
 | 
			
		||||
	Version  string `json:"version,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RepositoryToCPE map[string][]string
 | 
			
		||||
 | 
			
		||||
type Supercedence map[string][]string
 | 
			
		||||
							
								
								
									
										1
									
								
								pkg/db/util/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
package util
 | 
			
		||||
							
								
								
									
										179
									
								
								pkg/detect/cpe/cpe.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,179 @@
 | 
			
		||||
package cpe
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/hashicorp/go-version"
 | 
			
		||||
	"github.com/knqyf263/go-cpe/common"
 | 
			
		||||
	"github.com/knqyf263/go-cpe/matching"
 | 
			
		||||
	"github.com/knqyf263/go-cpe/naming"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db"
 | 
			
		||||
	dbTypes "github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Detector struct{}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Name() string {
 | 
			
		||||
	return "cpe detector"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Detect(ctx context.Context, host *types.Host) error {
 | 
			
		||||
	if host.ScannedCves == nil {
 | 
			
		||||
		host.ScannedCves = map[string]types.VulnInfo{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulndb, err := db.Open("boltdb", host.Config.Detect.Path, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s", host.Config.Detect.Path)
 | 
			
		||||
	}
 | 
			
		||||
	defer vulndb.Close()
 | 
			
		||||
 | 
			
		||||
	for key, cpe := range host.Packages.CPE {
 | 
			
		||||
		installed, err := naming.UnbindFS(cpe.CPE)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "unbind %s", cpe.CPE)
 | 
			
		||||
		}
 | 
			
		||||
		var runningOn common.WellFormedName
 | 
			
		||||
		if cpe.RunningOn != "" {
 | 
			
		||||
			runningOn, err = naming.UnbindFS(cpe.RunningOn)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "unbind %s", cpe.RunningOn)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cpes, err := vulndb.GetCPEConfiguration(fmt.Sprintf("%s:%s:%s", installed.GetString(common.AttributePart), installed.GetString(common.AttributeVendor), installed.GetString(common.AttributeProduct)))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get cpe configuration")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for cveid, datasrcs := range cpes {
 | 
			
		||||
			for datasrc, orcs := range datasrcs {
 | 
			
		||||
				for id, andcs := range orcs {
 | 
			
		||||
					for _, c := range andcs {
 | 
			
		||||
						affected, err := compare(installed, &runningOn, c)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							return errors.Wrap(err, "compare")
 | 
			
		||||
						}
 | 
			
		||||
						if affected {
 | 
			
		||||
							vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
							if !ok {
 | 
			
		||||
								host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
							}
 | 
			
		||||
							vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
								Name:   key,
 | 
			
		||||
								Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
							})
 | 
			
		||||
							vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
							host.ScannedCves[cveid] = vinfo
 | 
			
		||||
							break
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulns, err := vulndb.GetVulnerability(maps.Keys(host.ScannedCves))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get vulnerability")
 | 
			
		||||
	}
 | 
			
		||||
	for cveid, datasrcs := range vulns {
 | 
			
		||||
		vinfo := host.ScannedCves[cveid]
 | 
			
		||||
		vinfo.Content = map[string]dbTypes.Vulnerability{}
 | 
			
		||||
		for src, v := range datasrcs {
 | 
			
		||||
			vinfo.Content[src] = v
 | 
			
		||||
		}
 | 
			
		||||
		host.ScannedCves[cveid] = vinfo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func compare(installedCPE common.WellFormedName, installedRunningOn *common.WellFormedName, target dbTypes.CPEConfiguration) (bool, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		wfn common.WellFormedName
 | 
			
		||||
		err error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if target.Vulnerable.CPEVersion == "2.3" {
 | 
			
		||||
		wfn, err = naming.UnbindFS(target.Vulnerable.CPE)
 | 
			
		||||
	} else {
 | 
			
		||||
		wfn, err = naming.UnbindURI(target.Vulnerable.CPE)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, errors.Wrapf(err, "unbind %s", target.Vulnerable.CPE)
 | 
			
		||||
	}
 | 
			
		||||
	if !matching.IsEqual(installedCPE, wfn) && !matching.IsSubset(installedCPE, wfn) {
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, runningOn := range target.RunningOn {
 | 
			
		||||
		if runningOn.CPEVersion == "2.3" {
 | 
			
		||||
			wfn, err = naming.UnbindFS(runningOn.CPE)
 | 
			
		||||
		} else {
 | 
			
		||||
			wfn, err = naming.UnbindURI(runningOn.CPE)
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, errors.Wrapf(err, "unbind %s", runningOn.CPE)
 | 
			
		||||
		}
 | 
			
		||||
		if !matching.IsEqual(*installedRunningOn, wfn) && !matching.IsSubset(*installedRunningOn, wfn) {
 | 
			
		||||
			return false, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(target.Vulnerable.Version) == 0 {
 | 
			
		||||
		return true, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attrver := installedCPE.GetString(common.AttributeVersion)
 | 
			
		||||
	switch attrver {
 | 
			
		||||
	case "ANY":
 | 
			
		||||
		return true, nil
 | 
			
		||||
	case "NA":
 | 
			
		||||
		return false, nil
 | 
			
		||||
	default:
 | 
			
		||||
		v, err := version.NewVersion(strings.ReplaceAll(attrver, "\\", ""))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, errors.Wrapf(err, "parse version in %s", installedCPE.GetString(common.AttributeVersion))
 | 
			
		||||
		}
 | 
			
		||||
		for _, vconf := range target.Vulnerable.Version {
 | 
			
		||||
			vconfv, err := version.NewVersion(vconf.Version)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return false, errors.Wrapf(err, "parse version in %s", vconf.Version)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			switch vconf.Operator {
 | 
			
		||||
			case "eq":
 | 
			
		||||
				if !v.Equal(vconfv) {
 | 
			
		||||
					return false, nil
 | 
			
		||||
				}
 | 
			
		||||
			case "lt":
 | 
			
		||||
				if !v.LessThan(vconfv) {
 | 
			
		||||
					return false, nil
 | 
			
		||||
				}
 | 
			
		||||
			case "le":
 | 
			
		||||
				if !v.LessThanOrEqual(vconfv) {
 | 
			
		||||
					return false, nil
 | 
			
		||||
				}
 | 
			
		||||
			case "gt":
 | 
			
		||||
				if !v.GreaterThan(vconfv) {
 | 
			
		||||
					return false, nil
 | 
			
		||||
				}
 | 
			
		||||
			case "ge":
 | 
			
		||||
				if !v.GreaterThanOrEqual(vconfv) {
 | 
			
		||||
					return false, nil
 | 
			
		||||
				}
 | 
			
		||||
			default:
 | 
			
		||||
				return false, errors.New("not supported operator")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return true, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										151
									
								
								pkg/detect/debian/debian.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,151 @@
 | 
			
		||||
package debian
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	version "github.com/knqyf263/go-deb-version"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db"
 | 
			
		||||
	dbTypes "github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Detector struct{}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Name() string {
 | 
			
		||||
	return "debian detector"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Detect(ctx context.Context, host *types.Host) error {
 | 
			
		||||
	if host.ScannedCves == nil {
 | 
			
		||||
		host.ScannedCves = map[string]types.VulnInfo{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulndb, err := db.Open("boltdb", host.Config.Detect.Path, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s", host.Config.Detect.Path)
 | 
			
		||||
	}
 | 
			
		||||
	defer vulndb.Close()
 | 
			
		||||
 | 
			
		||||
	srcpkgs := map[string]string{}
 | 
			
		||||
	for _, p := range host.Packages.OSPkg {
 | 
			
		||||
		srcpkgs[p.SrcName] = p.SrcVersion
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for srcname, srcver := range srcpkgs {
 | 
			
		||||
		pkgs, err := vulndb.GetPackage(host.Family, host.Release, srcname)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get package")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for cveid, datasrcs := range pkgs {
 | 
			
		||||
			for datasrc, ps := range datasrcs {
 | 
			
		||||
				for id, p := range ps {
 | 
			
		||||
					switch p.Status {
 | 
			
		||||
					case "fixed":
 | 
			
		||||
						for _, andVs := range p.Version {
 | 
			
		||||
							affected := true
 | 
			
		||||
							for _, v := range andVs {
 | 
			
		||||
								r, err := compare(v.Operator, srcver, v.Version)
 | 
			
		||||
								if err != nil {
 | 
			
		||||
									return errors.Wrap(err, "compare")
 | 
			
		||||
								}
 | 
			
		||||
								if !r {
 | 
			
		||||
									affected = false
 | 
			
		||||
									break
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
							if affected {
 | 
			
		||||
								vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
								if !ok {
 | 
			
		||||
									host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
								}
 | 
			
		||||
								vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
									Name:   srcname,
 | 
			
		||||
									Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
									Status: p.Status,
 | 
			
		||||
								})
 | 
			
		||||
								vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
								host.ScannedCves[cveid] = vinfo
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					case "open":
 | 
			
		||||
						vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
						if !ok {
 | 
			
		||||
							host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
						}
 | 
			
		||||
						vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
							Name:   srcname,
 | 
			
		||||
							Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
							Status: p.Status,
 | 
			
		||||
						})
 | 
			
		||||
						vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
						host.ScannedCves[cveid] = vinfo
 | 
			
		||||
					case "not affected":
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulns, err := vulndb.GetVulnerability(maps.Keys(host.ScannedCves))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get vulnerability")
 | 
			
		||||
	}
 | 
			
		||||
	for cveid, datasrcs := range vulns {
 | 
			
		||||
		vinfo := host.ScannedCves[cveid]
 | 
			
		||||
		vinfo.Content = map[string]dbTypes.Vulnerability{}
 | 
			
		||||
		for src, v := range datasrcs {
 | 
			
		||||
			vinfo.Content[src] = v
 | 
			
		||||
		}
 | 
			
		||||
		host.ScannedCves[cveid] = vinfo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func compare(operator, srcver, ver string) (bool, error) {
 | 
			
		||||
	v1, err := version.NewVersion(srcver)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, errors.Wrap(err, "parse version")
 | 
			
		||||
	}
 | 
			
		||||
	v2, err := version.NewVersion(ver)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, errors.Wrap(err, "parse version")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := v1.Compare(v2)
 | 
			
		||||
	switch operator {
 | 
			
		||||
	case "eq":
 | 
			
		||||
		if r == 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "lt":
 | 
			
		||||
		if r < 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "le":
 | 
			
		||||
		if r <= 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "gt":
 | 
			
		||||
		if r > 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "ge":
 | 
			
		||||
		if r >= 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return false, errors.New("not supported operator")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								pkg/detect/detect.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,63 @@
 | 
			
		||||
package detect
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/cmd/version"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/detect/cpe"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/detect/debian"
 | 
			
		||||
	detectTypes "github.com/future-architect/vuls/pkg/detect/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/detect/ubuntu"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/detect/windows"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Detect(ctx context.Context, host *types.Host) error {
 | 
			
		||||
	if host.ScanError != "" {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var detectors []detectTypes.Detector
 | 
			
		||||
	if len(host.Packages.OSPkg) > 0 {
 | 
			
		||||
		switch host.Family {
 | 
			
		||||
		case "debian":
 | 
			
		||||
			detectors = append(detectors, debian.Detector{})
 | 
			
		||||
		case "ubuntu":
 | 
			
		||||
			detectors = append(detectors, ubuntu.Detector{})
 | 
			
		||||
		default:
 | 
			
		||||
			return errors.New("not implemented")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(host.Packages.KB) > 0 {
 | 
			
		||||
		detectors = append(detectors, windows.Detector{})
 | 
			
		||||
	}
 | 
			
		||||
	if len(host.Packages.CPE) > 0 {
 | 
			
		||||
		detectors = append(detectors, cpe.Detector{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	for {
 | 
			
		||||
		if len(detectors) == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		d := detectors[0]
 | 
			
		||||
		if err = d.Detect(ctx, host); err != nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		detectors = detectors[1:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t := time.Now()
 | 
			
		||||
	host.DetecteddAt = &t
 | 
			
		||||
	host.DetectedVersion = version.Version
 | 
			
		||||
	host.DetectedRevision = version.Revision
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "detect %s", host.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								pkg/detect/types/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,12 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Detector interface {
 | 
			
		||||
	Name() string
 | 
			
		||||
	Detect(context.Context, *types.Host) error
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										151
									
								
								pkg/detect/ubuntu/ubuntu.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,151 @@
 | 
			
		||||
package ubuntu
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	version "github.com/knqyf263/go-deb-version"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db"
 | 
			
		||||
	dbTypes "github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Detector struct{}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Name() string {
 | 
			
		||||
	return "ubuntu detector"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Detect(ctx context.Context, host *types.Host) error {
 | 
			
		||||
	if host.ScannedCves == nil {
 | 
			
		||||
		host.ScannedCves = map[string]types.VulnInfo{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulndb, err := db.Open("boltdb", host.Config.Detect.Path, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s", host.Config.Detect.Path)
 | 
			
		||||
	}
 | 
			
		||||
	defer vulndb.Close()
 | 
			
		||||
 | 
			
		||||
	srcpkgs := map[string]string{}
 | 
			
		||||
	for _, p := range host.Packages.OSPkg {
 | 
			
		||||
		srcpkgs[p.SrcName] = p.SrcVersion
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for srcname, srcver := range srcpkgs {
 | 
			
		||||
		pkgs, err := vulndb.GetPackage(host.Family, host.Release, srcname)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get package")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for cveid, datasrcs := range pkgs {
 | 
			
		||||
			for datasrc, ps := range datasrcs {
 | 
			
		||||
				for id, p := range ps {
 | 
			
		||||
					switch p.Status {
 | 
			
		||||
					case "released":
 | 
			
		||||
						for _, andVs := range p.Version {
 | 
			
		||||
							affected := true
 | 
			
		||||
							for _, v := range andVs {
 | 
			
		||||
								r, err := compare(v.Operator, srcver, v.Version)
 | 
			
		||||
								if err != nil {
 | 
			
		||||
									return errors.Wrap(err, "compare")
 | 
			
		||||
								}
 | 
			
		||||
								if !r {
 | 
			
		||||
									affected = false
 | 
			
		||||
									break
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
							if affected {
 | 
			
		||||
								vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
								if !ok {
 | 
			
		||||
									host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
								}
 | 
			
		||||
								vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
									Name:   srcname,
 | 
			
		||||
									Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
									Status: p.Status,
 | 
			
		||||
								})
 | 
			
		||||
								vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
								host.ScannedCves[cveid] = vinfo
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					case "needed", "deferred", "pending":
 | 
			
		||||
						vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
						if !ok {
 | 
			
		||||
							host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
						}
 | 
			
		||||
						vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
							Name:   srcname,
 | 
			
		||||
							Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
							Status: p.Status,
 | 
			
		||||
						})
 | 
			
		||||
						vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
						host.ScannedCves[cveid] = vinfo
 | 
			
		||||
					case "not-affected", "DNE":
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulns, err := vulndb.GetVulnerability(maps.Keys(host.ScannedCves))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get vulnerability")
 | 
			
		||||
	}
 | 
			
		||||
	for cveid, datasrcs := range vulns {
 | 
			
		||||
		vinfo := host.ScannedCves[cveid]
 | 
			
		||||
		vinfo.Content = map[string]dbTypes.Vulnerability{}
 | 
			
		||||
		for src, v := range datasrcs {
 | 
			
		||||
			vinfo.Content[src] = v
 | 
			
		||||
		}
 | 
			
		||||
		host.ScannedCves[cveid] = vinfo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func compare(operator, srcver, ver string) (bool, error) {
 | 
			
		||||
	v1, err := version.NewVersion(srcver)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, errors.Wrap(err, "parse version")
 | 
			
		||||
	}
 | 
			
		||||
	v2, err := version.NewVersion(ver)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, errors.Wrap(err, "parse version")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := v1.Compare(v2)
 | 
			
		||||
	switch operator {
 | 
			
		||||
	case "eq":
 | 
			
		||||
		if r == 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "lt":
 | 
			
		||||
		if r < 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "le":
 | 
			
		||||
		if r <= 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "gt":
 | 
			
		||||
		if r > 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "ge":
 | 
			
		||||
		if r >= 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return false, errors.New("not supported operator")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										120
									
								
								pkg/detect/windows/windows.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,120 @@
 | 
			
		||||
package windows
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
	"golang.org/x/exp/slices"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db"
 | 
			
		||||
	dbTypes "github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Detector struct{}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Name() string {
 | 
			
		||||
	return "windows detector"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Detect(ctx context.Context, host *types.Host) error {
 | 
			
		||||
	if host.ScannedCves == nil {
 | 
			
		||||
		host.ScannedCves = map[string]types.VulnInfo{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulndb, err := db.Open("boltdb", host.Config.Detect.Path, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s", host.Config.Detect.Path)
 | 
			
		||||
	}
 | 
			
		||||
	defer vulndb.Close()
 | 
			
		||||
 | 
			
		||||
	supercedences, err := vulndb.GetSupercedence(host.Packages.KB)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get supercedence")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var unapplied []string
 | 
			
		||||
	for _, kbs := range supercedences {
 | 
			
		||||
		var applied bool
 | 
			
		||||
		for _, kb := range kbs {
 | 
			
		||||
			if slices.Contains(host.Packages.KB, kb) {
 | 
			
		||||
				applied = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !applied {
 | 
			
		||||
			unapplied = append(unapplied, kbs...)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	unapplied = util.Unique(unapplied)
 | 
			
		||||
 | 
			
		||||
	products, err := vulndb.GetKBtoProduct(host.Release, append(host.Packages.KB, unapplied...))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get product from kb")
 | 
			
		||||
	}
 | 
			
		||||
	if !slices.Contains(products, host.Release) {
 | 
			
		||||
		products = append(products, host.Release)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, product := range util.Unique(products) {
 | 
			
		||||
		pkgs, err := vulndb.GetPackage(host.Family, host.Release, product)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get package")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for cveid, datasrcs := range pkgs {
 | 
			
		||||
			for datasrc, ps := range datasrcs {
 | 
			
		||||
				for id, p := range ps {
 | 
			
		||||
					switch p.Status {
 | 
			
		||||
					case "fixed":
 | 
			
		||||
						for _, v := range p.Version {
 | 
			
		||||
							if slices.Contains(unapplied, v[0].Version) {
 | 
			
		||||
								vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
								if !ok {
 | 
			
		||||
									host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
								}
 | 
			
		||||
								vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
									Name:   fmt.Sprintf("%s: KB%s", product, v[0].Version),
 | 
			
		||||
									Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
									Status: p.Status,
 | 
			
		||||
								})
 | 
			
		||||
								vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
								host.ScannedCves[cveid] = vinfo
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					case "unfixed":
 | 
			
		||||
						vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
						if !ok {
 | 
			
		||||
							host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
						}
 | 
			
		||||
						vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
							Name:   product,
 | 
			
		||||
							Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
							Status: p.Status,
 | 
			
		||||
						})
 | 
			
		||||
						vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
						host.ScannedCves[cveid] = vinfo
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulns, err := vulndb.GetVulnerability(maps.Keys(host.ScannedCves))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get vulnerability")
 | 
			
		||||
	}
 | 
			
		||||
	for cveid, datasrcs := range vulns {
 | 
			
		||||
		vinfo := host.ScannedCves[cveid]
 | 
			
		||||
		vinfo.Content = map[string]dbTypes.Vulnerability{}
 | 
			
		||||
		for src, v := range datasrcs {
 | 
			
		||||
			vinfo.Content[src] = v
 | 
			
		||||
		}
 | 
			
		||||
		host.ScannedCves[cveid] = vinfo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								pkg/log/log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,22 @@
 | 
			
		||||
package log
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ctxLogger struct{}
 | 
			
		||||
 | 
			
		||||
// ContextWithLogger adds logger to context
 | 
			
		||||
func ContextWithLogger(ctx context.Context, l *zap.Logger) context.Context {
 | 
			
		||||
	return context.WithValue(ctx, ctxLogger{}, l)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoggerFromContext returns logger from context
 | 
			
		||||
func LoggerFromContext(ctx context.Context) *zap.Logger {
 | 
			
		||||
	if l, ok := ctx.Value(ctxLogger{}).(*zap.Logger); ok {
 | 
			
		||||
		return l
 | 
			
		||||
	}
 | 
			
		||||
	return zap.L()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								pkg/scan/cpe/cpe.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,44 @@
 | 
			
		||||
package cpe
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/knqyf263/go-cpe/naming"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	scanTypes "github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Name() string {
 | 
			
		||||
	return "cpe analyzer"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
 | 
			
		||||
	ah.Host.Packages.CPE = map[string]types.CPE{}
 | 
			
		||||
	for _, c := range ah.Host.Config.Scan.CPE {
 | 
			
		||||
		if _, err := naming.UnbindFS(c.CPE); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "unbind %s", c.CPE)
 | 
			
		||||
		}
 | 
			
		||||
		key := c.CPE
 | 
			
		||||
 | 
			
		||||
		if c.RunningOn != "" {
 | 
			
		||||
			if _, err := naming.UnbindFS(c.RunningOn); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "unbind %s", c.RunningOn)
 | 
			
		||||
			}
 | 
			
		||||
			key = fmt.Sprintf("%s_on_%s", c.CPE, c.RunningOn)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ah.Host.Packages.CPE[key] = types.CPE{
 | 
			
		||||
			CPE:       c.CPE,
 | 
			
		||||
			RunningOn: c.RunningOn,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										93
									
								
								pkg/scan/os/os.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,93 @@
 | 
			
		||||
package os
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/ospkg/apk"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/ospkg/dpkg"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/ospkg/rpm"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Name() string {
 | 
			
		||||
	return "os analyzer"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Analyze(ctx context.Context, ah *types.AnalyzerHost) error {
 | 
			
		||||
	status, stdout, stderr, err := ah.Host.Exec(ctx, "cat /etc/os-release", false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, `exec "cat /etc/os-release"`)
 | 
			
		||||
	}
 | 
			
		||||
	if stderr != "" {
 | 
			
		||||
		return errors.New(stderr)
 | 
			
		||||
	}
 | 
			
		||||
	if status != 0 {
 | 
			
		||||
		return errors.Errorf("exit status is %d", status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ah.Host.Family, ah.Host.Release, err = ParseOSRelease(stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse /etc/os-release")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch ah.Host.Family {
 | 
			
		||||
	case "debian", "ubuntu":
 | 
			
		||||
		ah.Analyzers = append(ah.Analyzers, dpkg.Analyzer{})
 | 
			
		||||
	case "redhat", "centos", "alma", "rocky", "fedora", "opensuse", "opensuse.tumbleweed", "opensuse.leap", "suse.linux.enterprise.server", "suse.linux.enterprise.desktop":
 | 
			
		||||
		ah.Analyzers = append(ah.Analyzers, rpm.Analyzer{})
 | 
			
		||||
	case "alpine":
 | 
			
		||||
		ah.Analyzers = append(ah.Analyzers, apk.Analyzer{})
 | 
			
		||||
	case "":
 | 
			
		||||
		return errors.New("family is unknown")
 | 
			
		||||
	default:
 | 
			
		||||
		return errors.New("not supported OS")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseOSRelease(stdout string) (string, string, error) {
 | 
			
		||||
	var family, versionID string
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
 | 
			
		||||
		ss := strings.SplitN(line, "=", 2)
 | 
			
		||||
		if len(ss) != 2 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		key, value := strings.TrimSpace(ss[0]), strings.TrimSpace(ss[1])
 | 
			
		||||
 | 
			
		||||
		switch key {
 | 
			
		||||
		case "ID":
 | 
			
		||||
			switch id := strings.Trim(value, `"'`); id {
 | 
			
		||||
			case "almalinux":
 | 
			
		||||
				family = "alma"
 | 
			
		||||
			case "opensuse-leap", "opensuse-tumbleweed":
 | 
			
		||||
				family = strings.ReplaceAll(id, "-", ".")
 | 
			
		||||
			case "sles":
 | 
			
		||||
				family = "suse.linux.enterprise.server"
 | 
			
		||||
			case "sled":
 | 
			
		||||
				family = "suse.linux.enterprise.desktop"
 | 
			
		||||
			default:
 | 
			
		||||
				family = strings.ToLower(id)
 | 
			
		||||
			}
 | 
			
		||||
		case "VERSION_ID":
 | 
			
		||||
			versionID = strings.Trim(value, `"'`)
 | 
			
		||||
		default:
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if family == "" {
 | 
			
		||||
		return "", "", errors.New("family is unknown")
 | 
			
		||||
	}
 | 
			
		||||
	return family, versionID, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								pkg/scan/ospkg/apk/apk.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,71 @@
 | 
			
		||||
package apk
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	scanTypes "github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Name() string {
 | 
			
		||||
	return "apk analyzer"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
 | 
			
		||||
	status, stdout, stderr, err := ah.Host.Exec(ctx, "apk info -v", false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, `exec "apk info -v"`)
 | 
			
		||||
	}
 | 
			
		||||
	if stderr != "" {
 | 
			
		||||
		return errors.New(stderr)
 | 
			
		||||
	}
 | 
			
		||||
	if status != 0 {
 | 
			
		||||
		return errors.Errorf("exit status is %d", status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ah.Host.Packages.OSPkg, err = ParseInstalledPackage(stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse installed package")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseInstalledPackage(stdout string) (map[string]types.Package, error) {
 | 
			
		||||
	pkgs := map[string]types.Package{}
 | 
			
		||||
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		name, version, err := parseApkInfo(scanner.Text())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "parse apk info line")
 | 
			
		||||
		}
 | 
			
		||||
		if name == "" || version == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pkgs[name] = types.Package{
 | 
			
		||||
			Name:    name,
 | 
			
		||||
			Version: version,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return pkgs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseApkInfo(line string) (string, string, error) {
 | 
			
		||||
	ss := strings.Split(line, "-")
 | 
			
		||||
	if len(ss) < 3 {
 | 
			
		||||
		if strings.Contains(ss[0], "WARNING") {
 | 
			
		||||
			return "", "", nil
 | 
			
		||||
		}
 | 
			
		||||
		return "", "", errors.Errorf(`unexpected package line format. accepts: "<package name>-<version>-<release>", received: "%s"`, line)
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(ss[:len(ss)-2], "-"), strings.Join(ss[len(ss)-2:], "-"), nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										95
									
								
								pkg/scan/ospkg/dpkg/dpkg.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,95 @@
 | 
			
		||||
package dpkg
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	scanTypes "github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Name() string {
 | 
			
		||||
	return "dpkg analyzer"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
 | 
			
		||||
	status, stdout, stderr, err := ah.Host.Exec(ctx, `dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${Architecture},\${source:Package},\${source:Version}\n"`, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, `exec "dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${Architecture},\${source:Package},\${source:Version}\n"`)
 | 
			
		||||
	}
 | 
			
		||||
	if stderr != "" {
 | 
			
		||||
		return errors.New(stderr)
 | 
			
		||||
	}
 | 
			
		||||
	if status != 0 {
 | 
			
		||||
		return errors.Errorf("exit status is %d", status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ah.Host.Packages.OSPkg, err = ParseInstalledPackage(stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse installed package")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseInstalledPackage(stdout string) (map[string]types.Package, error) {
 | 
			
		||||
	pkgs := map[string]types.Package{}
 | 
			
		||||
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
 | 
			
		||||
			name, status, version, arch, srcName, srcVersion, err := parseDPKGQueryLine(trimmed)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, errors.Wrap(err, "parse dpkq query line")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			packageStatus := status[1]
 | 
			
		||||
			// Package status:
 | 
			
		||||
			//     n = Not-installed
 | 
			
		||||
			//     c = Config-files
 | 
			
		||||
			//     H = Half-installed
 | 
			
		||||
			//     U = Unpacked
 | 
			
		||||
			//     F = Half-configured
 | 
			
		||||
			//     W = Triggers-awaiting
 | 
			
		||||
			//     t = Triggers-pending
 | 
			
		||||
			//     i = Installed
 | 
			
		||||
			if packageStatus != 'i' {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			pkgs[name] = types.Package{
 | 
			
		||||
				Name:       name,
 | 
			
		||||
				Version:    version,
 | 
			
		||||
				Arch:       arch,
 | 
			
		||||
				SrcName:    srcName,
 | 
			
		||||
				SrcVersion: srcVersion,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return pkgs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseDPKGQueryLine(line string) (string, string, string, string, string, string, error) {
 | 
			
		||||
	ss := strings.Split(line, ",")
 | 
			
		||||
	if len(ss) == 6 {
 | 
			
		||||
		// remove :amd64, i386...
 | 
			
		||||
		name, _, _ := strings.Cut(ss[0], ":")
 | 
			
		||||
		status := strings.TrimSpace(ss[1])
 | 
			
		||||
		if len(status) < 2 {
 | 
			
		||||
			return "", "", "", "", "", "", errors.Errorf(`unexpected db:Status-Abbrev format. accepts: "ii", received: "%s"`, status)
 | 
			
		||||
		}
 | 
			
		||||
		version := ss[2]
 | 
			
		||||
		arch := ss[3]
 | 
			
		||||
		srcName, _, _ := strings.Cut(ss[4], " ")
 | 
			
		||||
		srcVersion := ss[5]
 | 
			
		||||
		return name, status, version, arch, srcName, srcVersion, nil
 | 
			
		||||
	}
 | 
			
		||||
	return "", "", "", "", "", "", errors.Errorf(`unexpected package line format. accepts: "<bin name>,<status>,<bin version>,<arch>,<src name>,<src version>", received: "%s"`, line)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								pkg/scan/ospkg/rpm/rpm.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,114 @@
 | 
			
		||||
package rpm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/hashicorp/go-version"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	scanTypes "github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Name() string {
 | 
			
		||||
	return "rpm analyzer"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
 | 
			
		||||
	status, stdout, stderr, err := ah.Host.Exec(ctx, `rpm --version`, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, `exec "rpm --version"`)
 | 
			
		||||
	}
 | 
			
		||||
	if stderr != "" {
 | 
			
		||||
		return errors.New(stderr)
 | 
			
		||||
	}
 | 
			
		||||
	if status != 0 {
 | 
			
		||||
		return errors.Errorf("exit status is %d", status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH} %{VENDOR}\n"`
 | 
			
		||||
	rpmver, err := version.NewVersion(strings.TrimPrefix(strings.TrimSpace(stdout), "RPM version "))
 | 
			
		||||
	rpmModukaritylabel, err := version.NewVersion("4.15.0")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse rpm version for modularitylabel")
 | 
			
		||||
	}
 | 
			
		||||
	rpmEpochNum, err := version.NewVersion("4.8.0")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse rpm version for epochnum")
 | 
			
		||||
	}
 | 
			
		||||
	if rpmver.GreaterThanOrEqual(rpmModukaritylabel) {
 | 
			
		||||
		cmd = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH} %{VENDOR} %{MODULARITYLABEL}\n"`
 | 
			
		||||
	} else if rpmver.GreaterThanOrEqual(rpmEpochNum) {
 | 
			
		||||
		cmd = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH} %{VENDOR}\n"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	status, stdout, stderr, err = ah.Host.Exec(ctx, cmd, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, `exec "%s"`, cmd)
 | 
			
		||||
	}
 | 
			
		||||
	if stderr != "" {
 | 
			
		||||
		return errors.New(stderr)
 | 
			
		||||
	}
 | 
			
		||||
	if status != 0 {
 | 
			
		||||
		return errors.Errorf("exit status is %d", status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ah.Host.Packages.OSPkg, err = ParseInstalledPackage(stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse installed package")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseInstalledPackage(stdout string) (map[string]types.Package, error) {
 | 
			
		||||
	pkgs := map[string]types.Package{}
 | 
			
		||||
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
 | 
			
		||||
			name, version, release, arch, vendor, modularitylabel, err := parseRpmQaLine(trimmed)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, errors.Wrap(err, "parse rpm -qa line")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			pkgs[name] = types.Package{
 | 
			
		||||
				Name:            name,
 | 
			
		||||
				Version:         version,
 | 
			
		||||
				Release:         release,
 | 
			
		||||
				Arch:            arch,
 | 
			
		||||
				Vendor:          vendor,
 | 
			
		||||
				ModularityLabel: modularitylabel,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return pkgs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseRpmQaLine(line string) (string, string, string, string, string, string, error) {
 | 
			
		||||
	ss := strings.Fields(line)
 | 
			
		||||
	if len(ss) < 6 {
 | 
			
		||||
		return "", "", "", "", "", "", errors.Errorf(`unexpected rpm -qa line format. accepts: "<name> <epoch> <version> <release> <arch> <vendor>( <modularitylabel>)", received: "%s"`, line)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ver := ss[2]
 | 
			
		||||
	epoch := ss[1]
 | 
			
		||||
	if epoch != "0" && epoch != "(none)" {
 | 
			
		||||
		ver = fmt.Sprintf("%s:%s", epoch, ss[2])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var modularitylabel string
 | 
			
		||||
	if len(ss) == 7 {
 | 
			
		||||
		modularitylabel = ss[5]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ss[0], ver, ss[3], ss[4], ss[5], modularitylabel, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								pkg/scan/scan.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,54 @@
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/cmd/version"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/cpe"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/os"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/systeminfo"
 | 
			
		||||
	scanTypes "github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Scan(ctx context.Context, host *types.Host) error {
 | 
			
		||||
	ah := scanTypes.AnalyzerHost{Host: host}
 | 
			
		||||
	if ah.Host.Config.Scan.OSPkg != nil {
 | 
			
		||||
		if runtime.GOOS == "windows" {
 | 
			
		||||
			ah.Analyzers = append(ah.Analyzers, systeminfo.Analyzer{})
 | 
			
		||||
		} else {
 | 
			
		||||
			ah.Analyzers = append(ah.Analyzers, os.Analyzer{})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(ah.Host.Config.Scan.CPE) > 0 {
 | 
			
		||||
		ah.Analyzers = append(ah.Analyzers, cpe.Analyzer{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		err error
 | 
			
		||||
	)
 | 
			
		||||
	for {
 | 
			
		||||
		if len(ah.Analyzers) == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		a := ah.Analyzers[0]
 | 
			
		||||
		if err = a.Analyze(ctx, &ah); err != nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		ah.Analyzers = ah.Analyzers[1:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t := time.Now()
 | 
			
		||||
	ah.Host.ScannedAt = &t
 | 
			
		||||
	ah.Host.ScannedVersion = version.Version
 | 
			
		||||
	ah.Host.ScannedRevision = version.Revision
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "analyze %s", ah.Host.Name)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										480
									
								
								pkg/scan/systeminfo/systeminfo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,480 @@
 | 
			
		||||
package systeminfo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Name() string {
 | 
			
		||||
	return "systeminfo analyzer"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Analyze(ctx context.Context, ah *types.AnalyzerHost) error {
 | 
			
		||||
	status, stdout, stderr, err := ah.Host.Exec(ctx, "systeminfo", false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, `exec "systeminfo"`)
 | 
			
		||||
	}
 | 
			
		||||
	if stderr != "" {
 | 
			
		||||
		return errors.New(stderr)
 | 
			
		||||
	}
 | 
			
		||||
	if status != 0 {
 | 
			
		||||
		return errors.Errorf("exit status is %d", status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ah.Host.Family, ah.Host.Release, ah.Host.Packages.KB, err = ParseSysteminfo(stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse systeminfo")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ah.Host.Family == "" {
 | 
			
		||||
		return errors.New("family is unknown")
 | 
			
		||||
	}
 | 
			
		||||
	if ah.Host.Release == "" {
 | 
			
		||||
		return errors.New("release is unknown")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseSysteminfo(stdout string) (string, string, []string, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		o   osInfo
 | 
			
		||||
		kbs []string
 | 
			
		||||
	)
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case strings.HasPrefix(line, "OS Name:"):
 | 
			
		||||
			o.productName = strings.TrimSpace(strings.TrimPrefix(line, "OS Name:"))
 | 
			
		||||
		case strings.HasPrefix(line, "OS Version:"):
 | 
			
		||||
			s := strings.TrimSpace(strings.TrimPrefix(line, "OS Version:"))
 | 
			
		||||
			lhs, build, _ := strings.Cut(s, " Build ")
 | 
			
		||||
			vb, sp, _ := strings.Cut(lhs, " ")
 | 
			
		||||
			o.version = strings.TrimSuffix(vb, fmt.Sprintf(".%s", build))
 | 
			
		||||
			o.build = build
 | 
			
		||||
			if sp != "N/A" {
 | 
			
		||||
				o.servicePack = sp
 | 
			
		||||
			}
 | 
			
		||||
		case strings.HasPrefix(line, "System Type:"):
 | 
			
		||||
			o.arch = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "System Type:"), "PC"))
 | 
			
		||||
		case strings.HasPrefix(line, "OS Configuration:"):
 | 
			
		||||
			switch {
 | 
			
		||||
			case strings.Contains(line, "Server"):
 | 
			
		||||
				o.installationType = "Server"
 | 
			
		||||
			case strings.Contains(line, "Workstation"):
 | 
			
		||||
				o.installationType = "Client"
 | 
			
		||||
			default:
 | 
			
		||||
				return "", "", nil, errors.Errorf(`installation type not found from "%s"`, line)
 | 
			
		||||
			}
 | 
			
		||||
		case strings.HasPrefix(line, "Hotfix(s):"):
 | 
			
		||||
			nKB, err := strconv.Atoi(strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "Hotfix(s):"), " Hotfix(s) Installed.")))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", "", nil, errors.Errorf(`number of installed hotfix from "%s"`, line)
 | 
			
		||||
			}
 | 
			
		||||
			for i := 0; i < nKB; i++ {
 | 
			
		||||
				scanner.Scan()
 | 
			
		||||
				line := scanner.Text()
 | 
			
		||||
				_, rhs, found := strings.Cut(line, ":")
 | 
			
		||||
				if !found {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				s := strings.TrimSpace(rhs)
 | 
			
		||||
				if strings.HasPrefix(s, "KB") {
 | 
			
		||||
					kbs = append(kbs, strings.TrimPrefix(s, "KB"))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	release, err := detectOSName(o)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", "", nil, errors.Wrap(err, "detect os name")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "windows", release, kbs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type osInfo struct {
 | 
			
		||||
	productName      string
 | 
			
		||||
	version          string
 | 
			
		||||
	build            string
 | 
			
		||||
	edition          string
 | 
			
		||||
	servicePack      string
 | 
			
		||||
	arch             string
 | 
			
		||||
	installationType string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectOSName(osInfo osInfo) (string, error) {
 | 
			
		||||
	osName, err := detectOSNameFromOSInfo(osInfo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", errors.Wrapf(err, "detect OS Name from OSInfo: %#v", osInfo)
 | 
			
		||||
	}
 | 
			
		||||
	return osName, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectOSNameFromOSInfo(osInfo osInfo) (string, error) {
 | 
			
		||||
	switch osInfo.version {
 | 
			
		||||
	case "5.0":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Microsoft Windows 2000 %s", osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return "Microsoft Windows 2000", nil
 | 
			
		||||
		case "Server":
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Microsoft Windows 2000 Server %s", osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return "Microsoft Windows 2000 Server", nil
 | 
			
		||||
		}
 | 
			
		||||
	case "5.1":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			var n string
 | 
			
		||||
			switch osInfo.edition {
 | 
			
		||||
			case "Professional":
 | 
			
		||||
				n = "Microsoft Windows XP Professional"
 | 
			
		||||
			case "Media Center":
 | 
			
		||||
				n = "Microsoft Windows XP Media Center Edition 2005"
 | 
			
		||||
			case "Tablet PC":
 | 
			
		||||
				n = "Microsoft Windows XP Tablet PC Edition 2005"
 | 
			
		||||
			default:
 | 
			
		||||
				n = "Microsoft Windows XP"
 | 
			
		||||
			}
 | 
			
		||||
			switch osInfo.arch {
 | 
			
		||||
			case "x64":
 | 
			
		||||
				n = fmt.Sprintf("%s x64 Edition", n)
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return n, nil
 | 
			
		||||
		}
 | 
			
		||||
	case "5.2":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			var n string
 | 
			
		||||
			switch osInfo.edition {
 | 
			
		||||
			case "Professional":
 | 
			
		||||
				n = "Microsoft Windows XP Professional"
 | 
			
		||||
			case "Media Center":
 | 
			
		||||
				n = "Microsoft Windows XP Media Center Edition 2005"
 | 
			
		||||
			case "Tablet PC":
 | 
			
		||||
				n = "Microsoft Windows XP Tablet PC Edition 2005"
 | 
			
		||||
			default:
 | 
			
		||||
				n = "Microsoft Windows XP"
 | 
			
		||||
			}
 | 
			
		||||
			switch osInfo.arch {
 | 
			
		||||
			case "x64":
 | 
			
		||||
				n = fmt.Sprintf("%s x64 Edition", n)
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return n, nil
 | 
			
		||||
		case "Server":
 | 
			
		||||
			n := "Microsoft Windows Server 2003"
 | 
			
		||||
			if strings.Contains(osInfo.productName, "R2") {
 | 
			
		||||
				n = "Microsoft Windows Server 2003 R2"
 | 
			
		||||
			}
 | 
			
		||||
			switch osInfo.arch {
 | 
			
		||||
			case "x64":
 | 
			
		||||
				n = fmt.Sprintf("%s x64 Edition", n)
 | 
			
		||||
			case "IA64":
 | 
			
		||||
				if osInfo.edition == "Enterprise" {
 | 
			
		||||
					n = fmt.Sprintf("%s, Enterprise Edition for Itanium-based Systems", n)
 | 
			
		||||
				} else {
 | 
			
		||||
					n = fmt.Sprintf("%s for Itanium-based Systems", n)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return n, nil
 | 
			
		||||
		}
 | 
			
		||||
	case "6.0":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			var n string
 | 
			
		||||
			switch osInfo.arch {
 | 
			
		||||
			case "x64":
 | 
			
		||||
				n = "Windows Vista x64 Editions"
 | 
			
		||||
			default:
 | 
			
		||||
				n = "Windows Vista"
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return n, nil
 | 
			
		||||
		case "Server":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Windows Server 2008 for %s Systems %s", arch, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows Server 2008 for %s Systems", arch), nil
 | 
			
		||||
		case "Server Core":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Windows Server 2008 for %s Systems %s (Server Core installation)", arch, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows Server 2008 for %s Systems (Server Core installation)", arch), nil
 | 
			
		||||
		}
 | 
			
		||||
	case "6.1":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Windows 7 for %s Systems %s", arch, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows 7 for %s Systems", arch), nil
 | 
			
		||||
		case "Server":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Windows Server 2008 R2 for %s Systems %s", arch, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows Server 2008 R2 for %s Systems", arch), nil
 | 
			
		||||
		case "Server Core":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Windows Server 2008 R2 for %s Systems %s (Server Core installation)", arch, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows Server 2008 R2 for %s Systems (Server Core installation)", arch), nil
 | 
			
		||||
		}
 | 
			
		||||
	case "6.2":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows 8 for %s Systems", arch), nil
 | 
			
		||||
		case "Server":
 | 
			
		||||
			return "Windows Server 2012", nil
 | 
			
		||||
		case "Server Core":
 | 
			
		||||
			return "Windows Server 2012 (Server Core installation)", nil
 | 
			
		||||
		}
 | 
			
		||||
	case "6.3":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows 8.1 for %s Systems", arch), nil
 | 
			
		||||
		case "Server":
 | 
			
		||||
			return "Windows Server 2012 R2", nil
 | 
			
		||||
		case "Server Core":
 | 
			
		||||
			return "Windows Server 2012 R2 (Server Core installation)", nil
 | 
			
		||||
		}
 | 
			
		||||
	case "10.0":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			if strings.Contains(osInfo.productName, "Windows 10") {
 | 
			
		||||
				arch, err := formatArch(osInfo.arch)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				name, err := formatNamebyBuild("10", osInfo.build)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				return fmt.Sprintf("%s for %s Systems", name, arch), nil
 | 
			
		||||
			}
 | 
			
		||||
			if strings.Contains(osInfo.productName, "Windows 11") {
 | 
			
		||||
				arch, err := formatArch(osInfo.arch)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				name, err := formatNamebyBuild("11", osInfo.build)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				return fmt.Sprintf("%s for %s Systems", name, arch), nil
 | 
			
		||||
			}
 | 
			
		||||
		case "Server":
 | 
			
		||||
			return formatNamebyBuild("Server", osInfo.build)
 | 
			
		||||
		case "Server Core":
 | 
			
		||||
			name, err := formatNamebyBuild("Server", osInfo.build)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("%s (Server Core installation)", name), nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return "", errors.New("OS Name not found")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatArch(arch string) (string, error) {
 | 
			
		||||
	switch arch {
 | 
			
		||||
	case "x64-based":
 | 
			
		||||
		return "x64-based", nil
 | 
			
		||||
	case "ARM64-based":
 | 
			
		||||
		return "ARM64-based", nil
 | 
			
		||||
	case "Itanium-based":
 | 
			
		||||
		return "Itanium-based", nil
 | 
			
		||||
	case "X86-based":
 | 
			
		||||
		return "32-bit", nil
 | 
			
		||||
	default:
 | 
			
		||||
		return "", errors.New("CPU Architecture not found")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type buildNumber struct {
 | 
			
		||||
	build string
 | 
			
		||||
	name  string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	winBuilds = map[string][]buildNumber{
 | 
			
		||||
		"10": {
 | 
			
		||||
			{
 | 
			
		||||
				build: "10240",
 | 
			
		||||
				name:  "Windows 10", // not "Windows 10 Version 1507"
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "10586",
 | 
			
		||||
				name:  "Windows 10 Version 1511",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "14393",
 | 
			
		||||
				name:  "Windows 10 Version 1607",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "15063",
 | 
			
		||||
				name:  "Windows 10 Version 1703",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "16299",
 | 
			
		||||
				name:  "Windows 10 Version 1709",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "17134",
 | 
			
		||||
				name:  "Windows 10 Version 1803",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "17763",
 | 
			
		||||
				name:  "Windows 10 Version 1809",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "18362",
 | 
			
		||||
				name:  "Windows 10 Version 1903",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "18363",
 | 
			
		||||
				name:  "Windows 10 Version 1909",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "19041",
 | 
			
		||||
				name:  "Windows 10 Version 2004",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "19042",
 | 
			
		||||
				name:  "Windows 10 Version 20H2",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "19043",
 | 
			
		||||
				name:  "Windows 10 Version 21H1",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "19044",
 | 
			
		||||
				name:  "Windows 10 Version 21H2",
 | 
			
		||||
			},
 | 
			
		||||
			// It seems that there are cases where the Product Name is Windows 10 even though it is Windows 11
 | 
			
		||||
			// ref: https://docs.microsoft.com/en-us/answers/questions/586548/in-the-official-version-of-windows-11-why-the-key.html
 | 
			
		||||
			{
 | 
			
		||||
				build: "22000",
 | 
			
		||||
				name:  "Windows 11",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"11": {
 | 
			
		||||
			{
 | 
			
		||||
				build: "22000",
 | 
			
		||||
				name:  "Windows 11", // not "Windows 11 Version 21H2"
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"Server": {
 | 
			
		||||
			{
 | 
			
		||||
				build: "14393",
 | 
			
		||||
				name:  "Windows Server 2016",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "16299",
 | 
			
		||||
				name:  "Windows Server, Version 1709",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "17134",
 | 
			
		||||
				name:  "Windows Server, Version 1809",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "17763",
 | 
			
		||||
				name:  "Windows Server 2019",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "18362",
 | 
			
		||||
				name:  "Windows Server, Version 1903",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "18363",
 | 
			
		||||
				name:  "Windows Server, Version 1909",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "19041",
 | 
			
		||||
				name:  "Windows Server, Version 2004",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "19042",
 | 
			
		||||
				name:  "Windows Server, Version 20H2",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "20348",
 | 
			
		||||
				name:  "Windows Server 2022",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func formatNamebyBuild(osType string, mybuild string) (string, error) {
 | 
			
		||||
	builds, ok := winBuilds[osType]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return "", errors.New("OS Type not found")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v := builds[0].name
 | 
			
		||||
	for _, b := range builds {
 | 
			
		||||
		if mybuild == b.build {
 | 
			
		||||
			return b.name, nil
 | 
			
		||||
		}
 | 
			
		||||
		if mybuild < b.build {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		v = b.name
 | 
			
		||||
	}
 | 
			
		||||
	return v, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								pkg/scan/types/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,17 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer interface {
 | 
			
		||||
	Name() string
 | 
			
		||||
	Analyze(context.Context, *AnalyzerHost) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AnalyzerHost struct {
 | 
			
		||||
	Host      *types.Host
 | 
			
		||||
	Analyzers []Analyzer
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										108
									
								
								pkg/server/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,108 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/labstack/echo/v4"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/cmd/version"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/config"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/detect"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/os"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/ospkg/apk"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/ospkg/dpkg"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/ospkg/rpm"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/systeminfo"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type scanContents struct {
 | 
			
		||||
	Contents []struct {
 | 
			
		||||
		ContentType string `json:"type,omitempty"`
 | 
			
		||||
		Content     string `json:"content,omitempty"`
 | 
			
		||||
	} `json:"contents,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Scan() echo.HandlerFunc {
 | 
			
		||||
	return func(c echo.Context) error {
 | 
			
		||||
		s := new(scanContents)
 | 
			
		||||
		if err := c.Bind(s); err != nil {
 | 
			
		||||
			return c.JSON(http.StatusBadRequest, "bad request")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		h := types.Host{Name: uuid.NewString()}
 | 
			
		||||
 | 
			
		||||
		for _, cont := range s.Contents {
 | 
			
		||||
			switch cont.ContentType {
 | 
			
		||||
			case "os-release":
 | 
			
		||||
				family, release, err := os.ParseOSRelease(cont.Content)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					h.ScanError = err.Error()
 | 
			
		||||
					return c.JSON(http.StatusInternalServerError, h)
 | 
			
		||||
				}
 | 
			
		||||
				h.Family = family
 | 
			
		||||
				h.Release = release
 | 
			
		||||
			case "systeminfo":
 | 
			
		||||
				family, release, kbs, err := systeminfo.ParseSysteminfo(cont.Content)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					h.ScanError = err.Error()
 | 
			
		||||
					return c.JSON(http.StatusInternalServerError, h)
 | 
			
		||||
				}
 | 
			
		||||
				h.Family = family
 | 
			
		||||
				h.Release = release
 | 
			
		||||
				h.Packages.KB = kbs
 | 
			
		||||
			case "apk":
 | 
			
		||||
				pkgs, err := apk.ParseInstalledPackage(cont.Content)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					h.ScanError = err.Error()
 | 
			
		||||
					return c.JSON(http.StatusInternalServerError, h)
 | 
			
		||||
				}
 | 
			
		||||
				h.Packages.OSPkg = pkgs
 | 
			
		||||
			case "dpkg":
 | 
			
		||||
				pkgs, err := dpkg.ParseInstalledPackage(cont.Content)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					h.ScanError = err.Error()
 | 
			
		||||
					return c.JSON(http.StatusInternalServerError, h)
 | 
			
		||||
				}
 | 
			
		||||
				h.Packages.OSPkg = pkgs
 | 
			
		||||
			case "rpm":
 | 
			
		||||
				pkgs, err := rpm.ParseInstalledPackage(cont.Content)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					h.ScanError = err.Error()
 | 
			
		||||
					return c.JSON(http.StatusInternalServerError, h)
 | 
			
		||||
				}
 | 
			
		||||
				h.Packages.OSPkg = pkgs
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		t := time.Now()
 | 
			
		||||
		h.ScannedAt = &t
 | 
			
		||||
		h.ScannedVersion = version.Version
 | 
			
		||||
		h.ScannedRevision = version.Revision
 | 
			
		||||
		return c.JSON(http.StatusOK, h)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Detect(dbpath string) echo.HandlerFunc {
 | 
			
		||||
	return func(c echo.Context) error {
 | 
			
		||||
		h := new(types.Host)
 | 
			
		||||
		if err := c.Bind(h); err != nil {
 | 
			
		||||
			return c.JSON(http.StatusBadRequest, "bad request")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if h.Config.Detect == nil {
 | 
			
		||||
			h.Config.Detect = &config.Detect{}
 | 
			
		||||
		}
 | 
			
		||||
		h.Config.Detect.Path = dbpath
 | 
			
		||||
 | 
			
		||||
		if err := detect.Detect(context.Background(), h); err != nil {
 | 
			
		||||
			h.DetectError = err.Error()
 | 
			
		||||
			return c.JSON(http.StatusInternalServerError, h)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return c.JSON(http.StatusOK, h)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										182
									
								
								pkg/types/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,182 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/config"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Host struct {
 | 
			
		||||
	Name    string `json:"name,omitempty"`
 | 
			
		||||
	Family  string `json:"family,omitempty"`
 | 
			
		||||
	Release string `json:"release,omitempty"`
 | 
			
		||||
 | 
			
		||||
	ScannedAt       *time.Time `json:"scanned_at,omitempty"`
 | 
			
		||||
	ScannedVersion  string     `json:"scanned_version,omitempty"`
 | 
			
		||||
	ScannedRevision string     `json:"scanned_revision,omitempty"`
 | 
			
		||||
	ScanError       string     `json:"scan_error,omitempty"`
 | 
			
		||||
 | 
			
		||||
	DetecteddAt      *time.Time `json:"detectedd_at,omitempty"`
 | 
			
		||||
	DetectedVersion  string     `json:"detected_version,omitempty"`
 | 
			
		||||
	DetectedRevision string     `json:"detected_revision,omitempty"`
 | 
			
		||||
	DetectError      string     `json:"detect_error,omitempty"`
 | 
			
		||||
 | 
			
		||||
	ReportedAt       *time.Time `json:"reported_at,omitempty"`
 | 
			
		||||
	ReportedVersion  string     `json:"reported_version,omitempty"`
 | 
			
		||||
	ReportedRevision string     `json:"reported_revision,omitempty"`
 | 
			
		||||
 | 
			
		||||
	Packages    Packages            `json:"packages,omitempty"`
 | 
			
		||||
	ScannedCves map[string]VulnInfo `json:"scanned_cves,omitempty"`
 | 
			
		||||
 | 
			
		||||
	Config Config `json:"config,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *Host) Exec(ctx context.Context, cmd string, sudo bool) (int, string, string, error) {
 | 
			
		||||
	if sudo {
 | 
			
		||||
		cmd = fmt.Sprintf("sudo -S %s", cmd)
 | 
			
		||||
	}
 | 
			
		||||
	switch h.Config.Type {
 | 
			
		||||
	case "local":
 | 
			
		||||
		execCmd := exec.CommandContext(ctx, "/bin/sh", "-c", cmd)
 | 
			
		||||
		if runtime.GOOS == "windows" {
 | 
			
		||||
			execCmd = exec.CommandContext(ctx, cmd)
 | 
			
		||||
		}
 | 
			
		||||
		var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
		execCmd.Stdout = &stdoutBuf
 | 
			
		||||
		execCmd.Stderr = &stderrBuf
 | 
			
		||||
		if err := execCmd.Run(); err != nil {
 | 
			
		||||
			if e, ok := err.(*exec.ExitError); ok {
 | 
			
		||||
				if s, ok := e.Sys().(syscall.WaitStatus); ok {
 | 
			
		||||
					return s.ExitStatus(), stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
				} else {
 | 
			
		||||
					return 998, stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				return 999, stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			return 0, stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
		}
 | 
			
		||||
	case "remote":
 | 
			
		||||
		sshBinPath, err := exec.LookPath("ssh")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, "", "", errors.Wrap(err, "look path to ssh")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		args := []string{"-tt"}
 | 
			
		||||
 | 
			
		||||
		home, err := os.UserHomeDir()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, "", "", errors.Wrap(err, "find %s home directory")
 | 
			
		||||
		}
 | 
			
		||||
		args = append(args,
 | 
			
		||||
			"-o", "StrictHostKeyChecking=yes",
 | 
			
		||||
			"-o", "LogLevel=quiet",
 | 
			
		||||
			"-o", "ConnectionAttempts=3",
 | 
			
		||||
			"-o", "ConnectTimeout=10",
 | 
			
		||||
			"-o", "ControlMaster=auto",
 | 
			
		||||
			"-o", fmt.Sprintf("ControlPath=%s", filepath.Join(home, ".vuls", fmt.Sprintf("controlmaster-%%r-%s.%%p", h.Name))),
 | 
			
		||||
			"-o", "Controlpersist=10m",
 | 
			
		||||
			"-l", *h.Config.User,
 | 
			
		||||
		)
 | 
			
		||||
		if h.Config.Port != nil {
 | 
			
		||||
			args = append(args, "-p", *h.Config.Port)
 | 
			
		||||
		}
 | 
			
		||||
		if h.Config.SSHKey != nil {
 | 
			
		||||
			args = append(args, "-i", *h.Config.SSHKey, "-o", "PasswordAuthentication=no")
 | 
			
		||||
		}
 | 
			
		||||
		if runtime.GOOS == "windows" {
 | 
			
		||||
			args = append(args, *h.Config.Host, cmd)
 | 
			
		||||
		} else {
 | 
			
		||||
			args = append(args, *h.Config.Host, fmt.Sprintf("stty cols 1000; %s", cmd))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		execCmd := exec.CommandContext(ctx, sshBinPath, args...)
 | 
			
		||||
		var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
		execCmd.Stdout = &stdoutBuf
 | 
			
		||||
		execCmd.Stderr = &stderrBuf
 | 
			
		||||
		if err := execCmd.Run(); err != nil {
 | 
			
		||||
			if e, ok := err.(*exec.ExitError); ok {
 | 
			
		||||
				if s, ok := e.Sys().(syscall.WaitStatus); ok {
 | 
			
		||||
					return s.ExitStatus(), stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
				} else {
 | 
			
		||||
					return 998, stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				return 999, stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			return 0, stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return 0, "", "", errors.Errorf("%s is not implemented", h.Config.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Packages struct {
 | 
			
		||||
	Kernel Kernel             `json:"kernel,omitempty"`
 | 
			
		||||
	OSPkg  map[string]Package `json:"ospkg,omitempty"`
 | 
			
		||||
	CPE    map[string]CPE     `json:"cpe,omitempty"`
 | 
			
		||||
	KB     []string           `json:"kb,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Kernel struct {
 | 
			
		||||
	Version         string `json:"version,omitempty"`
 | 
			
		||||
	Release         string `json:"release,omitempty"`
 | 
			
		||||
	RebootRrequired bool   `json:"reboot_rrequired,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Package struct {
 | 
			
		||||
	Name            string `json:"name,omitempty"`
 | 
			
		||||
	Version         string `json:"version,omitempty"`
 | 
			
		||||
	Release         string `json:"release,omitempty"`
 | 
			
		||||
	NewVersion      string `json:"new_version,omitempty"`
 | 
			
		||||
	NewRelease      string `json:"new_release,omitempty"`
 | 
			
		||||
	Arch            string `json:"arch,omitempty"`
 | 
			
		||||
	Vendor          string `json:"vendor,omitempty"`
 | 
			
		||||
	Repository      string `json:"repository,omitempty"`
 | 
			
		||||
	ModularityLabel string `json:"modularity_label,omitempty"`
 | 
			
		||||
 | 
			
		||||
	SrcName    string `json:"src_name,omitempty"`
 | 
			
		||||
	SrcVersion string `json:"src_version,omitempty"`
 | 
			
		||||
	SrcArch    string `json:"src_arch,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CPE struct {
 | 
			
		||||
	CPE       string `json:"cpe,omitempty"`
 | 
			
		||||
	RunningOn string `json:"running_on,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type VulnInfo struct {
 | 
			
		||||
	ID               string                         `json:"id,omitempty"`
 | 
			
		||||
	Content          map[string]types.Vulnerability `json:"content,omitempty"`
 | 
			
		||||
	AffectedPackages []AffectedPackage              `json:"affected_packages,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AffectedPackage struct {
 | 
			
		||||
	Name   string `json:"name,omitempty"`
 | 
			
		||||
	Source string `json:"source,omitempty"`
 | 
			
		||||
	Status string `json:"status,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Type      string         `json:"type,omitempty"`
 | 
			
		||||
	Host      *string        `json:"host,omitempty"`
 | 
			
		||||
	Port      *string        `json:"port,omitempty"`
 | 
			
		||||
	User      *string        `json:"user,omitempty"`
 | 
			
		||||
	SSHConfig *string        `json:"ssh_config,omitempty"`
 | 
			
		||||
	SSHKey    *string        `json:"ssh_key,omitempty"`
 | 
			
		||||
	Scan      *config.Scan   `json:"scan,omitempty"`
 | 
			
		||||
	Detect    *config.Detect `json:"detect,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								pkg/util/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,76 @@
 | 
			
		||||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"compress/bzip2"
 | 
			
		||||
	"compress/gzip"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/ulikunitz/xz"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func CacheDir() string {
 | 
			
		||||
	cacheDir, err := os.UserCacheDir()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cacheDir = os.TempDir()
 | 
			
		||||
	}
 | 
			
		||||
	dir := filepath.Join(cacheDir, "vuls")
 | 
			
		||||
	return dir
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Unique[T comparable](s []T) []T {
 | 
			
		||||
	m := map[T]struct{}{}
 | 
			
		||||
	for _, v := range s {
 | 
			
		||||
		m[v] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	return maps.Keys(m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Read(path string) ([]byte, error) {
 | 
			
		||||
	f, err := os.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrapf(err, "open %s", path)
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
 | 
			
		||||
	switch filepath.Ext(path) {
 | 
			
		||||
	case ".gz":
 | 
			
		||||
		gr, err := gzip.NewReader(f)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "create gzip reader")
 | 
			
		||||
		}
 | 
			
		||||
		defer gr.Close()
 | 
			
		||||
 | 
			
		||||
		bs, err := io.ReadAll(gr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "read data")
 | 
			
		||||
		}
 | 
			
		||||
		return bs, nil
 | 
			
		||||
	case ".bz2":
 | 
			
		||||
		bs, err := io.ReadAll(bzip2.NewReader(f))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "read data")
 | 
			
		||||
		}
 | 
			
		||||
		return bs, nil
 | 
			
		||||
	case ".xz":
 | 
			
		||||
		xr, err := xz.NewReader(f)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "create xz reader")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bs, err := io.ReadAll(xr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "read data")
 | 
			
		||||
		}
 | 
			
		||||
		return bs, nil
 | 
			
		||||
	default:
 | 
			
		||||
		bs, err := io.ReadAll(f)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "read data")
 | 
			
		||||
		}
 | 
			
		||||
		return bs, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,37 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// JSONWriter writes report as JSON format
 | 
			
		||||
type JSONWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
	var j []byte
 | 
			
		||||
	if j, err = json.MarshalIndent(scanResults, "", "  "); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(string(j))
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,51 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	formatter "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LogrusWriter write to logfile
 | 
			
		||||
type LogrusWriter struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w LogrusWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
	path := "/var/log/vuls/report.log"
 | 
			
		||||
	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	log := logrus.New()
 | 
			
		||||
	log.Formatter = &formatter.TextFormatter{}
 | 
			
		||||
	log.Out = f
 | 
			
		||||
	log.Level = logrus.InfoLevel
 | 
			
		||||
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		text, err := toPlainText(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		log.Infof(text)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,70 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"gopkg.in/gomail.v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MailWriter send mail
 | 
			
		||||
type MailWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w MailWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		m := gomail.NewMessage()
 | 
			
		||||
		m.SetHeader("From", conf.Mail.From)
 | 
			
		||||
		m.SetHeader("To", conf.Mail.To...)
 | 
			
		||||
		m.SetHeader("Cc", conf.Mail.Cc...)
 | 
			
		||||
 | 
			
		||||
		subject := fmt.Sprintf("%s%s %s",
 | 
			
		||||
			conf.Mail.SubjectPrefix,
 | 
			
		||||
			s.ServerName,
 | 
			
		||||
			s.CveSummary(),
 | 
			
		||||
		)
 | 
			
		||||
		m.SetHeader("Subject", subject)
 | 
			
		||||
 | 
			
		||||
		var body string
 | 
			
		||||
		if body, err = toPlainText(s); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		m.SetBody("text/plain", body)
 | 
			
		||||
		port, _ := strconv.Atoi(conf.Mail.SMTPPort)
 | 
			
		||||
		d := gomail.NewPlainDialer(
 | 
			
		||||
			conf.Mail.SMTPAddr,
 | 
			
		||||
			port,
 | 
			
		||||
			conf.Mail.User,
 | 
			
		||||
			conf.Mail.Password,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		d.TLSConfig = &tls.Config{
 | 
			
		||||
			InsecureSkipVerify: true,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := d.DialAndSend(m); err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										236
									
								
								report/slack.go
									
									
									
									
									
								
							
							
						
						@@ -1,236 +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"
 | 
			
		||||
	"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/parnurzeal/gorequest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type field struct {
 | 
			
		||||
	Title string `json:"title"`
 | 
			
		||||
	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"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SlackWriter send report to slack
 | 
			
		||||
type SlackWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w SlackWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
	conf := config.Conf.Slack
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
 | 
			
		||||
		channel := conf.Channel
 | 
			
		||||
		if channel == "${servername}" {
 | 
			
		||||
			channel = fmt.Sprintf("#%s", s.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		msg := message{
 | 
			
		||||
			Text:        msgText(s),
 | 
			
		||||
			Username:    conf.AuthUser,
 | 
			
		||||
			IconEmoji:   conf.IconEmoji,
 | 
			
		||||
			Channel:     channel,
 | 
			
		||||
			Attachments: toSlackAttachments(s),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bytes, _ := json.Marshal(msg)
 | 
			
		||||
		jsonBody := string(bytes)
 | 
			
		||||
		f := func() (err error) {
 | 
			
		||||
			resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).
 | 
			
		||||
				Send(string(jsonBody)).End()
 | 
			
		||||
			if resp.StatusCode != 200 {
 | 
			
		||||
				log.Errorf("Resonse body: %s", body)
 | 
			
		||||
				if len(errs) > 0 {
 | 
			
		||||
					return errs[0]
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			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 {
 | 
			
		||||
 | 
			
		||||
	notifyUsers := ""
 | 
			
		||||
	if 0 < len(r.KnownCves) || 0 < len(r.UnknownCves) {
 | 
			
		||||
		notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hostinfo := fmt.Sprintf(
 | 
			
		||||
		"*%s* (%s %s)",
 | 
			
		||||
		r.ServerName,
 | 
			
		||||
		r.Family,
 | 
			
		||||
		r.Release,
 | 
			
		||||
	)
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, hostinfo, r.CveSummary())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
 | 
			
		||||
 | 
			
		||||
	scanResult.KnownCves = append(scanResult.KnownCves, scanResult.UnknownCves...)
 | 
			
		||||
	for _, cveInfo := range scanResult.KnownCves {
 | 
			
		||||
		cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
 | 
			
		||||
		curentPackages := []string{}
 | 
			
		||||
		for _, p := range cveInfo.Packages {
 | 
			
		||||
			curentPackages = append(curentPackages, p.ToStringCurrentVersion())
 | 
			
		||||
		}
 | 
			
		||||
		for _, cpename := range cveInfo.CpeNames {
 | 
			
		||||
			curentPackages = append(curentPackages, cpename.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		newPackages := []string{}
 | 
			
		||||
		for _, p := range cveInfo.Packages {
 | 
			
		||||
			newPackages = append(newPackages, p.ToStringNewVersion())
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a := attachment{
 | 
			
		||||
			Title:     cveID,
 | 
			
		||||
			TitleLink: fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID),
 | 
			
		||||
			Text:      attachmentText(cveInfo, scanResult.Family),
 | 
			
		||||
			MrkdwnIn:  []string{"text", "pretext"},
 | 
			
		||||
			Fields: []*field{
 | 
			
		||||
				{
 | 
			
		||||
					//  Title: "Current Package/CPE",
 | 
			
		||||
					Title: "Installed",
 | 
			
		||||
					Value: strings.Join(curentPackages, "\n"),
 | 
			
		||||
					Short: true,
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Title: "Candidate",
 | 
			
		||||
					Value: strings.Join(newPackages, "\n"),
 | 
			
		||||
					Short: true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Color: color(cveInfo.CveDetail.CvssScore(config.Conf.Lang)),
 | 
			
		||||
		}
 | 
			
		||||
		attaches = append(attaches, &a)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://api.slack.com/docs/attachments
 | 
			
		||||
func color(cvssScore float64) string {
 | 
			
		||||
	switch {
 | 
			
		||||
	case 7 <= cvssScore:
 | 
			
		||||
		return "danger"
 | 
			
		||||
	case 4 <= cvssScore && cvssScore < 7:
 | 
			
		||||
		return "warning"
 | 
			
		||||
	case cvssScore < 0:
 | 
			
		||||
		return "#C0C0C0"
 | 
			
		||||
	default:
 | 
			
		||||
		return "good"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func attachmentText(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
 | 
			
		||||
	linkText := links(cveInfo, osFamily)
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case config.Conf.Lang == "ja" &&
 | 
			
		||||
		cveInfo.CveDetail.Jvn.ID != 0 &&
 | 
			
		||||
		0 < cveInfo.CveDetail.CvssScore("ja"):
 | 
			
		||||
 | 
			
		||||
		jvn := cveInfo.CveDetail.Jvn
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
 | 
			
		||||
			cveInfo.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
			jvn.Severity,
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, jvn.Vector),
 | 
			
		||||
			jvn.Vector,
 | 
			
		||||
			jvn.Title,
 | 
			
		||||
			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.Severity(),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, nvd.CvssVector()),
 | 
			
		||||
			nvd.CvssVector(),
 | 
			
		||||
			nvd.Summary,
 | 
			
		||||
			linkText,
 | 
			
		||||
		)
 | 
			
		||||
	default:
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		return fmt.Sprintf("?\n%s\n%s", nvd.Summary, linkText)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func links(cveInfo models.CveInfo, 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)
 | 
			
		||||
	}
 | 
			
		||||
	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, " / ")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// See testcase
 | 
			
		||||
func getNotifyUsers(notifyUsers []string) string {
 | 
			
		||||
	slackStyleTexts := []string{}
 | 
			
		||||
	for _, username := range notifyUsers {
 | 
			
		||||
		slackStyleTexts = append(slackStyleTexts, fmt.Sprintf("<%s>", username))
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(slackStyleTexts, " ")
 | 
			
		||||
}
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestGetNotifyUsers(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       []string
 | 
			
		||||
		expected string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			[]string{"@user1", "@user2"},
 | 
			
		||||
			"<@user1> <@user2>",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := getNotifyUsers(tt.in)
 | 
			
		||||
		if tt.expected != actual {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", tt.expected, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,38 +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"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TextWriter write to stdout
 | 
			
		||||
type TextWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w TextWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		text, err := toPlainText(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println(text)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										772
									
								
								report/tui.go
									
									
									
									
									
								
							
							
						
						@@ -1,772 +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 (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"text/template"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/db"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"github.com/gosuri/uitable"
 | 
			
		||||
	"github.com/jroimartin/gocui"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var scanHistory models.ScanHistory
 | 
			
		||||
var currentScanResult models.ScanResult
 | 
			
		||||
var currentCveInfo int
 | 
			
		||||
var currentDetailLimitY int
 | 
			
		||||
 | 
			
		||||
// RunTui execute main logic
 | 
			
		||||
func RunTui() subcommands.ExitStatus {
 | 
			
		||||
	var err error
 | 
			
		||||
	scanHistory, err = latestScanHistory()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g := gocui.NewGui()
 | 
			
		||||
	if err := g.Init(); err != nil {
 | 
			
		||||
		log.Panicln(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer g.Close()
 | 
			
		||||
 | 
			
		||||
	g.SetLayout(layout)
 | 
			
		||||
	if err := keybindings(g); err != nil {
 | 
			
		||||
		log.Panicln(err)
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func latestScanHistory() (latest models.ScanHistory, err error) {
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		return latest, fmt.Errorf(
 | 
			
		||||
			"Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	latest, err = db.SelectLatestScanHistory()
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func keybindings(g *gocui.Gui) (err error) {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	// Move beetween views
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyTab, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlH, gocui.ModNone, previousView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlL, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowRight, gocui.ModAlt, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeySpace, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlN, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlP, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, nextView))
 | 
			
		||||
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, showMsg))
 | 
			
		||||
 | 
			
		||||
	// summary
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyTab, gocui.ModNone, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlQ, gocui.ModNone, previousView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlH, gocui.ModNone, previousView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlL, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowLeft, gocui.ModAlt, previousView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModAlt, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeySpace, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyEnter, gocui.ModNone, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
 | 
			
		||||
 | 
			
		||||
	// detail
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyTab, gocui.ModNone, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlQ, gocui.ModNone, previousView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlH, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlL, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModAlt, previousView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowLeft, gocui.ModAlt, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeySpace, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", 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))
 | 
			
		||||
 | 
			
		||||
	//TODO Help Ctrl-h
 | 
			
		||||
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, getLine))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
 | 
			
		||||
 | 
			
		||||
	for _, e := range errs {
 | 
			
		||||
		if e != nil {
 | 
			
		||||
			return e
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func nextView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		return g.SetCurrentView("summary")
 | 
			
		||||
	case "summary":
 | 
			
		||||
		return g.SetCurrentView("detail")
 | 
			
		||||
	case "detail":
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
	default:
 | 
			
		||||
		return g.SetCurrentView("summary")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func previousView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
	case "summary":
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
	case "detail":
 | 
			
		||||
		return g.SetCurrentView("summary")
 | 
			
		||||
	default:
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		yLimit = len(scanHistory.ScanResults) - 1
 | 
			
		||||
		if yLimit < nextY {
 | 
			
		||||
			return false, yLimit
 | 
			
		||||
		}
 | 
			
		||||
		return true, yLimit
 | 
			
		||||
	case "summary":
 | 
			
		||||
		yLimit = len(currentScanResult.KnownCves) - 1
 | 
			
		||||
		if yLimit < nextY {
 | 
			
		||||
			return false, yLimit
 | 
			
		||||
		}
 | 
			
		||||
		return true, yLimit
 | 
			
		||||
	case "detail":
 | 
			
		||||
		if currentDetailLimitY < nextY {
 | 
			
		||||
			return false, currentDetailLimitY
 | 
			
		||||
		}
 | 
			
		||||
		return true, currentDetailLimitY
 | 
			
		||||
	default:
 | 
			
		||||
		return true, 0
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func pageUpDownJumpCount(v *gocui.View) int {
 | 
			
		||||
	var jump int
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side", "summary":
 | 
			
		||||
		jump = 8
 | 
			
		||||
	case "detail":
 | 
			
		||||
		jump = 30
 | 
			
		||||
	default:
 | 
			
		||||
		jump = 8
 | 
			
		||||
	}
 | 
			
		||||
	return jump
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// redraw views
 | 
			
		||||
func onMovingCursorRedrawView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "summary":
 | 
			
		||||
		if err := redrawDetail(g); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case "side":
 | 
			
		||||
		if err := changeHost(g, v); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cursorDown(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		cx, cy := v.Cursor()
 | 
			
		||||
		ox, oy := v.Origin()
 | 
			
		||||
		//  ok,  := movable(v, oy+cy+1)
 | 
			
		||||
		//  _, maxY := v.Size()
 | 
			
		||||
		ok, _ := movable(v, oy+cy+1)
 | 
			
		||||
		//  log.Info(cy, oy, maxY, yLimit)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if err := v.SetCursor(cx, cy+1); err != nil {
 | 
			
		||||
			if err := v.SetOrigin(ox, oy+1); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		onMovingCursorRedrawView(g, v)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cursorMoveTop(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		cx, _ := v.Cursor()
 | 
			
		||||
		v.SetCursor(cx, 0)
 | 
			
		||||
	}
 | 
			
		||||
	onMovingCursorRedrawView(g, v)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cursorMoveBottom(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		_, maxY := v.Size()
 | 
			
		||||
		cx, _ := v.Cursor()
 | 
			
		||||
		v.SetCursor(cx, maxY-1)
 | 
			
		||||
	}
 | 
			
		||||
	onMovingCursorRedrawView(g, v)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cursorMoveMiddle(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		_, maxY := v.Size()
 | 
			
		||||
		cx, _ := v.Cursor()
 | 
			
		||||
		v.SetCursor(cx, maxY/2)
 | 
			
		||||
	}
 | 
			
		||||
	onMovingCursorRedrawView(g, v)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cursorPageDown(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	jump := pageUpDownJumpCount(v)
 | 
			
		||||
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		cx, cy := v.Cursor()
 | 
			
		||||
		ox, oy := v.Origin()
 | 
			
		||||
		ok, yLimit := movable(v, oy+cy+jump)
 | 
			
		||||
		_, maxY := v.Size()
 | 
			
		||||
 | 
			
		||||
		if !ok {
 | 
			
		||||
			if yLimit < maxY {
 | 
			
		||||
				v.SetCursor(cx, yLimit)
 | 
			
		||||
			} else {
 | 
			
		||||
				v.SetCursor(cx, maxY-1)
 | 
			
		||||
				v.SetOrigin(ox, yLimit-maxY+1)
 | 
			
		||||
			}
 | 
			
		||||
		} else if yLimit < oy+jump+maxY {
 | 
			
		||||
			if yLimit < maxY {
 | 
			
		||||
				v.SetCursor(cx, yLimit)
 | 
			
		||||
			} else {
 | 
			
		||||
				v.SetOrigin(ox, yLimit-maxY+1)
 | 
			
		||||
				v.SetCursor(cx, maxY-1)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			v.SetCursor(cx, cy)
 | 
			
		||||
			v.SetOrigin(ox, oy+jump)
 | 
			
		||||
		}
 | 
			
		||||
		onMovingCursorRedrawView(g, v)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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.SetOrigin(ox, oy-1); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	onMovingCursorRedrawView(g, v)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cursorPageUp(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	jump := pageUpDownJumpCount(v)
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		cx, _ := v.Cursor()
 | 
			
		||||
		ox, oy := v.Origin()
 | 
			
		||||
		if err := v.SetOrigin(ox, oy-jump); err != nil {
 | 
			
		||||
			v.SetOrigin(ox, 0)
 | 
			
		||||
			v.SetCursor(cx, 0)
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
		onMovingCursorRedrawView(g, v)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func previousSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		// cursor to summary
 | 
			
		||||
		if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// move next line
 | 
			
		||||
		if err := cursorUp(g, g.CurrentView()); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// cursor to detail
 | 
			
		||||
		if err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func nextSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		// cursor to summary
 | 
			
		||||
		if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// move next line
 | 
			
		||||
		if err := cursorDown(g, g.CurrentView()); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// cursor to detail
 | 
			
		||||
		if err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func changeHost(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
 | 
			
		||||
	if err := g.DeleteView("summary"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := g.DeleteView("detail"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, cy := v.Cursor()
 | 
			
		||||
	l, err := v.Line(cy)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	serverName := strings.TrimSpace(l)
 | 
			
		||||
 | 
			
		||||
	for _, r := range scanHistory.ScanResults {
 | 
			
		||||
		if serverName == r.ServerName {
 | 
			
		||||
			currentScanResult = r
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := setSummaryLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := setDetailLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func redrawDetail(g *gocui.Gui) error {
 | 
			
		||||
	if err := g.DeleteView("detail"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := setDetailLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getLine(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	var l string
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	_, cy := v.Cursor()
 | 
			
		||||
	if l, err = v.Line(cy); err != nil {
 | 
			
		||||
		l = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	maxX, maxY := g.Size()
 | 
			
		||||
	if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintln(v, l)
 | 
			
		||||
		if err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func showMsg(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	jump := 8
 | 
			
		||||
	_, cy := v.Cursor()
 | 
			
		||||
	_, oy := v.Origin()
 | 
			
		||||
	ok, yLimit := movable(v, oy+cy+jump)
 | 
			
		||||
	//  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)
 | 
			
		||||
	//  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 {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintln(v, l)
 | 
			
		||||
		if err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func delMsg(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if err := g.DeleteView("msg"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func quit(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	return gocui.ErrQuit
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func layout(g *gocui.Gui) error {
 | 
			
		||||
	if err := setSideLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := setSummaryLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := setDetailLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setSideLayout(g *gocui.Gui) error {
 | 
			
		||||
	_, maxY := g.Size()
 | 
			
		||||
	if v, err := g.SetView("side", -1, -1, 30, maxY); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		v.Highlight = true
 | 
			
		||||
 | 
			
		||||
		for _, result := range scanHistory.ScanResults {
 | 
			
		||||
			fmt.Fprintln(v, result.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
		currentScanResult = scanHistory.ScanResults[0]
 | 
			
		||||
		if err := g.SetCurrentView("side"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setSummaryLayout(g *gocui.Gui) error {
 | 
			
		||||
	maxX, maxY := g.Size()
 | 
			
		||||
	if v, err := g.SetView("summary", 30, -1, maxX, int(float64(maxY)*0.2)); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lines := summaryLines(currentScanResult)
 | 
			
		||||
		fmt.Fprintf(v, lines)
 | 
			
		||||
 | 
			
		||||
		v.Highlight = true
 | 
			
		||||
		v.Editable = false
 | 
			
		||||
		v.Wrap = false
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func summaryLines(data models.ScanResult) string {
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = 1000
 | 
			
		||||
	stable.Wrap = false
 | 
			
		||||
 | 
			
		||||
	indexFormat := ""
 | 
			
		||||
	if len(data.KnownCves) < 10 {
 | 
			
		||||
		indexFormat = "[%1d]"
 | 
			
		||||
	} else if len(data.KnownCves) < 100 {
 | 
			
		||||
		indexFormat = "[%2d]"
 | 
			
		||||
	} else {
 | 
			
		||||
		indexFormat = "[%3d]"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, d := range data.KnownCves {
 | 
			
		||||
		var cols []string
 | 
			
		||||
		//  packs := []string{}
 | 
			
		||||
		//  for _, pack := range d.Packages {
 | 
			
		||||
		//      packs = append(packs, pack.Name)
 | 
			
		||||
		//  }
 | 
			
		||||
		if config.Conf.Lang == "ja" && 0 < d.CveDetail.Jvn.CvssScore() {
 | 
			
		||||
			summary := d.CveDetail.Jvn.Title
 | 
			
		||||
			cols = []string{
 | 
			
		||||
				fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("|  %-4.1f(%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Jvn.Severity,
 | 
			
		||||
				),
 | 
			
		||||
				//  strings.Join(packs, ","),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			summary := d.CveDetail.Nvd.Summary
 | 
			
		||||
 | 
			
		||||
			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.Severity(),
 | 
			
		||||
				)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			cols = []string{
 | 
			
		||||
				fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				cvssScore,
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		icols := make([]interface{}, len(cols))
 | 
			
		||||
		for j := range cols {
 | 
			
		||||
			icols[j] = cols[j]
 | 
			
		||||
		}
 | 
			
		||||
		stable.AddRow(icols...)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s", stable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setDetailLayout(g *gocui.Gui) error {
 | 
			
		||||
	maxX, maxY := g.Size()
 | 
			
		||||
 | 
			
		||||
	summaryView, err := g.View("summary")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	_, cy := summaryView.Cursor()
 | 
			
		||||
	_, oy := summaryView.Origin()
 | 
			
		||||
	currentCveInfo = cy + oy
 | 
			
		||||
 | 
			
		||||
	if v, err := g.SetView("detail", 30, int(float64(maxY)*0.2), maxX, 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
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprint(v, text)
 | 
			
		||||
		v.Editable = false
 | 
			
		||||
		v.Wrap = true
 | 
			
		||||
 | 
			
		||||
		currentDetailLimitY = len(strings.Split(text, "\n")) - 1
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dataForTmpl struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	CvssScore        string
 | 
			
		||||
	CvssVector       string
 | 
			
		||||
	CvssSeverity     string
 | 
			
		||||
	Summary          string
 | 
			
		||||
	VulnSiteLinks    []string
 | 
			
		||||
	References       []cve.Reference
 | 
			
		||||
	Packages         []string
 | 
			
		||||
	CpeNames         []models.CpeName
 | 
			
		||||
	PublishedDate    time.Time
 | 
			
		||||
	LastModifiedDate time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detailLines() (string, error) {
 | 
			
		||||
	if len(currentScanResult.KnownCves) == 0 {
 | 
			
		||||
		return "No vulnerable packages", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveInfo := currentScanResult.KnownCves[currentCveInfo]
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
 | 
			
		||||
	tmpl, err := template.New("detail").Parse(detailTemplate())
 | 
			
		||||
	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.Severity
 | 
			
		||||
		cvssVector = jvn.Vector
 | 
			
		||||
		summary = fmt.Sprintf("%s\n%s", jvn.Title, jvn.Summary)
 | 
			
		||||
		refs = jvn.References
 | 
			
		||||
	default:
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		cvssSeverity = nvd.Severity()
 | 
			
		||||
		cvssVector = nvd.CvssVector()
 | 
			
		||||
		summary = nvd.Summary
 | 
			
		||||
		refs = nvd.References
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cvssScore string
 | 
			
		||||
	if cveInfo.CveDetail.CvssScore(config.Conf.Lang) == -1 {
 | 
			
		||||
		cvssScore = "?"
 | 
			
		||||
	} else {
 | 
			
		||||
		cvssScore = fmt.Sprintf("%4.1f", cveInfo.CveDetail.CvssScore(config.Conf.Lang))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packages := []string{}
 | 
			
		||||
	for _, pack := range cveInfo.Packages {
 | 
			
		||||
		packages = append(packages,
 | 
			
		||||
			fmt.Sprintf(
 | 
			
		||||
				"%s -> %s",
 | 
			
		||||
				pack.ToStringCurrentVersion(),
 | 
			
		||||
				pack.ToStringNewVersion()))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data := dataForTmpl{
 | 
			
		||||
		CveID:         cveID,
 | 
			
		||||
		CvssScore:     cvssScore,
 | 
			
		||||
		CvssSeverity:  cvssSeverity,
 | 
			
		||||
		CvssVector:    cvssVector,
 | 
			
		||||
		Summary:       summary,
 | 
			
		||||
		VulnSiteLinks: links,
 | 
			
		||||
		References:    refs,
 | 
			
		||||
		Packages:      packages,
 | 
			
		||||
		CpeNames:      cveInfo.CpeNames,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := bytes.NewBuffer(nil) // create empty buffer
 | 
			
		||||
	if err := tmpl.Execute(buf, data); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return string(buf.Bytes()), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  * {{.Name}}-{{.Version}}-{{.Release}}
 | 
			
		||||
 | 
			
		||||
func detailTemplate() string {
 | 
			
		||||
	return `
 | 
			
		||||
{{.CveID}}
 | 
			
		||||
==============
 | 
			
		||||
 | 
			
		||||
CVSS Score
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{.CvssScore}} ({{.CvssSeverity}}) {{.CvssVector}}
 | 
			
		||||
 | 
			
		||||
Summary
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
 {{.Summary }}
 | 
			
		||||
 | 
			
		||||
Package/CPE
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range $pack := .Packages -}}
 | 
			
		||||
* {{$pack}}
 | 
			
		||||
{{end -}}
 | 
			
		||||
{{range .CpeNames -}}
 | 
			
		||||
* {{.Name}}
 | 
			
		||||
{{end}}
 | 
			
		||||
Links
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range $link := .VulnSiteLinks -}}
 | 
			
		||||
* {{$link}}
 | 
			
		||||
{{end}}
 | 
			
		||||
References
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range .References -}}
 | 
			
		||||
* [{{.Source}}]( {{.Link}} )
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										334
									
								
								report/util.go
									
									
									
									
									
								
							
							
						
						@@ -1,334 +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 (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/gosuri/uitable"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func toPlainText(scanResult models.ScanResult) (string, error) {
 | 
			
		||||
	hostinfo := fmt.Sprintf(
 | 
			
		||||
		"%s (%s %s)",
 | 
			
		||||
		scanResult.ServerName,
 | 
			
		||||
		scanResult.Family,
 | 
			
		||||
		scanResult.Release,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	var buffer bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(hostinfo); i++ {
 | 
			
		||||
		buffer.WriteString("=")
 | 
			
		||||
	}
 | 
			
		||||
	header := fmt.Sprintf("%s\n%s", hostinfo, buffer.String())
 | 
			
		||||
 | 
			
		||||
	if len(scanResult.KnownCves) == 0 && len(scanResult.UnknownCves) == 0 {
 | 
			
		||||
		return fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
No unsecure packages.
 | 
			
		||||
`, header), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	summary := ToPlainTextSummary(scanResult)
 | 
			
		||||
	scoredReport, unscoredReport := []string{}, []string{}
 | 
			
		||||
	scoredReport, unscoredReport = toPlainTextDetails(scanResult, scanResult.Family)
 | 
			
		||||
 | 
			
		||||
	scored := strings.Join(scoredReport, "\n\n")
 | 
			
		||||
	unscored := strings.Join(unscoredReport, "\n\n")
 | 
			
		||||
	detail := fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
 | 
			
		||||
%s
 | 
			
		||||
`,
 | 
			
		||||
		scored,
 | 
			
		||||
		unscored,
 | 
			
		||||
	)
 | 
			
		||||
	text := fmt.Sprintf("%s\n%s\n%s\n", header, summary, detail)
 | 
			
		||||
 | 
			
		||||
	return text, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToPlainTextSummary format summary for plain text.
 | 
			
		||||
func ToPlainTextSummary(r models.ScanResult) string {
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = 84
 | 
			
		||||
	stable.Wrap = true
 | 
			
		||||
	cves := append(r.KnownCves, r.UnknownCves...)
 | 
			
		||||
	for _, d := range cves {
 | 
			
		||||
		var scols []string
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case config.Conf.Lang == "ja" &&
 | 
			
		||||
			d.CveDetail.Jvn.ID != 0 &&
 | 
			
		||||
			0 < d.CveDetail.CvssScore("ja"):
 | 
			
		||||
 | 
			
		||||
			summary := d.CveDetail.Jvn.Title
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("%-4.1f (%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Jvn.Severity,
 | 
			
		||||
				),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		case 0 < d.CveDetail.CvssScore("en"):
 | 
			
		||||
			summary := d.CveDetail.Nvd.Summary
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("%-4.1f",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
				),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				"?",
 | 
			
		||||
				d.CveDetail.Nvd.Summary,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cols := make([]interface{}, len(scols))
 | 
			
		||||
		for i := range cols {
 | 
			
		||||
			cols[i] = scols[i]
 | 
			
		||||
		}
 | 
			
		||||
		stable.AddRow(cols...)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s", stable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO Distro Advisory
 | 
			
		||||
func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
 | 
			
		||||
	for _, cve := range data.KnownCves {
 | 
			
		||||
		switch config.Conf.Lang {
 | 
			
		||||
		case "en":
 | 
			
		||||
			if cve.CveDetail.Nvd.ID != 0 {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
			} else {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
			}
 | 
			
		||||
		case "ja":
 | 
			
		||||
			if cve.CveDetail.Jvn.ID != 0 {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangJa(cve, osFamily))
 | 
			
		||||
			} else if cve.CveDetail.Nvd.ID != 0 {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
			} else {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cve := range data.UnknownCves {
 | 
			
		||||
		unscoredReport = append(
 | 
			
		||||
			unscoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
	}
 | 
			
		||||
	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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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.Severity,
 | 
			
		||||
			))
 | 
			
		||||
	} else {
 | 
			
		||||
		dtable.AddRow("Score", "?")
 | 
			
		||||
	}
 | 
			
		||||
	dtable.AddRow("Vector", jvn.Vector)
 | 
			
		||||
	dtable.AddRow("Title", jvn.Title)
 | 
			
		||||
	dtable.AddRow("Description", jvn.Summary)
 | 
			
		||||
 | 
			
		||||
	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.Severity(),
 | 
			
		||||
			))
 | 
			
		||||
	} else {
 | 
			
		||||
		dtable.AddRow("Score", "?")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dtable.AddRow("Vector", nvd.CvssVector())
 | 
			
		||||
	dtable.AddRow("Summary", nvd.Summary)
 | 
			
		||||
	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
 | 
			
		||||
		}
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
@@ -1,39 +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 "github.com/future-architect/vuls/models"
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ResultWriter Interface
 | 
			
		||||
type ResultWriter interface {
 | 
			
		||||
	Write([]models.ScanResult) error
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										655
									
								
								scan/debian.go
									
									
									
									
									
								
							
							
						
						@@ -1,655 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type debian struct {
 | 
			
		||||
	linux
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewDebian is constructor
 | 
			
		||||
func newDebian(c config.ServerInfo) *debian {
 | 
			
		||||
	d := &debian{}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ubuntu, Debian
 | 
			
		||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/debian.rb
 | 
			
		||||
func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface) {
 | 
			
		||||
 | 
			
		||||
	deb = newDebian(c)
 | 
			
		||||
 | 
			
		||||
	// set sudo option flag
 | 
			
		||||
	c.SudoOpt = config.SudoOption{ExecBySudo: true}
 | 
			
		||||
	deb.setServerInfo(c)
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
 | 
			
		||||
		Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return false, deb
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "lsb_release -ir", noSudo); r.isSuccess() {
 | 
			
		||||
		//  e.g.
 | 
			
		||||
		//  root@fa3ec524be43:/# lsb_release -ir
 | 
			
		||||
		//  Distributor ID:	Ubuntu
 | 
			
		||||
		//  Release:	14.04
 | 
			
		||||
		re, _ := regexp.Compile(
 | 
			
		||||
			`(?s)^Distributor ID:\s*(.+?)\n*Release:\s*(.+?)$`)
 | 
			
		||||
		result := re.FindStringSubmatch(trim(r.Stdout))
 | 
			
		||||
 | 
			
		||||
		if len(result) == 0 {
 | 
			
		||||
			deb.setDistributionInfo("debian/ubuntu", "unknown")
 | 
			
		||||
			Log.Warnf(
 | 
			
		||||
				"Unknown Debian/Ubuntu version. lsb_release -ir: %s, Host: %s:%s",
 | 
			
		||||
				r.Stdout, c.Host, c.Port)
 | 
			
		||||
		} else {
 | 
			
		||||
			distro := strings.ToLower(trim(result[1]))
 | 
			
		||||
			deb.setDistributionInfo(distro, trim(result[2]))
 | 
			
		||||
		}
 | 
			
		||||
		return true, deb
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
 | 
			
		||||
		//  e.g.
 | 
			
		||||
		//  DISTRIB_ID=Ubuntu
 | 
			
		||||
		//  DISTRIB_RELEASE=14.04
 | 
			
		||||
		//  DISTRIB_CODENAME=trusty
 | 
			
		||||
		//  DISTRIB_DESCRIPTION="Ubuntu 14.04.2 LTS"
 | 
			
		||||
		re, _ := regexp.Compile(
 | 
			
		||||
			`(?s)^DISTRIB_ID=(.+?)\n*DISTRIB_RELEASE=(.+?)\n.*$`)
 | 
			
		||||
		result := re.FindStringSubmatch(trim(r.Stdout))
 | 
			
		||||
		if len(result) == 0 {
 | 
			
		||||
			Log.Warnf(
 | 
			
		||||
				"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s, Host: %s:%s",
 | 
			
		||||
				r.Stdout, c.Host, c.Port)
 | 
			
		||||
			deb.setDistributionInfo("debian/ubuntu", "unknown")
 | 
			
		||||
		} else {
 | 
			
		||||
			distro := strings.ToLower(trim(result[1]))
 | 
			
		||||
			deb.setDistributionInfo(distro, trim(result[2]))
 | 
			
		||||
		}
 | 
			
		||||
		return true, deb
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Debian
 | 
			
		||||
	cmd := "cat /etc/debian_version"
 | 
			
		||||
	if r := sshExec(c, cmd, noSudo); r.isSuccess() {
 | 
			
		||||
		deb.setDistributionInfo("debian", trim(r.Stdout))
 | 
			
		||||
		return true, deb
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
	return false, deb
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func trim(str string) string {
 | 
			
		||||
	return strings.TrimSpace(str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) install() error {
 | 
			
		||||
 | 
			
		||||
	// apt-get update
 | 
			
		||||
	o.log.Infof("apt-get update...")
 | 
			
		||||
	cmd := util.PrependProxyEnv("apt-get update")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		o.log.Errorf(msg)
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.Family == "debian" {
 | 
			
		||||
		// install aptitude
 | 
			
		||||
		cmd = util.PrependProxyEnv("apt-get install --force-yes -y aptitude")
 | 
			
		||||
		if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
			msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
				cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
		o.log.Infof("Installed: aptitude")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// install unattended-upgrades
 | 
			
		||||
	if !config.Conf.UseUnattendedUpgrades {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := o.ssh("type unattended-upgrade", noSudo); r.isSuccess() {
 | 
			
		||||
		o.log.Infof(
 | 
			
		||||
			"Ignored: unattended-upgrade already installed")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd = util.PrependProxyEnv(
 | 
			
		||||
		"apt-get install --force-yes -y unattended-upgrades")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		o.log.Errorf(msg)
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Infof("Installed: unattended-upgrades")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackages() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	var packs []models.PackageInfo
 | 
			
		||||
	if packs, err = o.scanInstalledPackages(); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setPackages(packs)
 | 
			
		||||
 | 
			
		||||
	var unsecurePacks []CvePacksInfo
 | 
			
		||||
	if unsecurePacks, err = o.scanUnsecurePackages(packs); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan vulnerable packages")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setUnsecurePackages(unsecurePacks)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error) {
 | 
			
		||||
	r := o.ssh("dpkg-query -W", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return packs, fmt.Errorf(
 | 
			
		||||
			"Failed to scan packages. status: %d, stdout:%s, stderr: %s",
 | 
			
		||||
			r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//  e.g.
 | 
			
		||||
	//  curl	7.19.7-40.el6_6.4
 | 
			
		||||
	//  openldap	2.4.39-8.el6
 | 
			
		||||
	lines := strings.Split(r.Stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
 | 
			
		||||
			name, version, err := o.parseScanedPackagesLine(trimmed)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, fmt.Errorf(
 | 
			
		||||
					"Debian: Failed to parse package line: %s", line)
 | 
			
		||||
			}
 | 
			
		||||
			packs = append(packs, models.PackageInfo{
 | 
			
		||||
				Name:    name,
 | 
			
		||||
				Version: version,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) parseScanedPackagesLine(line string) (name, version string, err error) {
 | 
			
		||||
	re, _ := regexp.Compile(`^([^\t']+)\t(.+)$`)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) == 3 {
 | 
			
		||||
		// remove :amd64, i386...
 | 
			
		||||
		name = regexp.MustCompile(":.+").ReplaceAllString(result[1], "")
 | 
			
		||||
		version = result[2]
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "", "", fmt.Errorf("Unknown format: %s", line)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  unattended-upgrade command need to check security upgrades).
 | 
			
		||||
func (o *debian) checkRequiredPackagesInstalled() error {
 | 
			
		||||
 | 
			
		||||
	if o.Family == "debian" {
 | 
			
		||||
		if r := o.ssh("test -f /usr/bin/aptitude", sudo); !r.isSuccess() {
 | 
			
		||||
			msg := "aptitude is not installed"
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !config.Conf.UseUnattendedUpgrades {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := o.ssh("type unattended-upgrade", noSudo); !r.isSuccess() {
 | 
			
		||||
		msg := "unattended-upgrade is not installed"
 | 
			
		||||
		o.log.Errorf(msg)
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO return whether already expired.
 | 
			
		||||
func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInfo, error) {
 | 
			
		||||
	//  cmd := prependProxyEnv(conf.HTTPProxy, "apt-get update | cat; echo 1")
 | 
			
		||||
	cmd := util.PrependProxyEnv("apt-get update")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var upgradablePackNames []string
 | 
			
		||||
	var err error
 | 
			
		||||
	if config.Conf.UseUnattendedUpgrades {
 | 
			
		||||
		upgradablePackNames, err = o.GetUnsecurePackNamesUsingUnattendedUpgrades()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return []CvePacksInfo{}, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		upgradablePackNames, err = o.GetUpgradablePackNames()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return []CvePacksInfo{}, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Convert package name to PackageInfo struct
 | 
			
		||||
	var unsecurePacks []models.PackageInfo
 | 
			
		||||
	for _, name := range upgradablePackNames {
 | 
			
		||||
		for _, pack := range packs {
 | 
			
		||||
			if pack.Name == name {
 | 
			
		||||
				unsecurePacks = append(unsecurePacks, pack)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	unsecurePacks, err = o.fillCandidateVersion(unsecurePacks)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Collect CVE information of upgradable packages
 | 
			
		||||
	cvePacksInfos, err := o.scanPackageCveInfos(unsecurePacks)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cvePacksInfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) fillCandidateVersion(packs []models.PackageInfo) ([]models.PackageInfo, error) {
 | 
			
		||||
	reqChan := make(chan models.PackageInfo, len(packs))
 | 
			
		||||
	resChan := make(chan models.PackageInfo, len(packs))
 | 
			
		||||
	errChan := make(chan error, len(packs))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, pack := range packs {
 | 
			
		||||
			reqChan <- pack
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(5 * 60 * time.Second)
 | 
			
		||||
	concurrency := 5
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for range packs {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case pack := <-reqChan:
 | 
			
		||||
				func(p models.PackageInfo) {
 | 
			
		||||
					cmd := fmt.Sprintf("apt-cache policy %s", p.Name)
 | 
			
		||||
					r := o.ssh(cmd, sudo)
 | 
			
		||||
					if !r.isSuccess() {
 | 
			
		||||
						errChan <- fmt.Errorf(
 | 
			
		||||
							"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
							cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					ver, err := o.parseAptCachePolicy(r.Stdout, p.Name)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						errChan <- fmt.Errorf("Failed to parse %s", err)
 | 
			
		||||
					}
 | 
			
		||||
					p.NewVersion = ver.Candidate
 | 
			
		||||
					resChan <- p
 | 
			
		||||
				}(pack)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result := []models.PackageInfo{}
 | 
			
		||||
	for i := 0; i < len(packs); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case pack := <-resChan:
 | 
			
		||||
			result = append(result, pack)
 | 
			
		||||
			o.log.Infof("(%d/%d) Upgradable: %s-%s -> %s",
 | 
			
		||||
				i+1, len(packs), pack.Name, pack.Version, pack.NewVersion)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			return nil, err
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return nil, fmt.Errorf("Timeout fillCandidateVersion")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) GetUnsecurePackNamesUsingUnattendedUpgrades() (packNames []string, err error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("unattended-upgrades --dry-run -d 2>&1 ")
 | 
			
		||||
	release, err := strconv.ParseFloat(o.Release, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return packNames, fmt.Errorf(
 | 
			
		||||
			"OS Release Version is invalid, %s, %s", o.Family, o.Release)
 | 
			
		||||
	}
 | 
			
		||||
	switch {
 | 
			
		||||
	case release < 12:
 | 
			
		||||
		return packNames, fmt.Errorf(
 | 
			
		||||
			"Support expired. %s, %s", o.Family, o.Release)
 | 
			
		||||
 | 
			
		||||
	case 12 < release && release < 14:
 | 
			
		||||
		cmd += `| grep 'pkgs that look like they should be upgraded:' |
 | 
			
		||||
			sed -e 's/pkgs that look like they should be upgraded://g'`
 | 
			
		||||
 | 
			
		||||
	case 14 < release:
 | 
			
		||||
		cmd += `| grep 'Packages that will be upgraded:' |
 | 
			
		||||
			sed -e 's/Packages that will be upgraded://g'`
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		return packNames, fmt.Errorf(
 | 
			
		||||
			"Not supported yet. %s, %s", o.Family, o.Release)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := o.ssh(cmd, sudo)
 | 
			
		||||
	if r.isSuccess(0, 1) {
 | 
			
		||||
		packNames = strings.Split(strings.TrimSpace(r.Stdout), " ")
 | 
			
		||||
		return packNames, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return packNames, fmt.Errorf(
 | 
			
		||||
		"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
		cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("apt-get upgrade --dry-run")
 | 
			
		||||
	r := o.ssh(cmd, sudo)
 | 
			
		||||
	if r.isSuccess(0, 1) {
 | 
			
		||||
		return o.parseAptGetUpgrade(r.Stdout)
 | 
			
		||||
	}
 | 
			
		||||
	return packNames, fmt.Errorf(
 | 
			
		||||
		"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
		cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, err error) {
 | 
			
		||||
	startRe, _ := regexp.Compile(`The following packages will be upgraded:`)
 | 
			
		||||
	stopRe, _ := regexp.Compile(`^(\d+) upgraded.*`)
 | 
			
		||||
	startLineFound, stopLineFound := false, false
 | 
			
		||||
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		if !startLineFound {
 | 
			
		||||
			if matche := startRe.MatchString(line); matche {
 | 
			
		||||
				startLineFound = true
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		result := stopRe.FindStringSubmatch(line)
 | 
			
		||||
		if len(result) == 2 {
 | 
			
		||||
			numUpgradablePacks, err := strconv.Atoi(result[1])
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, fmt.Errorf(
 | 
			
		||||
					"Failed to scan upgradable packages number. line: %s", line)
 | 
			
		||||
			}
 | 
			
		||||
			if numUpgradablePacks != len(upgradableNames) {
 | 
			
		||||
				return nil, fmt.Errorf(
 | 
			
		||||
					"Failed to scan upgradable packages, expected: %s, detected: %d",
 | 
			
		||||
					result[1], len(upgradableNames))
 | 
			
		||||
			}
 | 
			
		||||
			stopLineFound = true
 | 
			
		||||
			o.log.Debugf("Found the stop line. line: %s", line)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		upgradableNames = append(upgradableNames, strings.Fields(line)...)
 | 
			
		||||
	}
 | 
			
		||||
	if !startLineFound {
 | 
			
		||||
		// no upgrades
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if !stopLineFound {
 | 
			
		||||
		// There are upgrades, but not found the stop line.
 | 
			
		||||
		return nil, fmt.Errorf("Failed to scan upgradable packages")
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePacksList CvePacksList, err error) {
 | 
			
		||||
 | 
			
		||||
	// { CVE ID: [packageInfo] }
 | 
			
		||||
	cvePackages := make(map[string][]models.PackageInfo)
 | 
			
		||||
 | 
			
		||||
	type strarray []string
 | 
			
		||||
	resChan := make(chan struct {
 | 
			
		||||
		models.PackageInfo
 | 
			
		||||
		strarray
 | 
			
		||||
	}, len(unsecurePacks))
 | 
			
		||||
	errChan := make(chan error, len(unsecurePacks))
 | 
			
		||||
	reqChan := make(chan models.PackageInfo, len(unsecurePacks))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, pack := range unsecurePacks {
 | 
			
		||||
			reqChan <- pack
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(30 * 60 * time.Second)
 | 
			
		||||
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for range unsecurePacks {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case pack := <-reqChan:
 | 
			
		||||
				func(p models.PackageInfo) {
 | 
			
		||||
					if cveIds, err := o.scanPackageCveIds(p); err != nil {
 | 
			
		||||
						errChan <- err
 | 
			
		||||
					} else {
 | 
			
		||||
						resChan <- struct {
 | 
			
		||||
							models.PackageInfo
 | 
			
		||||
							strarray
 | 
			
		||||
						}{p, cveIds}
 | 
			
		||||
					}
 | 
			
		||||
				}(pack)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < len(unsecurePacks); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case pair := <-resChan:
 | 
			
		||||
			pack := pair.PackageInfo
 | 
			
		||||
			cveIds := pair.strarray
 | 
			
		||||
			for _, cveID := range cveIds {
 | 
			
		||||
				cvePackages[cveID] = appendPackIfMissing(cvePackages[cveID], pack)
 | 
			
		||||
			}
 | 
			
		||||
			o.log.Infof("(%d/%d) Scanned %s-%s : %s",
 | 
			
		||||
				i+1, len(unsecurePacks), pair.Name, pair.PackageInfo.Version, cveIds)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return nil, fmt.Errorf("Timeout scanPackageCveIds")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cveIds []string
 | 
			
		||||
	for k := range cvePackages {
 | 
			
		||||
		cveIds = append(cveIds, k)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Debugf("%d Cves are found. cves: %v", len(cveIds), cveIds)
 | 
			
		||||
 | 
			
		||||
	o.log.Info("Fetching CVE details...")
 | 
			
		||||
	cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIds)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Info("Done")
 | 
			
		||||
 | 
			
		||||
	for _, detail := range cveDetails {
 | 
			
		||||
		cvePacksList = append(cvePacksList, CvePacksInfo{
 | 
			
		||||
			CveID:     detail.CveID,
 | 
			
		||||
			CveDetail: detail,
 | 
			
		||||
			Packs:     cvePackages[detail.CveID],
 | 
			
		||||
			//  CvssScore: cinfo.CvssScore(conf.Lang),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(CvePacksList(cvePacksList))
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackageCveIds(pack models.PackageInfo) (cveIds []string, err error) {
 | 
			
		||||
	cmd := ""
 | 
			
		||||
	switch o.Family {
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		cmd = fmt.Sprintf(`apt-get changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
 | 
			
		||||
	case "debian":
 | 
			
		||||
		cmd = fmt.Sprintf(`aptitude changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
 | 
			
		||||
	}
 | 
			
		||||
	cmd = util.PrependProxyEnv(cmd)
 | 
			
		||||
 | 
			
		||||
	r := o.ssh(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		o.log.Warnf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		// Ignore this Error.
 | 
			
		||||
		return nil, nil
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	cveIds, err = o.getCveIDParsingChangelog(r.Stdout, pack.Name, pack.Version)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		trimUbuntu := strings.Split(pack.Version, "ubuntu")[0]
 | 
			
		||||
		return o.getCveIDParsingChangelog(r.Stdout, pack.Name, trimUbuntu)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) getCveIDParsingChangelog(changelog string,
 | 
			
		||||
	packName string, versionOrLater string) (cveIDs []string, err error) {
 | 
			
		||||
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, versionOrLater)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ver := strings.Split(versionOrLater, "ubuntu")[0]
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, ver)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	splittedByColon := strings.Split(versionOrLater, ":")
 | 
			
		||||
	if 1 < len(splittedByColon) {
 | 
			
		||||
		ver = splittedByColon[1]
 | 
			
		||||
	}
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, ver)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//TODO report as unable to parse changelog.
 | 
			
		||||
	o.log.Warn(err)
 | 
			
		||||
	return []string{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Collect CVE-IDs included in the changelog.
 | 
			
		||||
// The version which specified in argument(versionOrLater) is excluded.
 | 
			
		||||
func (o *debian) parseChangelog(changelog string,
 | 
			
		||||
	packName string, versionOrLater string) (cveIDs []string, err error) {
 | 
			
		||||
 | 
			
		||||
	cveRe, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`)
 | 
			
		||||
	stopRe, _ := regexp.Compile(fmt.Sprintf(`\(%s\)`, regexp.QuoteMeta(versionOrLater)))
 | 
			
		||||
	stopLineFound := false
 | 
			
		||||
	lines := strings.Split(changelog, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		if matche := stopRe.MatchString(line); matche {
 | 
			
		||||
			o.log.Debugf("Found the stop line. line: %s", line)
 | 
			
		||||
			stopLineFound = true
 | 
			
		||||
			break
 | 
			
		||||
		} else if matches := cveRe.FindAllString(line, -1); len(matches) > 0 {
 | 
			
		||||
			for _, m := range matches {
 | 
			
		||||
				cveIDs = util.AppendIfMissing(cveIDs, m)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !stopLineFound {
 | 
			
		||||
		return []string{}, fmt.Errorf(
 | 
			
		||||
			"Failed to scan CVE IDs. The version is not in changelog. name: %s, version: %s",
 | 
			
		||||
			packName,
 | 
			
		||||
			versionOrLater,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type packCandidateVer struct {
 | 
			
		||||
	Name      string
 | 
			
		||||
	Installed string
 | 
			
		||||
	Candidate string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseAptCachePolicy the stdout of parse pat-get cache policy
 | 
			
		||||
func (o *debian) parseAptCachePolicy(stdout, name string) (packCandidateVer, error) {
 | 
			
		||||
	ver := packCandidateVer{Name: name}
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		fields := strings.Fields(line)
 | 
			
		||||
		if len(fields) != 2 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		switch fields[0] {
 | 
			
		||||
		case "Installed:":
 | 
			
		||||
			ver.Installed = fields[1]
 | 
			
		||||
		case "Candidate:":
 | 
			
		||||
			ver.Candidate = fields[1]
 | 
			
		||||
			return ver, nil
 | 
			
		||||
		default:
 | 
			
		||||
			// nop
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ver, fmt.Errorf("Unknown Format: %s", stdout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func appendPackIfMissing(slice []models.PackageInfo, s models.PackageInfo) []models.PackageInfo {
 | 
			
		||||
	for _, ele := range slice {
 | 
			
		||||
		if ele.Name == s.Name &&
 | 
			
		||||
			ele.Version == s.Version &&
 | 
			
		||||
			ele.Release == s.Release {
 | 
			
		||||
			return slice
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return append(slice, s)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,601 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseScanedPackagesLineDebian(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.parseScanedPackagesLine(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) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       []string
 | 
			
		||||
		expected []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			// verubuntu1
 | 
			
		||||
			[]string{
 | 
			
		||||
				"systemd",
 | 
			
		||||
				"228-4ubuntu1",
 | 
			
		||||
				`systemd (229-2) unstable; urgency=medium
 | 
			
		||||
systemd (229-1) unstable; urgency=medium
 | 
			
		||||
systemd (228-6) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
systemd (228-5) unstable; urgency=medium
 | 
			
		||||
systemd (228-4) unstable; urgency=medium
 | 
			
		||||
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`,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// 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
 | 
			
		||||
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",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// 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`,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// 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`,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		for i := range tt.expected {
 | 
			
		||||
			if actual[i] != tt.expected[i] {
 | 
			
		||||
				t.Errorf("expected %s, actual %s", tt.expected[i], actual[i])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		_, err := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], "version number do'nt match case")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Returning error is unexpected")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetUpdatablePackNames(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []string
 | 
			
		||||
	}{
 | 
			
		||||
		{ // Ubuntu 12.04
 | 
			
		||||
			`Reading package lists... Done
 | 
			
		||||
Building dependency tree
 | 
			
		||||
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])`,
 | 
			
		||||
			[]string{
 | 
			
		||||
				"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",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{ // Ubuntu 14.04
 | 
			
		||||
			`Reading package lists... Done
 | 
			
		||||
Building dependency tree
 | 
			
		||||
Reading state information... Done
 | 
			
		||||
Calculating upgrade... Done
 | 
			
		||||
The following packages will be upgraded:
 | 
			
		||||
  apt apt-utils base-files bsdutils coreutils cpio dh-python dpkg e2fslibs
 | 
			
		||||
  e2fsprogs gcc-4.8-base gcc-4.9-base gnupg gpgv ifupdown initscripts iproute2
 | 
			
		||||
  isc-dhcp-client isc-dhcp-common libapt-inst1.5 libapt-pkg4.12 libblkid1
 | 
			
		||||
  libc-bin libc6 libcgmanager0 libcomerr2 libdrm2 libexpat1 libffi6 libgcc1
 | 
			
		||||
  libgcrypt11 libgnutls-openssl27 libgnutls26 libmount1 libpcre3 libpng12-0
 | 
			
		||||
  libpython3.4-minimal libpython3.4-stdlib libsqlite3-0 libss2 libssl1.0.0
 | 
			
		||||
  libstdc++6 libtasn1-6 libudev1 libuuid1 login mount multiarch-support
 | 
			
		||||
  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",
 | 
			
		||||
				"apt-utils",
 | 
			
		||||
				"base-files",
 | 
			
		||||
				"bsdutils",
 | 
			
		||||
				"coreutils",
 | 
			
		||||
				"cpio",
 | 
			
		||||
				"dh-python",
 | 
			
		||||
				"dpkg",
 | 
			
		||||
				"e2fslibs",
 | 
			
		||||
				"e2fsprogs",
 | 
			
		||||
				"gcc-4.8-base",
 | 
			
		||||
				"gcc-4.9-base",
 | 
			
		||||
				"gnupg",
 | 
			
		||||
				"gpgv",
 | 
			
		||||
				"ifupdown",
 | 
			
		||||
				"initscripts",
 | 
			
		||||
				"iproute2",
 | 
			
		||||
				"isc-dhcp-client",
 | 
			
		||||
				"isc-dhcp-common",
 | 
			
		||||
				"libapt-inst1.5",
 | 
			
		||||
				"libapt-pkg4.12",
 | 
			
		||||
				"libblkid1",
 | 
			
		||||
				"libc-bin",
 | 
			
		||||
				"libc6",
 | 
			
		||||
				"libcgmanager0",
 | 
			
		||||
				"libcomerr2",
 | 
			
		||||
				"libdrm2",
 | 
			
		||||
				"libexpat1",
 | 
			
		||||
				"libffi6",
 | 
			
		||||
				"libgcc1",
 | 
			
		||||
				"libgcrypt11",
 | 
			
		||||
				"libgnutls-openssl27",
 | 
			
		||||
				"libgnutls26",
 | 
			
		||||
				"libmount1",
 | 
			
		||||
				"libpcre3",
 | 
			
		||||
				"libpng12-0",
 | 
			
		||||
				"libpython3.4-minimal",
 | 
			
		||||
				"libpython3.4-stdlib",
 | 
			
		||||
				"libsqlite3-0",
 | 
			
		||||
				"libss2",
 | 
			
		||||
				"libssl1.0.0",
 | 
			
		||||
				"libstdc++6",
 | 
			
		||||
				"libtasn1-6",
 | 
			
		||||
				"libudev1",
 | 
			
		||||
				"libuuid1",
 | 
			
		||||
				"login",
 | 
			
		||||
				"mount",
 | 
			
		||||
				"multiarch-support",
 | 
			
		||||
				"ntpdate",
 | 
			
		||||
				"passwd",
 | 
			
		||||
				"python3.4",
 | 
			
		||||
				"python3.4-minimal",
 | 
			
		||||
				"rsyslog",
 | 
			
		||||
				"sudo",
 | 
			
		||||
				"sysv-rc",
 | 
			
		||||
				"sysvinit-utils",
 | 
			
		||||
				"tzdata",
 | 
			
		||||
				"udev",
 | 
			
		||||
				"util-linux",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//Ubuntu12.04
 | 
			
		||||
			`Reading package lists... Done
 | 
			
		||||
Building dependency tree
 | 
			
		||||
Reading state information... Done
 | 
			
		||||
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
 | 
			
		||||
			[]string{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//Ubuntu14.04
 | 
			
		||||
			`Reading package lists... Done
 | 
			
		||||
Building dependency tree
 | 
			
		||||
Reading state information... Done
 | 
			
		||||
Calculating upgrade... Done
 | 
			
		||||
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
 | 
			
		||||
			[]string{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, err := d.parseAptGetUpgrade(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Returning error is unexpected")
 | 
			
		||||
		}
 | 
			
		||||
		if len(tt.expected) != len(actual) {
 | 
			
		||||
			t.Errorf("Result length is not as same as expected. expected: %d, actual: %d", len(tt.expected), len(actual))
 | 
			
		||||
			pp.Println(tt.expected)
 | 
			
		||||
			pp.Println(actual)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		for i := range tt.expected {
 | 
			
		||||
			if tt.expected[i] != actual[i] {
 | 
			
		||||
				t.Errorf("[%d] expected %s, actual %s", i, tt.expected[i], actual[i])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseAptCachePolicy(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		stdout   string
 | 
			
		||||
		name     string
 | 
			
		||||
		expected packCandidateVer
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			// Ubuntu 16.04
 | 
			
		||||
			`openssl:
 | 
			
		||||
  Installed: 1.0.2f-2ubuntu1
 | 
			
		||||
  Candidate: 1.0.2g-1ubuntu2
 | 
			
		||||
  Version table:
 | 
			
		||||
     1.0.2g-1ubuntu2 500
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages
 | 
			
		||||
 *** 1.0.2f-2ubuntu1 100
 | 
			
		||||
        100 /var/lib/dpkg/status`,
 | 
			
		||||
			"openssl",
 | 
			
		||||
			packCandidateVer{
 | 
			
		||||
				Name:      "openssl",
 | 
			
		||||
				Installed: "1.0.2f-2ubuntu1",
 | 
			
		||||
				Candidate: "1.0.2g-1ubuntu2",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// Ubuntu 14.04
 | 
			
		||||
			`openssl:
 | 
			
		||||
  Installed: 1.0.1f-1ubuntu2.16
 | 
			
		||||
  Candidate: 1.0.1f-1ubuntu2.17
 | 
			
		||||
  Version table:
 | 
			
		||||
     1.0.1f-1ubuntu2.17 0
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ trusty-updates/main amd64 Packages
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ trusty-security/main amd64 Packages
 | 
			
		||||
 *** 1.0.1f-1ubuntu2.16 0
 | 
			
		||||
        100 /var/lib/dpkg/status
 | 
			
		||||
     1.0.1f-1ubuntu2 0
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ trusty/main amd64 Packages`,
 | 
			
		||||
			"openssl",
 | 
			
		||||
			packCandidateVer{
 | 
			
		||||
				Name:      "openssl",
 | 
			
		||||
				Installed: "1.0.1f-1ubuntu2.16",
 | 
			
		||||
				Candidate: "1.0.1f-1ubuntu2.17",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// Ubuntu 12.04
 | 
			
		||||
			`openssl:
 | 
			
		||||
  Installed: 1.0.1-4ubuntu5.33
 | 
			
		||||
  Candidate: 1.0.1-4ubuntu5.34
 | 
			
		||||
  Version table:
 | 
			
		||||
     1.0.1-4ubuntu5.34 0
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ precise-updates/main amd64 Packages
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ precise-security/main amd64 Packages
 | 
			
		||||
 *** 1.0.1-4ubuntu5.33 0
 | 
			
		||||
        100 /var/lib/dpkg/status
 | 
			
		||||
     1.0.1-4ubuntu3 0
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ precise/main amd64 Packages`,
 | 
			
		||||
			"openssl",
 | 
			
		||||
			packCandidateVer{
 | 
			
		||||
				Name:      "openssl",
 | 
			
		||||
				Installed: "1.0.1-4ubuntu5.33",
 | 
			
		||||
				Candidate: "1.0.1-4ubuntu5.34",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, err := d.parseAptCachePolicy(tt.stdout, tt.name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Error has occurred: %s, actual: %#v", err, actual)
 | 
			
		||||
		}
 | 
			
		||||
		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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||