Compare commits
1003 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 |
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 ./...
|
||||
36
.gitignore
vendored
@@ -1,14 +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
|
||||
*.db
|
||||
tags
|
||||
.gitmodules
|
||||
coverage.out
|
||||
issues/
|
||||
vendor/
|
||||
log/
|
||||
results/
|
||||
*config.toml
|
||||
|
||||
# Vuls
|
||||
vuls
|
||||
!cmd/vuls
|
||||
vuls.db
|
||||
config.json
|
||||
results
|
||||
208
CHANGELOG.md
@@ -1,208 +0,0 @@
|
||||
# Change Log
|
||||
|
||||
## [v0.1.6](https://github.com/future-architect/vuls/tree/v0.1.6) (2016-09-12)
|
||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.5...v0.1.6)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- High speed scan on Ubuntu/Debian [\#172](https://github.com/future-architect/vuls/pull/172) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Support CWE\(Common Weakness Enumeration\) [\#169](https://github.com/future-architect/vuls/pull/169) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Enable to scan without sudo on amazon linux [\#167](https://github.com/future-architect/vuls/pull/167) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Remove deprecated options -use-unattended-upgrades,-use-yum-plugin-security [\#161](https://github.com/future-architect/vuls/pull/161) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- delete sqlite3 [\#152](https://github.com/future-architect/vuls/pull/152) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Failed to setup vuls docker [\#170](https://github.com/future-architect/vuls/issues/170)
|
||||
- yum check-update error occurred when no reboot after kernel updating [\#165](https://github.com/future-architect/vuls/issues/165)
|
||||
- error thrown from 'docker build .' [\#157](https://github.com/future-architect/vuls/issues/157)
|
||||
- CVE-ID is truncated to 4 digits [\#153](https://github.com/future-architect/vuls/issues/153)
|
||||
- 'yum update --changelog' stalled in 'vuls scan'. if ssh user is not 'root'. [\#150](https://github.com/future-architect/vuls/issues/150)
|
||||
- Panic on packet scan [\#131](https://github.com/future-architect/vuls/issues/131)
|
||||
- Update glide.lock \#170 [\#171](https://github.com/future-architect/vuls/pull/171) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix detecting a platform on Azure [\#168](https://github.com/future-architect/vuls/pull/168) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix parse error for yum check-update \#165 [\#166](https://github.com/future-architect/vuls/pull/166) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix bug: Vuls on Docker [\#159](https://github.com/future-architect/vuls/pull/159) ([tjinjin](https://github.com/tjinjin))
|
||||
- Fix CVE-ID is truncated to 4 digits [\#155](https://github.com/future-architect/vuls/pull/155) ([usiusi360](https://github.com/usiusi360))
|
||||
- Fix yum update --changelog stalled when non-root ssh user on CentOS \#150 [\#151](https://github.com/future-architect/vuls/pull/151) ([kotakanbe](https://github.com/kotakanbe))
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Support su for root privilege escalation [\#44](https://github.com/future-architect/vuls/issues/44)
|
||||
- Support FreeBSD [\#34](https://github.com/future-architect/vuls/issues/34)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Change scripts for data fetching from jvn [\#164](https://github.com/future-architect/vuls/pull/164) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix: setup vulsrepo [\#162](https://github.com/future-architect/vuls/pull/162) ([tjinjin](https://github.com/tjinjin))
|
||||
- Fix-docker-vulsrepo-install [\#160](https://github.com/future-architect/vuls/pull/160) ([usiusi360](https://github.com/usiusi360))
|
||||
- Reduce regular expression compilation [\#158](https://github.com/future-architect/vuls/pull/158) ([itchyny](https://github.com/itchyny))
|
||||
- Add testcases for \#153 [\#156](https://github.com/future-architect/vuls/pull/156) ([kotakanbe](https://github.com/kotakanbe))
|
||||
|
||||
## [v0.1.5](https://github.com/future-architect/vuls/tree/v0.1.5) (2016-08-16)
|
||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.4...v0.1.5)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Enable to scan without running go-cve-dictionary as server mode [\#84](https://github.com/future-architect/vuls/issues/84)
|
||||
- Support high-speed scanning for CentOS [\#138](https://github.com/future-architect/vuls/pull/138) ([tai-ga](https://github.com/tai-ga))
|
||||
- Add configtest subcommand. skip un-ssh-able servers. [\#134](https://github.com/future-architect/vuls/pull/134) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Support -report-azure-blob option [\#130](https://github.com/future-architect/vuls/pull/130) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add optional key-values that will be outputted to JSON in config [\#117](https://github.com/future-architect/vuls/pull/117) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Change dir structure [\#115](https://github.com/future-architect/vuls/pull/115) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add some validation of loading config. user, host and port [\#113](https://github.com/future-architect/vuls/pull/113) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Support scanning with external ssh command [\#101](https://github.com/future-architect/vuls/pull/101) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Detect Platform and get instance-id of amazon ec2 [\#95](https://github.com/future-architect/vuls/pull/95) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add -report-s3 option [\#92](https://github.com/future-architect/vuls/pull/92) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Added FreeBSD support. [\#90](https://github.com/future-architect/vuls/pull/90) ([justyntemme](https://github.com/justyntemme))
|
||||
- Add glide files for vendoring [\#89](https://github.com/future-architect/vuls/pull/89) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix README, change -cvedbpath to -cve-dictionary-dbpath \#84 [\#85](https://github.com/future-architect/vuls/pull/85) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add option for it get cve detail from cve.sqlite3. [\#81](https://github.com/future-architect/vuls/pull/81) ([ymd38](https://github.com/ymd38))
|
||||
- Add -report-text option, Fix small bug of report in japanese [\#78](https://github.com/future-architect/vuls/pull/78) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add JSONWriter, Fix CVE sort order of report [\#77](https://github.com/future-architect/vuls/pull/77) ([kotakanbe](https://github.com/kotakanbe))
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Docker: Panic [\#76](https://github.com/future-architect/vuls/issues/76)
|
||||
- Fix apt command to scan correctly when system locale is not english [\#149](https://github.com/future-architect/vuls/pull/149) ([kit494way](https://github.com/kit494way))
|
||||
- Disable -ask-sudo-password for security reasons [\#148](https://github.com/future-architect/vuls/pull/148) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix no tty error while executing with -external-ssh option [\#143](https://github.com/future-architect/vuls/pull/143) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- wrong log packages [\#141](https://github.com/future-architect/vuls/pull/141) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
|
||||
- Fix platform detection. [\#137](https://github.com/future-architect/vuls/pull/137) ([Rompei](https://github.com/Rompei))
|
||||
- Fix nil pointer when scan with -cve-dictionary-dbpath and cpeNames [\#111](https://github.com/future-architect/vuls/pull/111) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Remove vulndb file before pkg audit [\#110](https://github.com/future-architect/vuls/pull/110) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add error handling when unable to connect via ssh. status code: 255 [\#108](https://github.com/future-architect/vuls/pull/108) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Enable to detect vulnerabilities on FreeBSD [\#98](https://github.com/future-architect/vuls/pull/98) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix unknown format err while check-update on RHEL6.5 [\#93](https://github.com/future-architect/vuls/pull/93) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
|
||||
- Fix type of SMTP Port of discovery command's output [\#88](https://github.com/future-architect/vuls/pull/88) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix error msg when go-cve-dictionary is unavailable \#84 [\#86](https://github.com/future-architect/vuls/pull/86) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix error handling to avoid nil pointer err on debian [\#83](https://github.com/future-architect/vuls/pull/83) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix nil pointer while doing apt-cache policy on ubuntu \#76 [\#82](https://github.com/future-architect/vuls/pull/82) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- fix log import url [\#79](https://github.com/future-architect/vuls/pull/79) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
|
||||
- Fix error handling of gorequest [\#75](https://github.com/future-architect/vuls/pull/75) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix freezing forever when no args specified in TUI mode [\#73](https://github.com/future-architect/vuls/pull/73) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- mv version.go version/version.go to run main.go without compile [\#71](https://github.com/future-architect/vuls/pull/71) ([sadayuki-matsuno](https://github.com/sadayuki-matsuno))
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- SSh password authentication failed on FreeBSD [\#99](https://github.com/future-architect/vuls/issues/99)
|
||||
- BUG: -o pipefail is not work on FreeBSD's /bin/sh. because it isn't bash [\#91](https://github.com/future-architect/vuls/issues/91)
|
||||
- Use ~/.ssh/config [\#62](https://github.com/future-architect/vuls/issues/62)
|
||||
- SSH ciphers [\#37](https://github.com/future-architect/vuls/issues/37)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Update README \#138 [\#144](https://github.com/future-architect/vuls/pull/144) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix a typo [\#142](https://github.com/future-architect/vuls/pull/142) ([dtan4](https://github.com/dtan4))
|
||||
- Remove unnecessary step in readme of docker setup [\#140](https://github.com/future-architect/vuls/pull/140) ([mikkame](https://github.com/mikkame))
|
||||
- Update logo [\#139](https://github.com/future-architect/vuls/pull/139) ([chanomaru](https://github.com/chanomaru))
|
||||
- Update README.ja.md to fix wrong tips. [\#135](https://github.com/future-architect/vuls/pull/135) ([a2atsu](https://github.com/a2atsu))
|
||||
- add tips about NVD JVN issue [\#133](https://github.com/future-architect/vuls/pull/133) ([a2atsu](https://github.com/a2atsu))
|
||||
- Fix README wrong links [\#129](https://github.com/future-architect/vuls/pull/129) ([aomoriringo](https://github.com/aomoriringo))
|
||||
- Add logo [\#126](https://github.com/future-architect/vuls/pull/126) ([chanomaru](https://github.com/chanomaru))
|
||||
- Improve setup/docker [\#125](https://github.com/future-architect/vuls/pull/125) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix scan command help [\#124](https://github.com/future-architect/vuls/pull/124) ([aomoriringo](https://github.com/aomoriringo))
|
||||
- added dockernized-vuls with vulsrepo [\#121](https://github.com/future-architect/vuls/pull/121) ([hikachan](https://github.com/hikachan))
|
||||
- Fix detect platform on azure and degital ocean [\#119](https://github.com/future-architect/vuls/pull/119) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Remove json marshall-indent [\#118](https://github.com/future-architect/vuls/pull/118) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Improve Readme.ja [\#116](https://github.com/future-architect/vuls/pull/116) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add architecture diag to README.md [\#114](https://github.com/future-architect/vuls/pull/114) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Rename linux.go to base.go [\#100](https://github.com/future-architect/vuls/pull/100) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Update README.md [\#74](https://github.com/future-architect/vuls/pull/74) ([yoshi-taka](https://github.com/yoshi-taka))
|
||||
- Refactoring debian.go [\#72](https://github.com/future-architect/vuls/pull/72) ([kotakanbe](https://github.com/kotakanbe))
|
||||
|
||||
## [v0.1.4](https://github.com/future-architect/vuls/tree/v0.1.4) (2016-05-24)
|
||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.3...v0.1.4)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Initial fetch from NVD is too heavy \(2.3 GB of memory consumed\) [\#27](https://github.com/future-architect/vuls/issues/27)
|
||||
- Enable to show previous scan result [\#69](https://github.com/future-architect/vuls/pull/69) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add ignore-unscored-cves option [\#68](https://github.com/future-architect/vuls/pull/68) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Support dynamic scanning docker container [\#67](https://github.com/future-architect/vuls/pull/67) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Add version flag [\#65](https://github.com/future-architect/vuls/pull/65) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Update Dockerfile [\#57](https://github.com/future-architect/vuls/pull/57) ([theonlydoo](https://github.com/theonlydoo))
|
||||
- Update run.sh [\#56](https://github.com/future-architect/vuls/pull/56) ([theonlydoo](https://github.com/theonlydoo))
|
||||
- Support Windows [\#33](https://github.com/future-architect/vuls/pull/33) ([mattn](https://github.com/mattn))
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- vuls scan -cvss-over does not work. [\#59](https://github.com/future-architect/vuls/issues/59)
|
||||
- `panic: runtime error: invalid memory address or nil pointer dereference` when scan CentOS5.5 [\#58](https://github.com/future-architect/vuls/issues/58)
|
||||
- It rans out of memory. [\#47](https://github.com/future-architect/vuls/issues/47)
|
||||
- BUG: vuls scan on CentOS with Japanese environment. [\#43](https://github.com/future-architect/vuls/issues/43)
|
||||
- yum --color=never [\#36](https://github.com/future-architect/vuls/issues/36)
|
||||
- Failed to parse yum check-update [\#32](https://github.com/future-architect/vuls/issues/32)
|
||||
- Pointless sudo [\#29](https://github.com/future-architect/vuls/issues/29)
|
||||
- Can't init database in a path having blanks [\#26](https://github.com/future-architect/vuls/issues/26)
|
||||
- Fix pointless sudo in debian.go \#29 [\#66](https://github.com/future-architect/vuls/pull/66) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix error handling of httpGet in cve-client \#58 [\#64](https://github.com/future-architect/vuls/pull/64) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix nil pointer at error handling of cve\_client \#58 [\#63](https://github.com/future-architect/vuls/pull/63) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Set language en\_US. [\#61](https://github.com/future-architect/vuls/pull/61) ([pabroff](https://github.com/pabroff))
|
||||
- Fix -cvss-over flag \#59 [\#60](https://github.com/future-architect/vuls/pull/60) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix scan on Japanese environment. [\#55](https://github.com/future-architect/vuls/pull/55) ([pabroff](https://github.com/pabroff))
|
||||
- Fix a typo: replace Depricated by Deprecated. [\#54](https://github.com/future-architect/vuls/pull/54) ([jody-frankowski](https://github.com/jody-frankowski))
|
||||
- Fix yes no infinite loop while doing yum update --changelog on root@CentOS \#47 [\#50](https://github.com/future-architect/vuls/pull/50) ([pabroff](https://github.com/pabroff))
|
||||
- Fix $servername in output of discover command [\#45](https://github.com/future-architect/vuls/pull/45) ([kotakanbe](https://github.com/kotakanbe))
|
||||
|
||||
## [v0.1.3](https://github.com/future-architect/vuls/tree/v0.1.3) (2016-04-21)
|
||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.2...v0.1.3)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Add sudo support for prepare [\#11](https://github.com/future-architect/vuls/issues/11)
|
||||
- Dockerfile? [\#10](https://github.com/future-architect/vuls/issues/10)
|
||||
- Update README [\#41](https://github.com/future-architect/vuls/pull/41) ([theonlydoo](https://github.com/theonlydoo))
|
||||
- Sparse dockerization [\#38](https://github.com/future-architect/vuls/pull/38) ([theonlydoo](https://github.com/theonlydoo))
|
||||
- No password in config [\#35](https://github.com/future-architect/vuls/pull/35) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fr readme translation [\#23](https://github.com/future-architect/vuls/pull/23) ([novakin](https://github.com/novakin))
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Issues updating CVE database behind https proxy [\#39](https://github.com/future-architect/vuls/issues/39)
|
||||
- Vuls failed to parse yum check-update [\#24](https://github.com/future-architect/vuls/issues/24)
|
||||
- Fix yum to yum --color=never \#36 [\#42](https://github.com/future-architect/vuls/pull/42) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fix parse yum check update [\#40](https://github.com/future-architect/vuls/pull/40) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- fix typo [\#31](https://github.com/future-architect/vuls/pull/31) ([blue119](https://github.com/blue119))
|
||||
- Fix error while parsing yum check-update \#24 [\#30](https://github.com/future-architect/vuls/pull/30) ([kotakanbe](https://github.com/kotakanbe))
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Unable to scan on ubuntu because changelog.ubuntu.com is down... [\#21](https://github.com/future-architect/vuls/issues/21)
|
||||
- err: Not initialize\(d\) yet.. [\#16](https://github.com/future-architect/vuls/issues/16)
|
||||
- Errors when using fish shell [\#8](https://github.com/future-architect/vuls/issues/8)
|
||||
|
||||
## [v0.1.2](https://github.com/future-architect/vuls/tree/v0.1.2) (2016-04-12)
|
||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.1...v0.1.2)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Maximum 6 nodes available to scan [\#12](https://github.com/future-architect/vuls/issues/12)
|
||||
- panic: runtime error: index out of range [\#5](https://github.com/future-architect/vuls/issues/5)
|
||||
- Fix sudo option on RedHat like Linux and change some messages. [\#20](https://github.com/future-architect/vuls/pull/20) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Typo fix and updated readme [\#19](https://github.com/future-architect/vuls/pull/19) ([Euan-Kerr](https://github.com/Euan-Kerr))
|
||||
- remove a period at the end of error messages. [\#18](https://github.com/future-architect/vuls/pull/18) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- fix error while yum updateinfo --security update on rhel@aws [\#17](https://github.com/future-architect/vuls/pull/17) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Fixed typos [\#15](https://github.com/future-architect/vuls/pull/15) ([radarhere](https://github.com/radarhere))
|
||||
- Typo fix in error messages [\#14](https://github.com/future-architect/vuls/pull/14) ([Bregor](https://github.com/Bregor))
|
||||
- Fix index out of range error when the number of servers is over 6. \#12 [\#13](https://github.com/future-architect/vuls/pull/13) ([kotakanbe](https://github.com/kotakanbe))
|
||||
- Revise small grammar mistakes in serverapi.go [\#9](https://github.com/future-architect/vuls/pull/9) ([cpobrien](https://github.com/cpobrien))
|
||||
- Fix error handling in HTTP backoff function [\#7](https://github.com/future-architect/vuls/pull/7) ([kotakanbe](https://github.com/kotakanbe))
|
||||
|
||||
## [v0.1.1](https://github.com/future-architect/vuls/tree/v0.1.1) (2016-04-06)
|
||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.1.0...v0.1.1)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Typo in Exapmle [\#6](https://github.com/future-architect/vuls/pull/6) ([toli](https://github.com/toli))
|
||||
|
||||
## [v0.1.0](https://github.com/future-architect/vuls/tree/v0.1.0) (2016-04-04)
|
||||
**Merged pull requests:**
|
||||
|
||||
- English translation [\#4](https://github.com/future-architect/vuls/pull/4) ([hikachan](https://github.com/hikachan))
|
||||
- English translation [\#3](https://github.com/future-architect/vuls/pull/3) ([chewyinping](https://github.com/chewyinping))
|
||||
- Add a Bitdeli Badge to README [\#2](https://github.com/future-architect/vuls/pull/2) ([bitdeli-chef](https://github.com/bitdeli-chef))
|
||||
|
||||
|
||||
|
||||
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
|
||||
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 = ./. ./config ./models ./report ./cveapi ./scan ./util ./commands ./cache
|
||||
|
||||
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)
|
||||
1211
README.ja.md
173
cache/bolt.go
vendored
@@ -1,173 +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 cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
// Bolt holds a pointer of bolt.DB
|
||||
// boltdb is used to store a cache of Changelogs of Ubuntu/Debian
|
||||
type Bolt struct {
|
||||
Path string
|
||||
Log *logrus.Entry
|
||||
db *bolt.DB
|
||||
}
|
||||
|
||||
// SetupBolt opens a boltdb and creates a meta bucket if not exists.
|
||||
func SetupBolt(path string, l *logrus.Entry) error {
|
||||
l.Infof("Open boltDB: %s", path)
|
||||
db, err := bolt.Open(path, 0600, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b := Bolt{
|
||||
Path: path,
|
||||
Log: l,
|
||||
db: db,
|
||||
}
|
||||
if err = b.createBucketIfNotExists(metabucket); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
DB = b
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close a db.
|
||||
func (b Bolt) Close() error {
|
||||
if b.db == nil {
|
||||
return nil
|
||||
}
|
||||
return b.db.Close()
|
||||
}
|
||||
|
||||
// CreateBucketIfNotExists creates a buket that is specified by arg.
|
||||
func (b *Bolt) createBucketIfNotExists(name string) error {
|
||||
return b.db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create bucket: %s", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetMeta gets a Meta Information os the servername to boltdb.
|
||||
func (b Bolt) GetMeta(serverName string) (meta Meta, found bool, err error) {
|
||||
err = b.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket([]byte(metabucket))
|
||||
v := bkt.Get([]byte(serverName))
|
||||
if len(v) == 0 {
|
||||
found = false
|
||||
return nil
|
||||
}
|
||||
if e := json.Unmarshal(v, &meta); e != nil {
|
||||
return e
|
||||
}
|
||||
found = true
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// EnsureBuckets puts a Meta information and create a buket that holds changelogs.
|
||||
func (b Bolt) EnsureBuckets(meta Meta) error {
|
||||
jsonBytes, err := json.Marshal(meta)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to marshal to JSON: %s", err)
|
||||
}
|
||||
return b.db.Update(func(tx *bolt.Tx) error {
|
||||
b.Log.Debugf("Put to meta: %s", meta.Name)
|
||||
bkt := tx.Bucket([]byte(metabucket))
|
||||
if err := bkt.Put([]byte(meta.Name), jsonBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// re-create a bucket (bucket name: servername)
|
||||
bkt = tx.Bucket([]byte(meta.Name))
|
||||
if bkt != nil {
|
||||
b.Log.Debugf("Delete bucket: %s", meta.Name)
|
||||
if err := tx.DeleteBucket([]byte(meta.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
b.Log.Debugf("Bucket deleted: %s", meta.Name)
|
||||
}
|
||||
b.Log.Debugf("Create bucket: %s", meta.Name)
|
||||
if _, err := tx.CreateBucket([]byte(meta.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
b.Log.Debugf("Bucket created: %s", meta.Name)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PrettyPrint is for debuging
|
||||
func (b Bolt) PrettyPrint(meta Meta) error {
|
||||
return b.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket([]byte(metabucket))
|
||||
v := bkt.Get([]byte(meta.Name))
|
||||
b.Log.Debugf("key:%s, value:%s", meta.Name, v)
|
||||
|
||||
bkt = tx.Bucket([]byte(meta.Name))
|
||||
c := bkt.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
b.Log.Debugf("key:%s, len: %d, %s...",
|
||||
k, len(v), util.Truncate(string(v), 30))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetChangelog get the changelgo of specified packName from the Bucket
|
||||
func (b Bolt) GetChangelog(servername, packName string) (changelog string, err error) {
|
||||
err = b.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket([]byte(servername))
|
||||
if bkt == nil {
|
||||
return fmt.Errorf("Faild to get Bucket: %s", servername)
|
||||
}
|
||||
v := bkt.Get([]byte(packName))
|
||||
if v == nil {
|
||||
changelog = ""
|
||||
return nil
|
||||
}
|
||||
changelog = string(v)
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// PutChangelog put the changelgo of specified packName into the Bucket
|
||||
func (b Bolt) PutChangelog(servername, packName, changelog string) error {
|
||||
return b.db.Update(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket([]byte(servername))
|
||||
if bkt == nil {
|
||||
return fmt.Errorf("Faild to get Bucket: %s", servername)
|
||||
}
|
||||
if err := bkt.Put([]byte(packName), []byte(changelog)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
134
cache/bolt_test.go
vendored
@@ -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 cache
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
const path = "/tmp/vuls-test-cache-11111111.db"
|
||||
const servername = "server1"
|
||||
|
||||
var meta = Meta{
|
||||
Name: servername,
|
||||
Distro: config.Distro{
|
||||
Family: "ubuntu",
|
||||
Release: "16.04",
|
||||
},
|
||||
Packs: []models.PackageInfo{
|
||||
{
|
||||
Name: "apt",
|
||||
Version: "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestSetupBolt(t *testing.T) {
|
||||
log := logrus.NewEntry(&logrus.Logger{})
|
||||
err := SetupBolt(path, log)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to setup bolt: %s", err)
|
||||
}
|
||||
defer os.Remove(path)
|
||||
|
||||
if err := DB.Close(); err != nil {
|
||||
t.Errorf("Failed to close bolt: %s", err)
|
||||
}
|
||||
|
||||
// check if meta bucket exists
|
||||
db, err := bolt.Open(path, 0600, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to open bolt: %s", err)
|
||||
}
|
||||
|
||||
db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket([]byte(metabucket))
|
||||
if bkt == nil {
|
||||
t.Errorf("Meta bucket nof found")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestEnsureBuckets(t *testing.T) {
|
||||
log := logrus.NewEntry(&logrus.Logger{})
|
||||
if err := SetupBolt(path, log); err != nil {
|
||||
t.Errorf("Failed to setup bolt: %s", err)
|
||||
}
|
||||
if err := DB.EnsureBuckets(meta); err != nil {
|
||||
t.Errorf("Failed to ensure buckets: %s", err)
|
||||
}
|
||||
defer os.Remove(path)
|
||||
|
||||
m, found, err := DB.GetMeta(servername)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get meta: %s", err)
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Not Found in meta")
|
||||
}
|
||||
if !reflect.DeepEqual(meta, m) {
|
||||
t.Errorf("expected %v, actual %v", meta, m)
|
||||
}
|
||||
if err := DB.Close(); err != nil {
|
||||
t.Errorf("Failed to close bolt: %s", err)
|
||||
}
|
||||
|
||||
db, err := bolt.Open(path, 0600, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to open bolt: %s", err)
|
||||
}
|
||||
db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket([]byte(servername))
|
||||
if bkt == nil {
|
||||
t.Errorf("Meta bucket nof found")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestPutGetChangelog(t *testing.T) {
|
||||
clog := "changelog-text"
|
||||
log := logrus.NewEntry(&logrus.Logger{})
|
||||
if err := SetupBolt(path, log); err != nil {
|
||||
t.Errorf("Failed to setup bolt: %s", err)
|
||||
}
|
||||
defer os.Remove(path)
|
||||
|
||||
if err := DB.EnsureBuckets(meta); err != nil {
|
||||
t.Errorf("Failed to ensure buckets: %s", err)
|
||||
}
|
||||
if err := DB.PutChangelog(servername, "apt", clog); err != nil {
|
||||
t.Errorf("Failed to put changelog: %s", err)
|
||||
}
|
||||
if actual, err := DB.GetChangelog(servername, "apt"); err != nil {
|
||||
t.Errorf("Failed to get changelog: %s", err)
|
||||
} else {
|
||||
if actual != clog {
|
||||
t.Errorf("changelog is not same. e: %s, a: %s", clog, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
56
cache/db.go
vendored
@@ -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 cache
|
||||
|
||||
import (
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
// DB has a cache instance
|
||||
var DB Cache
|
||||
|
||||
const metabucket = "changelog-meta"
|
||||
|
||||
// Cache is a interface of cache
|
||||
type Cache interface {
|
||||
Close() error
|
||||
GetMeta(string) (Meta, bool, error)
|
||||
EnsureBuckets(Meta) error
|
||||
PrettyPrint(Meta) error
|
||||
GetChangelog(string, string) (string, error)
|
||||
PutChangelog(string, string, string) error
|
||||
}
|
||||
|
||||
// Meta holds a server name, distro information of the scanned server and
|
||||
// package information that was collected at the last scan.
|
||||
type Meta struct {
|
||||
Name string
|
||||
Distro config.Distro
|
||||
Packs []models.PackageInfo
|
||||
}
|
||||
|
||||
// FindPack search a PackageInfo
|
||||
func (m Meta) FindPack(name string) (pack models.PackageInfo, found bool) {
|
||||
for _, p := range m.Packs {
|
||||
if name == p.Name {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
return pack, false
|
||||
}
|
||||
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,103 +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"
|
||||
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/google/subcommands"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// HistoryCmd is Subcommand of list scanned results
|
||||
type HistoryCmd struct {
|
||||
debug bool
|
||||
debugSQL bool
|
||||
jsonBaseDir string
|
||||
}
|
||||
|
||||
// Name return subcommand name
|
||||
func (*HistoryCmd) Name() string { return "history" }
|
||||
|
||||
// Synopsis return synopsis
|
||||
func (*HistoryCmd) Synopsis() string {
|
||||
return `List history of scanning.`
|
||||
}
|
||||
|
||||
// Usage return usage
|
||||
func (*HistoryCmd) Usage() string {
|
||||
return `history:
|
||||
history
|
||||
[-results-dir=/path/to/results]
|
||||
`
|
||||
}
|
||||
|
||||
// SetFlags set flag
|
||||
func (p *HistoryCmd) SetFlags(f *flag.FlagSet) {
|
||||
f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
|
||||
|
||||
wd, _ := os.Getwd()
|
||||
defaultJSONBaseDir := filepath.Join(wd, "results")
|
||||
f.StringVar(&p.jsonBaseDir, "results-dir", defaultJSONBaseDir, "/path/to/results")
|
||||
}
|
||||
|
||||
// Execute execute
|
||||
func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
|
||||
c.Conf.DebugSQL = p.debugSQL
|
||||
c.Conf.JSONBaseDir = p.jsonBaseDir
|
||||
|
||||
var err error
|
||||
var jsonDirs report.JSONDirs
|
||||
if jsonDirs, err = report.GetValidJSONDirs(); err != nil {
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
for _, d := range jsonDirs {
|
||||
var files []os.FileInfo
|
||||
if files, err = ioutil.ReadDir(d); err != nil {
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
var hosts []string
|
||||
for _, f := range files {
|
||||
// TODO this "if block" will be deleted in a future release
|
||||
if f.Name() == "all.json" {
|
||||
continue
|
||||
}
|
||||
if filepath.Ext(f.Name()) != ".json" {
|
||||
continue
|
||||
}
|
||||
fileBase := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
|
||||
hosts = append(hosts, fileBase)
|
||||
}
|
||||
splitPath := strings.Split(d, string(os.PathSeparator))
|
||||
timeStr := splitPath[len(splitPath)-1]
|
||||
fmt.Printf("%s scanned %d servers: %s\n",
|
||||
timeStr,
|
||||
len(hosts),
|
||||
strings.Join(hosts, ", "),
|
||||
)
|
||||
}
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
@@ -1,153 +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
|
||||
}
|
||||
|
||||
// Name return subcommand name
|
||||
func (*PrepareCmd) Name() string { return "prepare" }
|
||||
|
||||
// Synopsis return synopsis
|
||||
func (*PrepareCmd) Synopsis() string {
|
||||
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",
|
||||
)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
}
|
||||
406
commands/scan.go
@@ -1,406 +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"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
"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
|
||||
|
||||
jsonBaseDir string
|
||||
cvedbpath string
|
||||
cveDictionaryURL string
|
||||
cacheDBPath 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
|
||||
|
||||
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]
|
||||
[-results-dir=/path/to/results]
|
||||
[-cve-dictionary-dbpath=/path/to/cve.sqlite3]
|
||||
[-cve-dictionary-url=http://127.0.0.1:1323]
|
||||
[-cache-dbpath=/path/to/cache.db]
|
||||
[-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")
|
||||
|
||||
defaultJSONBaseDir := filepath.Join(wd, "results")
|
||||
f.StringVar(&p.jsonBaseDir, "results-dir", defaultJSONBaseDir, "/path/to/results")
|
||||
|
||||
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")
|
||||
|
||||
defaultCacheDBPath := filepath.Join(wd, "cache.db")
|
||||
f.StringVar(
|
||||
&p.cacheDBPath,
|
||||
"cache-dbpath",
|
||||
defaultCacheDBPath,
|
||||
"/path/to/cache.db (local cache of changelog for Ubuntu/Debian)")
|
||||
|
||||
f.Float64Var(
|
||||
&p.cvssScoreOver,
|
||||
"cvss-over",
|
||||
0,
|
||||
"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
|
||||
|
||||
f.BoolVar(
|
||||
&p.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",
|
||||
)
|
||||
}
|
||||
|
||||
// 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{})
|
||||
scannedAt := time.Now()
|
||||
|
||||
// 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{ScannedAt: scannedAt})
|
||||
}
|
||||
if p.reportText {
|
||||
reports = append(reports, report.TextFileWriter{ScannedAt: scannedAt})
|
||||
}
|
||||
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 len(c.Conf.AzureAccount) == 0 {
|
||||
c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
|
||||
}
|
||||
|
||||
c.Conf.AzureKey = p.azureKey
|
||||
if len(c.Conf.AzureKey) == 0 {
|
||||
c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
|
||||
}
|
||||
|
||||
c.Conf.AzureContainer = p.azureContainer
|
||||
if len(c.Conf.AzureContainer) == 0 {
|
||||
Log.Error("Azure storage container name is requied with --azure-container option")
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
if err := report.CheckIfAzureContainerExists(); err != nil {
|
||||
Log.Errorf("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.JSONBaseDir = p.jsonBaseDir
|
||||
c.Conf.CveDBPath = p.cvedbpath
|
||||
c.Conf.CveDictionaryURL = p.cveDictionaryURL
|
||||
c.Conf.CacheDBPath = p.cacheDBPath
|
||||
c.Conf.CvssScoreOver = p.cvssScoreOver
|
||||
c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
|
||||
c.Conf.SSHExternal = p.sshExternal
|
||||
c.Conf.HTTPProxy = p.httpProxy
|
||||
|
||||
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("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
|
||||
}
|
||||
105
commands/tui.go
@@ -1,105 +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"
|
||||
|
||||
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
|
||||
jsonBaseDir 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 [-results-dir=/path/to/results]
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
defaultJSONBaseDir := filepath.Join(wd, "results")
|
||||
f.StringVar(&p.jsonBaseDir, "results-dir", defaultJSONBaseDir, "/path/to/results")
|
||||
}
|
||||
|
||||
// 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.JSONBaseDir = p.jsonBaseDir
|
||||
|
||||
var jsonDirName string
|
||||
var err error
|
||||
if 0 < len(f.Args()) {
|
||||
var jsonDirs report.JSONDirs
|
||||
if jsonDirs, err = report.GetValidJSONDirs(); err != nil {
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
for _, d := range jsonDirs {
|
||||
splitPath := strings.Split(d, string(os.PathSeparator))
|
||||
if splitPath[len(splitPath)-1] == f.Args()[0] {
|
||||
jsonDirName = f.Args()[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(jsonDirName) == 0 {
|
||||
log.Errorf("First Argument have to be JSON directory name : %s", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
} 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) {
|
||||
jsonDirName = fields[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
return report.RunTui(jsonDirName)
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
269
config/config.go
@@ -1,269 +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"`
|
||||
JSONBaseDir string
|
||||
CveDBPath string
|
||||
CacheDBPath string
|
||||
|
||||
AwsProfile string
|
||||
AwsRegion string
|
||||
S3Bucket string
|
||||
|
||||
AzureAccount string
|
||||
AzureKey string
|
||||
AzureContainer string
|
||||
|
||||
// CpeNames []string
|
||||
// SummaryMode bool
|
||||
}
|
||||
|
||||
// Validate configuration
|
||||
func (c Config) Validate() bool {
|
||||
errs := []error{}
|
||||
|
||||
if len(c.JSONBaseDir) != 0 {
|
||||
if ok, _ := valid.IsFilePath(c.JSONBaseDir); !ok {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.JSONBaseDir))
|
||||
}
|
||||
}
|
||||
|
||||
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. -cve-dictionary-dbpath: %s", c.CveDBPath))
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.CacheDBPath) != 0 {
|
||||
if ok, _ := valid.IsFilePath(c.CacheDBPath); !ok {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"Cache DB path must be a *Absolute* file path. -cache-dbpath: %s", c.CacheDBPath))
|
||||
}
|
||||
}
|
||||
|
||||
_, 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
|
||||
Distro Distro
|
||||
}
|
||||
|
||||
// Distro has distribution info
|
||||
type Distro struct {
|
||||
Family string
|
||||
Release string
|
||||
}
|
||||
|
||||
func (l Distro) String() string {
|
||||
return fmt.Sprintf("%s %s", l.Family, l.Release)
|
||||
}
|
||||
|
||||
// IsContainer returns whether this ServerInfo is about container
|
||||
func (s ServerInfo) IsContainer() bool {
|
||||
return 0 < len(s.Container.ContainerID)
|
||||
}
|
||||
|
||||
// SetContainer set container
|
||||
func (s *ServerInfo) SetContainer(d Container) {
|
||||
s.Container = d
|
||||
}
|
||||
|
||||
// Container has Container information.
|
||||
type Container struct {
|
||||
ContainerID string
|
||||
Name string
|
||||
Type string
|
||||
}
|
||||
@@ -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 len(s.Host) == 0 {
|
||||
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 len(s.KeyPath) == 0 {
|
||||
s.KeyPath = d.KeyPath
|
||||
}
|
||||
if s.KeyPath != "" {
|
||||
if _, err := os.Stat(s.KeyPath); err != nil {
|
||||
return fmt.Errorf(
|
||||
"%s is invalid. keypath: %s not exists", name, s.KeyPath)
|
||||
}
|
||||
}
|
||||
|
||||
// s.KeyPassword = keyPass
|
||||
s.KeyPassword = v.KeyPassword
|
||||
if len(s.KeyPassword) == 0 {
|
||||
s.KeyPassword = d.KeyPassword
|
||||
}
|
||||
|
||||
s.CpeNames = v.CpeNames
|
||||
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,249 +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 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
|
||||
return false, fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v", url, errs)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
|
||||
return fmt.Errorf("HTTP POST error: %v, url: %s, resp: %v", errs, url, resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, t time.Duration) {
|
||||
log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %s", t, err)
|
||||
}
|
||||
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
|
||||
}
|
||||
121
glide.lock
generated
@@ -1,121 +0,0 @@
|
||||
hash: 28d14f88e90c0765c1b660ddde796e51e197239d353bb79bfc5d8f8cf9b5f9ee
|
||||
updated: 2016-09-08T19:35:45.581570944+09:00
|
||||
imports:
|
||||
- name: github.com/asaskevich/govalidator
|
||||
version: 593d64559f7600f29581a3ee42177f5dbded27a9
|
||||
- name: github.com/aws/aws-sdk-go
|
||||
version: bc572378d109481c50d45d9dba4490d80386e98e
|
||||
subpackages:
|
||||
- aws
|
||||
- aws/credentials
|
||||
- aws/session
|
||||
- service/s3
|
||||
- aws/awserr
|
||||
- aws/client
|
||||
- aws/corehandlers
|
||||
- aws/credentials/stscreds
|
||||
- aws/defaults
|
||||
- aws/request
|
||||
- private/endpoints
|
||||
- aws/awsutil
|
||||
- aws/client/metadata
|
||||
- aws/signer/v4
|
||||
- private/protocol
|
||||
- private/protocol/restxml
|
||||
- private/waiter
|
||||
- service/sts
|
||||
- aws/credentials/ec2rolecreds
|
||||
- aws/credentials/endpointcreds
|
||||
- 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: 34467930a15f0d2872168deb11435b8ac3d863bb
|
||||
subpackages:
|
||||
- storage
|
||||
- name: github.com/BurntSushi/toml
|
||||
version: 99064174e013895bbd9b025c31100bd1d9b590ca
|
||||
- name: github.com/cenkalti/backoff
|
||||
version: 8edc80b07f38c27352fb186d971c628a6c32552b
|
||||
- name: github.com/cheggaaa/pb
|
||||
version: ad4efe000aa550bb54918c06ebbadc0ff17687b9
|
||||
- name: github.com/go-ini/ini
|
||||
version: 6e4869b434bd001f6983749881c7ead3545887d8
|
||||
- name: github.com/google/subcommands
|
||||
version: 1c7173745a6001f67d8d96ab4e178284c77f7759
|
||||
- name: github.com/gosuri/uitable
|
||||
version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42
|
||||
subpackages:
|
||||
- util/strutil
|
||||
- util/wordwrap
|
||||
- name: github.com/howeyc/gopass
|
||||
version: 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d
|
||||
- name: github.com/jinzhu/gorm
|
||||
version: 02f6ae3c4ed211472b0492cee02ff3ddfdc1830d
|
||||
- name: github.com/jinzhu/inflection
|
||||
version: 74387dc39a75e970e7a3ae6a3386b5bd2e5c5cff
|
||||
- name: github.com/jmespath/go-jmespath
|
||||
version: bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
|
||||
- name: github.com/jroimartin/gocui
|
||||
version: 30f7d65597dc2c421ce452b164c36b7014ef94be
|
||||
- name: github.com/k0kubun/pp
|
||||
version: f5dce6ed0ccf6c350f1679964ff6b61f3d6d2033
|
||||
- name: github.com/kotakanbe/go-cve-dictionary
|
||||
version: f9f68fee57dca8e60fb5d9d6b34d3215d854fc06
|
||||
subpackages:
|
||||
- config
|
||||
- models
|
||||
- db
|
||||
- log
|
||||
- jvn
|
||||
- nvd
|
||||
- util
|
||||
- name: github.com/kotakanbe/go-pingscanner
|
||||
version: 58e188a3e4f6ab1a6371e33421e4502e26fa1e80
|
||||
- name: github.com/kotakanbe/logrus-prefixed-formatter
|
||||
version: f4f7d41649cf1e75e736884da8d05324aa76ea25
|
||||
- name: github.com/mattn/go-colorable
|
||||
version: ed8eb9e318d7a84ce5915b495b7d35e0cfe7b5a8
|
||||
- name: github.com/mattn/go-isatty
|
||||
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
||||
- name: github.com/mattn/go-runewidth
|
||||
version: d6bea18f789704b5f83375793155289da36a3c7f
|
||||
- name: github.com/mattn/go-sqlite3
|
||||
version: 3fb7a0e792edd47bf0cf1e919dfc14e2be412e15
|
||||
- name: github.com/mgutz/ansi
|
||||
version: c286dcecd19ff979eeb73ea444e479b903f2cfcb
|
||||
- name: github.com/moul/http2curl
|
||||
version: b1479103caacaa39319f75e7f57fc545287fca0d
|
||||
- name: github.com/nsf/termbox-go
|
||||
version: e8f6d27f72a2f2bb598eb3579afd5ea364ef67f7
|
||||
- name: github.com/parnurzeal/gorequest
|
||||
version: 2aea80ce763523ecc6452e61c3727ae9595a5809
|
||||
- name: github.com/rifflock/lfshook
|
||||
version: f9d14dda07b109a7aa56f135c31b34062eb14392
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: 3ec0642a7fb6488f65b06f9040adc67e3990296a
|
||||
- name: golang.org/x/crypto
|
||||
version: 9e590154d2353f3f5e1b24da7275686040dcf491
|
||||
subpackages:
|
||||
- ssh
|
||||
- ssh/agent
|
||||
- ssh/terminal
|
||||
- curve25519
|
||||
- ed25519
|
||||
- ed25519/internal/edwards25519
|
||||
- name: golang.org/x/net
|
||||
version: 9313baa13d9262e49d07b20ed57dceafcd7240cc
|
||||
subpackages:
|
||||
- context
|
||||
- publicsuffix
|
||||
- name: golang.org/x/sys
|
||||
version: 30de6d19a3bd89a5f38ae4028e23aaa5582648af
|
||||
subpackages:
|
||||
- unix
|
||||
- name: gopkg.in/alexcesaro/quotedprintable.v3
|
||||
version: 2caba252f4dc53eaf6b553000885530023f54623
|
||||
- name: gopkg.in/gomail.v2
|
||||
version: 81ebce5c23dfd25c6c67194b37d3dd3f338c98b1
|
||||
devImports: []
|
||||
38
glide.yaml
@@ -1,38 +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
|
||||
- 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,443 +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">Check upgradable packages
|
||||
Debian/Ubuntu: apt-get upgrade --dry-run<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n4">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.loopLimit">
|
||||
<y:Geometry height="51.10998735777497" width="137.19216182048035" x="75.40391908975982" y="376.28592169721867"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="131.751953125" x="2.7201043477401754" y="9.422181178887513">foreach
|
||||
upgradable packages<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="5.551115123125783E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n5">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.process">
|
||||
<y:Geometry height="56.0" width="268.0" x="10.0" y="459.8409153761062"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="213.619140625" x="27.1904296875" y="11.8671875">Parse changelog and get CVE IDs
|
||||
Debian/Ubuntu: aptitude changelog<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n6">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.loopLimitEnd">
|
||||
<y:Geometry height="50.0" width="137.0" x="75.5" y="545.8409153761062"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="55.24609375" x="40.876953125" y="15.93359375">end loop<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n7">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.process">
|
||||
<y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="625.8409153761062"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="194.904296875" x="36.5478515625" y="18.93359375">Select the CVE detail information<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n8">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.process">
|
||||
<y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="287.8409153761062"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" modelName="custom" textColor="#000000" visible="true" width="232.744140625" x="17.6279296875" y="4.80078125">Get CVE IDs by using package manager
|
||||
Amazon/RHEL: yum plugin security
|
||||
FreeBSD: pkg audit<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n9">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.dataBase">
|
||||
<y:Geometry height="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="293.06640625" x="-12.533203124999943" y="11.8671875">Get all changelogs of updatable packages at once
|
||||
CentOS: yum update --changelog<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="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="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="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="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)))
|
||||
}
|
||||
338
models/models.go
@@ -1,338 +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:"-"`
|
||||
ScannedAt time.Time
|
||||
|
||||
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 len(r.Container.ContainerID) == 0 {
|
||||
hostinfo = fmt.Sprintf(
|
||||
"%s (%s%s)",
|
||||
r.ServerName,
|
||||
r.Family,
|
||||
r.Release,
|
||||
)
|
||||
} else {
|
||||
hostinfo = fmt.Sprintf(
|
||||
"%s / %s (%s%s) on %s",
|
||||
r.Container.Name,
|
||||
r.Container.ContainerID,
|
||||
r.Family,
|
||||
r.Release,
|
||||
r.ServerName,
|
||||
)
|
||||
}
|
||||
return hostinfo
|
||||
}
|
||||
|
||||
// ServerInfoTui returns server infromation for TUI sidebar
|
||||
func (r ScanResult) ServerInfoTui() string {
|
||||
hostinfo := ""
|
||||
if len(r.Container.ContainerID) == 0 {
|
||||
hostinfo = fmt.Sprintf(
|
||||
"%s (%s%s)",
|
||||
r.ServerName,
|
||||
r.Family,
|
||||
r.Release,
|
||||
)
|
||||
} else {
|
||||
hostinfo = fmt.Sprintf(
|
||||
"|-- %s (%s%s)",
|
||||
r.Container.Name,
|
||||
r.Family,
|
||||
r.Release,
|
||||
// r.Container.ContainerID,
|
||||
)
|
||||
}
|
||||
return hostinfo
|
||||
}
|
||||
|
||||
// CveSummary summarize the number of CVEs group by CVSSv2 Severity
|
||||
func (r ScanResult) CveSummary() string {
|
||||
var high, middle, low, unknown int
|
||||
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[j].CveDetail.CvssScore(lang) < c[i].CveDetail.CvssScore(lang)
|
||||
}
|
||||
|
||||
// CveInfo has Cve Information.
|
||||
type CveInfo struct {
|
||||
gorm.Model `json:"-"`
|
||||
ScanResultID uint `json:"-"`
|
||||
|
||||
CveDetail cve.CveDetail
|
||||
Packages []PackageInfo
|
||||
DistroAdvisories []DistroAdvisory
|
||||
CpeNames []CpeName
|
||||
}
|
||||
|
||||
// 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 len(res.Container.ContainerID) == 0 {
|
||||
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
|
||||
}
|
||||
163
report/json.go
@@ -1,163 +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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
// JSONDirs array of json files path.
|
||||
type JSONDirs []string
|
||||
|
||||
func (d JSONDirs) Len() int {
|
||||
return len(d)
|
||||
}
|
||||
func (d JSONDirs) Swap(i, j int) {
|
||||
d[i], d[j] = d[j], d[i]
|
||||
}
|
||||
func (d JSONDirs) Less(i, j int) bool {
|
||||
return d[j] < d[i]
|
||||
}
|
||||
|
||||
// JSONWriter writes results to file.
|
||||
type JSONWriter struct {
|
||||
ScannedAt time.Time
|
||||
}
|
||||
|
||||
func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
|
||||
var path string
|
||||
if path, err = ensureResultDir(w.ScannedAt); err != nil {
|
||||
return fmt.Errorf("Failed to make direcotory/symlink : %s", err)
|
||||
}
|
||||
|
||||
for _, scanResult := range scanResults {
|
||||
scanResult.ScannedAt = w.ScannedAt
|
||||
}
|
||||
|
||||
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 len(r.Container.ContainerID) == 0 {
|
||||
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
|
||||
}
|
||||
|
||||
// JSONDirPattern is file name pattern of JSON directory
|
||||
var JSONDirPattern = regexp.MustCompile(`^\d{8}_\d{4}$`)
|
||||
|
||||
// GetValidJSONDirs return valid json directory as array
|
||||
func GetValidJSONDirs() (jsonDirs JSONDirs, err error) {
|
||||
var dirInfo []os.FileInfo
|
||||
if dirInfo, err = ioutil.ReadDir(c.Conf.JSONBaseDir); err != nil {
|
||||
err = fmt.Errorf("Failed to read %s: %s", c.Conf.JSONBaseDir, err)
|
||||
return
|
||||
}
|
||||
for _, d := range dirInfo {
|
||||
if d.IsDir() && JSONDirPattern.MatchString(d.Name()) {
|
||||
jsonDir := filepath.Join(c.Conf.JSONBaseDir, d.Name())
|
||||
jsonDirs = append(jsonDirs, jsonDir)
|
||||
}
|
||||
}
|
||||
sort.Sort(jsonDirs)
|
||||
return
|
||||
}
|
||||
|
||||
// LoadOneScanHistory read JSON data
|
||||
func LoadOneScanHistory(jsonDir string) (scanHistory models.ScanHistory, err error) {
|
||||
var scanResults []models.ScanResult
|
||||
var files []os.FileInfo
|
||||
if files, err = ioutil.ReadDir(jsonDir); err != nil {
|
||||
err = fmt.Errorf("Failed to read %s: %s", jsonDir, err)
|
||||
return
|
||||
}
|
||||
for _, file := range files {
|
||||
// TODO this "if block" will be deleted in a future release
|
||||
if file.Name() == "all.json" {
|
||||
continue
|
||||
}
|
||||
if filepath.Ext(file.Name()) != ".json" {
|
||||
continue
|
||||
}
|
||||
var scanResult models.ScanResult
|
||||
var data []byte
|
||||
jsonPath := filepath.Join(jsonDir, file.Name())
|
||||
if data, err = ioutil.ReadFile(jsonPath); err != nil {
|
||||
err = fmt.Errorf("Failed to read %s: %s", jsonPath, err)
|
||||
return
|
||||
}
|
||||
if json.Unmarshal(data, &scanResult) != nil {
|
||||
err = fmt.Errorf("Failed to parse %s: %s", jsonPath, err)
|
||||
return
|
||||
}
|
||||
scanResults = append(scanResults, scanResult)
|
||||
}
|
||||
if len(scanResults) == 0 {
|
||||
err = fmt.Errorf("There is no json file under %s", jsonDir)
|
||||
return
|
||||
}
|
||||
|
||||
var scannedAt time.Time
|
||||
if scanResults[0].ScannedAt.IsZero() {
|
||||
splitPath := strings.Split(jsonDir, string(os.PathSeparator))
|
||||
timeStr := splitPath[len(splitPath)-1]
|
||||
timeformat := "20060102_1504"
|
||||
if scannedAt, err = time.Parse(timeformat, timeStr); err != nil {
|
||||
err = fmt.Errorf("Failed to parse %s: %s", timeStr, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
scannedAt = scanResults[0].ScannedAt
|
||||
}
|
||||
|
||||
scanHistory = models.ScanHistory{
|
||||
ScanResults: scanResults,
|
||||
ScannedAt: scannedAt,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -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 len(r.Container.ContainerID) == 0 {
|
||||
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
|
||||
}
|
||||
245
report/slack.go
@@ -1,245 +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 0 < len(errs) {
|
||||
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{}
|
||||
|
||||
cweID := cveInfo.CveDetail.CweID()
|
||||
if 0 < len(cweID) {
|
||||
links = append(links, fmt.Sprintf("<%s|%s>",
|
||||
cweURL(cweID), cweID))
|
||||
if config.Conf.Lang == "ja" {
|
||||
links = append(links, fmt.Sprintf("<%s|%s(JVN)>",
|
||||
cweJvnURL(cweID), cweID))
|
||||
}
|
||||
}
|
||||
|
||||
cveID := cveInfo.CveDetail.CveID
|
||||
if config.Conf.Lang == "ja" && 0 < len(cveInfo.CveDetail.Jvn.Link()) {
|
||||
jvn := fmt.Sprintf("<%s|JVN>", cveInfo.CveDetail.Jvn.Link())
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||