Compare commits
	
		
			1042 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 | 
							
								
								
									
										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 ./...
 | 
			
		||||
							
								
								
									
										34
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,12 +1,24 @@
 | 
			
		||||
vuls
 | 
			
		||||
# 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
 | 
			
		||||
*.txt
 | 
			
		||||
*.json
 | 
			
		||||
*.sqlite3
 | 
			
		||||
.gitmodules
 | 
			
		||||
coverage.out
 | 
			
		||||
issues/
 | 
			
		||||
vendor/
 | 
			
		||||
log/
 | 
			
		||||
results/
 | 
			
		||||
*config.toml
 | 
			
		||||
 | 
			
		||||
# Vuls
 | 
			
		||||
vuls
 | 
			
		||||
!cmd/vuls
 | 
			
		||||
vuls.db
 | 
			
		||||
config.json
 | 
			
		||||
results
 | 
			
		||||
							
								
								
									
										169
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						@@ -1,169 +0,0 @@
 | 
			
		||||
# Change Log
 | 
			
		||||
 | 
			
		||||
## [v0.1.5](https://github.com/future-architect/vuls/tree/v0.1.5) (2016-08-16)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.4...v0.1.5)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- Enable to scan without running go-cve-dictionary as server mode [\#84](https://github.com/future-architect/vuls/issues/84)
 | 
			
		||||
- Support high-speed scanning for CentOS [\#138](https://github.com/future-architect/vuls/pull/138) ([tai-ga](https://github.com/tai-ga))
 | 
			
		||||
- Add configtest subcommand. skip un-ssh-able servers. [\#134](https://github.com/future-architect/vuls/pull/134) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Support -report-azure-blob option [\#130](https://github.com/future-architect/vuls/pull/130) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add optional key-values that will be outputted to JSON in config [\#117](https://github.com/future-architect/vuls/pull/117) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Change dir structure [\#115](https://github.com/future-architect/vuls/pull/115) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add some validation of loading config. user, host and port [\#113](https://github.com/future-architect/vuls/pull/113) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Support scanning with external ssh command [\#101](https://github.com/future-architect/vuls/pull/101) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Detect Platform and get instance-id of amazon ec2 [\#95](https://github.com/future-architect/vuls/pull/95) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add -report-s3 option [\#92](https://github.com/future-architect/vuls/pull/92) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Added FreeBSD support. [\#90](https://github.com/future-architect/vuls/pull/90) ([justyntemme](https://github.com/justyntemme))
 | 
			
		||||
- Add glide files for vendoring [\#89](https://github.com/future-architect/vuls/pull/89) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix README, change -cvedbpath to -cve-dictionary-dbpath \#84 [\#85](https://github.com/future-architect/vuls/pull/85) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add option for it get cve detail from cve.sqlite3. [\#81](https://github.com/future-architect/vuls/pull/81) ([ymd38](https://github.com/ymd38))
 | 
			
		||||
- Add -report-text option, Fix small bug of report in japanese [\#78](https://github.com/future-architect/vuls/pull/78) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add JSONWriter, Fix CVE sort order of report [\#77](https://github.com/future-architect/vuls/pull/77) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Docker: Panic [\#76](https://github.com/future-architect/vuls/issues/76)
 | 
			
		||||
- Fix apt command to scan correctly when system locale is not english [\#149](https://github.com/future-architect/vuls/pull/149) ([kit494way](https://github.com/kit494way))
 | 
			
		||||
- Disable -ask-sudo-password for security reasons [\#148](https://github.com/future-architect/vuls/pull/148) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix no tty error while executing with -external-ssh option [\#143](https://github.com/future-architect/vuls/pull/143) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- wrong log packages [\#141](https://github.com/future-architect/vuls/pull/141) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Fix platform detection. [\#137](https://github.com/future-architect/vuls/pull/137) ([Rompei](https://github.com/Rompei))
 | 
			
		||||
- Fix nil pointer when scan with -cve-dictionary-dbpath and cpeNames [\#111](https://github.com/future-architect/vuls/pull/111) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Remove vulndb file before pkg audit [\#110](https://github.com/future-architect/vuls/pull/110) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add error handling when unable to connect via ssh. status code: 255 [\#108](https://github.com/future-architect/vuls/pull/108) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Enable to detect vulnerabilities on FreeBSD [\#98](https://github.com/future-architect/vuls/pull/98) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix unknown format err while check-update on RHEL6.5 [\#93](https://github.com/future-architect/vuls/pull/93) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Fix type of SMTP Port of discovery command's output [\#88](https://github.com/future-architect/vuls/pull/88) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix error msg when go-cve-dictionary is unavailable \#84 [\#86](https://github.com/future-architect/vuls/pull/86) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix error handling to avoid nil pointer err on debian [\#83](https://github.com/future-architect/vuls/pull/83) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix nil pointer while doing apt-cache policy on ubuntu \#76 [\#82](https://github.com/future-architect/vuls/pull/82) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix log import url [\#79](https://github.com/future-architect/vuls/pull/79) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
- Fix error handling of gorequest [\#75](https://github.com/future-architect/vuls/pull/75) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix freezing forever when no args specified in TUI mode [\#73](https://github.com/future-architect/vuls/pull/73) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- mv version.go version/version.go to run main.go without compile [\#71](https://github.com/future-architect/vuls/pull/71) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
 | 
			
		||||
 | 
			
		||||
**Closed issues:**
 | 
			
		||||
 | 
			
		||||
- SSh password authentication failed on FreeBSD [\#99](https://github.com/future-architect/vuls/issues/99)
 | 
			
		||||
- BUG: -o pipefail is not work on FreeBSD's /bin/sh. because it isn't bash [\#91](https://github.com/future-architect/vuls/issues/91)
 | 
			
		||||
- Use ~/.ssh/config [\#62](https://github.com/future-architect/vuls/issues/62)
 | 
			
		||||
- SSH ciphers [\#37](https://github.com/future-architect/vuls/issues/37)
 | 
			
		||||
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- Update README \#138 [\#144](https://github.com/future-architect/vuls/pull/144) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix a typo [\#142](https://github.com/future-architect/vuls/pull/142) ([dtan4](https://github.com/dtan4))
 | 
			
		||||
- Remove unnecessary step in readme of docker setup [\#140](https://github.com/future-architect/vuls/pull/140) ([mikkame](https://github.com/mikkame))
 | 
			
		||||
- Update logo [\#139](https://github.com/future-architect/vuls/pull/139) ([chanomaru](https://github.com/chanomaru))
 | 
			
		||||
- Update README.ja.md to fix wrong tips. [\#135](https://github.com/future-architect/vuls/pull/135) ([a2atsu](https://github.com/a2atsu))
 | 
			
		||||
- add tips about NVD JVN issue [\#133](https://github.com/future-architect/vuls/pull/133) ([a2atsu](https://github.com/a2atsu))
 | 
			
		||||
- Fix README wrong links [\#129](https://github.com/future-architect/vuls/pull/129) ([aomoriringo](https://github.com/aomoriringo))
 | 
			
		||||
- Add logo [\#126](https://github.com/future-architect/vuls/pull/126) ([chanomaru](https://github.com/chanomaru))
 | 
			
		||||
- Improve setup/docker [\#125](https://github.com/future-architect/vuls/pull/125) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix scan command help [\#124](https://github.com/future-architect/vuls/pull/124) ([aomoriringo](https://github.com/aomoriringo))
 | 
			
		||||
- added dockernized-vuls with vulsrepo [\#121](https://github.com/future-architect/vuls/pull/121) ([hikachan](https://github.com/hikachan))
 | 
			
		||||
- Fix detect platform on azure and degital ocean [\#119](https://github.com/future-architect/vuls/pull/119) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Remove json marshall-indent [\#118](https://github.com/future-architect/vuls/pull/118) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Improve Readme.ja [\#116](https://github.com/future-architect/vuls/pull/116) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add architecture diag to README.md [\#114](https://github.com/future-architect/vuls/pull/114) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Rename linux.go to base.go [\#100](https://github.com/future-architect/vuls/pull/100) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Update README.md [\#74](https://github.com/future-architect/vuls/pull/74) ([yoshi-taka](https://github.com/yoshi-taka))
 | 
			
		||||
- Refactoring debian.go [\#72](https://github.com/future-architect/vuls/pull/72) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
## [v0.1.4](https://github.com/future-architect/vuls/tree/v0.1.4) (2016-05-24)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.3...v0.1.4)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- Initial fetch from NVD is too heavy \(2.3 GB of memory consumed\) [\#27](https://github.com/future-architect/vuls/issues/27)
 | 
			
		||||
- Enable to show previous scan result [\#69](https://github.com/future-architect/vuls/pull/69) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add ignore-unscored-cves option [\#68](https://github.com/future-architect/vuls/pull/68) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Support dynamic scanning docker container [\#67](https://github.com/future-architect/vuls/pull/67) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Add version flag [\#65](https://github.com/future-architect/vuls/pull/65) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Update Dockerfile [\#57](https://github.com/future-architect/vuls/pull/57) ([theonlydoo](https://github.com/theonlydoo))
 | 
			
		||||
- Update run.sh [\#56](https://github.com/future-architect/vuls/pull/56) ([theonlydoo](https://github.com/theonlydoo))
 | 
			
		||||
- Support Windows [\#33](https://github.com/future-architect/vuls/pull/33) ([mattn](https://github.com/mattn))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- vuls scan -cvss-over does not work. [\#59](https://github.com/future-architect/vuls/issues/59)
 | 
			
		||||
- `panic: runtime error: invalid memory address or nil pointer dereference` when scan CentOS5.5 [\#58](https://github.com/future-architect/vuls/issues/58)
 | 
			
		||||
-  It rans out of memory. [\#47](https://github.com/future-architect/vuls/issues/47)
 | 
			
		||||
- BUG: vuls scan on CentOS with Japanese environment. [\#43](https://github.com/future-architect/vuls/issues/43)
 | 
			
		||||
- yum --color=never [\#36](https://github.com/future-architect/vuls/issues/36)
 | 
			
		||||
- Failed to parse yum check-update [\#32](https://github.com/future-architect/vuls/issues/32)
 | 
			
		||||
- Pointless sudo [\#29](https://github.com/future-architect/vuls/issues/29)
 | 
			
		||||
- Can't init database in a path having blanks [\#26](https://github.com/future-architect/vuls/issues/26)
 | 
			
		||||
- Fix pointless sudo in debian.go \#29 [\#66](https://github.com/future-architect/vuls/pull/66) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix error handling of httpGet in cve-client \#58 [\#64](https://github.com/future-architect/vuls/pull/64) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix nil pointer at error handling of cve\_client \#58 [\#63](https://github.com/future-architect/vuls/pull/63) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Set language en\_US. [\#61](https://github.com/future-architect/vuls/pull/61) ([pabroff](https://github.com/pabroff))
 | 
			
		||||
- Fix -cvss-over flag \#59 [\#60](https://github.com/future-architect/vuls/pull/60) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix scan on Japanese environment. [\#55](https://github.com/future-architect/vuls/pull/55) ([pabroff](https://github.com/pabroff))
 | 
			
		||||
- Fix a typo: replace Depricated by Deprecated. [\#54](https://github.com/future-architect/vuls/pull/54) ([jody-frankowski](https://github.com/jody-frankowski))
 | 
			
		||||
- Fix yes no infinite loop while doing yum update --changelog on root@CentOS \#47 [\#50](https://github.com/future-architect/vuls/pull/50) ([pabroff](https://github.com/pabroff))
 | 
			
		||||
- Fix $servername in output of discover command [\#45](https://github.com/future-architect/vuls/pull/45) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
## [v0.1.3](https://github.com/future-architect/vuls/tree/v0.1.3) (2016-04-21)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.2...v0.1.3)
 | 
			
		||||
 | 
			
		||||
**Implemented enhancements:**
 | 
			
		||||
 | 
			
		||||
- Add sudo support for prepare [\#11](https://github.com/future-architect/vuls/issues/11)
 | 
			
		||||
- Dockerfile? [\#10](https://github.com/future-architect/vuls/issues/10)
 | 
			
		||||
- Update README [\#41](https://github.com/future-architect/vuls/pull/41) ([theonlydoo](https://github.com/theonlydoo))
 | 
			
		||||
- Sparse dockerization [\#38](https://github.com/future-architect/vuls/pull/38) ([theonlydoo](https://github.com/theonlydoo))
 | 
			
		||||
- No password in config [\#35](https://github.com/future-architect/vuls/pull/35) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fr readme translation [\#23](https://github.com/future-architect/vuls/pull/23) ([novakin](https://github.com/novakin))
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Issues updating CVE database behind https proxy [\#39](https://github.com/future-architect/vuls/issues/39)
 | 
			
		||||
- Vuls failed to parse yum check-update [\#24](https://github.com/future-architect/vuls/issues/24)
 | 
			
		||||
- Fix yum to yum --color=never \#36 [\#42](https://github.com/future-architect/vuls/pull/42) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fix parse yum check update [\#40](https://github.com/future-architect/vuls/pull/40) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix typo [\#31](https://github.com/future-architect/vuls/pull/31) ([blue119](https://github.com/blue119))
 | 
			
		||||
- Fix error while parsing yum check-update \#24 [\#30](https://github.com/future-architect/vuls/pull/30) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
**Closed issues:**
 | 
			
		||||
 | 
			
		||||
- Unable to scan on ubuntu because changelog.ubuntu.com is down... [\#21](https://github.com/future-architect/vuls/issues/21)
 | 
			
		||||
- err: Not initialize\(d\) yet.. [\#16](https://github.com/future-architect/vuls/issues/16)
 | 
			
		||||
- Errors when using fish shell [\#8](https://github.com/future-architect/vuls/issues/8)
 | 
			
		||||
 | 
			
		||||
## [v0.1.2](https://github.com/future-architect/vuls/tree/v0.1.2) (2016-04-12)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.1...v0.1.2)
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Maximum 6 nodes available to scan [\#12](https://github.com/future-architect/vuls/issues/12)
 | 
			
		||||
- panic: runtime error: index out of range [\#5](https://github.com/future-architect/vuls/issues/5)
 | 
			
		||||
- Fix sudo option on RedHat like Linux and change some messages. [\#20](https://github.com/future-architect/vuls/pull/20) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Typo fix and updated readme [\#19](https://github.com/future-architect/vuls/pull/19) ([Euan-Kerr](https://github.com/Euan-Kerr))
 | 
			
		||||
- remove a period at the end of error messages. [\#18](https://github.com/future-architect/vuls/pull/18) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- fix error while yum updateinfo --security update on rhel@aws [\#17](https://github.com/future-architect/vuls/pull/17) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Fixed typos [\#15](https://github.com/future-architect/vuls/pull/15) ([radarhere](https://github.com/radarhere))
 | 
			
		||||
- Typo fix in error messages [\#14](https://github.com/future-architect/vuls/pull/14) ([Bregor](https://github.com/Bregor))
 | 
			
		||||
- Fix index out of range error when the number of servers is over 6. \#12 [\#13](https://github.com/future-architect/vuls/pull/13) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
- Revise small grammar mistakes in serverapi.go [\#9](https://github.com/future-architect/vuls/pull/9) ([cpobrien](https://github.com/cpobrien))
 | 
			
		||||
- Fix error handling in HTTP backoff function [\#7](https://github.com/future-architect/vuls/pull/7) ([kotakanbe](https://github.com/kotakanbe))
 | 
			
		||||
 | 
			
		||||
## [v0.1.1](https://github.com/future-architect/vuls/tree/v0.1.1) (2016-04-06)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.0...v0.1.1)
 | 
			
		||||
 | 
			
		||||
**Fixed bugs:**
 | 
			
		||||
 | 
			
		||||
- Typo in Exapmle [\#6](https://github.com/future-architect/vuls/pull/6) ([toli](https://github.com/toli))
 | 
			
		||||
 | 
			
		||||
## [v0.1.0](https://github.com/future-architect/vuls/tree/v0.1.0) (2016-04-04)
 | 
			
		||||
**Merged pull requests:**
 | 
			
		||||
 | 
			
		||||
- English translation [\#4](https://github.com/future-architect/vuls/pull/4) ([hikachan](https://github.com/hikachan))
 | 
			
		||||
- English translation [\#3](https://github.com/future-architect/vuls/pull/3) ([chewyinping](https://github.com/chewyinping))
 | 
			
		||||
- Add a Bitdeli Badge to README [\#2](https://github.com/future-architect/vuls/pull/2) ([bitdeli-chef](https://github.com/bitdeli-chef))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
 | 
			
		||||
							
								
								
									
										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>.
 | 
			
		||||
							
								
								
									
										51
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						@@ -1,51 +0,0 @@
 | 
			
		||||
.PHONY: \
 | 
			
		||||
	all \
 | 
			
		||||
	vendor \
 | 
			
		||||
	lint \
 | 
			
		||||
	vet \
 | 
			
		||||
	fmt \
 | 
			
		||||
	fmtcheck \
 | 
			
		||||
	pretest \
 | 
			
		||||
	test \
 | 
			
		||||
	cov \
 | 
			
		||||
	clean
 | 
			
		||||
 | 
			
		||||
SRCS = $(shell git ls-files '*.go')
 | 
			
		||||
PKGS = ./. ./db ./config ./models ./report ./cveapi ./scan ./util ./commands
 | 
			
		||||
 | 
			
		||||
all: test
 | 
			
		||||
 | 
			
		||||
#  vendor:
 | 
			
		||||
#          @ go get -v github.com/mjibson/party
 | 
			
		||||
#          party -d external -c -u
 | 
			
		||||
 | 
			
		||||
lint:
 | 
			
		||||
	@ go get -v github.com/golang/lint/golint
 | 
			
		||||
	$(foreach file,$(SRCS),golint $(file) || exit;)
 | 
			
		||||
 | 
			
		||||
vet:
 | 
			
		||||
	#  @-go get -v golang.org/x/tools/cmd/vet
 | 
			
		||||
	$(foreach pkg,$(PKGS),go vet $(pkg);)
 | 
			
		||||
 | 
			
		||||
fmt:
 | 
			
		||||
	gofmt -w $(SRCS)
 | 
			
		||||
 | 
			
		||||
fmtcheck:
 | 
			
		||||
	$(foreach file,$(SRCS),gofmt -d $(file);)
 | 
			
		||||
 | 
			
		||||
pretest: lint vet fmtcheck
 | 
			
		||||
 | 
			
		||||
test: pretest
 | 
			
		||||
	$(foreach pkg,$(PKGS),go test -v $(pkg) || exit;)
 | 
			
		||||
 | 
			
		||||
unused :
 | 
			
		||||
	$(foreach pkg,$(PKGS),unused $(pkg);)
 | 
			
		||||
 | 
			
		||||
cov:
 | 
			
		||||
	@ go get -v github.com/axw/gocov/gocov
 | 
			
		||||
	@ go get golang.org/x/tools/cmd/cover
 | 
			
		||||
	gocov test | gocov report
 | 
			
		||||
 | 
			
		||||
clean:
 | 
			
		||||
	$(foreach pkg,$(PKGS),go clean $(pkg) || exit;)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										224
									
								
								README.fr.md
									
									
									
									
									
								
							
							
						
						@@ -1,224 +0,0 @@
 | 
			
		||||
 | 
			
		||||
# Vuls: VULnerability Scanner
 | 
			
		||||
 | 
			
		||||
[](http://goo.gl/forms/xm5KFo35tu)
 | 
			
		||||
 | 
			
		||||
Scanneur de vulnérabilité Linux, sans agent, écrit en golang
 | 
			
		||||
 | 
			
		||||
Nous avons une équipe Slack. [Rejoignez notre Slack Team](http://goo.gl/forms/xm5KFo35tu)  
 | 
			
		||||
 | 
			
		||||
[README en English](https://github.com/future-architect/vuls/blob/master/README.md)  
 | 
			
		||||
[README en Japonais](https://github.com/future-architect/vuls/blob/master/README.ja.md)  
 | 
			
		||||
 | 
			
		||||
[](https://asciinema.org/a/3y9zrf950agiko7klg8abvyck)
 | 
			
		||||
 | 
			
		||||

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

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

 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
For more information see [README in English](https://github.com/future-architect/vuls/blob/master/README.md)  
 | 
			
		||||
							
								
								
									
										1201
									
								
								README.ja.md
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										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,162 +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"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/scan"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ConfigtestCmd is Subcommand
 | 
			
		||||
type ConfigtestCmd struct {
 | 
			
		||||
	configPath     string
 | 
			
		||||
	askKeyPassword bool
 | 
			
		||||
	sshExternal    bool
 | 
			
		||||
 | 
			
		||||
	debug bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*ConfigtestCmd) Name() string { return "configtest" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*ConfigtestCmd) Synopsis() string { return "Test configuration" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*ConfigtestCmd) Usage() string {
 | 
			
		||||
	return `configtest:
 | 
			
		||||
	configtest
 | 
			
		||||
		        [-config=/path/to/config.toml]
 | 
			
		||||
	        	[-ask-key-password]
 | 
			
		||||
	        	[-ssh-external]
 | 
			
		||||
		        [-debug]
 | 
			
		||||
 | 
			
		||||
		        [SERVER]...
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.sshExternal,
 | 
			
		||||
		"ssh-external",
 | 
			
		||||
		false,
 | 
			
		||||
		"Use external ssh command. Default: Use the Go native implementation")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var servernames []string
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		servernames = f.Args()
 | 
			
		||||
	} else {
 | 
			
		||||
		stat, _ := os.Stdin.Stat()
 | 
			
		||||
		if (stat.Mode() & os.ModeCharDevice) == 0 {
 | 
			
		||||
			bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			fields := strings.Fields(string(bytes))
 | 
			
		||||
			if 0 < len(fields) {
 | 
			
		||||
				servernames = fields
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
	for _, arg := range servernames {
 | 
			
		||||
		found := false
 | 
			
		||||
		for servername, info := range c.Conf.Servers {
 | 
			
		||||
			if servername == arg {
 | 
			
		||||
				target[servername] = info
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			logrus.Errorf("%s is not in config", arg)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(servernames) {
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// logger
 | 
			
		||||
	Log := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	Log.Info("Validating Config...")
 | 
			
		||||
	if !c.Conf.Validate() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting Server/Contianer OS... ")
 | 
			
		||||
	scan.InitServers(Log)
 | 
			
		||||
 | 
			
		||||
	Log.Info("Checking sudo configuration... ")
 | 
			
		||||
	if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	scan.PrintSSHableServerNames()
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
@@ -1,165 +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"
 | 
			
		||||
#cpeNames = [
 | 
			
		||||
#  "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
#]
 | 
			
		||||
#containers = ["${running}"]
 | 
			
		||||
#optional = [
 | 
			
		||||
#    ["key", "value"],
 | 
			
		||||
#]
 | 
			
		||||
 | 
			
		||||
[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",
 | 
			
		||||
#]
 | 
			
		||||
#containers = ["${running}"]
 | 
			
		||||
#optional = [
 | 
			
		||||
#    ["key", "value"],
 | 
			
		||||
#]
 | 
			
		||||
{{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,108 +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"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/db"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HistoryCmd is Subcommand of list scanned results
 | 
			
		||||
type HistoryCmd struct {
 | 
			
		||||
	debug    bool
 | 
			
		||||
	debugSQL bool
 | 
			
		||||
 | 
			
		||||
	dbpath string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*HistoryCmd) Name() string { return "history" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*HistoryCmd) Synopsis() string {
 | 
			
		||||
	return `List history of scanning.`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*HistoryCmd) Usage() string {
 | 
			
		||||
	return `history:
 | 
			
		||||
	history
 | 
			
		||||
		[-dbpath=/path/to/vuls.sqlite3]
 | 
			
		||||
	`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *HistoryCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultDBPath := filepath.Join(wd, "vuls.sqlite3")
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.DBPath = p.dbpath
 | 
			
		||||
 | 
			
		||||
	//  _, err := scanHistories()
 | 
			
		||||
	histories, err := scanHistories()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Error("Failed to select scan histories: ", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	const timeLayout = "2006-01-02 15:04"
 | 
			
		||||
	for _, history := range histories {
 | 
			
		||||
		names := []string{}
 | 
			
		||||
		for _, result := range history.ScanResults {
 | 
			
		||||
			if 0 < len(result.Container.ContainerID) {
 | 
			
		||||
				names = append(names, result.Container.Name)
 | 
			
		||||
			} else {
 | 
			
		||||
				names = append(names, result.ServerName)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Printf("%-3d %s scanned %d servers: %s\n",
 | 
			
		||||
			history.ID,
 | 
			
		||||
			history.ScannedAt.Format(timeLayout),
 | 
			
		||||
			len(history.ScanResults),
 | 
			
		||||
			strings.Join(names, ", "),
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanHistories() (histories []models.ScanHistory, err error) {
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		return histories, fmt.Errorf(
 | 
			
		||||
			"Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	histories, err = db.SelectScanHistories()
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
@@ -1,164 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/scan"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// PrepareCmd is Subcommand of host discovery mode
 | 
			
		||||
type PrepareCmd struct {
 | 
			
		||||
	debug      bool
 | 
			
		||||
	configPath string
 | 
			
		||||
 | 
			
		||||
	askSudoPassword bool
 | 
			
		||||
	askKeyPassword  bool
 | 
			
		||||
 | 
			
		||||
	useUnattendedUpgrades bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*PrepareCmd) Name() string { return "prepare" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*PrepareCmd) Synopsis() string {
 | 
			
		||||
	//  return "Install packages Ubuntu: unattended-upgrade, CentOS: yum-plugin-security)"
 | 
			
		||||
	return `Install required packages to scan.
 | 
			
		||||
				CentOS: yum-plugin-security, yum-plugin-changelog
 | 
			
		||||
				Amazon: None
 | 
			
		||||
				RHEL:   TODO
 | 
			
		||||
				Ubuntu: None
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*PrepareCmd) Usage() string {
 | 
			
		||||
	return `prepare:
 | 
			
		||||
	prepare
 | 
			
		||||
			[-config=/path/to/config.toml]
 | 
			
		||||
			[-ask-key-password]
 | 
			
		||||
			[-debug]
 | 
			
		||||
 | 
			
		||||
		    [SERVER]...
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *PrepareCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askSudoPassword,
 | 
			
		||||
		"ask-sudo-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASON. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useUnattendedUpgrades,
 | 
			
		||||
		"use-unattended-upgrades",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] For Ubuntu, install unattended-upgrades",
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if p.askSudoPassword {
 | 
			
		||||
		logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Infof("Start Preparing (config: %s)", p.configPath)
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
	for _, arg := range f.Args() {
 | 
			
		||||
		found := false
 | 
			
		||||
		for servername, info := range c.Conf.Servers {
 | 
			
		||||
			if servername == arg {
 | 
			
		||||
				target[servername] = info
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			logrus.Errorf("%s is not in config", arg)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
 | 
			
		||||
 | 
			
		||||
	// Set up custom logger
 | 
			
		||||
	logger := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	logger.Info("Detecting OS... ")
 | 
			
		||||
	scan.InitServers(logger)
 | 
			
		||||
 | 
			
		||||
	logger.Info("Installing...")
 | 
			
		||||
	if errs := scan.Prepare(); 0 < len(errs) {
 | 
			
		||||
		for _, e := range errs {
 | 
			
		||||
			logger.Errorf("Failed: %s", e)
 | 
			
		||||
		}
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Info("Success")
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										430
									
								
								commands/scan.go
									
									
									
									
									
								
							
							
						
						@@ -1,430 +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"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/db"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/scan"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanCmd is Subcommand of host discovery mode
 | 
			
		||||
type ScanCmd struct {
 | 
			
		||||
	lang     string
 | 
			
		||||
	debug    bool
 | 
			
		||||
	debugSQL bool
 | 
			
		||||
 | 
			
		||||
	configPath string
 | 
			
		||||
 | 
			
		||||
	dbpath           string
 | 
			
		||||
	cvedbpath        string
 | 
			
		||||
	cveDictionaryURL string
 | 
			
		||||
 | 
			
		||||
	cvssScoreOver      float64
 | 
			
		||||
	ignoreUnscoredCves bool
 | 
			
		||||
 | 
			
		||||
	httpProxy       string
 | 
			
		||||
	askSudoPassword bool
 | 
			
		||||
	askKeyPassword  bool
 | 
			
		||||
 | 
			
		||||
	// reporting
 | 
			
		||||
	reportSlack     bool
 | 
			
		||||
	reportMail      bool
 | 
			
		||||
	reportJSON      bool
 | 
			
		||||
	reportText      bool
 | 
			
		||||
	reportS3        bool
 | 
			
		||||
	reportAzureBlob bool
 | 
			
		||||
 | 
			
		||||
	awsProfile  string
 | 
			
		||||
	awsS3Bucket string
 | 
			
		||||
	awsRegion   string
 | 
			
		||||
 | 
			
		||||
	azureAccount   string
 | 
			
		||||
	azureKey       string
 | 
			
		||||
	azureContainer string
 | 
			
		||||
 | 
			
		||||
	useYumPluginSecurity  bool
 | 
			
		||||
	useUnattendedUpgrades bool
 | 
			
		||||
 | 
			
		||||
	sshExternal bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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-dbpath=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cve-dictionary-url=http://127.0.0.1:1323]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ssh-external]
 | 
			
		||||
		[-report-azure-blob]
 | 
			
		||||
		[-report-json]
 | 
			
		||||
		[-report-mail]
 | 
			
		||||
		[-report-s3]
 | 
			
		||||
		[-report-slack]
 | 
			
		||||
		[-report-text]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-ask-key-password]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-aws-profile=default]
 | 
			
		||||
		[-aws-region=us-west-2]
 | 
			
		||||
		[-aws-s3-bucket=bucket_name]
 | 
			
		||||
		[-azure-account=accout]
 | 
			
		||||
		[-azure-key=key]
 | 
			
		||||
		[-azure-container=container]
 | 
			
		||||
 | 
			
		||||
		[SERVER]...
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultDBPath := filepath.Join(wd, "vuls.sqlite3")
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbpath,
 | 
			
		||||
		"cve-dictionary-dbpath",
 | 
			
		||||
		"",
 | 
			
		||||
		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	defaultURL := "http://127.0.0.1:1323"
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDictionaryURL,
 | 
			
		||||
		"cve-dictionary-url",
 | 
			
		||||
		defaultURL,
 | 
			
		||||
		"http://CVE.Dictionary")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(
 | 
			
		||||
		&p.cvssScoreOver,
 | 
			
		||||
		"cvss-over",
 | 
			
		||||
		0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.ignoreUnscoredCves,
 | 
			
		||||
		"ignore-unscored-cves",
 | 
			
		||||
		false,
 | 
			
		||||
		"Don't report the unscored CVEs")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.sshExternal,
 | 
			
		||||
		"ssh-external",
 | 
			
		||||
		false,
 | 
			
		||||
		"Use external ssh command. Default: Use the Go native implementation")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.httpProxy,
 | 
			
		||||
		"http-proxy",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://proxy-url:port (default: empty)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.reportSlack, "report-slack", false, "Send report via Slack")
 | 
			
		||||
	f.BoolVar(&p.reportMail, "report-mail", false, "Send report via Email")
 | 
			
		||||
	f.BoolVar(&p.reportJSON,
 | 
			
		||||
		"report-json",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Write report to JSON files (%s/results/current)", wd),
 | 
			
		||||
	)
 | 
			
		||||
	f.BoolVar(&p.reportText,
 | 
			
		||||
		"report-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Write report to text files (%s/results/current)", wd),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.reportS3,
 | 
			
		||||
		"report-s3",
 | 
			
		||||
		false,
 | 
			
		||||
		"Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json)",
 | 
			
		||||
	)
 | 
			
		||||
	f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
 | 
			
		||||
	f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
 | 
			
		||||
	f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.reportAzureBlob,
 | 
			
		||||
		"report-azure-blob",
 | 
			
		||||
		false,
 | 
			
		||||
		"Write report to S3 (container/yyyyMMdd_HHmm/servername.json)",
 | 
			
		||||
	)
 | 
			
		||||
	f.StringVar(&p.azureAccount, "azure-account", "", "Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureKey, "azure-key", "", "Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureContainer, "azure-container", "", "Azure storage container name")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askSudoPassword,
 | 
			
		||||
		"ask-sudo-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useYumPluginSecurity,
 | 
			
		||||
		"use-yum-plugin-security",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useUnattendedUpgrades,
 | 
			
		||||
		"use-unattended-upgrades",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Deprecated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if p.askSudoPassword {
 | 
			
		||||
		logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Info("Start scanning")
 | 
			
		||||
	logrus.Infof("config: %s", p.configPath)
 | 
			
		||||
	if p.cvedbpath != "" {
 | 
			
		||||
		logrus.Infof("cve-dictionary: %s", p.cvedbpath)
 | 
			
		||||
	} else {
 | 
			
		||||
		logrus.Infof("cve-dictionary: %s", p.cveDictionaryURL)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var servernames []string
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		servernames = f.Args()
 | 
			
		||||
	} else {
 | 
			
		||||
		stat, _ := os.Stdin.Stat()
 | 
			
		||||
		if (stat.Mode() & os.ModeCharDevice) == 0 {
 | 
			
		||||
			bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			fields := strings.Fields(string(bytes))
 | 
			
		||||
			if 0 < len(fields) {
 | 
			
		||||
				servernames = fields
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
	for _, arg := range servernames {
 | 
			
		||||
		found := false
 | 
			
		||||
		for servername, info := range c.Conf.Servers {
 | 
			
		||||
			if servername == arg {
 | 
			
		||||
				target[servername] = info
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			logrus.Errorf("%s is not in config", arg)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(servernames) {
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Lang = p.lang
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
 | 
			
		||||
	// logger
 | 
			
		||||
	Log := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	// report
 | 
			
		||||
	reports := []report.ResultWriter{
 | 
			
		||||
		report.StdoutWriter{},
 | 
			
		||||
		report.LogrusWriter{},
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportSlack {
 | 
			
		||||
		reports = append(reports, report.SlackWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportMail {
 | 
			
		||||
		reports = append(reports, report.MailWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportJSON {
 | 
			
		||||
		reports = append(reports, report.JSONWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportText {
 | 
			
		||||
		reports = append(reports, report.TextFileWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportS3 {
 | 
			
		||||
		c.Conf.AwsRegion = p.awsRegion
 | 
			
		||||
		c.Conf.AwsProfile = p.awsProfile
 | 
			
		||||
		c.Conf.S3Bucket = p.awsS3Bucket
 | 
			
		||||
		if err := report.CheckIfBucketExists(); err != nil {
 | 
			
		||||
			Log.Errorf("Failed to access to the S3 bucket. err: %s", err)
 | 
			
		||||
			Log.Error("Ensure the bucket or check AWS config before scanning")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.S3Writer{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportAzureBlob {
 | 
			
		||||
		c.Conf.AzureAccount = p.azureAccount
 | 
			
		||||
		if c.Conf.AzureAccount == "" {
 | 
			
		||||
			c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureKey = p.azureKey
 | 
			
		||||
		if c.Conf.AzureKey == "" {
 | 
			
		||||
			c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureContainer = p.azureContainer
 | 
			
		||||
		if c.Conf.AzureContainer == "" {
 | 
			
		||||
			Log.Error("Azure storage container name is requied with --azure-container option")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		if err := report.CheckIfAzureContainerExists(); err != nil {
 | 
			
		||||
			Log.Errorf("Failed to access to the Azure Blob container. err: %s", err)
 | 
			
		||||
			Log.Error("Ensure the container or check Azure config before scanning")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.AzureBlobWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.DBPath = p.dbpath
 | 
			
		||||
	c.Conf.CveDBPath = p.cvedbpath
 | 
			
		||||
	c.Conf.CveDictionaryURL = p.cveDictionaryURL
 | 
			
		||||
	c.Conf.CvssScoreOver = p.cvssScoreOver
 | 
			
		||||
	c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
 | 
			
		||||
	c.Conf.SSHExternal = p.sshExternal
 | 
			
		||||
	c.Conf.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. err: %s", err)
 | 
			
		||||
		Log.Errorf("Run go-cve-dictionary as server mode or specify -cve-dictionary-dbpath option")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting Server/Contianer OS... ")
 | 
			
		||||
	scan.InitServers(Log)
 | 
			
		||||
 | 
			
		||||
	Log.Info("Checking sudo configuration... ")
 | 
			
		||||
	if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting Platforms... ")
 | 
			
		||||
	scan.DetectPlatforms(Log)
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerabilities... ")
 | 
			
		||||
	if errs := scan.Scan(); 0 < len(errs) {
 | 
			
		||||
		for _, e := range errs {
 | 
			
		||||
			Log.Errorf("Failed to scan. err: %s", e)
 | 
			
		||||
		}
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scanResults, err := scan.GetScanResults()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		Log.Fatal(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Insert to DB...")
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.MigrateDB(); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to migrate. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := db.Insert(scanResults); err != nil {
 | 
			
		||||
		Log.Fatalf("Failed to insert. dbpath: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Reporting...")
 | 
			
		||||
	filtered := scanResults.FilterByCvssOver()
 | 
			
		||||
	for _, w := range reports {
 | 
			
		||||
		if err := w.Write(filtered); err != nil {
 | 
			
		||||
			Log.Fatalf("Failed to report, err: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
@@ -1,97 +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"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"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")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultDBPath := filepath.Join(wd, "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
 | 
			
		||||
 | 
			
		||||
	historyID := ""
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		if _, err := strconv.Atoi(f.Args()[0]); err != nil {
 | 
			
		||||
			log.Errorf("First Argument have to be scan_histores record ID: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		historyID = f.Args()[0]
 | 
			
		||||
	} else {
 | 
			
		||||
		stat, _ := os.Stdin.Stat()
 | 
			
		||||
		if (stat.Mode() & os.ModeCharDevice) == 0 {
 | 
			
		||||
			bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			fields := strings.Fields(string(bytes))
 | 
			
		||||
			if 0 < len(fields) {
 | 
			
		||||
				historyID = fields[0]
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return report.RunTui(historyID)
 | 
			
		||||
}
 | 
			
		||||
@@ -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"
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										253
									
								
								config/config.go
									
									
									
									
									
								
							
							
						
						@@ -1,253 +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
 | 
			
		||||
	IgnoreUnscoredCves bool
 | 
			
		||||
 | 
			
		||||
	SSHExternal bool
 | 
			
		||||
 | 
			
		||||
	HTTPProxy string `valid:"url"`
 | 
			
		||||
	DBPath    string
 | 
			
		||||
	CveDBPath string
 | 
			
		||||
 | 
			
		||||
	AwsProfile string
 | 
			
		||||
	AwsRegion  string
 | 
			
		||||
	S3Bucket   string
 | 
			
		||||
 | 
			
		||||
	AzureAccount   string
 | 
			
		||||
	AzureKey       string
 | 
			
		||||
	AzureContainer string
 | 
			
		||||
 | 
			
		||||
	//  CpeNames      []string
 | 
			
		||||
	//  SummaryMode          bool
 | 
			
		||||
	UseYumPluginSecurity  bool
 | 
			
		||||
	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))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.CveDBPath) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB(Cve Dictionary) path must be a *Absolute* file path. dbpath: %s", c.CveDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := valid.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfo has SSH Info, additional CPE packages to scan.
 | 
			
		||||
type ServerInfo struct {
 | 
			
		||||
	ServerName  string
 | 
			
		||||
	User        string
 | 
			
		||||
	Host        string
 | 
			
		||||
	Port        string
 | 
			
		||||
	KeyPath     string
 | 
			
		||||
	KeyPassword string
 | 
			
		||||
 | 
			
		||||
	CpeNames []string
 | 
			
		||||
 | 
			
		||||
	// Container Names or IDs
 | 
			
		||||
	Containers []string
 | 
			
		||||
 | 
			
		||||
	// Optional key-value set that will be outputted to JSON
 | 
			
		||||
	Optional [][]interface{}
 | 
			
		||||
 | 
			
		||||
	// used internal
 | 
			
		||||
	LogMsgAnsiColor string // DebugLog Color
 | 
			
		||||
	Container       Container
 | 
			
		||||
	Family          string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsContainer returns whether this ServerInfo is about container
 | 
			
		||||
func (s ServerInfo) IsContainer() bool {
 | 
			
		||||
	return 0 < len(s.Container.ContainerID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetContainer set container
 | 
			
		||||
func (s *ServerInfo) SetContainer(d Container) {
 | 
			
		||||
	s.Container = d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Container has Container information.
 | 
			
		||||
type Container struct {
 | 
			
		||||
	ContainerID string
 | 
			
		||||
	Name        string
 | 
			
		||||
	Type        string
 | 
			
		||||
}
 | 
			
		||||
@@ -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,30 +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 string) error {
 | 
			
		||||
	var loader Loader
 | 
			
		||||
	loader = TOMLLoader{}
 | 
			
		||||
	return loader.Load(path, keyPass)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Loader is interface of concrete loader
 | 
			
		||||
type Loader interface {
 | 
			
		||||
	Load(string, string) error
 | 
			
		||||
}
 | 
			
		||||
@@ -1,134 +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 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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	i := 0
 | 
			
		||||
	for name, v := range conf.Servers {
 | 
			
		||||
 | 
			
		||||
		if 0 < len(v.KeyPassword) {
 | 
			
		||||
			log.Warn("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE.")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s := ServerInfo{ServerName: name}
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case v.User != "":
 | 
			
		||||
			s.User = v.User
 | 
			
		||||
		case d.User != "":
 | 
			
		||||
			s.User = d.User
 | 
			
		||||
		default:
 | 
			
		||||
			return fmt.Errorf("%s is invalid. User is empty", name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Host = v.Host
 | 
			
		||||
		if s.Host == "" {
 | 
			
		||||
			return fmt.Errorf("%s is invalid. host is empty", name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case v.Port != "":
 | 
			
		||||
			s.Port = v.Port
 | 
			
		||||
		case d.Port != "":
 | 
			
		||||
			s.Port = d.Port
 | 
			
		||||
		default:
 | 
			
		||||
			s.Port = "22"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.KeyPath = v.KeyPath
 | 
			
		||||
		if s.KeyPath == "" {
 | 
			
		||||
			s.KeyPath = d.KeyPath
 | 
			
		||||
		}
 | 
			
		||||
		if s.KeyPath != "" {
 | 
			
		||||
			if _, err := os.Stat(s.KeyPath); err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"%s is invalid. keypath: %s not exists", name, s.KeyPath)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//  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.Containers = v.Containers
 | 
			
		||||
		if len(s.Containers) == 0 {
 | 
			
		||||
			s.Containers = d.Containers
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Optional = v.Optional
 | 
			
		||||
		for _, dkv := range d.Optional {
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, kv := range s.Optional {
 | 
			
		||||
				if dkv[0] == kv[0] {
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !found {
 | 
			
		||||
				s.Optional = append(s.Optional, dkv)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.LogMsgAnsiColor = Colors[i%len(Colors)]
 | 
			
		||||
		i++
 | 
			
		||||
 | 
			
		||||
		servers[name] = s
 | 
			
		||||
	}
 | 
			
		||||
	log.Debug("Config loaded")
 | 
			
		||||
	log.Debugf("%s", pp.Sprintf("%v", servers))
 | 
			
		||||
	Conf.Servers = servers
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
@@ -1,284 +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"
 | 
			
		||||
	cveconfig "github.com/kotakanbe/go-cve-dictionary/config"
 | 
			
		||||
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
 | 
			
		||||
	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) {
 | 
			
		||||
	if config.Conf.CveDBPath != "" {
 | 
			
		||||
		log.Debugf("get cve-dictionary from sqlite3")
 | 
			
		||||
		return true, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.initialize()
 | 
			
		||||
	url := fmt.Sprintf("%s/health", api.baseURL)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if len(errs) > 0 || resp == nil || 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) {
 | 
			
		||||
	if config.Conf.CveDBPath != "" {
 | 
			
		||||
		return api.FetchCveDetailsFromCveDB(cveIDs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
	reqChan := make(chan string, len(cveIDs))
 | 
			
		||||
	resChan := make(chan response, len(cveIDs))
 | 
			
		||||
	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) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails cve.CveDetails, err error) {
 | 
			
		||||
	log.Debugf("open cve-dictionary db")
 | 
			
		||||
	cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	if err := cvedb.OpenDB(); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to open DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	for _, cveID := range cveIDs {
 | 
			
		||||
		cveDetail := cvedb.Get(cveID)
 | 
			
		||||
		if len(cveDetail.CveID) == 0 {
 | 
			
		||||
			cveDetails = append(cveDetails, cve.CveDetail{
 | 
			
		||||
				CveID: cveID,
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			cveDetails = append(cveDetails, cveDetail)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// order by CVE ID desc
 | 
			
		||||
	sort.Sort(cveDetails)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
	var body string
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		//  resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v", errs, url, resp)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		log.Warnf("Failed to 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) {
 | 
			
		||||
	if config.Conf.CveDBPath != "" {
 | 
			
		||||
		return api.FetchCveDetailsByCpeNameFromDB(cpeName)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
	url, err := util.URLPathJoin(api.baseURL, "cpes")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []cve.CveDetail{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return fmt.Errorf("HTTP POST error: %v, url: %s, resp: %v", errs, url, resp)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeNameFromDB(cpeName string) ([]cve.CveDetail, error) {
 | 
			
		||||
	log.Debugf("open cve-dictionary db")
 | 
			
		||||
	cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	if err := cvedb.OpenDB(); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to open DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return cvedb.GetByCpeName(cpeName), nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										324
									
								
								db/db.go
									
									
									
									
									
								
							
							
						
						@@ -1,324 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	m "github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/jinzhu/gorm"
 | 
			
		||||
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var db *gorm.DB
 | 
			
		||||
 | 
			
		||||
// OpenDB opens Database
 | 
			
		||||
func OpenDB() (err error) {
 | 
			
		||||
	db, err = gorm.Open("sqlite3", config.Conf.DBPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
 | 
			
		||||
		return
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	db.LogMode(config.Conf.DebugSQL)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MigrateDB migrates Database
 | 
			
		||||
func MigrateDB() error {
 | 
			
		||||
	if err := db.AutoMigrate(
 | 
			
		||||
		&m.ScanHistory{},
 | 
			
		||||
		&m.ScanResult{},
 | 
			
		||||
		//  &m.NWLink{},
 | 
			
		||||
		&m.Container{},
 | 
			
		||||
		&m.CveInfo{},
 | 
			
		||||
		&m.CpeName{},
 | 
			
		||||
		&m.PackageInfo{},
 | 
			
		||||
		&m.DistroAdvisory{},
 | 
			
		||||
		&cve.CveDetail{},
 | 
			
		||||
		&cve.Jvn{},
 | 
			
		||||
		&cve.Nvd{},
 | 
			
		||||
		&cve.Reference{},
 | 
			
		||||
		&cve.Cpe{},
 | 
			
		||||
	).Error; err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to migrate. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errMsg := "Failed to create index. err: %s"
 | 
			
		||||
	//  if err := db.Model(&m.NWLink{}).
 | 
			
		||||
	//      AddIndex("idx_n_w_links_scan_result_id", "scan_result_id").Error; err != nil {
 | 
			
		||||
	//      return fmt.Errorf(errMsg, err)
 | 
			
		||||
	//  }
 | 
			
		||||
	if err := db.Model(&m.Container{}).
 | 
			
		||||
		AddIndex("idx_containers_scan_result_id", "scan_result_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.CveInfo{}).
 | 
			
		||||
		AddIndex("idx_cve_infos_scan_result_id", "scan_result_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.CpeName{}).
 | 
			
		||||
		AddIndex("idx_cpe_names_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.PackageInfo{}).
 | 
			
		||||
		AddIndex("idx_package_infos_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.DistroAdvisory{}).
 | 
			
		||||
		//TODO check table name
 | 
			
		||||
		AddIndex("idx_distro_advisories_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.CveDetail{}).
 | 
			
		||||
		AddIndex("idx_cve_details_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.CveDetail{}).
 | 
			
		||||
		AddIndex("idx_cve_details_cveid", "cve_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Nvd{}).
 | 
			
		||||
		AddIndex("idx_nvds_cve_detail_id", "cve_detail_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Jvn{}).
 | 
			
		||||
		AddIndex("idx_jvns_cve_detail_id", "cve_detail_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Cpe{}).
 | 
			
		||||
		AddIndex("idx_cpes_jvn_id", "jvn_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Reference{}).
 | 
			
		||||
		AddIndex("idx_references_jvn_id", "jvn_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Cpe{}).
 | 
			
		||||
		AddIndex("idx_cpes_nvd_id", "nvd_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Reference{}).
 | 
			
		||||
		AddIndex("idx_references_nvd_id", "nvd_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Insert inserts scan results into DB
 | 
			
		||||
func Insert(results []m.ScanResult) error {
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		r.KnownCves = resetGormIDs(r.KnownCves)
 | 
			
		||||
		r.UnknownCves = resetGormIDs(r.UnknownCves)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	history := m.ScanHistory{
 | 
			
		||||
		ScanResults: results,
 | 
			
		||||
		ScannedAt:   time.Now(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db = db.Set("gorm:save_associations", false)
 | 
			
		||||
	if err := db.Create(&history).Error; err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, scanResult := range history.ScanResults {
 | 
			
		||||
		scanResult.ScanHistoryID = history.ID
 | 
			
		||||
		if err := db.Create(&scanResult).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		scanResult.Container.ScanResultID = scanResult.ID
 | 
			
		||||
		if err := db.Create(&scanResult.Container).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := insertCveInfos(scanResult.ID, scanResult.KnownCves); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := insertCveInfos(scanResult.ID, scanResult.UnknownCves); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func insertCveInfos(scanResultID uint, infos []m.CveInfo) error {
 | 
			
		||||
	for _, cveInfo := range infos {
 | 
			
		||||
		cveInfo.ScanResultID = scanResultID
 | 
			
		||||
		if err := db.Create(&cveInfo).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, pack := range cveInfo.Packages {
 | 
			
		||||
			pack.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&pack).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, distroAdvisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			distroAdvisory.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&distroAdvisory).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, cpeName := range cveInfo.CpeNames {
 | 
			
		||||
			cpeName.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&cpeName).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		db = db.Set("gorm:save_associations", true)
 | 
			
		||||
		cveDetail := cveInfo.CveDetail
 | 
			
		||||
		cveDetail.CveInfoID = cveInfo.ID
 | 
			
		||||
		if err := db.Create(&cveDetail).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		db = db.Set("gorm:save_associations", false)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func resetGormIDs(infos []m.CveInfo) []m.CveInfo {
 | 
			
		||||
	for i := range infos {
 | 
			
		||||
		infos[i].CveDetail.ID = 0
 | 
			
		||||
		// NVD
 | 
			
		||||
		infos[i].CveDetail.Nvd.ID = 0
 | 
			
		||||
		for j := range infos[i].CveDetail.Nvd.Cpes {
 | 
			
		||||
			infos[i].CveDetail.Nvd.Cpes[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
		for j := range infos[i].CveDetail.Nvd.References {
 | 
			
		||||
			infos[i].CveDetail.Nvd.References[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// JVN
 | 
			
		||||
		infos[i].CveDetail.Jvn.ID = 0
 | 
			
		||||
		for j := range infos[i].CveDetail.Jvn.Cpes {
 | 
			
		||||
			infos[i].CveDetail.Jvn.Cpes[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
		for j := range infos[i].CveDetail.Jvn.References {
 | 
			
		||||
			infos[i].CveDetail.Jvn.References[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//Packages
 | 
			
		||||
		for j := range infos[i].Packages {
 | 
			
		||||
			infos[i].Packages[j].ID = 0
 | 
			
		||||
			infos[i].Packages[j].CveInfoID = 0
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return infos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SelectScanHistory select scan history from DB
 | 
			
		||||
func SelectScanHistory(historyID string) (m.ScanHistory, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	scanHistory := m.ScanHistory{}
 | 
			
		||||
	if historyID == "" {
 | 
			
		||||
		// select latest
 | 
			
		||||
		db.Order("scanned_at desc").First(&scanHistory)
 | 
			
		||||
	} else {
 | 
			
		||||
		var id int
 | 
			
		||||
		if id, err = strconv.Atoi(historyID); err != nil {
 | 
			
		||||
			return m.ScanHistory{},
 | 
			
		||||
				fmt.Errorf("historyID have to be numeric number: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		db.First(&scanHistory, id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if scanHistory.ID == 0 {
 | 
			
		||||
		return m.ScanHistory{}, fmt.Errorf("No scanHistory records")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//  results := []m.ScanResult{}
 | 
			
		||||
	results := m.ScanResults{}
 | 
			
		||||
	db.Model(&scanHistory).Related(&results, "ScanResults")
 | 
			
		||||
	scanHistory.ScanResults = results
 | 
			
		||||
 | 
			
		||||
	for i, r := range results {
 | 
			
		||||
		//  nw := []m.NWLink{}
 | 
			
		||||
		//  db.Model(&r).Related(&nw, "NWLinks")
 | 
			
		||||
		//  scanHistory.ScanResults[i].NWLinks = nw
 | 
			
		||||
 | 
			
		||||
		di := m.Container{}
 | 
			
		||||
		db.Model(&r).Related(&di, "Container")
 | 
			
		||||
		scanHistory.ScanResults[i].Container = di
 | 
			
		||||
 | 
			
		||||
		knownCves := selectCveInfos(&r, "KnownCves")
 | 
			
		||||
		sort.Sort(m.CveInfos(knownCves))
 | 
			
		||||
		scanHistory.ScanResults[i].KnownCves = knownCves
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Sort(scanHistory.ScanResults)
 | 
			
		||||
	return scanHistory, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func selectCveInfos(result *m.ScanResult, fieldName string) []m.CveInfo {
 | 
			
		||||
	cveInfos := []m.CveInfo{}
 | 
			
		||||
	db.Model(&result).Related(&cveInfos, fieldName)
 | 
			
		||||
 | 
			
		||||
	for i, cveInfo := range cveInfos {
 | 
			
		||||
		cveDetail := cve.CveDetail{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&cveDetail, "CveDetail")
 | 
			
		||||
		id := cveDetail.CveID
 | 
			
		||||
		filledCveDetail := cvedb.Get(id, db)
 | 
			
		||||
		cveInfos[i].CveDetail = filledCveDetail
 | 
			
		||||
 | 
			
		||||
		packs := []m.PackageInfo{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&packs, "Packages")
 | 
			
		||||
		cveInfos[i].Packages = packs
 | 
			
		||||
 | 
			
		||||
		advisories := []m.DistroAdvisory{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&advisories, "DistroAdvisories")
 | 
			
		||||
		cveInfos[i].DistroAdvisories = advisories
 | 
			
		||||
 | 
			
		||||
		names := []m.CpeName{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&names, "CpeNames")
 | 
			
		||||
		cveInfos[i].CpeNames = names
 | 
			
		||||
	}
 | 
			
		||||
	return cveInfos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SelectScanHistories select latest scan history from DB
 | 
			
		||||
func SelectScanHistories() ([]m.ScanHistory, error) {
 | 
			
		||||
	scanHistories := []m.ScanHistory{}
 | 
			
		||||
	db.Order("scanned_at desc").Find(&scanHistories)
 | 
			
		||||
 | 
			
		||||
	if len(scanHistories) == 0 {
 | 
			
		||||
		return []m.ScanHistory{}, fmt.Errorf("No scanHistory records")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, history := range scanHistories {
 | 
			
		||||
		results := m.ScanResults{}
 | 
			
		||||
		db.Model(&history).Related(&results, "ScanResults")
 | 
			
		||||
		scanHistories[i].ScanResults = results
 | 
			
		||||
 | 
			
		||||
		for j, r := range results {
 | 
			
		||||
			di := m.Container{}
 | 
			
		||||
			db.Model(&r).Related(&di, "Container")
 | 
			
		||||
			scanHistories[i].ScanResults[j].Container = di
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return scanHistories, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								glide.lock
									
									
									
										generated
									
									
									
								
							
							
						
						@@ -1,117 +0,0 @@
 | 
			
		||||
hash: 9683c87b3cf998e7fac1b12c4a94bf2bd18cb5422e9108539811546e703a439a
 | 
			
		||||
updated: 2016-07-12T16:20:45.462913061+09:00
 | 
			
		||||
imports:
 | 
			
		||||
- name: github.com/asaskevich/govalidator
 | 
			
		||||
  version: df81827fdd59d8b4fb93d8910b286ab7a3919520
 | 
			
		||||
- name: github.com/aws/aws-sdk-go
 | 
			
		||||
  version: 90dec2183a5f5458ee79cbaf4b8e9ab910bc81a6
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - aws
 | 
			
		||||
  - aws/credentials
 | 
			
		||||
  - aws/session
 | 
			
		||||
  - service/s3
 | 
			
		||||
  - aws/awserr
 | 
			
		||||
  - aws/client
 | 
			
		||||
  - aws/corehandlers
 | 
			
		||||
  - aws/defaults
 | 
			
		||||
  - aws/request
 | 
			
		||||
  - private/endpoints
 | 
			
		||||
  - aws/awsutil
 | 
			
		||||
  - aws/client/metadata
 | 
			
		||||
  - aws/signer/v4
 | 
			
		||||
  - private/protocol
 | 
			
		||||
  - private/protocol/restxml
 | 
			
		||||
  - private/waiter
 | 
			
		||||
  - aws/credentials/ec2rolecreds
 | 
			
		||||
  - aws/ec2metadata
 | 
			
		||||
  - private/protocol/rest
 | 
			
		||||
  - private/protocol/query
 | 
			
		||||
  - private/protocol/xml/xmlutil
 | 
			
		||||
  - private/protocol/query/queryutil
 | 
			
		||||
- name: github.com/Azure/azure-sdk-for-go
 | 
			
		||||
  version: 58a13e378daf3b06e65925397185684b16321111
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - storage
 | 
			
		||||
- name: github.com/BurntSushi/toml
 | 
			
		||||
  version: ffaa107fbd880f6d18cd6fec9b511668dcad8639
 | 
			
		||||
- name: github.com/cenkalti/backoff
 | 
			
		||||
  version: cdf48bbc1eb78d1349cbda326a4a037f7ba565c6
 | 
			
		||||
- name: github.com/cheggaaa/pb
 | 
			
		||||
  version: 04b234c80d661c663dbcebd52fc7218fdacc6d0c
 | 
			
		||||
- name: github.com/go-ini/ini
 | 
			
		||||
  version: cf53f9204df4fbdd7ec4164b57fa6184ba168292
 | 
			
		||||
- name: github.com/google/subcommands
 | 
			
		||||
  version: 1c7173745a6001f67d8d96ab4e178284c77f7759
 | 
			
		||||
- name: github.com/gosuri/uitable
 | 
			
		||||
  version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - util/strutil
 | 
			
		||||
  - util/wordwrap
 | 
			
		||||
- name: github.com/howeyc/gopass
 | 
			
		||||
  version: 66487b23f2880ba32e185121d2cd51a338ea069a
 | 
			
		||||
- name: github.com/jinzhu/gorm
 | 
			
		||||
  version: 613c0655691abb7691b70c5fda80a716d9e20b1b
 | 
			
		||||
- name: github.com/jinzhu/inflection
 | 
			
		||||
  version: 8f4d3a0d04ce0b7c0cf3126fb98524246d00d102
 | 
			
		||||
- name: github.com/jmespath/go-jmespath
 | 
			
		||||
  version: 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74
 | 
			
		||||
- name: github.com/jroimartin/gocui
 | 
			
		||||
  version: 2dcda558bf18ec07c7065bf1eaf071b5305f7c0c
 | 
			
		||||
- name: github.com/k0kubun/pp
 | 
			
		||||
  version: f5dce6ed0ccf6c350f1679964ff6b61f3d6d2033
 | 
			
		||||
- name: github.com/kotakanbe/go-cve-dictionary
 | 
			
		||||
  version: 1a336b8ac785badfe89a175ee926d39574901232
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - config
 | 
			
		||||
  - db
 | 
			
		||||
  - models
 | 
			
		||||
  - log
 | 
			
		||||
  - jvn
 | 
			
		||||
  - nvd
 | 
			
		||||
- name: github.com/kotakanbe/go-pingscanner
 | 
			
		||||
  version: 58e188a3e4f6ab1a6371e33421e4502e26fa1e80
 | 
			
		||||
- name: github.com/kotakanbe/logrus-prefixed-formatter
 | 
			
		||||
  version: f4f7d41649cf1e75e736884da8d05324aa76ea25
 | 
			
		||||
- name: github.com/mattn/go-colorable
 | 
			
		||||
  version: 9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59
 | 
			
		||||
- name: github.com/mattn/go-isatty
 | 
			
		||||
  version: 56b76bdf51f7708750eac80fa38b952bb9f32639
 | 
			
		||||
- name: github.com/mattn/go-runewidth
 | 
			
		||||
  version: d6bea18f789704b5f83375793155289da36a3c7f
 | 
			
		||||
- name: github.com/mattn/go-sqlite3
 | 
			
		||||
  version: 38ee283dabf11c9cbdb968eebd79b1fa7acbabe6
 | 
			
		||||
- name: github.com/mgutz/ansi
 | 
			
		||||
  version: c286dcecd19ff979eeb73ea444e479b903f2cfcb
 | 
			
		||||
- name: github.com/moul/http2curl
 | 
			
		||||
  version: b1479103caacaa39319f75e7f57fc545287fca0d
 | 
			
		||||
- name: github.com/nsf/termbox-go
 | 
			
		||||
  version: c45773466a30b680355d6494cc8826113c93cd0f
 | 
			
		||||
- name: github.com/parnurzeal/gorequest
 | 
			
		||||
  version: 6e8ad4ebdee4bec2934ed5afaaa1c7b877832a17
 | 
			
		||||
- name: github.com/rifflock/lfshook
 | 
			
		||||
  version: 05a24e24fa8d3a2eca8c2baf23aa2d5a2c51490c
 | 
			
		||||
- name: github.com/Sirupsen/logrus
 | 
			
		||||
  version: f3cfb454f4c209e6668c95216c4744b8fddb2356
 | 
			
		||||
- name: golang.org/x/crypto
 | 
			
		||||
  version: c2f4947f41766b144bb09066e919466da5eddeae
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - ssh
 | 
			
		||||
  - ssh/agent
 | 
			
		||||
  - ssh/terminal
 | 
			
		||||
  - curve25519
 | 
			
		||||
  - ed25519
 | 
			
		||||
  - ed25519/internal/edwards25519
 | 
			
		||||
- name: golang.org/x/net
 | 
			
		||||
  version: f841c39de738b1d0df95b5a7187744f0e03d8112
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - context
 | 
			
		||||
  - publicsuffix
 | 
			
		||||
- name: golang.org/x/sys
 | 
			
		||||
  version: a408501be4d17ee978c04a618e7a1b22af058c0e
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - unix
 | 
			
		||||
- name: gopkg.in/alexcesaro/quotedprintable.v3
 | 
			
		||||
  version: 2caba252f4dc53eaf6b553000885530023f54623
 | 
			
		||||
- name: gopkg.in/gomail.v2
 | 
			
		||||
  version: 81ebce5c23dfd25c6c67194b37d3dd3f338c98b1
 | 
			
		||||
devImports: []
 | 
			
		||||
							
								
								
									
										39
									
								
								glide.yaml
									
									
									
									
									
								
							
							
						
						@@ -1,39 +0,0 @@
 | 
			
		||||
package: github.com/future-architect/vuls
 | 
			
		||||
import:
 | 
			
		||||
- package: github.com/Azure/azure-sdk-for-go
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - storage
 | 
			
		||||
- package: github.com/BurntSushi/toml
 | 
			
		||||
- package: github.com/Sirupsen/logrus
 | 
			
		||||
- package: github.com/asaskevich/govalidator
 | 
			
		||||
- package: github.com/aws/aws-sdk-go
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - aws
 | 
			
		||||
  - aws/credentials
 | 
			
		||||
  - aws/session
 | 
			
		||||
  - service/s3
 | 
			
		||||
- package: github.com/cenkalti/backoff
 | 
			
		||||
- package: github.com/google/subcommands
 | 
			
		||||
- package: github.com/gosuri/uitable
 | 
			
		||||
- package: github.com/howeyc/gopass
 | 
			
		||||
- package: github.com/jinzhu/gorm
 | 
			
		||||
- package: github.com/jroimartin/gocui
 | 
			
		||||
- package: github.com/k0kubun/pp
 | 
			
		||||
- package: github.com/kotakanbe/go-cve-dictionary
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - config
 | 
			
		||||
  - db
 | 
			
		||||
  - models
 | 
			
		||||
- package: github.com/kotakanbe/go-pingscanner
 | 
			
		||||
- package: github.com/kotakanbe/logrus-prefixed-formatter
 | 
			
		||||
- package: github.com/mattn/go-sqlite3
 | 
			
		||||
- package: github.com/parnurzeal/gorequest
 | 
			
		||||
- package: github.com/rifflock/lfshook
 | 
			
		||||
- package: golang.org/x/crypto
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - ssh
 | 
			
		||||
  - ssh/agent
 | 
			
		||||
- package: golang.org/x/net
 | 
			
		||||
  subpackages:
 | 
			
		||||
  - context
 | 
			
		||||
- package: gopkg.in/gomail.v2
 | 
			
		||||
							
								
								
									
										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  | 
| 
		 Before Width: | Height: | Size: 77 KiB  | 
| 
		 Before Width: | Height: | Size: 36 KiB  | 
@@ -1,446 +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.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="0.0"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="83.482421875" x="92.2587890625" y="18.93359375">Detect the OS<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n1">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.decision">
 | 
			
		||||
          <y:Geometry height="40.0" width="80.0" x="403.6849206349206" y="206.44247787610618"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="38.0" y="18.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n2">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="90.44247787610618" width="268.0" x="309.6849206349206" y="86.0"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="60.53125" modelName="custom" textColor="#000000" visible="true" width="170.763671875" x="48.61816406250006" y="14.95561393805309">Get installed packages
 | 
			
		||||
Debian/Ubuntu: dpkg-query
 | 
			
		||||
Amazon/RHEL/CentOS: rpm
 | 
			
		||||
FreeBSD: pkg<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n3">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="10.0" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="260.83984375" x="3.580078125" y="11.8671875">Get upgradable packages
 | 
			
		||||
Debian/Ubuntu: apt-get upgrade --dry-run<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n4">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.loopLimit">
 | 
			
		||||
          <y:Geometry height="51.10998735777497" width="137.19216182048035" x="75.40391908975982" y="376.28592169721867"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="131.751953125" x="2.7201043477401754" y="9.422181178887513">foreach 
 | 
			
		||||
upgradable  packages<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="5.551115123125783E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n5">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="10.0" y="459.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="213.619140625" x="27.1904296875" y="11.8671875">Parse changelog and get  CVE IDs
 | 
			
		||||
Debian/Ubuntu: aptitude changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n6">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.loopLimitEnd">
 | 
			
		||||
          <y:Geometry height="50.0" width="137.0" x="75.5" y="545.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="55.24609375" x="40.876953125" y="15.93359375">end loop<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n7">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="625.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="194.904296875" x="36.5478515625" y="18.93359375">Select the CVE detail information<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n8">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" modelName="custom" textColor="#000000" visible="true" width="232.744140625" x="17.6279296875" y="4.80078125">Get CVE IDs by using package manager
 | 
			
		||||
Amazon/RHEL: yum plugin security
 | 
			
		||||
FreeBSD: pkg audit<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n9">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
          <y:Geometry height="64.1719342604298" width="111.96965865992411" x="687.3850119398792" y="807.0697396491782"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="48.56640625" x="31.701626204962054" y="23.019560880214726">Vuls DB<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="-8.881784197001252E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n10">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
          <y:Geometry height="65.22882427307195" width="136.83944374209864" x="411.5802781289507" y="687.385587863464"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="117.970703125" x="9.434370308549205" y="23.548005886535975">CVE DB (NVD / JVN)<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-8.326672684688674E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n11">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="716.4553275126422"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="126.396484375" x="70.8017578125" y="11.8671875">Insert results into DB
 | 
			
		||||
Reporting<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n12">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="271.369140625" x="-1.6845703124999432" y="11.8671875">Get all changelogs by using package manager
 | 
			
		||||
CentOS: yum update --changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n13">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="373.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="205.52734375" x="31.236328125000057" y="18.93359375">Parse changelogs and get CVE IDs <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n2" target="n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="45.22123893805309" tx="0.0" ty="-20.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e1" source="n1" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="-40.0" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="144.0" y="226.44247787610618"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="46.697265625" x="-56.79057374984495" y="-34.26562148912808">Debian
 | 
			
		||||
Ubuntu<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="right" ratio="0.02215389573439544" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e2" source="n3" target="n4">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-25.554993678887485"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e3" source="n4" target="n5">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="25.554993678887485" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e4" source="n5" target="n6">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-25.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e5" source="n6" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="68.5" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="570.8409153761062"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e6" source="n1" target="n8">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="40.0" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="226.44247787610618"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="51.806640625" x="10.125014629061297" y="-48.39843398912805">Amazon
 | 
			
		||||
RHEL
 | 
			
		||||
FreeBSD<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="left" ratio="0.022401276994204813" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e7" source="n8" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e8" source="n0" target="n2">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-45.22123893805309"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e9" source="n7" target="n11">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e10" source="n7" target="n10">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="-134.01566143419018" sy="6.159084623893818" tx="0.0" ty="-29.333162136535975">
 | 
			
		||||
            <y:Point x="480.0" y="660.0"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e11" source="n11" target="n9">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.86721713021484"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e12" source="n1" target="n12">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="20.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="46.708984375" x="-53.35447755843876" y="11.632816010871807">CentOS<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e13" source="n12" target="n13">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e14" source="n13" target="n7">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="134.00000000000006" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="401.8409153761062"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
  </graph>
 | 
			
		||||
  <data key="d7">
 | 
			
		||||
    <y:Resources/>
 | 
			
		||||
  </data>
 | 
			
		||||
</graphml>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 85 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  | 
| 
		 Before Width: | Height: | Size: 19 KiB  | 
| 
		 Before Width: | Height: | Size: 8.8 KiB  | 
| 
		 Before Width: | Height: | Size: 56 KiB  | 
							
								
								
									
										56
									
								
								main.go
									
									
									
									
									
								
							
							
						
						@@ -1,56 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/commands"
 | 
			
		||||
	"github.com/future-architect/vuls/version"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
 | 
			
		||||
	_ "github.com/mattn/go-sqlite3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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")
 | 
			
		||||
	subcommands.Register(&commands.HistoryCmd{}, "history")
 | 
			
		||||
	subcommands.Register(&commands.ConfigtestCmd{}, "configtest")
 | 
			
		||||
 | 
			
		||||
	var v = flag.Bool("v", false, "Show version")
 | 
			
		||||
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	if *v {
 | 
			
		||||
		fmt.Printf("%s %s\n", version.Name, version.Version)
 | 
			
		||||
		os.Exit(int(subcommands.ExitSuccess))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	os.Exit(int(subcommands.Execute(ctx)))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										337
									
								
								models/models.go
									
									
									
									
									
								
							
							
						
						@@ -1,337 +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 ScanResults
 | 
			
		||||
	ScannedAt   time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResults is slice of ScanResult.
 | 
			
		||||
type ScanResults []ScanResult
 | 
			
		||||
 | 
			
		||||
// Len implement Sort Interface
 | 
			
		||||
func (s ScanResults) Len() int {
 | 
			
		||||
	return len(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Swap implement Sort Interface
 | 
			
		||||
func (s ScanResults) Swap(i, j int) {
 | 
			
		||||
	s[i], s[j] = s[j], s[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Less implement Sort Interface
 | 
			
		||||
func (s ScanResults) Less(i, j int) bool {
 | 
			
		||||
	if s[i].ServerName == s[j].ServerName {
 | 
			
		||||
		return s[i].Container.ContainerID < s[i].Container.ContainerID
 | 
			
		||||
	}
 | 
			
		||||
	return s[i].ServerName < s[j].ServerName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver is filter function.
 | 
			
		||||
func (s ScanResults) FilterByCvssOver() (filtered ScanResults) {
 | 
			
		||||
	for _, result := range s {
 | 
			
		||||
		cveInfos := []CveInfo{}
 | 
			
		||||
		for _, cveInfo := range result.KnownCves {
 | 
			
		||||
			if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
 | 
			
		||||
				cveInfos = append(cveInfos, cveInfo)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		result.KnownCves = cveInfos
 | 
			
		||||
		filtered = append(filtered, result)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResult has the result of scanned CVE information.
 | 
			
		||||
type ScanResult struct {
 | 
			
		||||
	gorm.Model    `json:"-"`
 | 
			
		||||
	ScanHistoryID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	ServerName string // TOML Section key
 | 
			
		||||
	//  Hostname    string
 | 
			
		||||
	Family  string
 | 
			
		||||
	Release string
 | 
			
		||||
 | 
			
		||||
	Container Container
 | 
			
		||||
 | 
			
		||||
	Platform Platform
 | 
			
		||||
 | 
			
		||||
	//  Fqdn        string
 | 
			
		||||
	//  NWLinks     []NWLink
 | 
			
		||||
	KnownCves   []CveInfo
 | 
			
		||||
	UnknownCves []CveInfo
 | 
			
		||||
 | 
			
		||||
	Optional [][]interface{} `gorm:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfo returns server name one line
 | 
			
		||||
func (r ScanResult) ServerInfo() string {
 | 
			
		||||
	hostinfo := ""
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"%s (%s%s)",
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
		)
 | 
			
		||||
	} else {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"%s / %s (%s%s) on %s",
 | 
			
		||||
			r.Container.Name,
 | 
			
		||||
			r.Container.ContainerID,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	return hostinfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfoTui returns server infromation for TUI sidebar
 | 
			
		||||
func (r ScanResult) ServerInfoTui() string {
 | 
			
		||||
	hostinfo := ""
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"%s (%s%s)",
 | 
			
		||||
			r.ServerName,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
		)
 | 
			
		||||
	} else {
 | 
			
		||||
		hostinfo = fmt.Sprintf(
 | 
			
		||||
			"|-- %s (%s%s)",
 | 
			
		||||
			r.Container.Name,
 | 
			
		||||
			r.Family,
 | 
			
		||||
			r.Release,
 | 
			
		||||
			//  r.Container.ContainerID,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	return hostinfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveSummary summarize the number of CVEs group by CVSSv2 Severity
 | 
			
		||||
func (r ScanResult) CveSummary() string {
 | 
			
		||||
	var high, middle, low, unknown int
 | 
			
		||||
	cves := append(r.KnownCves, r.UnknownCves...)
 | 
			
		||||
	for _, cveInfo := range cves {
 | 
			
		||||
		score := cveInfo.CveDetail.CvssScore(config.Conf.Lang)
 | 
			
		||||
		switch {
 | 
			
		||||
		case 7.0 < score:
 | 
			
		||||
			high++
 | 
			
		||||
		case 4.0 < score:
 | 
			
		||||
			middle++
 | 
			
		||||
		case 0 < score:
 | 
			
		||||
			low++
 | 
			
		||||
		default:
 | 
			
		||||
			unknown++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d)",
 | 
			
		||||
			high+middle+low, high, middle, low)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d ?:%d)",
 | 
			
		||||
		high+middle+low+unknown, high, middle, low, unknown)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NWLink has network link information.
 | 
			
		||||
type NWLink struct {
 | 
			
		||||
	gorm.Model   `json:"-"`
 | 
			
		||||
	ScanResultID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	IPAddress string
 | 
			
		||||
	Netmask   string
 | 
			
		||||
	DevName   string
 | 
			
		||||
	LinkState string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveInfos is for sorting
 | 
			
		||||
type CveInfos []CveInfo
 | 
			
		||||
 | 
			
		||||
func (c CveInfos) Len() int {
 | 
			
		||||
	return len(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c CveInfos) Swap(i, j int) {
 | 
			
		||||
	c[i], c[j] = c[j], c[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c CveInfos) Less(i, j int) bool {
 | 
			
		||||
	lang := config.Conf.Lang
 | 
			
		||||
	if c[i].CveDetail.CvssScore(lang) == c[j].CveDetail.CvssScore(lang) {
 | 
			
		||||
		return c[i].CveDetail.CveID < c[j].CveDetail.CveID
 | 
			
		||||
	}
 | 
			
		||||
	return c[i].CveDetail.CvssScore(lang) > c[j].CveDetail.CvssScore(lang)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveInfo has Cve Information.
 | 
			
		||||
type CveInfo struct {
 | 
			
		||||
	gorm.Model   `json:"-"`
 | 
			
		||||
	ScanResultID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	CveDetail        cve.CveDetail
 | 
			
		||||
	Packages         []PackageInfo
 | 
			
		||||
	DistroAdvisories []DistroAdvisory
 | 
			
		||||
	CpeNames         []CpeName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CpeName has CPE name
 | 
			
		||||
type CpeName struct {
 | 
			
		||||
	gorm.Model `json:"-"`
 | 
			
		||||
	CveInfoID  uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	Name string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageInfoList is slice of PackageInfo
 | 
			
		||||
type PackageInfoList []PackageInfo
 | 
			
		||||
 | 
			
		||||
// Exists returns true if exists the name
 | 
			
		||||
func (ps PackageInfoList) Exists(name string) bool {
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if p.Name == name {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UniqByName be uniq by name.
 | 
			
		||||
func (ps PackageInfoList) UniqByName() (distincted PackageInfoList) {
 | 
			
		||||
	set := make(map[string]PackageInfo)
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		set[p.Name] = p
 | 
			
		||||
	}
 | 
			
		||||
	//sort by key
 | 
			
		||||
	keys := []string{}
 | 
			
		||||
	for key := range set {
 | 
			
		||||
		keys = append(keys, key)
 | 
			
		||||
	}
 | 
			
		||||
	sort.Strings(keys)
 | 
			
		||||
	for _, key := range keys {
 | 
			
		||||
		distincted = append(distincted, set[key])
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindByName search PackageInfo by name
 | 
			
		||||
func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found bool) {
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if p.Name == name {
 | 
			
		||||
			return p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return PackageInfo{}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Find search PackageInfo by name-version-release
 | 
			
		||||
//  func (ps PackageInfoList) find(nameVersionRelease string) (PackageInfo, bool) {
 | 
			
		||||
//      for _, p := range ps {
 | 
			
		||||
//          joined := p.Name
 | 
			
		||||
//          if 0 < len(p.Version) {
 | 
			
		||||
//              joined = fmt.Sprintf("%s-%s", joined, p.Version)
 | 
			
		||||
//          }
 | 
			
		||||
//          if 0 < len(p.Release) {
 | 
			
		||||
//              joined = fmt.Sprintf("%s-%s", joined, p.Release)
 | 
			
		||||
//          }
 | 
			
		||||
//          if joined == nameVersionRelease {
 | 
			
		||||
//              return p, true
 | 
			
		||||
//          }
 | 
			
		||||
//      }
 | 
			
		||||
//      return PackageInfo{}, false
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// PackageInfo has installed packages.
 | 
			
		||||
type PackageInfo struct {
 | 
			
		||||
	gorm.Model `json:"-"`
 | 
			
		||||
	CveInfoID  uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	Name    string
 | 
			
		||||
	Version string
 | 
			
		||||
	Release string
 | 
			
		||||
 | 
			
		||||
	NewVersion string
 | 
			
		||||
	NewRelease string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToStringCurrentVersion returns package name-version-release
 | 
			
		||||
func (p PackageInfo) ToStringCurrentVersion() string {
 | 
			
		||||
	str := p.Name
 | 
			
		||||
	if 0 < len(p.Version) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.Version)
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(p.Release) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.Release)
 | 
			
		||||
	}
 | 
			
		||||
	return str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToStringNewVersion returns package name-version-release
 | 
			
		||||
func (p PackageInfo) ToStringNewVersion() string {
 | 
			
		||||
	str := p.Name
 | 
			
		||||
	if 0 < len(p.NewVersion) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.NewVersion)
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(p.NewRelease) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.NewRelease)
 | 
			
		||||
	}
 | 
			
		||||
	return str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
 | 
			
		||||
type DistroAdvisory struct {
 | 
			
		||||
	gorm.Model `json:"-"`
 | 
			
		||||
	CveInfoID  uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	AdvisoryID string
 | 
			
		||||
	Severity   string
 | 
			
		||||
	Issued     time.Time
 | 
			
		||||
	Updated    time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Container has Container information
 | 
			
		||||
type Container struct {
 | 
			
		||||
	gorm.Model   `json:"-"`
 | 
			
		||||
	ScanResultID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	ContainerID string
 | 
			
		||||
	Name        string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Platform has platform information
 | 
			
		||||
type Platform struct {
 | 
			
		||||
	gorm.Model   `json:"-"`
 | 
			
		||||
	ScanResultID uint `json:"-"`
 | 
			
		||||
 | 
			
		||||
	Name       string // aws or azure or gcp or other...
 | 
			
		||||
	InstanceID string
 | 
			
		||||
}
 | 
			
		||||
@@ -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,140 +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"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Azure/azure-sdk-for-go/storage"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AzureBlobWriter writes results to AzureBlob
 | 
			
		||||
type AzureBlobWriter struct{}
 | 
			
		||||
 | 
			
		||||
// CheckIfAzureContainerExists check the existence of Azure storage container
 | 
			
		||||
func CheckIfAzureContainerExists() error {
 | 
			
		||||
	cli, err := getBlobClient()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	ok, err := cli.ContainerExists(c.Conf.AzureContainer)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf("Container not found. Container: %s", c.Conf.AzureContainer)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getBlobClient() (storage.BlobStorageClient, error) {
 | 
			
		||||
	api, err := storage.NewBasicClient(c.Conf.AzureAccount, c.Conf.AzureKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return storage.BlobStorageClient{}, err
 | 
			
		||||
	}
 | 
			
		||||
	return api.GetBlobService(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write results to Azure Blob storage
 | 
			
		||||
func (w AzureBlobWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
	reqChan := make(chan models.ScanResult, len(scanResults))
 | 
			
		||||
	resChan := make(chan bool)
 | 
			
		||||
	errChan := make(chan error, len(scanResults))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(10 * 60 * time.Second)
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, r := range scanResults {
 | 
			
		||||
			reqChan <- r
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	for range scanResults {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case sresult := <-reqChan:
 | 
			
		||||
				func(r models.ScanResult) {
 | 
			
		||||
					err := w.upload(r)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						errChan <- err
 | 
			
		||||
					}
 | 
			
		||||
					resChan <- true
 | 
			
		||||
				}(sresult)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
	for i := 0; i < len(scanResults); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-resChan:
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			errs = append(errs, fmt.Errorf("Timeout while uploading to azure Blob"))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		return fmt.Errorf("Failed to upload json to Azure Blob: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w AzureBlobWriter) upload(res models.ScanResult) (err error) {
 | 
			
		||||
	cli, err := getBlobClient()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	timestr := time.Now().Format("20060102_1504")
 | 
			
		||||
	name := ""
 | 
			
		||||
	if res.Container.ContainerID == "" {
 | 
			
		||||
		name = fmt.Sprintf("%s/%s.json", timestr, res.ServerName)
 | 
			
		||||
	} else {
 | 
			
		||||
		name = fmt.Sprintf("%s/%s_%s.json", timestr, res.ServerName, res.Container.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	jsonBytes, err := json.Marshal(res)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = cli.CreateBlockBlobFromReader(
 | 
			
		||||
		c.Conf.AzureContainer,
 | 
			
		||||
		name,
 | 
			
		||||
		uint64(len(jsonBytes)),
 | 
			
		||||
		bytes.NewReader(jsonBytes),
 | 
			
		||||
		map[string]string{},
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return fmt.Errorf("%s/%s, %s",
 | 
			
		||||
			c.Conf.AzureContainer, name, err)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
@@ -1,62 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// JSONWriter writes results to file.
 | 
			
		||||
type JSONWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
 | 
			
		||||
	path, err := ensureResultDir()
 | 
			
		||||
 | 
			
		||||
	var jsonBytes []byte
 | 
			
		||||
	if jsonBytes, err = json.Marshal(scanResults); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	all := filepath.Join(path, "all.json")
 | 
			
		||||
	if err := ioutil.WriteFile(all, jsonBytes, 0644); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to write JSON. path: %s, err: %s", all, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range scanResults {
 | 
			
		||||
		jsonPath := ""
 | 
			
		||||
		if r.Container.ContainerID == "" {
 | 
			
		||||
			jsonPath = filepath.Join(path, fmt.Sprintf("%s.json", r.ServerName))
 | 
			
		||||
		} else {
 | 
			
		||||
			jsonPath = filepath.Join(path,
 | 
			
		||||
				fmt.Sprintf("%s_%s.json", r.ServerName, r.Container.Name))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if jsonBytes, err = json.Marshal(r); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		if err := ioutil.WriteFile(jsonPath, jsonBytes, 0644); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to write JSON. path: %s, err: %s", jsonPath, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	formatter "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LogrusWriter write to logfile
 | 
			
		||||
type LogrusWriter struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w LogrusWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
	path := "/var/log/vuls/report.log"
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		path = filepath.Join(os.Getenv("APPDATA"), "vuls", "report.log")
 | 
			
		||||
	}
 | 
			
		||||
	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	log := logrus.New()
 | 
			
		||||
	log.Formatter = &formatter.TextFormatter{}
 | 
			
		||||
	log.Out = f
 | 
			
		||||
	log.Level = logrus.InfoLevel
 | 
			
		||||
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		text, err := toPlainText(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		log.Infof(text)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,70 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"gopkg.in/gomail.v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MailWriter send mail
 | 
			
		||||
type MailWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w MailWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		m := gomail.NewMessage()
 | 
			
		||||
		m.SetHeader("From", conf.Mail.From)
 | 
			
		||||
		m.SetHeader("To", conf.Mail.To...)
 | 
			
		||||
		m.SetHeader("Cc", conf.Mail.Cc...)
 | 
			
		||||
 | 
			
		||||
		subject := fmt.Sprintf("%s%s %s",
 | 
			
		||||
			conf.Mail.SubjectPrefix,
 | 
			
		||||
			s.ServerInfo(),
 | 
			
		||||
			s.CveSummary(),
 | 
			
		||||
		)
 | 
			
		||||
		m.SetHeader("Subject", subject)
 | 
			
		||||
 | 
			
		||||
		var body string
 | 
			
		||||
		if body, err = toPlainText(s); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		m.SetBody("text/plain", body)
 | 
			
		||||
		port, _ := strconv.Atoi(conf.Mail.SMTPPort)
 | 
			
		||||
		d := gomail.NewPlainDialer(
 | 
			
		||||
			conf.Mail.SMTPAddr,
 | 
			
		||||
			port,
 | 
			
		||||
			conf.Mail.User,
 | 
			
		||||
			conf.Mail.Password,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		d.TLSConfig = &tls.Config{
 | 
			
		||||
			InsecureSkipVerify: true,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := d.DialAndSend(m); err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										112
									
								
								report/s3.go
									
									
									
									
									
								
							
							
						
						@@ -1,112 +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"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/credentials"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/session"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/service/s3"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CheckIfBucketExists check the existence of S3 bucket
 | 
			
		||||
func CheckIfBucketExists() error {
 | 
			
		||||
	svc := getS3()
 | 
			
		||||
	result, err := svc.ListBuckets(&s3.ListBucketsInput{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"Failed to list buckets. err: %s, profile: %s, region: %s",
 | 
			
		||||
			err, c.Conf.AwsProfile, c.Conf.AwsRegion)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	found := false
 | 
			
		||||
	for _, bucket := range result.Buckets {
 | 
			
		||||
		if *bucket.Name == c.Conf.S3Bucket {
 | 
			
		||||
			found = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"Failed to find the buckets. profile: %s, region: %s, bukdet: %s",
 | 
			
		||||
			c.Conf.AwsProfile, c.Conf.AwsRegion, c.Conf.S3Bucket)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// S3Writer writes results to S3
 | 
			
		||||
type S3Writer struct{}
 | 
			
		||||
 | 
			
		||||
func getS3() *s3.S3 {
 | 
			
		||||
	return s3.New(session.New(&aws.Config{
 | 
			
		||||
		Region:      aws.String(c.Conf.AwsRegion),
 | 
			
		||||
		Credentials: credentials.NewSharedCredentials("", c.Conf.AwsProfile),
 | 
			
		||||
	}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write results to S3
 | 
			
		||||
func (w S3Writer) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
 | 
			
		||||
	var jsonBytes []byte
 | 
			
		||||
	if jsonBytes, err = json.Marshal(scanResults); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// http://docs.aws.amazon.com/sdk-for-go/latest/v1/developerguide/common-examples.title.html
 | 
			
		||||
	svc := getS3()
 | 
			
		||||
	timestr := time.Now().Format("20060102_1504")
 | 
			
		||||
	key := fmt.Sprintf("%s/%s", timestr, "all.json")
 | 
			
		||||
	_, err = svc.PutObject(&s3.PutObjectInput{
 | 
			
		||||
		Bucket: &c.Conf.S3Bucket,
 | 
			
		||||
		Key:    &key,
 | 
			
		||||
		Body:   bytes.NewReader(jsonBytes),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to upload data to %s/%s, %s", c.Conf.S3Bucket, key, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range scanResults {
 | 
			
		||||
		key := ""
 | 
			
		||||
		if r.Container.ContainerID == "" {
 | 
			
		||||
			key = fmt.Sprintf("%s/%s.json", timestr, r.ServerName)
 | 
			
		||||
		} else {
 | 
			
		||||
			key = fmt.Sprintf("%s/%s_%s.json", timestr, r.ServerName, r.Container.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if jsonBytes, err = json.Marshal(r); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		_, err = svc.PutObject(&s3.PutObjectInput{
 | 
			
		||||
			Bucket: &c.Conf.S3Bucket,
 | 
			
		||||
			Key:    &key,
 | 
			
		||||
			Body:   bytes.NewReader(jsonBytes),
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to upload data to %s/%s, %s", c.Conf.S3Bucket, key, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										234
									
								
								report/slack.go
									
									
									
									
									
								
							
							
						
						@@ -1,234 +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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, serverInfo, r.CveSummary())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
 | 
			
		||||
 | 
			
		||||
	cves := scanResult.KnownCves
 | 
			
		||||
	if !config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		cves = append(cves, scanResult.UnknownCves...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, cveInfo := range cves {
 | 
			
		||||
		cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
 | 
			
		||||
		curentPackages := []string{}
 | 
			
		||||
		for _, p := range cveInfo.Packages {
 | 
			
		||||
			curentPackages = append(curentPackages, p.ToStringCurrentVersion())
 | 
			
		||||
		}
 | 
			
		||||
		for _, cpename := range cveInfo.CpeNames {
 | 
			
		||||
			curentPackages = append(curentPackages, cpename.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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" &&
 | 
			
		||||
		0 < cveInfo.CveDetail.Jvn.CvssScore():
 | 
			
		||||
 | 
			
		||||
		jvn := cveInfo.CveDetail.Jvn
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
 | 
			
		||||
			cveInfo.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
			jvn.CvssSeverity(),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, jvn.CvssVector()),
 | 
			
		||||
			jvn.CvssVector(),
 | 
			
		||||
			jvn.CveTitle(),
 | 
			
		||||
			linkText,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
	case 0 < cveInfo.CveDetail.CvssScore("en"):
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
 | 
			
		||||
			cveInfo.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
			nvd.CvssSeverity(),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, nvd.CvssVector()),
 | 
			
		||||
			nvd.CvssVector(),
 | 
			
		||||
			nvd.CveSummary(),
 | 
			
		||||
			linkText,
 | 
			
		||||
		)
 | 
			
		||||
	default:
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		return fmt.Sprintf("?\n%s\n%s", nvd.CveSummary(), linkText)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// StdoutWriter write to stdout
 | 
			
		||||
type StdoutWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w StdoutWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		text, err := toPlainText(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println(text)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,63 +0,0 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TextFileWriter writes results to file.
 | 
			
		||||
type TextFileWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w TextFileWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
 | 
			
		||||
	path, err := ensureResultDir()
 | 
			
		||||
 | 
			
		||||
	all := []string{}
 | 
			
		||||
	for _, r := range scanResults {
 | 
			
		||||
		textFilePath := ""
 | 
			
		||||
		if r.Container.ContainerID == "" {
 | 
			
		||||
			textFilePath = filepath.Join(path, fmt.Sprintf("%s.txt", r.ServerName))
 | 
			
		||||
		} else {
 | 
			
		||||
			textFilePath = filepath.Join(path,
 | 
			
		||||
				fmt.Sprintf("%s_%s.txt", r.ServerName, r.Container.Name))
 | 
			
		||||
		}
 | 
			
		||||
		text, err := toPlainText(r)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		all = append(all, text)
 | 
			
		||||
		b := []byte(text)
 | 
			
		||||
		if err := ioutil.WriteFile(textFilePath, b, 0644); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to write text files. path: %s, err: %s", textFilePath, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	text := strings.Join(all, "\n\n")
 | 
			
		||||
	b := []byte(text)
 | 
			
		||||
	allPath := filepath.Join(path, "all.txt")
 | 
			
		||||
	if err := ioutil.WriteFile(allPath, b, 0644); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to write text files. path: %s, err: %s", allPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||