Compare commits
1245 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3605645ff6 | ||
|
|
1d97e91341 | ||
|
|
96333f38c9 | ||
|
|
8b5d1c8e92 | ||
|
|
dea80f860c | ||
|
|
6eb4c5a5fe | ||
|
|
b219a8495e | ||
|
|
eb87d5d4e1 | ||
|
|
6963442a5e | ||
|
|
f7299b9dba | ||
|
|
379fc8a1a1 | ||
|
|
947fbbb29e | ||
|
|
06d2032c9c | ||
|
|
d055c48827 | ||
|
|
2a00339da1 | ||
|
|
2d959b3af8 | ||
|
|
595e26db41 | ||
|
|
1e457320c5 | ||
|
|
a06e689502 | ||
|
|
ca3f6b1dbf | ||
|
|
f1c78e42a2 | ||
|
|
2f3b8bf3cc | ||
|
|
ab54266f9e | ||
|
|
d79d138440 | ||
|
|
139f3a81b6 | ||
|
|
d1a617cfff | ||
|
|
48f7597bcf | ||
|
|
93731311a1 | ||
|
|
999529a05b | ||
|
|
847d820af7 | ||
|
|
5234306ded | ||
|
|
86b60e1478 | ||
|
|
42fdc08933 | ||
|
|
38b1d622f6 | ||
|
|
2477f9a8f8 | ||
|
|
ec6e90acd3 | ||
|
|
2aca2e4352 | ||
|
|
14518d925e | ||
|
|
948f8c0751 | ||
|
|
1c1e40058e | ||
|
|
2158fc6cb1 | ||
|
|
91ed318c5d | ||
|
|
bfc3828ce1 | ||
|
|
c7eac4e7fe | ||
|
|
cc63a0eccf | ||
|
|
fd18df1dd4 | ||
|
|
8775b5efdf | ||
|
|
a9f29a6c5d | ||
|
|
05fdde48f9 | ||
|
|
3dfbd6b616 | ||
|
|
04f246cf8b | ||
|
|
7500f41655 | ||
|
|
a1cc152e81 | ||
|
|
1c77bc1ba3 | ||
|
|
ec31c54caf | ||
|
|
2f05864813 | ||
|
|
2fbc0a001e | ||
|
|
7d8a24ee1a | ||
|
|
7750347010 | ||
|
|
9bcffcd721 | ||
|
|
787604de6a | ||
|
|
5164fb1423 | ||
|
|
07335617d3 | ||
|
|
e5855922c1 | ||
|
|
671be3f2f7 | ||
|
|
fe8d252c51 | ||
|
|
0cdc7a3af5 | ||
|
|
1cfe155a3a | ||
|
|
2923cbc645 | ||
|
|
7c209cc9dc | ||
|
|
84fa4ce432 | ||
|
|
f2e9cd9668 | ||
|
|
77049d6cbb | ||
|
|
b4c23c158b | ||
|
|
964b4aa389 | ||
|
|
dc5aa35db7 | ||
|
|
43c05d06fc | ||
|
|
a3f7d1d7e7 | ||
|
|
bb4a1ca6c2 | ||
|
|
57cce640e1 | ||
|
|
1eb5d36668 | ||
|
|
6bc4850596 | ||
|
|
24005ae7ae | ||
|
|
7aa296bb57 | ||
|
|
3829ed2f8e | ||
|
|
2b7294a504 | ||
|
|
0c6a892893 | ||
|
|
89d94ad85a | ||
|
|
ffdb78962f | ||
|
|
321dae37ce | ||
|
|
a31797af0b | ||
|
|
32999cf432 | ||
|
|
88218f5d92 | ||
|
|
15761933ac | ||
|
|
0b62842f0e | ||
|
|
6bceddeeda | ||
|
|
2dcbff8cd5 | ||
|
|
8659668177 | ||
|
|
e07b6a9160 | ||
|
|
aac5ef1438 | ||
|
|
d780a73297 | ||
|
|
9ef8cee36e | ||
|
|
77808a2c05 | ||
|
|
177e553d12 | ||
|
|
40f8272a28 | ||
|
|
a7eb1141ae | ||
|
|
c73ed7f32f | ||
|
|
f047a6fe0c | ||
|
|
7f15a86d6a | ||
|
|
da1e515253 | ||
|
|
591786fde6 | ||
|
|
47e6ea249d | ||
|
|
4a72295de7 | ||
|
|
9ed5f2cac5 | ||
|
|
3e67f04fe4 | ||
|
|
b9416ae062 | ||
|
|
b4e49e093e | ||
|
|
020f6ac609 | ||
|
|
7e71cbdd46 | ||
|
|
1003f62212 | ||
|
|
9b18e1f9f0 | ||
|
|
24f790f474 | ||
|
|
fb8749fc5e | ||
|
|
96c3592db1 | ||
|
|
d65421cf46 | ||
|
|
c52ba448cd | ||
|
|
21adce463b | ||
|
|
f24240bf90 | ||
|
|
ff83cadd6e | ||
|
|
e8c09282d9 | ||
|
|
5f4d68cde4 | ||
|
|
9077a83ea8 | ||
|
|
543dc99ecd | ||
|
|
f0b3a8b1db | ||
|
|
0b9ec05181 | ||
|
|
0bf12412d6 | ||
|
|
0ea4d58c63 | ||
|
|
5755b00576 | ||
|
|
1c8e074c9d | ||
|
|
0e0e5ce4be | ||
|
|
23dfe53885 | ||
|
|
8e6351a9e4 | ||
|
|
3086e2760f | ||
|
|
b8db2e0b74 | ||
|
|
43b46cb324 | ||
|
|
d0559c7719 | ||
|
|
231c63cf62 | ||
|
|
2a9aebe059 | ||
|
|
4e535d792f | ||
|
|
4b487503d4 | ||
|
|
0095c40e69 | ||
|
|
82c1abfd3a | ||
|
|
40988401bd | ||
|
|
e8e3f4d138 | ||
|
|
7eb77f5b51 | ||
|
|
e115235299 | ||
|
|
151d4b2d30 | ||
|
|
e553f8b4c5 | ||
|
|
47652ef0fb | ||
|
|
ab0e950800 | ||
|
|
a7b0ce1c85 | ||
|
|
dc9c0edece | ||
|
|
17ae386d1e | ||
|
|
2d369d0cfe | ||
|
|
c36e645d9b | ||
|
|
40039c07e2 | ||
|
|
a692cec0ef | ||
|
|
e7ca491a94 | ||
|
|
23f3e2fc11 | ||
|
|
27b3e17b79 | ||
|
|
740781af56 | ||
|
|
36c9c229b8 | ||
|
|
183fdcbdef | ||
|
|
a2a697900a | ||
|
|
6fef4db8a0 | ||
|
|
e879ff1e9e | ||
|
|
9bfe0627ae | ||
|
|
0179f4299a | ||
|
|
56017e57a0 | ||
|
|
cda91e0906 | ||
|
|
5d47adb5c9 | ||
|
|
54e73c2f54 | ||
|
|
2d075079f1 | ||
|
|
2a8ee4b22b | ||
|
|
1ec31d7be9 | ||
|
|
02286b0c59 | ||
|
|
1d0c5dea9f | ||
|
|
1c4a12c4b7 | ||
|
|
3f2ac45d71 | ||
|
|
518f4dc039 | ||
|
|
2cdeef4ffe | ||
|
|
03579126fd | ||
|
|
e3c27e1817 | ||
|
|
aeaf308679 | ||
|
|
f5e47bea40 | ||
|
|
50cf13a7f2 | ||
|
|
abd8041772 | ||
|
|
847c6438e7 | ||
|
|
ef8309df27 | ||
|
|
0dff6cf983 | ||
|
|
4c04acbd9e | ||
|
|
1c4f231572 | ||
|
|
51b8e169d2 | ||
|
|
b4611ae9b7 | ||
|
|
cd6722017b | ||
|
|
290edffccf | ||
|
|
64a6222bf9 | ||
|
|
adb686b7c9 | ||
|
|
d4af341b0f | ||
|
|
fea7e93c8d | ||
|
|
8b6b8d0f2e | ||
|
|
4dcbd865cc | ||
|
|
39b19444fe | ||
|
|
644d5a5462 | ||
|
|
8e18451e3f | ||
|
|
3dbdd01f97 | ||
|
|
a89079c005 | ||
|
|
a8c0926b4f | ||
|
|
dd2959a31b | ||
|
|
51099f42c3 | ||
|
|
63f170cc7a | ||
|
|
3c1489e588 | ||
|
|
e4f1e03f62 | ||
|
|
83d48ec990 | ||
|
|
b20d2b2684 | ||
|
|
2b918c70ae | ||
|
|
1100c133ba | ||
|
|
88899f0e89 | ||
|
|
59dc0059bc | ||
|
|
986fb304c0 | ||
|
|
d6435d2885 | ||
|
|
affb456499 | ||
|
|
705ed0a0ac | ||
|
|
dfffe5b508 | ||
|
|
fca102edba | ||
|
|
554b6345a2 | ||
|
|
aa954dc84c | ||
|
|
b5506a1368 | ||
|
|
0b55f94828 | ||
|
|
a67052f48c | ||
|
|
6eff6a9329 | ||
|
|
69d32d4511 | ||
|
|
d7a613b710 | ||
|
|
669c019287 | ||
|
|
fcc4901a10 | ||
|
|
4359503484 | ||
|
|
b13f93a2d3 | ||
|
|
8405e0fad6 | ||
|
|
aceb3f1826 | ||
|
|
a206675f3e | ||
|
|
f4253d74ae | ||
|
|
aaea15e516 | ||
|
|
83d1f80959 | ||
|
|
a33cff8f13 | ||
|
|
8679759f60 | ||
|
|
53deaee3d7 | ||
|
|
5a14a58fe4 | ||
|
|
fb1fbf8f95 | ||
|
|
cfbf779f9b | ||
|
|
d576b6c6c1 | ||
|
|
514eb71482 | ||
|
|
43ed904db1 | ||
|
|
0a440ca629 | ||
|
|
eff1dbf95b | ||
|
|
9a32a94806 | ||
|
|
2534098509 | ||
|
|
9497365758 | ||
|
|
101c44c9c0 | ||
|
|
ffd745c004 | ||
|
|
5fea4eaef8 | ||
|
|
1f610043cf | ||
|
|
3f8de02683 | ||
|
|
d02535d053 | ||
|
|
75fceff5f7 | ||
|
|
ebd3834a35 | ||
|
|
93059b74c3 | ||
|
|
2fc3462d35 | ||
|
|
f78dab50cb | ||
|
|
edb324c3d9 | ||
|
|
83bcca6e66 | ||
|
|
a124518d78 | ||
|
|
94bf630e29 | ||
|
|
31bb33fd90 | ||
|
|
4b680b9960 | ||
|
|
8a8ab8cb18 | ||
|
|
8146f5fd1b | ||
|
|
425c585e47 | ||
|
|
4f1578b2d6 | ||
|
|
7969b343b0 | ||
|
|
58cf1f4c8e | ||
|
|
a5b87af862 | ||
|
|
a0e592b934 | ||
|
|
7eccc538bb | ||
|
|
59daa8570a | ||
|
|
3f52d318bc | ||
|
|
11a7a0c934 | ||
|
|
89f49b0e29 | ||
|
|
72457cbf8e | ||
|
|
c11ba27509 | ||
|
|
8a611f9ba6 | ||
|
|
4a73875e4d | ||
|
|
d9d5e612ff | ||
|
|
4d8599e4fc | ||
|
|
59c7061d29 | ||
|
|
996557c667 | ||
|
|
519fb19a77 | ||
|
|
36456cb151 | ||
|
|
4ae87cc36c | ||
|
|
b37df89fb1 | ||
|
|
d18e7a751d | ||
|
|
8d5ea98e50 | ||
|
|
835dc08049 | ||
|
|
62c9409fe9 | ||
|
|
2374f578ed | ||
|
|
34e2f033d8 | ||
|
|
420825cacc | ||
|
|
466ec93d8e | ||
|
|
3f5bb6ab29 | ||
|
|
ebe5f858c8 | ||
|
|
9dd025437b | ||
|
|
c0ebac305a | ||
|
|
1f23ab7ba4 | ||
|
|
ea3b63998d | ||
|
|
3093426458 | ||
|
|
37716feac7 | ||
|
|
56b12c38d2 | ||
|
|
749ead5d4a | ||
|
|
3be50ab8da | ||
|
|
649f4a6991 | ||
|
|
0ff7641471 | ||
|
|
1679bfae20 | ||
|
|
45aa364436 | ||
|
|
778516c4d9 | ||
|
|
464d523c42 | ||
|
|
0f6a1987d4 | ||
|
|
20c6247ce5 | ||
|
|
a10dd67e0f | ||
|
|
5729ad6026 | ||
|
|
9aa0d87a21 | ||
|
|
fe3f1b9924 | ||
|
|
00e52a88fa | ||
|
|
5811dffe7a | ||
|
|
7278982af4 | ||
|
|
c17b4154ec | ||
|
|
d6e74cce08 | ||
|
|
3f80749241 | ||
|
|
7f72b6ac69 | ||
|
|
03e7b90b9f | ||
|
|
7936b3533b | ||
|
|
bd7e61d7cc | ||
|
|
69214e0c22 | ||
|
|
45bff26558 | ||
|
|
b2e429ccc6 | ||
|
|
76363c227b | ||
|
|
d5a3e5c2c5 | ||
|
|
2b02807ef0 | ||
|
|
be659ae094 | ||
|
|
b2c105adbc | ||
|
|
c61f462948 | ||
|
|
3ffed18e02 | ||
|
|
f54e7257d1 | ||
|
|
cc13b6a27c | ||
|
|
8877db1979 | ||
|
|
af58122c91 | ||
|
|
b7ca5e5590 | ||
|
|
69b6d875e6 | ||
|
|
1fbd516b83 | ||
|
|
dec5d3b165 | ||
|
|
d5e2040cef | ||
|
|
4326befdec | ||
|
|
3d4a5d9917 | ||
|
|
d770034788 | ||
|
|
a977533c78 | ||
|
|
c5e13dd5e4 | ||
|
|
a8040fe4d2 | ||
|
|
9e066008c3 | ||
|
|
22c6601526 | ||
|
|
425464fd76 | ||
|
|
ccb0751ffd | ||
|
|
f832de81b7 | ||
|
|
8a37de0686 | ||
|
|
836e4704f8 | ||
|
|
3e5390309c | ||
|
|
f8c0b38716 | ||
|
|
65e6070e5f | ||
|
|
7b78ebbc42 | ||
|
|
03c3189c02 | ||
|
|
4a34dfe0e9 | ||
|
|
4cf9a723fe | ||
|
|
bd1b135db3 | ||
|
|
8c3b305149 | ||
|
|
a3719038b8 | ||
|
|
c68a261c0b | ||
|
|
75fea79ac1 | ||
|
|
eb9f9680ec | ||
|
|
3634afdb81 | ||
|
|
77b5df896a | ||
|
|
b81f64058c | ||
|
|
a8a90d7c63 | ||
|
|
17bb575002 | ||
|
|
abcea1a14d | ||
|
|
10942f7c08 | ||
|
|
87ee829e80 | ||
|
|
fcc2c1e4c7 | ||
|
|
269095d034 | ||
|
|
40492ee00a | ||
|
|
64cdd5aedc | ||
|
|
3bb650cb77 | ||
|
|
774544c975 | ||
|
|
299805a726 | ||
|
|
276363e793 | ||
|
|
e750bd53fc | ||
|
|
98fee7b5d2 | ||
|
|
53aaea9fe2 | ||
|
|
824fbb6368 | ||
|
|
80566b91ab | ||
|
|
533d05a1b5 | ||
|
|
6a1fc4fade | ||
|
|
9008d0ddf0 | ||
|
|
583f4577bc | ||
|
|
e5716d5092 | ||
|
|
7192ae1287 | ||
|
|
99c65eff48 | ||
|
|
91df593566 | ||
|
|
07aeaeb989 | ||
|
|
cfeecdacd0 | ||
|
|
564dfa8b62 | ||
|
|
75dd6f2010 | ||
|
|
e26fd0b759 | ||
|
|
d630680a51 | ||
|
|
1723c3f6a0 | ||
|
|
53dd90302e | ||
|
|
5c6e06b05e | ||
|
|
cf6fb0c8a5 | ||
|
|
e0e71b2eae | ||
|
|
53f4a29fb1 | ||
|
|
89d58d1abc | ||
|
|
d6b6969cb3 | ||
|
|
e7bf6fa69d | ||
|
|
6e51970b91 | ||
|
|
56d7d43768 | ||
|
|
256c99ffa2 | ||
|
|
9c0bc3b13b | ||
|
|
9b8a323d85 | ||
|
|
3178c1e326 | ||
|
|
321d68e03a | ||
|
|
3d8753c621 | ||
|
|
967c56909d | ||
|
|
7c4831d2d1 | ||
|
|
4b49e11a33 | ||
|
|
d84a6a8627 | ||
|
|
63b7f4a8db | ||
|
|
ca2160264a | ||
|
|
7842594f53 | ||
|
|
7db056102c | ||
|
|
a5a800fa0a | ||
|
|
9147ec148d | ||
|
|
b3260588c6 | ||
|
|
7d31328271 | ||
|
|
6e82981ee3 | ||
|
|
9d7b115bb5 | ||
|
|
8eae5002a3 | ||
|
|
31bd6c0371 | ||
|
|
7585f9d537 | ||
|
|
76037cdf72 | ||
|
|
98c5421edc | ||
|
|
e63fc7e3f5 | ||
|
|
6ed9cf3fb4 | ||
|
|
9865eab2c0 | ||
|
|
678e72a8b6 | ||
|
|
ec41899089 | ||
|
|
b2d913cc21 | ||
|
|
bc86c24e6a | ||
|
|
87a77dd95c | ||
|
|
e8188f3432 | ||
|
|
50506be546 | ||
|
|
4ded028258 | ||
|
|
6da8b3c4a1 | ||
|
|
d5c92cbcb3 | ||
|
|
ed5f98d6f0 | ||
|
|
f854b8f908 | ||
|
|
de7a6159d4 | ||
|
|
6090a34037 | ||
|
|
f566745479 | ||
|
|
153234b623 | ||
|
|
ac510d21ff | ||
|
|
44fa2c5800 | ||
|
|
d785fc2a54 | ||
|
|
ea800e04bc | ||
|
|
fe582ac635 | ||
|
|
330edb3bce | ||
|
|
212fec7115 | ||
|
|
24d7021c47 | ||
|
|
e3a01ff6a8 | ||
|
|
81f2ba8a46 | ||
|
|
9e9370b178 | ||
|
|
ced6114a95 | ||
|
|
3144faae5d | ||
|
|
8960c67a82 | ||
|
|
f8ca924434 | ||
|
|
399a08775e | ||
|
|
92f36ca558 | ||
|
|
3dcc58205a | ||
|
|
09779962cf | ||
|
|
9cc78770a3 | ||
|
|
f653ca9131 | ||
|
|
6f9fd91849 | ||
|
|
cb1aec4fc0 | ||
|
|
7cebaf8a76 | ||
|
|
241c943424 | ||
|
|
d5d88d8cf0 | ||
|
|
cf9d26068c | ||
|
|
308a93dc72 | ||
|
|
d6a7e65e4c | ||
|
|
e0a5c5d3b8 | ||
|
|
314f775243 | ||
|
|
7a1644135a | ||
|
|
5076326589 | ||
|
|
ce56261b52 | ||
|
|
baa0e897b2 | ||
|
|
1d49c0e1ce | ||
|
|
08755e446e | ||
|
|
bb12d9dadb | ||
|
|
fd1429fef0 | ||
|
|
d3c421a4a8 | ||
|
|
0c919da4b1 | ||
|
|
9afbf1255f | ||
|
|
50b105c4af | ||
|
|
028508c1f7 | ||
|
|
f0137a3695 | ||
|
|
e6d3a1718c | ||
|
|
86ba551e07 | ||
|
|
26418be937 | ||
|
|
092a19bdc1 | ||
|
|
6d3398574c | ||
|
|
b08969ad89 | ||
|
|
0653656526 | ||
|
|
7a5793c562 | ||
|
|
562ff7807d | ||
|
|
7971bdf7f7 | ||
|
|
d926b7fd6d | ||
|
|
c00404793a | ||
|
|
a0e0ee6c1e | ||
|
|
4ccbee705b | ||
|
|
db43d55b2c | ||
|
|
5a3a333eec | ||
|
|
039edf1616 | ||
|
|
47498bbf23 | ||
|
|
cc28bf4ae2 | ||
|
|
0e8736045e | ||
|
|
19b581edef | ||
|
|
295f6656d9 | ||
|
|
1214d8c14d | ||
|
|
b4cd96fc9a | ||
|
|
3238a9b898 | ||
|
|
c0f66320f6 | ||
|
|
383220f384 | ||
|
|
76a9c37e6b | ||
|
|
e788e6a5ad | ||
|
|
d00e912934 | ||
|
|
8ebb663368 | ||
|
|
445ffc4123 | ||
|
|
6af49f4d55 | ||
|
|
1de9e8c086 | ||
|
|
59b0812adf | ||
|
|
719785c1ed | ||
|
|
8e5f627e59 | ||
|
|
5ced3c72b8 | ||
|
|
c002f0168c | ||
|
|
00c690f516 | ||
|
|
ab68ad5cc5 | ||
|
|
5c84ebefab | ||
|
|
eb2acaff22 | ||
|
|
84d0655c52 | ||
|
|
e137ebb9c2 | ||
|
|
10d690d929 | ||
|
|
14611d2fd9 | ||
|
|
0665bfe15f | ||
|
|
473096d35d | ||
|
|
0eae26e261 | ||
|
|
a32845f652 | ||
|
|
15a0f7eadb | ||
|
|
5a0a6abf11 | ||
|
|
032b8d9572 | ||
|
|
5798e3af83 | ||
|
|
8e15b9ce1c | ||
|
|
7a1f132c1f | ||
|
|
a8483b2195 | ||
|
|
83bbbd0cb0 | ||
|
|
132432dce6 | ||
|
|
e5eb8e42f5 | ||
|
|
1095ebea24 | ||
|
|
1541a602b2 | ||
|
|
03a141c252 | ||
|
|
5f2183fc8e | ||
|
|
820831fa5d | ||
|
|
6d2d767c52 | ||
|
|
e0c3a728ae | ||
|
|
ec92f7797f | ||
|
|
0ba490c6df | ||
|
|
cfd668e11d | ||
|
|
a8bc25321e | ||
|
|
fec13bcb86 | ||
|
|
cb1c07f998 | ||
|
|
6312b97faa | ||
|
|
21f13b55eb | ||
|
|
187598382b | ||
|
|
551fdd5022 | ||
|
|
58b0d03e28 | ||
|
|
3790197699 | ||
|
|
579fff122c | ||
|
|
feb3f79a13 | ||
|
|
b5cb08ac43 | ||
|
|
4ac5d9e0da | ||
|
|
93f741da35 | ||
|
|
648a999514 | ||
|
|
71490aebd9 | ||
|
|
9e90c0f912 | ||
|
|
de65073f61 | ||
|
|
6129ac7bd4 | ||
|
|
b5d4d27312 | ||
|
|
823fcd91f4 | ||
|
|
477e12d5cf | ||
|
|
a36a226ae2 | ||
|
|
886a21c633 | ||
|
|
fd19fa2082 | ||
|
|
843f1a462f | ||
|
|
5c5b8a361d | ||
|
|
417df0582d | ||
|
|
999d8f5866 | ||
|
|
47a444e795 | ||
|
|
dbceca8780 | ||
|
|
c66898e608 | ||
|
|
ee20cb59a5 | ||
|
|
5c51d83573 | ||
|
|
47b3b3848b | ||
|
|
95eb980f58 | ||
|
|
f738622c28 | ||
|
|
577509bbf9 | ||
|
|
774c78add0 | ||
|
|
b14406e329 | ||
|
|
29cf4bb517 | ||
|
|
a233e08929 | ||
|
|
cbd1c12773 | ||
|
|
0a3f0f9ffc | ||
|
|
d3014025b0 | ||
|
|
2887dc0d36 | ||
|
|
5f49e7da8e | ||
|
|
9e0032b258 | ||
|
|
008da49b83 | ||
|
|
9899cba816 | ||
|
|
27724a2faf | ||
|
|
8b6a283114 | ||
|
|
4379b8bacf | ||
|
|
56603dcfae | ||
|
|
1752736714 | ||
|
|
b1428b6758 | ||
|
|
9b6d84def6 | ||
|
|
ed162d7d6e | ||
|
|
1aae425945 | ||
|
|
26e447f11a | ||
|
|
ffbaa0a508 | ||
|
|
a9ebac3818 | ||
|
|
738e9fb119 | ||
|
|
7778783dd8 | ||
|
|
c442a433b0 | ||
|
|
f7aa85746d | ||
|
|
1883da3b2a | ||
|
|
997dd6022f | ||
|
|
63394a2400 | ||
|
|
a662b038dc | ||
|
|
e9df2bfa01 | ||
|
|
a7951b727c | ||
|
|
c6ad9ea57a | ||
|
|
a14810bbd4 | ||
|
|
bc5a95ebb3 | ||
|
|
306182e2ae | ||
|
|
ad096196ee | ||
|
|
af66e44427 | ||
|
|
0a012273ec | ||
|
|
73b011eba7 | ||
|
|
a31974a3c0 | ||
|
|
eb02bdd95a | ||
|
|
74805c6be8 | ||
|
|
d9bc4499a4 | ||
|
|
9128e2748b | ||
|
|
7f8c975bd7 | ||
|
|
8b6c841b1e | ||
|
|
4fcdea3ccb | ||
|
|
3be11cf52f | ||
|
|
b285cb0e57 | ||
|
|
dd5a7920e5 | ||
|
|
cfb848918f | ||
|
|
b977558f38 | ||
|
|
210e3dc990 | ||
|
|
f36671784e | ||
|
|
d626cc8a8b | ||
|
|
f26b61d773 | ||
|
|
12c2d3cbc6 | ||
|
|
209ca704de | ||
|
|
2e37d3adc1 | ||
|
|
509fb045b6 | ||
|
|
a2c364f9eb | ||
|
|
17a4e532c1 | ||
|
|
c103b79ec2 | ||
|
|
b545b5d0a3 | ||
|
|
342a1c6cff | ||
|
|
aafbdcd34d | ||
|
|
ec092501c3 | ||
|
|
bb708db89f | ||
|
|
085a9dcb79 | ||
|
|
037e12b0bd | ||
|
|
c9ab956f8f | ||
|
|
587c87b3a0 | ||
|
|
1a319859eb | ||
|
|
c989c31aeb | ||
|
|
e5d32c8764 | ||
|
|
23c177ed4a | ||
|
|
10a27042b5 | ||
|
|
2cec20c7ee | ||
|
|
7ecd09f497 | ||
|
|
8bf7f6cac5 | ||
|
|
067a2315df | ||
|
|
fecd1ad464 | ||
|
|
a3f2555bc1 | ||
|
|
5bf4cd46ff | ||
|
|
f878e225cc | ||
|
|
eb2598f3b3 | ||
|
|
e20a59b991 | ||
|
|
703c142659 | ||
|
|
8335b40368 | ||
|
|
05884c2d29 | ||
|
|
33b2aa2d52 | ||
|
|
9ab0622886 | ||
|
|
b33cd54916 | ||
|
|
d4bec0dd9a | ||
|
|
bdf6efeaac | ||
|
|
74431ca63f | ||
|
|
c90be385ef | ||
|
|
b0d9c0b550 | ||
|
|
9255132f9b | ||
|
|
d5c0092fa3 | ||
|
|
c7019debb9 | ||
|
|
7131270cad | ||
|
|
af5a1204bc | ||
|
|
58afcfc49a | ||
|
|
986762ca85 | ||
|
|
6342cf79f5 | ||
|
|
5fbf67f971 | ||
|
|
e441e5a696 | ||
|
|
d201efb029 | ||
|
|
25960126c7 | ||
|
|
63d5a6f584 | ||
|
|
2030951a8f | ||
|
|
cd841462cd | ||
|
|
735aa835a6 | ||
|
|
92e213ca32 | ||
|
|
d077c29716 | ||
|
|
d6eba48a50 | ||
|
|
2a1608d1d2 | ||
|
|
cc7d3dc2aa | ||
|
|
a5c4c682f5 | ||
|
|
688cfd6872 | ||
|
|
7e268dbae1 | ||
|
|
ce6a4231ef | ||
|
|
e1de8ab626 | ||
|
|
0058eaf357 | ||
|
|
732d95098a | ||
|
|
52f0943207 | ||
|
|
41f99f2b65 | ||
|
|
1f9e5c6263 | ||
|
|
2f3eddd2ab | ||
|
|
619a0ee700 | ||
|
|
b1b5c2c9a0 | ||
|
|
a86035c0bf | ||
|
|
c66b0f4db4 | ||
|
|
a4cf4bd314 | ||
|
|
f1cd9383c1 | ||
|
|
6fa57abe10 | ||
|
|
6e77c714b5 | ||
|
|
fbab020e6e | ||
|
|
5581a5cce7 | ||
|
|
b4be11775e | ||
|
|
b079f5e52e | ||
|
|
f9bf470a37 | ||
|
|
9d783dd2ab | ||
|
|
1b9aafbbaf | ||
|
|
1d3ee6a241 | ||
|
|
2f9c3071a6 | ||
|
|
4b0be4f115 | ||
|
|
1419c7c8c6 | ||
|
|
851cecdd73 | ||
|
|
753da3aad7 | ||
|
|
65c10d6d8e | ||
|
|
1b8b423131 | ||
|
|
55b1264c7d | ||
|
|
902a1888d4 | ||
|
|
98151f7d0e | ||
|
|
a6f0c559f8 | ||
|
|
e7ec5b841d | ||
|
|
d6f72ac0f3 | ||
|
|
7e3a10025a | ||
|
|
e16ec15226 | ||
|
|
6935b56c9d | ||
|
|
0e3a0b64e7 | ||
|
|
74e6aee236 | ||
|
|
db0602b7b8 | ||
|
|
c9b7c3f179 | ||
|
|
5bd9f4afb4 | ||
|
|
9d2ba5912e | ||
|
|
9986c4a6f3 | ||
|
|
df2c9697ef | ||
|
|
ab0388e882 | ||
|
|
c05d8a36eb | ||
|
|
492753d905 | ||
|
|
6e08bd23f4 | ||
|
|
a687c97808 | ||
|
|
c6864289cb | ||
|
|
97d85258c5 | ||
|
|
bee25f5aa2 | ||
|
|
386b97d2be | ||
|
|
00660485b7 | ||
|
|
1e8f24dedb | ||
|
|
2be190f863 | ||
|
|
ec7c6e6c85 | ||
|
|
c52bc53fd8 | ||
|
|
981631503a | ||
|
|
48de3a6a4f | ||
|
|
d1983a6978 | ||
|
|
f821a26aec | ||
|
|
3380e905de | ||
|
|
b5c2718756 | ||
|
|
a03a803b89 | ||
|
|
e743177ae6 | ||
|
|
6e12c69953 | ||
|
|
019ab77466 | ||
|
|
1730caf124 | ||
|
|
59d1533795 | ||
|
|
a6278ab7ea | ||
|
|
42a6004c7d | ||
|
|
6084c1b1d3 | ||
|
|
c96fbc1dba | ||
|
|
5546a8b093 | ||
|
|
6b76b38dcd | ||
|
|
941e50b460 | ||
|
|
5a10e5c9ff | ||
|
|
883fe13756 | ||
|
|
2e7c34cf9f | ||
|
|
9216efbd2f | ||
|
|
6c8100e5b6 | ||
|
|
e7ef50bedf | ||
|
|
386ca3565a | ||
|
|
2d854cd64d | ||
|
|
49b4b8be22 | ||
|
|
db975ebfee | ||
|
|
d60a41139b | ||
|
|
f62d869d27 | ||
|
|
6cbe3cdb93 | ||
|
|
b13e7b9da4 | ||
|
|
8fe34c8474 | ||
|
|
bef29be50f | ||
|
|
20275a1063 | ||
|
|
910385b084 | ||
|
|
8e779374a7 | ||
|
|
44fc6f728e | ||
|
|
1f62dcf22a | ||
|
|
0416c3b561 | ||
|
|
a6912cae76 | ||
|
|
63dfe8a952 | ||
|
|
62d1b761bd | ||
|
|
082b10a15b | ||
|
|
1a6bcd82b0 | ||
|
|
6ecd70220b | ||
|
|
e9f55f5772 | ||
|
|
155cadf901 | ||
|
|
cb29289167 | ||
|
|
e4db9d1d91 | ||
|
|
7b2e2cb817 | ||
|
|
c717f8d15d | ||
|
|
8db147acab | ||
|
|
e6de7aa9ca | ||
|
|
46f96740a2 | ||
|
|
8f9fb5c262 | ||
|
|
171d6d6684 | ||
|
|
f648b5ad0a | ||
|
|
ef21376f0a | ||
|
|
58958d68d8 | ||
|
|
a06b565ee9 | ||
|
|
a7db27ce5a | ||
|
|
cda69dc7f0 | ||
|
|
39f9594548 | ||
|
|
6d82ad32a9 | ||
|
|
cfcd8bf223 | ||
|
|
8149ad00b5 | ||
|
|
2310522806 | ||
|
|
e40ef656d6 | ||
|
|
e060d40a32 | ||
|
|
a522218c4e | ||
|
|
820455399c | ||
|
|
959d612534 | ||
|
|
cd81e6eab2 | ||
|
|
e6ec6920ad | ||
|
|
18a92fa1ca | ||
|
|
f95af9897b | ||
|
|
b61adcb1fd | ||
|
|
1bbf320755 | ||
|
|
159f26171c | ||
|
|
8ac00f6c0d | ||
|
|
ce2daf2493 | ||
|
|
f014f8fd59 | ||
|
|
f50a39a9e2 | ||
|
|
e0d8147104 | ||
|
|
c5cfac62da | ||
|
|
83469ce5cc | ||
|
|
7cd7b4a9a2 | ||
|
|
7681b277cf | ||
|
|
406efa96c0 | ||
|
|
9a7a30c0bc | ||
|
|
64bdfa0e80 | ||
|
|
067089973c | ||
|
|
85e6d753c7 | ||
|
|
4094984642 | ||
|
|
85c0009a43 | ||
|
|
234e312ee2 | ||
|
|
ce3ca64678 | ||
|
|
b042a600c3 | ||
|
|
686e9f07a9 | ||
|
|
bb6725372b | ||
|
|
6f012fc9c5 | ||
|
|
4c82458481 | ||
|
|
a0ac863998 | ||
|
|
d23ef838f8 | ||
|
|
f81ac197f5 | ||
|
|
652b37e630 | ||
|
|
c57e430393 | ||
|
|
fff6047df9 | ||
|
|
1e2b93d55b | ||
|
|
66b27a7795 | ||
|
|
63f0a272c4 | ||
|
|
8d2180cf5a | ||
|
|
1986f7e4dd | ||
|
|
21beb396b4 | ||
|
|
cb5a6f38d6 | ||
|
|
67e4aaede0 | ||
|
|
b42805d00c | ||
|
|
95d6888c87 | ||
|
|
549b315a65 | ||
|
|
5b80b16684 | ||
|
|
0cd0a4bf2b | ||
|
|
b5cf06cad8 | ||
|
|
b964d19d82 | ||
|
|
cf7990d444 | ||
|
|
738ccf7dbb | ||
|
|
fc2ea48c1d | ||
|
|
3af93b93d7 | ||
|
|
f386c3be92 | ||
|
|
239d910dbe | ||
|
|
48929deabd | ||
|
|
79523de1db | ||
|
|
fbfc14dfeb | ||
|
|
a8dc886f89 | ||
|
|
cfc9e064b9 | ||
|
|
e72fa3362a | ||
|
|
26364421e8 | ||
|
|
4a07974b54 | ||
|
|
eaddc7f2ba | ||
|
|
85056aaa00 | ||
|
|
c077c740fa | ||
|
|
c2eab87a3f | ||
|
|
ea582d2d2e | ||
|
|
2f89a24100 | ||
|
|
73ebb94f67 | ||
|
|
95bf387ecc | ||
|
|
f17a8452f9 | ||
|
|
920ffe1f33 | ||
|
|
093bcb7477 | ||
|
|
c06b3ec9eb | ||
|
|
ac6fe6f9fc | ||
|
|
2dffdaac42 | ||
|
|
cb445c9504 | ||
|
|
e3fc3aa9d1 | ||
|
|
97c3f5d642 | ||
|
|
0a52fc9a56 | ||
|
|
c831339b0d | ||
|
|
058ccf575f | ||
|
|
92be12bc2f | ||
|
|
1aa2f4b5b1 | ||
|
|
bba9431985 | ||
|
|
3c39f1e737 | ||
|
|
e6f4d07a87 | ||
|
|
e43358a0d2 | ||
|
|
f0644e8a9d | ||
|
|
11b010b281 | ||
|
|
c751029127 | ||
|
|
fb70d1b2f0 | ||
|
|
3d68783b7f | ||
|
|
0d77853912 | ||
|
|
ea1b5dd8f7 | ||
|
|
2dcb7d5ce1 | ||
|
|
99cab34527 | ||
|
|
f5eeed0bc2 | ||
|
|
1b85e56961 | ||
|
|
8a8ac5fd22 | ||
|
|
00c0354a8e | ||
|
|
a2a6973ba1 | ||
|
|
dd1d3a05fa | ||
|
|
2afe2d2640 | ||
|
|
29678f9b59 | ||
|
|
77edb251bb | ||
|
|
29151fa267 | ||
|
|
b3f13790bd | ||
|
|
38857c3356 | ||
|
|
d75990d9fd | ||
|
|
ed063f6534 | ||
|
|
c8a9bdc517 | ||
|
|
595729cdf8 | ||
|
|
6119f79748 | ||
|
|
d4fb46c9ba | ||
|
|
c41301afca | ||
|
|
50fd80830e | ||
|
|
1c203b4272 | ||
|
|
c545e9045d | ||
|
|
2721dc0647 | ||
|
|
51d13f4234 | ||
|
|
a60a5d6eab | ||
|
|
5959235425 | ||
|
|
d8e6d4e5fc | ||
|
|
7dfc9815b3 | ||
|
|
0c53b187a4 | ||
|
|
42dadfed8f | ||
|
|
a46c603c77 | ||
|
|
ad0020d9a6 | ||
|
|
a224f0bfd4 | ||
|
|
d8dc3650d3 | ||
|
|
30f7527f10 | ||
|
|
b1f5bdd8b2 | ||
|
|
c8e7c8b9fa | ||
|
|
30bf3223f8 | ||
|
|
886710ec30 | ||
|
|
510dc8d828 | ||
|
|
5ff7b2aab4 | ||
|
|
1e33536205 | ||
|
|
8b264a564a | ||
|
|
227da93c13 | ||
|
|
f939041606 | ||
|
|
e5b1a0bef8 | ||
|
|
b9404d0880 | ||
|
|
d6f12868be | ||
|
|
b79e96f6cf | ||
|
|
b066cc819e | ||
|
|
4b669a0d49 | ||
|
|
5e9de5d91a | ||
|
|
da68b061e3 | ||
|
|
6c3802071f | ||
|
|
ad84f09bce | ||
|
|
04166632d3 | ||
|
|
376238b1ad | ||
|
|
4f0dbff059 | ||
|
|
f506e2b50a | ||
|
|
88d2fbf5e2 | ||
|
|
7fd8cc5449 | ||
|
|
d033463b34 | ||
|
|
740208cf74 | ||
|
|
0036c0b10e | ||
|
|
834c832390 | ||
|
|
5bc99dfd25 | ||
|
|
c92d2d064a | ||
|
|
a60c21323c | ||
|
|
34d6d6e709 | ||
|
|
f2ddafc718 | ||
|
|
267afdd15d | ||
|
|
48b7b82e33 | ||
|
|
84e5e5432e | ||
|
|
201e18eac2 | ||
|
|
3f3f0b1fec | ||
|
|
ca697c5038 | ||
|
|
5aeeb4e8b4 | ||
|
|
c285f9f587 | ||
|
|
d046608426 | ||
|
|
b91ed9cff5 | ||
|
|
185d85bfdd | ||
|
|
44b2c1464a | ||
|
|
a0762a0a6c | ||
|
|
2ad7660c09 | ||
|
|
d8b8c38182 | ||
|
|
1d50e5126a | ||
|
|
aa55e30358 | ||
|
|
f662de50db | ||
|
|
24c798ad3a | ||
|
|
0e304ae546 | ||
|
|
cd604cbfe7 | ||
|
|
b8e66d9df0 | ||
|
|
a2c738e57b | ||
|
|
ae16cd708c | ||
|
|
2ed0443f88 | ||
|
|
38f1c5075d | ||
|
|
55043a6348 | ||
|
|
1f6eb55b86 | ||
|
|
d9d8500484 | ||
|
|
0fca75c2db | ||
|
|
a7dcccbdf9 | ||
|
|
396eb5aec2 | ||
|
|
79d2076e09 | ||
|
|
693dca4ca2 | ||
|
|
4047076033 | ||
|
|
acb0b71f1b | ||
|
|
32d9352048 | ||
|
|
0246556f7c | ||
|
|
a17284681f | ||
|
|
adb66e3298 | ||
|
|
ad062d777d | ||
|
|
ffe1ff73a5 | ||
|
|
54f9202d74 | ||
|
|
ef3e173fb2 | ||
|
|
1aeec2ae51 | ||
|
|
1f50bfd801 | ||
|
|
d3466eabe5 | ||
|
|
8aff1af939 | ||
|
|
af35303432 | ||
|
|
0ef1a5a3ce | ||
|
|
e958bc8212 | ||
|
|
e0ca6e89d1 | ||
|
|
55d8ae124a | ||
|
|
5e28ec22e1 | ||
|
|
c3deb93489 | ||
|
|
a9aca94848 | ||
|
|
f3c06890dd | ||
|
|
d9d0e629fd | ||
|
|
17181405e3 | ||
|
|
c209564945 | ||
|
|
2da01db438 | ||
|
|
8c4913d411 | ||
|
|
e7ffc24844 | ||
|
|
259f23f6ee | ||
|
|
0de38b99c2 | ||
|
|
1044fb8574 | ||
|
|
e5bfa1bd6f | ||
|
|
a29b2a2ad9 | ||
|
|
b6899ce461 | ||
|
|
32c11af07c | ||
|
|
6ff55d24d0 | ||
|
|
055aacd7f6 | ||
|
|
5ecf58fd56 | ||
|
|
8a9106052f | ||
|
|
91264547c9 | ||
|
|
3190b877ae | ||
|
|
f8a8cc4676 | ||
|
|
93ee329315 | ||
|
|
b45163388d | ||
|
|
6029784f76 | ||
|
|
058ab55a6f | ||
|
|
1005d241b8 | ||
|
|
33b1ccba67 | ||
|
|
a5549fb500 | ||
|
|
b057ed3e77 | ||
|
|
1e88cc10e7 | ||
|
|
2f8634383e | ||
|
|
86f9e5ce96 | ||
|
|
9ae42d647c | ||
|
|
54d6217b93 | ||
|
|
150b1c2406 | ||
|
|
51b6f1b5f3 | ||
|
|
3eae14cef6 | ||
|
|
cc6dc1ca69 | ||
|
|
7f2361f58c | ||
|
|
7cb02d77ae | ||
|
|
52cc9b0cc0 | ||
|
|
d91bf61038 | ||
|
|
d5f81674f8 | ||
|
|
9381883835 | ||
|
|
f82e5a281d | ||
|
|
904e6241e4 | ||
|
|
ce39a3daf9 | ||
|
|
f2c7f74beb | ||
|
|
20db997fc2 | ||
|
|
7188e97444 | ||
|
|
6d528e741d | ||
|
|
d356e8370d | ||
|
|
5e336b5928 | ||
|
|
787ad0629b | ||
|
|
53e4adf24e | ||
|
|
6af811d63e | ||
|
|
359dab3380 | ||
|
|
97a8e6e965 | ||
|
|
8ea699aa08 | ||
|
|
7d924d2b0c | ||
|
|
3c85613ada | ||
|
|
c536d26db3 | ||
|
|
4350ff2692 | ||
|
|
0b9a1e7bb4 | ||
|
|
714ad18fa0 | ||
|
|
f81f785813 | ||
|
|
76c32af46f | ||
|
|
cd108263e1 | ||
|
|
093c47b59c | ||
|
|
56a40ec51a | ||
|
|
1337be2b84 | ||
|
|
eecd2c60f5 | ||
|
|
da071cb120 | ||
|
|
012cfa3cbe | ||
|
|
21180847dc | ||
|
|
9e9e538846 | ||
|
|
66025b1ae2 | ||
|
|
5999361358 | ||
|
|
e8699d1cb7 | ||
|
|
9292448e73 | ||
|
|
d7e156613d | ||
|
|
c3604aa66d | ||
|
|
49dd12fef3 | ||
|
|
5e037b1743 | ||
|
|
ebc79805ed | ||
|
|
c37e56e51d | ||
|
|
28a93c02e6 | ||
|
|
0996c58894 | ||
|
|
56ecf32565 | ||
|
|
416fb3c937 | ||
|
|
d48b8315c9 | ||
|
|
7c6d1eb585 | ||
|
|
fae04dce81 | ||
|
|
86a5433312 | ||
|
|
d9cf63a9fe | ||
|
|
88bf643363 | ||
|
|
e0b680b305 | ||
|
|
d6356408b8 | ||
|
|
4d28de17b4 | ||
|
|
fdd918d970 | ||
|
|
da16f9673e | ||
|
|
b02b7c9081 | ||
|
|
ea82149dbe | ||
|
|
9d64f039ab | ||
|
|
cd9cbd795b | ||
|
|
929d561de8 | ||
|
|
245abe5b6b | ||
|
|
768364fc77 | ||
|
|
60a3e9532a | ||
|
|
dcd6ba0a82 | ||
|
|
9f2dc2c6a3 | ||
|
|
7498a540d4 | ||
|
|
26ae01d960 | ||
|
|
f72781c30c | ||
|
|
21e957159d | ||
|
|
a66b425da0 | ||
|
|
804fffd009 | ||
|
|
ac77cc1f87 | ||
|
|
d0d360a6e7 |
26
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=10m
|
||||
|
||||
- name: testing
|
||||
run: go test -race ./...
|
||||
29
.gitignore
vendored
@@ -1,9 +1,24 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
.vscode
|
||||
coverage.out
|
||||
issues/
|
||||
*.txt
|
||||
vendor/
|
||||
log/
|
||||
.gitmodules
|
||||
|
||||
# Vuls
|
||||
vuls
|
||||
*.sqlite3
|
||||
!cmd/vuls
|
||||
vuls.db
|
||||
config.json
|
||||
results
|
||||
19
CHANGELOG.md
@@ -1,19 +0,0 @@
|
||||
# Change Log
|
||||
|
||||
## [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)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Typo in Example [\#6](https://github.com/future-architect/vuls/pull/6) ([toli](https://github.com/toli))
|
||||
|
||||
## [v0.1.0](https://github.com/future-architect/vuls/tree/v0.1.0) (2016-04-04)
|
||||
**Merged pull requests:**
|
||||
|
||||
- English translation [\#4](https://github.com/future-architect/vuls/pull/4) ([hikachan](https://github.com/hikachan))
|
||||
- English translation [\#3](https://github.com/future-architect/vuls/pull/3) ([chewyinping](https://github.com/chewyinping))
|
||||
- Add a Bitdeli Badge to README [\#2](https://github.com/future-architect/vuls/pull/2) ([bitdeli-chef](https://github.com/bitdeli-chef))
|
||||
|
||||
|
||||
|
||||
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
|
||||
42
GNUmakefile
Normal file
@@ -0,0 +1,42 @@
|
||||
VERSION := $(shell git describe --tags --abbrev=0)
|
||||
ifeq ($(VERSION), )
|
||||
VERSION := $(shell git rev-parse --abbrev-ref HEAD)
|
||||
endif
|
||||
ifeq ($(shell git rev-parse --abbrev-ref HEAD), nightly)
|
||||
VERSION := nightly
|
||||
endif
|
||||
REVISION := $(shell git rev-parse --short HEAD)
|
||||
LDFLAGS := -ldflags "-s -w -X=github.com/future-architect/vuls/pkg/cmd/version.Version=$(VERSION) -X=github.com/future-architect/vuls/pkg/cmd/version.Revision=$(REVISION)"
|
||||
|
||||
GOPATH := $(shell go env GOPATH)
|
||||
GOBIN := $(GOPATH)/bin
|
||||
|
||||
$(GOBIN)/golangci-lint:
|
||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build $(LDFLAGS) ./cmd/vuls
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
go install $(LDFLAGS) ./cmd/vuls
|
||||
|
||||
.PHONY: test
|
||||
test: pretest
|
||||
go test -race ./...
|
||||
|
||||
.PHONY: pretest
|
||||
pretest: lint vet fmtcheck
|
||||
|
||||
.PHONY: lint
|
||||
lint: $(GOBIN)/golangci-lint
|
||||
golangci-lint run
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
.PHONY: fmtcheck
|
||||
fmtcheck:
|
||||
gofmt -s -d .
|
||||
674
LICENSE
@@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
Vuls Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
52
Makefile
@@ -1,52 +0,0 @@
|
||||
.PHONY: \
|
||||
all \
|
||||
vendor \
|
||||
lint \
|
||||
vet \
|
||||
fmt \
|
||||
fmtcheck \
|
||||
pretest \
|
||||
test \
|
||||
integration \
|
||||
cov \
|
||||
clean
|
||||
|
||||
SRCS = $(shell git ls-files '*.go')
|
||||
PKGS = ./. ./db ./config ./models ./report ./cveapi ./scan ./util ./commands
|
||||
|
||||
all: test
|
||||
|
||||
vendor:
|
||||
@ go get -v github.com/mjibson/party
|
||||
party -d external -c -u
|
||||
|
||||
lint:
|
||||
@ go get -v github.com/golang/lint/golint
|
||||
$(foreach file,$(SRCS),golint $(file) || exit;)
|
||||
|
||||
vet:
|
||||
@-go get -v golang.org/x/tools/cmd/vet
|
||||
$(foreach pkg,$(PKGS),go vet $(pkg);)
|
||||
|
||||
fmt:
|
||||
gofmt -w $(SRCS)
|
||||
|
||||
fmtcheck:
|
||||
$(foreach file,$(SRCS),gofmt -d $(file);)
|
||||
|
||||
pretest: lint vet fmtcheck
|
||||
|
||||
test: pretest
|
||||
$(foreach pkg,$(PKGS),go test -v $(pkg) || exit;)
|
||||
|
||||
unused :
|
||||
$(foreach pkg,$(PKGS),unused $(pkg);)
|
||||
|
||||
cov:
|
||||
@ go get -v github.com/axw/gocov/gocov
|
||||
@ go get golang.org/x/tools/cmd/cover
|
||||
gocov test | gocov report
|
||||
|
||||
clean:
|
||||
$(foreach pkg,$(PKGS),go clean $(pkg) || exit;)
|
||||
|
||||
109
README.ja.md
@@ -1,109 +0,0 @@
|
||||
|
||||
# Vuls: VULnerability Scanner
|
||||
|
||||
[](http://goo.gl/forms/xm5KFo35tu)
|
||||
|
||||
Vulnerability scanner for Linux, agentless, written in golang.
|
||||
|
||||
[README in English](https://github.com/future-architect/vuls/blob/master/README.md)
|
||||
Slackチームは[こちらから](http://goo.gl/forms/xm5KFo35tu)参加できます。(日本語でオッケーです)
|
||||
|
||||
[](https://asciinema.org/a/bazozlxrw1wtxfu9yojyihick)
|
||||
|
||||

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

|
||||
|
||||
----
|
||||
|
||||
# Main Features
|
||||
|
||||
- Linuxサーバに存在する脆弱性をスキャン
|
||||
- Ubuntu, Debian, CentOS, Amazon Linux, RHELに対応
|
||||
- クラウド、オンプレミス、Docker
|
||||
- OSパッケージ管理対象外のミドルウェアをスキャン
|
||||
- プログラミング言語のライブラリやフレームワーク、ミドルウェアの脆弱性スキャン
|
||||
- CPEに登録されているソフトウェアが対象
|
||||
- エージェントレスアーキテクチャ
|
||||
- スキャン対象サーバにSSH接続可能なマシン1台にセットアップするだけで動作
|
||||
- 設定ファイルのテンプレート自動生成
|
||||
- CIDRを指定してサーバを自動検出、設定ファイルのテンプレートを生成
|
||||
- EmailやSlackで通知可能(日本語でのレポートも可能)
|
||||
- 付属するTerminal-Based User Interfaceビューアでは、Vim風キーバインドでスキャン結果を参照可能
|
||||
|
||||
----
|
||||
|
||||
詳細は[README in English](https://github.com/future-architect/vuls/blob/master/README.md) を参照
|
||||
|
||||
# レポートの日本語化
|
||||
|
||||
- JVNから日本語の脆弱性情報を取得
|
||||
```
|
||||
$ go-cve-dictionary fetchjvn -help
|
||||
fetchjvn:
|
||||
fetchjvn [-dump-path=$PWD/cve] [-dpath=$PWD/vuls.sqlite3] [-week] [-month] [-entire]
|
||||
|
||||
-dbpath string
|
||||
/path/to/sqlite3/DBfile (default "$PWD/cve.sqlite3")
|
||||
-debug
|
||||
debug mode
|
||||
-debug-sql
|
||||
SQL debug mode
|
||||
-dump-path string
|
||||
/path/to/dump.json (default "$PWD/cve.json")
|
||||
-entire
|
||||
Fetch data for entire period.(This operation is time-consuming) (default: false)
|
||||
-month
|
||||
Fetch data in the last month (default: false)
|
||||
-week
|
||||
Fetch data in the last week. (default: false)
|
||||
|
||||
```
|
||||
|
||||
- すべての期間の脆弱性情報を取得(1時間以上かかる)
|
||||
```
|
||||
$ go-cve-dictionary fetchjvn -entire
|
||||
```
|
||||
|
||||
- 直近1ヶ月間に更新された脆弱性情報を取得(1分未満)
|
||||
```
|
||||
$ go-cve-dictionary fetchjvn -month
|
||||
```
|
||||
|
||||
- 直近1週間に更新された脆弱性情報を取得(1分未満)
|
||||
```
|
||||
$ go-cve-dictionary fetchjvn -week
|
||||
```
|
||||
|
||||
- 脆弱性情報の自動アップデート
|
||||
Cronなどのジョブスケジューラを用いて実現可能。
|
||||
-week オプションを指定して夜間の日次実行を推奨。
|
||||
|
||||
|
||||
## スキャン実行
|
||||
|
||||
```
|
||||
$ vuls scan -lang=ja
|
||||
```
|
||||
Scan時にlang=jaを指定すると脆弱性レポートが日本語になる
|
||||
slack, emailは日本語対応済み TUIは日本語表示未対応
|
||||
|
||||
15
cmd/vuls/main.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/cmd/root"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := root.NewCmdRoot().Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to exec vuls: %s\n", fmt.Sprintf("%+v", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -1,158 +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"
|
||||
#password = "password"
|
||||
#keyPath = "/home/username/.ssh/id_rsa"
|
||||
#keyPassword = "password"
|
||||
|
||||
[servers]
|
||||
{{- $names:= .Names}}
|
||||
{{range $i, $ip := .IPs}}
|
||||
[servers.{{index $names $i}}]
|
||||
host = "{{$ip}}"
|
||||
#port = "22"
|
||||
#user = "root"
|
||||
#password = "password"
|
||||
#keyPath = "/home/username/.ssh/id_rsa"
|
||||
#keyPassword = "password"
|
||||
#cpeNames = [
|
||||
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
|
||||
#]
|
||||
{{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,131 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/scan"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/google/subcommands"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// PrepareCmd is Subcommand of host discovery mode
|
||||
type PrepareCmd struct {
|
||||
debug bool
|
||||
configPath string
|
||||
|
||||
useUnattendedUpgrades bool
|
||||
}
|
||||
|
||||
// Name return subcommand name
|
||||
func (*PrepareCmd) Name() string { return "prepare" }
|
||||
|
||||
// Synopsis return synopsis
|
||||
func (*PrepareCmd) Synopsis() string {
|
||||
// return "Install packages Ubuntu: unattended-upgrade, CentOS: yum-plugin-security)"
|
||||
return `Install required packages to scan.
|
||||
CentOS: yum-plugin-security, yum-plugin-changelog
|
||||
Amazon: None
|
||||
RHEL: TODO
|
||||
Ubuntu: None
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
// Usage return usage
|
||||
func (*PrepareCmd) Usage() string {
|
||||
return `prepare:
|
||||
prepare [-config=/path/to/config.toml] [-debug]
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
// SetFlags set flag
|
||||
func (p *PrepareCmd) SetFlags(f *flag.FlagSet) {
|
||||
|
||||
f.BoolVar(&p.debug, "debug", false, "debug mode")
|
||||
|
||||
defaultConfPath := os.Getenv("PWD") + "/config.toml"
|
||||
f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
|
||||
|
||||
f.BoolVar(
|
||||
&p.useUnattendedUpgrades,
|
||||
"use-unattended-upgrades",
|
||||
false,
|
||||
"[Depricated] For Ubuntu, install unattended-upgrades",
|
||||
)
|
||||
}
|
||||
|
||||
// Execute execute
|
||||
func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
logrus.Infof("Start Preparing (config: %s)", p.configPath)
|
||||
|
||||
err := c.Load(p.configPath)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error loading %s, %s", p.configPath, err)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
target := make(map[string]c.ServerInfo)
|
||||
for _, arg := range f.Args() {
|
||||
found := false
|
||||
for servername, info := range c.Conf.Servers {
|
||||
if servername == arg {
|
||||
target[servername] = info
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
logrus.Errorf("%s is not in config", arg)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
}
|
||||
if 0 < len(f.Args()) {
|
||||
c.Conf.Servers = target
|
||||
}
|
||||
|
||||
c.Conf.Debug = p.debug
|
||||
c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
|
||||
|
||||
// Set up custom logger
|
||||
logger := util.NewCustomLogger(c.ServerInfo{})
|
||||
|
||||
logger.Info("Detecting OS... ")
|
||||
err = scan.InitServers(logger)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to init servers. err: %s", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
logger.Info("Installing...")
|
||||
if errs := scan.Prepare(); 0 < len(errs) {
|
||||
for _, e := range errs {
|
||||
logger.Errorf("Failed: %s", e)
|
||||
}
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
logger.Info("Success")
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
241
commands/scan.go
@@ -1,241 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
"github.com/future-architect/vuls/db"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/future-architect/vuls/scan"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/google/subcommands"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ScanCmd is Subcommand of host discovery mode
|
||||
type ScanCmd struct {
|
||||
lang string
|
||||
debug bool
|
||||
debugSQL bool
|
||||
|
||||
configPath string
|
||||
|
||||
dbpath string
|
||||
cveDictionaryURL string
|
||||
cvssScoreOver float64
|
||||
httpProxy string
|
||||
|
||||
useYumPluginSecurity bool
|
||||
useUnattendedUpgrades bool
|
||||
|
||||
// reporting
|
||||
reportSlack bool
|
||||
reportMail bool
|
||||
}
|
||||
|
||||
// Name return subcommand name
|
||||
func (*ScanCmd) Name() string { return "scan" }
|
||||
|
||||
// Synopsis return synopsis
|
||||
func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities" }
|
||||
|
||||
// Usage return usage
|
||||
func (*ScanCmd) Usage() string {
|
||||
return `scan:
|
||||
scan
|
||||
[-lang=en|ja]
|
||||
[-config=/path/to/config.toml]
|
||||
[-dbpath=/path/to/vuls.sqlite3]
|
||||
[-cve-dictionary-url=http://127.0.0.1:1323]
|
||||
[-cvss-over=7]
|
||||
[-report-slack]
|
||||
[-report-mail]
|
||||
[-http-proxy=http://192.168.0.1:8080]
|
||||
[-debug]
|
||||
[-debug-sql]
|
||||
`
|
||||
}
|
||||
|
||||
// SetFlags set flag
|
||||
func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&p.lang, "lang", "en", "[en|ja]")
|
||||
f.BoolVar(&p.debug, "debug", false, "debug mode")
|
||||
f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
|
||||
|
||||
defaultConfPath := os.Getenv("PWD") + "/config.toml"
|
||||
f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
|
||||
|
||||
defaultDBPath := os.Getenv("PWD") + "/vuls.sqlite3"
|
||||
f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3")
|
||||
|
||||
defaultURL := "http://127.0.0.1:1323"
|
||||
f.StringVar(
|
||||
&p.cveDictionaryURL,
|
||||
"cve-dictionary-url",
|
||||
defaultURL,
|
||||
"http://CVE.Dictionary")
|
||||
|
||||
f.Float64Var(
|
||||
&p.cvssScoreOver,
|
||||
"cvss-over",
|
||||
0,
|
||||
"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
|
||||
|
||||
f.StringVar(
|
||||
&p.httpProxy,
|
||||
"http-proxy",
|
||||
"",
|
||||
"http://proxy-url:port (default: empty)",
|
||||
)
|
||||
|
||||
f.BoolVar(&p.reportSlack, "report-slack", false, "Slack report")
|
||||
f.BoolVar(&p.reportMail, "report-mail", false, "Email report")
|
||||
|
||||
f.BoolVar(
|
||||
&p.useYumPluginSecurity,
|
||||
"use-yum-plugin-security",
|
||||
false,
|
||||
"[Depricated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)",
|
||||
)
|
||||
|
||||
f.BoolVar(
|
||||
&p.useUnattendedUpgrades,
|
||||
"use-unattended-upgrades",
|
||||
false,
|
||||
"[Depricated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)",
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
// Execute execute
|
||||
func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
|
||||
logrus.Infof("Start scanning (config: %s)", p.configPath)
|
||||
err := c.Load(p.configPath)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error loading %s, %s", p.configPath, err)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
target := make(map[string]c.ServerInfo)
|
||||
for _, arg := range f.Args() {
|
||||
found := false
|
||||
for servername, info := range c.Conf.Servers {
|
||||
if servername == arg {
|
||||
target[servername] = info
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
logrus.Errorf("%s is not in config", arg)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
}
|
||||
if 0 < len(f.Args()) {
|
||||
c.Conf.Servers = target
|
||||
}
|
||||
|
||||
c.Conf.Lang = p.lang
|
||||
c.Conf.Debug = p.debug
|
||||
c.Conf.DebugSQL = p.debugSQL
|
||||
|
||||
// logger
|
||||
Log := util.NewCustomLogger(c.ServerInfo{})
|
||||
|
||||
// report
|
||||
reports := []report.ResultWriter{
|
||||
report.TextWriter{},
|
||||
report.LogrusWriter{},
|
||||
}
|
||||
if p.reportSlack {
|
||||
reports = append(reports, report.SlackWriter{})
|
||||
}
|
||||
if p.reportMail {
|
||||
reports = append(reports, report.MailWriter{})
|
||||
}
|
||||
|
||||
c.Conf.DBPath = p.dbpath
|
||||
c.Conf.CveDictionaryURL = p.cveDictionaryURL
|
||||
c.Conf.HTTPProxy = p.httpProxy
|
||||
c.Conf.UseYumPluginSecurity = p.useYumPluginSecurity
|
||||
c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
|
||||
|
||||
Log.Info("Validating Config...")
|
||||
if !c.Conf.Validate() {
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
if ok, err := cveapi.CveClient.CheckHealth(); !ok {
|
||||
Log.Errorf("CVE HTTP server is not running. %#v", cveapi.CveClient)
|
||||
Log.Fatal(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
Log.Info("Detecting the type of OS... ")
|
||||
err = scan.InitServers(Log)
|
||||
if err != nil {
|
||||
Log.Errorf("Failed to init servers. Check the configuration. err: %s", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
Log.Info("Scanning vulnerabilities... ")
|
||||
if errs := scan.Scan(); 0 < len(errs) {
|
||||
for _, e := range errs {
|
||||
Log.Errorf("Failed to scan. err: %s", e)
|
||||
}
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
scanResults, err := scan.GetScanResults()
|
||||
if err != nil {
|
||||
Log.Fatal(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
Log.Info("Reporting...")
|
||||
filtered := scanResults.FilterByCvssOver()
|
||||
for _, w := range reports {
|
||||
if err := w.Write(filtered); err != nil {
|
||||
Log.Fatalf("Failed to output report, err: %s", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
}
|
||||
|
||||
Log.Info("Insert to DB...")
|
||||
if err := db.OpenDB(); err != nil {
|
||||
Log.Errorf("Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
if err := db.MigrateDB(); err != nil {
|
||||
Log.Errorf("Failed to migrate. err: %s", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
if err := db.Insert(scanResults); err != nil {
|
||||
Log.Fatalf("Failed to insert. dbpath: %s, err: %s", c.Conf.DBPath, err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/google/subcommands"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// TuiCmd is Subcommand of host discovery mode
|
||||
type TuiCmd struct {
|
||||
lang string
|
||||
debugSQL bool
|
||||
dbpath string
|
||||
}
|
||||
|
||||
// Name return subcommand name
|
||||
func (*TuiCmd) Name() string { return "tui" }
|
||||
|
||||
// Synopsis return synopsis
|
||||
func (*TuiCmd) Synopsis() string { return "Run Tui view to anayze vulnerabilites" }
|
||||
|
||||
// Usage return usage
|
||||
func (*TuiCmd) Usage() string {
|
||||
return `tui:
|
||||
tui [-dbpath=/path/to/vuls.sqlite3]
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
// SetFlags set flag
|
||||
func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
|
||||
// f.StringVar(&p.lang, "lang", "en", "[en|ja]")
|
||||
f.BoolVar(&p.debugSQL, "debug-sql", false, "debug SQL")
|
||||
|
||||
defaultDBPath := os.Getenv("PWD") + "/vuls.sqlite3"
|
||||
f.StringVar(&p.dbpath, "dbpath", defaultDBPath,
|
||||
fmt.Sprintf("/path/to/sqlite3 (default: %s)", defaultDBPath))
|
||||
}
|
||||
|
||||
// Execute execute
|
||||
func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
c.Conf.Lang = "en"
|
||||
c.Conf.DebugSQL = p.debugSQL
|
||||
c.Conf.DBPath = p.dbpath
|
||||
return report.RunTui()
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
var (
|
||||
// Colors has ansi color list
|
||||
Colors = []string{
|
||||
"\033[32m", // green
|
||||
"\033[33m", // yellow
|
||||
"\033[36m", // cyan
|
||||
"\033[35m", // magenta
|
||||
"\033[31m", // red
|
||||
"\033[34m", // blue
|
||||
}
|
||||
// ResetColor is reset color
|
||||
ResetColor = "\033[0m"
|
||||
)
|
||||
221
config/config.go
@@ -1,221 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
valid "github.com/asaskevich/govalidator"
|
||||
)
|
||||
|
||||
// Conf has Configuration
|
||||
var Conf Config
|
||||
|
||||
//Config is struct of Configuration
|
||||
type Config struct {
|
||||
Debug bool
|
||||
DebugSQL bool
|
||||
Lang string
|
||||
|
||||
Mail smtpConf
|
||||
Slack SlackConf
|
||||
Default ServerInfo
|
||||
Servers map[string]ServerInfo
|
||||
|
||||
CveDictionaryURL string `valid:"url"`
|
||||
|
||||
CvssScoreOver float64
|
||||
HTTPProxy string `valid:"url"`
|
||||
DBPath string
|
||||
// CpeNames []string
|
||||
// SummaryMode bool
|
||||
UseYumPluginSecurity bool
|
||||
UseUnattendedUpgrades bool
|
||||
}
|
||||
|
||||
// Validate configuration
|
||||
func (c Config) Validate() bool {
|
||||
errs := []error{}
|
||||
|
||||
if len(c.DBPath) != 0 {
|
||||
if ok, _ := valid.IsFilePath(c.DBPath); !ok {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"SQLite3 DB path must be a *Absolute* file path. dbpath: %s", c.DBPath))
|
||||
}
|
||||
}
|
||||
|
||||
_, err := valid.ValidateStruct(c)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if mailerrs := c.Mail.Validate(); 0 < len(mailerrs) {
|
||||
errs = append(errs, mailerrs...)
|
||||
}
|
||||
|
||||
if slackerrs := c.Slack.Validate(); 0 < len(slackerrs) {
|
||||
errs = append(errs, slackerrs...)
|
||||
}
|
||||
|
||||
for _, err := range errs {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
return len(errs) == 0
|
||||
}
|
||||
|
||||
// smtpConf is smtp config
|
||||
type smtpConf struct {
|
||||
SMTPAddr string
|
||||
SMTPPort string `valid:"port"`
|
||||
|
||||
User string
|
||||
Password string
|
||||
From string
|
||||
To []string
|
||||
Cc []string
|
||||
SubjectPrefix string
|
||||
|
||||
UseThisTime bool
|
||||
}
|
||||
|
||||
func checkEmails(emails []string) (errs []error) {
|
||||
for _, addr := range emails {
|
||||
if len(addr) == 0 {
|
||||
return
|
||||
}
|
||||
if ok := valid.IsEmail(addr); !ok {
|
||||
errs = append(errs, fmt.Errorf("Invalid email address. email: %s", addr))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Validate SMTP configuration
|
||||
func (c *smtpConf) Validate() (errs []error) {
|
||||
|
||||
if !c.UseThisTime {
|
||||
return
|
||||
}
|
||||
|
||||
// Check Emails fromat
|
||||
emails := []string{}
|
||||
emails = append(emails, c.From)
|
||||
emails = append(emails, c.To...)
|
||||
emails = append(emails, c.Cc...)
|
||||
|
||||
if emailErrs := checkEmails(emails); 0 < len(emailErrs) {
|
||||
errs = append(errs, emailErrs...)
|
||||
}
|
||||
|
||||
if len(c.SMTPAddr) == 0 {
|
||||
errs = append(errs, fmt.Errorf("smtpAddr must not be empty"))
|
||||
}
|
||||
if len(c.SMTPPort) == 0 {
|
||||
errs = append(errs, fmt.Errorf("smtpPort must not be empty"))
|
||||
}
|
||||
if len(c.To) == 0 {
|
||||
errs = append(errs, fmt.Errorf("To required at least one address"))
|
||||
}
|
||||
if len(c.From) == 0 {
|
||||
errs = append(errs, fmt.Errorf("From required at least one address"))
|
||||
}
|
||||
|
||||
_, err := valid.ValidateStruct(c)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SlackConf is slack config
|
||||
type SlackConf struct {
|
||||
HookURL string `valid:"url"`
|
||||
Channel string `json:"channel"`
|
||||
IconEmoji string `json:"icon_emoji"`
|
||||
AuthUser string `json:"username"`
|
||||
|
||||
NotifyUsers []string
|
||||
Text string `json:"text"`
|
||||
|
||||
UseThisTime bool
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *SlackConf) Validate() (errs []error) {
|
||||
|
||||
if !c.UseThisTime {
|
||||
return
|
||||
}
|
||||
|
||||
if len(c.HookURL) == 0 {
|
||||
errs = append(errs, fmt.Errorf("hookURL must not be empty"))
|
||||
}
|
||||
|
||||
if len(c.Channel) == 0 {
|
||||
errs = append(errs, fmt.Errorf("channel must not be empty"))
|
||||
} else {
|
||||
if !(strings.HasPrefix(c.Channel, "#") ||
|
||||
c.Channel == "${servername}") {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"channel's prefix must be '#', channel: %s", c.Channel))
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.AuthUser) == 0 {
|
||||
errs = append(errs, fmt.Errorf("authUser must not be empty"))
|
||||
}
|
||||
|
||||
_, err := valid.ValidateStruct(c)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// TODO check if slack configration is valid
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ServerInfo has SSH Info, additional CPE packages to scan.
|
||||
type ServerInfo struct {
|
||||
ServerName string
|
||||
User string
|
||||
Password string
|
||||
Host string
|
||||
Port string
|
||||
KeyPath string
|
||||
KeyPassword string
|
||||
SudoOpt SudoOption
|
||||
|
||||
CpeNames []string
|
||||
|
||||
// DebugLog Color
|
||||
LogMsgAnsiColor string
|
||||
}
|
||||
|
||||
// SudoOption is flag of sudo option.
|
||||
type SudoOption struct {
|
||||
|
||||
// echo pass | sudo -S ls
|
||||
ExecBySudo bool
|
||||
|
||||
// echo pass | sudo sh -C 'ls'
|
||||
ExecBySudoSh bool
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import "fmt"
|
||||
|
||||
// JSONLoader loads configuration
|
||||
type JSONLoader struct {
|
||||
}
|
||||
|
||||
// Load load the configuraiton JSON file specified by path arg.
|
||||
func (c JSONLoader) Load(path string) (err error) {
|
||||
return fmt.Errorf("Not implement yet")
|
||||
}
|
||||
@@ -1,33 +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 string) error {
|
||||
|
||||
//TODO if path's suffix .toml
|
||||
var loader Loader
|
||||
loader = TOMLLoader{}
|
||||
|
||||
return loader.Load(path)
|
||||
}
|
||||
|
||||
// Loader is interface of concrete loader
|
||||
type Loader interface {
|
||||
Load(string) error
|
||||
}
|
||||
@@ -1,98 +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 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)
|
||||
|
||||
i := 0
|
||||
for name, v := range conf.Servers {
|
||||
s := ServerInfo{ServerName: name}
|
||||
s.User = v.User
|
||||
if s.User == "" {
|
||||
s.User = d.User
|
||||
}
|
||||
|
||||
s.Password = v.Password
|
||||
if s.Password == "" {
|
||||
s.Password = d.Password
|
||||
}
|
||||
|
||||
s.Host = v.Host
|
||||
|
||||
s.Port = v.Port
|
||||
if s.Port == "" {
|
||||
s.Port = d.Port
|
||||
}
|
||||
|
||||
s.KeyPath = v.KeyPath
|
||||
if s.KeyPath == "" {
|
||||
s.KeyPath = d.KeyPath
|
||||
}
|
||||
if s.KeyPath != "" {
|
||||
if _, err := os.Stat(s.KeyPath); err != nil {
|
||||
return fmt.Errorf(
|
||||
"config.toml is invalid. keypath: %s not exists", s.KeyPath)
|
||||
}
|
||||
}
|
||||
|
||||
s.KeyPassword = v.KeyPassword
|
||||
if s.KeyPassword == "" {
|
||||
s.KeyPassword = d.KeyPassword
|
||||
}
|
||||
|
||||
s.CpeNames = v.CpeNames
|
||||
if len(s.CpeNames) == 0 {
|
||||
s.CpeNames = d.CpeNames
|
||||
}
|
||||
|
||||
s.LogMsgAnsiColor = Colors[i%len(Colors)]
|
||||
i++
|
||||
|
||||
servers[name] = s
|
||||
}
|
||||
log.Debug("Config loaded")
|
||||
log.Debugf("%s", pp.Sprintf("%v", servers))
|
||||
Conf.Servers = servers
|
||||
return
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cveapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/util"
|
||||
cve "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
)
|
||||
|
||||
// CveClient is api client of CVE disctionary service.
|
||||
var CveClient cvedictClient
|
||||
|
||||
type cvedictClient struct {
|
||||
// httpProxy string
|
||||
baseURL string
|
||||
}
|
||||
|
||||
func (api *cvedictClient) initialize() {
|
||||
api.baseURL = config.Conf.CveDictionaryURL
|
||||
}
|
||||
|
||||
func (api cvedictClient) CheckHealth() (ok bool, err error) {
|
||||
api.initialize()
|
||||
url := fmt.Sprintf("%s/health", api.baseURL)
|
||||
var errs []error
|
||||
var resp *http.Response
|
||||
resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
|
||||
// resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
|
||||
if len(errs) > 0 || resp.StatusCode != 200 {
|
||||
return false, fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v",
|
||||
url,
|
||||
errs,
|
||||
)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Key string
|
||||
CveDetail cve.CveDetail
|
||||
}
|
||||
|
||||
func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDetails, err error) {
|
||||
api.baseURL = config.Conf.CveDictionaryURL
|
||||
reqChan := make(chan string, len(cveIDs))
|
||||
resChan := make(chan response, len(cveIDs))
|
||||
errChan := make(chan error, len(cveIDs))
|
||||
defer close(reqChan)
|
||||
defer close(resChan)
|
||||
defer close(errChan)
|
||||
|
||||
go func() {
|
||||
for _, cveID := range cveIDs {
|
||||
reqChan <- cveID
|
||||
}
|
||||
}()
|
||||
|
||||
concurrency := 10
|
||||
tasks := util.GenWorkers(concurrency)
|
||||
for range cveIDs {
|
||||
tasks <- func() {
|
||||
select {
|
||||
case cveID := <-reqChan:
|
||||
url, err := util.URLPathJoin(api.baseURL, "cves", cveID)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
} else {
|
||||
log.Debugf("HTTP Request to %s", url)
|
||||
api.httpGet(cveID, url, resChan, errChan)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timeout := time.After(2 * 60 * time.Second)
|
||||
var errs []error
|
||||
for range cveIDs {
|
||||
select {
|
||||
case res := <-resChan:
|
||||
if len(res.CveDetail.CveID) == 0 {
|
||||
cveDetails = append(cveDetails, cve.CveDetail{
|
||||
CveID: res.Key,
|
||||
})
|
||||
} else {
|
||||
cveDetails = append(cveDetails, res.CveDetail)
|
||||
}
|
||||
case err := <-errChan:
|
||||
errs = append(errs, err)
|
||||
case <-timeout:
|
||||
return []cve.CveDetail{}, fmt.Errorf("Timeout Fetching CVE")
|
||||
}
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
return []cve.CveDetail{},
|
||||
fmt.Errorf("Failed to fetch CVE. err: %v", errs)
|
||||
}
|
||||
|
||||
// order by CVE ID desc
|
||||
sort.Sort(cveDetails)
|
||||
return
|
||||
}
|
||||
|
||||
func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
|
||||
var body string
|
||||
var errs []error
|
||||
var resp *http.Response
|
||||
f := func() (err error) {
|
||||
resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
|
||||
if len(errs) > 0 || resp.StatusCode != 200 {
|
||||
return fmt.Errorf("HTTP GET error: %v, code: %d, url: %s", errs, resp.StatusCode, url)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, t time.Duration) {
|
||||
log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", t, err)
|
||||
}
|
||||
err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("HTTP Error %s", err)
|
||||
}
|
||||
cveDetail := cve.CveDetail{}
|
||||
if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
|
||||
errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
|
||||
}
|
||||
resChan <- response{
|
||||
key,
|
||||
cveDetail,
|
||||
}
|
||||
}
|
||||
|
||||
// func (api cvedictClient) httpGet(key, url string, query map[string]string, resChan chan<- response, errChan chan<- error) {
|
||||
|
||||
// var body string
|
||||
// var errs []error
|
||||
// var resp *http.Response
|
||||
// f := func() (err error) {
|
||||
// req := gorequest.New().SetDebug(true).Proxy(api.httpProxy).Get(url)
|
||||
// for key := range query {
|
||||
// req = req.Query(fmt.Sprintf("%s=%s", key, query[key])).Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
// }
|
||||
// pp.Println(req)
|
||||
// resp, body, errs = req.End()
|
||||
// if len(errs) > 0 || resp.StatusCode != 200 {
|
||||
// errChan <- fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
// notify := func(err error, t time.Duration) {
|
||||
// log.Warnf("Failed to get. retrying in %s seconds. err: %s", t, err)
|
||||
// }
|
||||
// err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
|
||||
// if err != nil {
|
||||
// errChan <- fmt.Errorf("HTTP Error %s", err)
|
||||
// }
|
||||
// // resChan <- body
|
||||
// cveDetail := cve.CveDetail{}
|
||||
// if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
|
||||
// errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
|
||||
// }
|
||||
// resChan <- response{
|
||||
// key,
|
||||
// cveDetail,
|
||||
// }
|
||||
// }
|
||||
|
||||
type responseGetCveDetailByCpeName struct {
|
||||
CpeName string
|
||||
CveDetails []cve.CveDetail
|
||||
}
|
||||
|
||||
func (api cvedictClient) FetchCveDetailsByCpeName(cpeName string) ([]cve.CveDetail, error) {
|
||||
api.baseURL = config.Conf.CveDictionaryURL
|
||||
|
||||
url, err := util.URLPathJoin(api.baseURL, "cpes")
|
||||
if err != nil {
|
||||
return []cve.CveDetail{}, err
|
||||
}
|
||||
|
||||
query := map[string]string{"name": cpeName}
|
||||
log.Debugf("HTTP Request to %s, query: %#v", url, query)
|
||||
return api.httpPost(cpeName, url, query)
|
||||
}
|
||||
|
||||
func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]cve.CveDetail, error) {
|
||||
var body string
|
||||
var errs []error
|
||||
var resp *http.Response
|
||||
f := func() (err error) {
|
||||
req := gorequest.New().SetDebug(config.Conf.Debug).Post(url)
|
||||
for key := range query {
|
||||
req = req.Send(fmt.Sprintf("%s=%s", key, query[key])).Type("json")
|
||||
}
|
||||
resp, body, errs = req.End()
|
||||
if len(errs) > 0 || resp.StatusCode != 200 {
|
||||
return fmt.Errorf("HTTP POST errors: %v, code: %d, url: %s", errs, resp.StatusCode, url)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, t time.Duration) {
|
||||
log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %s", t, err)
|
||||
}
|
||||
err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
|
||||
if err != nil {
|
||||
return []cve.CveDetail{}, fmt.Errorf("HTTP Error %s", err)
|
||||
}
|
||||
|
||||
cveDetails := []cve.CveDetail{}
|
||||
if err := json.Unmarshal([]byte(body), &cveDetails); err != nil {
|
||||
return []cve.CveDetail{},
|
||||
fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
|
||||
}
|
||||
return cveDetails, nil
|
||||
}
|
||||
272
db/db.go
@@ -1,272 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
m "github.com/future-architect/vuls/models"
|
||||
"github.com/jinzhu/gorm"
|
||||
cvedb "github.com/kotakanbe/go-cve-dictionary/db"
|
||||
cve "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
)
|
||||
|
||||
var db *gorm.DB
|
||||
|
||||
// OpenDB opens Database
|
||||
func OpenDB() (err error) {
|
||||
db, err = gorm.Open("sqlite3", config.Conf.DBPath)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
|
||||
return
|
||||
|
||||
}
|
||||
db.LogMode(config.Conf.DebugSQL)
|
||||
return
|
||||
}
|
||||
|
||||
// MigrateDB migrates Database
|
||||
func MigrateDB() error {
|
||||
if err := db.AutoMigrate(
|
||||
&m.ScanHistory{},
|
||||
&m.ScanResult{},
|
||||
// &m.NWLink{},
|
||||
&m.CveInfo{},
|
||||
&m.CpeName{},
|
||||
&m.PackageInfo{},
|
||||
&m.DistroAdvisory{},
|
||||
&cve.CveDetail{},
|
||||
&cve.Jvn{},
|
||||
&cve.Nvd{},
|
||||
&cve.Reference{},
|
||||
&cve.Cpe{},
|
||||
).Error; err != nil {
|
||||
return fmt.Errorf("Failed to migrate. err: %s", err)
|
||||
}
|
||||
|
||||
errMsg := "Failed to create index. err: %s"
|
||||
// if err := db.Model(&m.NWLink{}).
|
||||
// AddIndex("idx_n_w_links_scan_result_id", "scan_result_id").Error; err != nil {
|
||||
// return fmt.Errorf(errMsg, err)
|
||||
// }
|
||||
if err := db.Model(&m.CveInfo{}).
|
||||
AddIndex("idx_cve_infos_scan_result_id", "scan_result_id").Error; err != nil {
|
||||
return fmt.Errorf(errMsg, err)
|
||||
}
|
||||
if err := db.Model(&m.CpeName{}).
|
||||
AddIndex("idx_cpe_names_cve_info_id", "cve_info_id").Error; err != nil {
|
||||
return fmt.Errorf(errMsg, err)
|
||||
}
|
||||
if err := db.Model(&m.PackageInfo{}).
|
||||
AddIndex("idx_package_infos_cve_info_id", "cve_info_id").Error; err != nil {
|
||||
return fmt.Errorf(errMsg, err)
|
||||
}
|
||||
if err := db.Model(&m.DistroAdvisory{}).
|
||||
//TODO check table name
|
||||
AddIndex("idx_distro_advisories_cve_info_id", "cve_info_id").Error; err != nil {
|
||||
return fmt.Errorf(errMsg, err)
|
||||
}
|
||||
if err := db.Model(&cve.CveDetail{}).
|
||||
AddIndex("idx_cve_detail_cve_info_id", "cve_info_id").Error; err != nil {
|
||||
return fmt.Errorf(errMsg, err)
|
||||
}
|
||||
if err := db.Model(&cve.CveDetail{}).
|
||||
AddIndex("idx_cve_detail_cveid", "cve_id").Error; err != nil {
|
||||
return fmt.Errorf(errMsg, err)
|
||||
}
|
||||
if err := db.Model(&cve.Nvd{}).
|
||||
AddIndex("idx_nvds_cve_detail_id", "cve_detail_id").Error; err != nil {
|
||||
return fmt.Errorf(errMsg, err)
|
||||
}
|
||||
if err := db.Model(&cve.Jvn{}).
|
||||
AddIndex("idx_jvns_cve_detail_id", "cve_detail_id").Error; err != nil {
|
||||
return fmt.Errorf(errMsg, err)
|
||||
}
|
||||
if err := db.Model(&cve.Cpe{}).
|
||||
AddIndex("idx_cpes_jvn_id", "jvn_id").Error; err != nil {
|
||||
return fmt.Errorf(errMsg, err)
|
||||
}
|
||||
if err := db.Model(&cve.Reference{}).
|
||||
AddIndex("idx_references_jvn_id", "jvn_id").Error; err != nil {
|
||||
return fmt.Errorf(errMsg, err)
|
||||
}
|
||||
if err := db.Model(&cve.Cpe{}).
|
||||
AddIndex("idx_cpes_nvd_id", "nvd_id").Error; err != nil {
|
||||
return fmt.Errorf(errMsg, err)
|
||||
}
|
||||
if err := db.Model(&cve.Reference{}).
|
||||
AddIndex("idx_references_nvd_id", "nvd_id").Error; err != nil {
|
||||
return fmt.Errorf(errMsg, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Insert inserts scan results into DB
|
||||
func Insert(results []m.ScanResult) error {
|
||||
for _, r := range results {
|
||||
r.KnownCves = resetGormIDs(r.KnownCves)
|
||||
r.UnknownCves = resetGormIDs(r.UnknownCves)
|
||||
}
|
||||
|
||||
history := m.ScanHistory{
|
||||
ScanResults: results,
|
||||
ScannedAt: time.Now(),
|
||||
}
|
||||
|
||||
db = db.Set("gorm:save_associations", false)
|
||||
if err := db.Create(&history).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, scanResult := range history.ScanResults {
|
||||
scanResult.ScanHistoryID = history.ID
|
||||
if err := db.Create(&scanResult).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertCveInfos(scanResult.ID, scanResult.KnownCves); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertCveInfos(scanResult.ID, scanResult.UnknownCves); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertCveInfos(scanResultID uint, infos []m.CveInfo) error {
|
||||
for _, cveInfo := range infos {
|
||||
cveInfo.ScanResultID = scanResultID
|
||||
if err := db.Create(&cveInfo).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pack := range cveInfo.Packages {
|
||||
pack.CveInfoID = cveInfo.ID
|
||||
if err := db.Create(&pack).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, distroAdvisory := range cveInfo.DistroAdvisories {
|
||||
distroAdvisory.CveInfoID = cveInfo.ID
|
||||
if err := db.Create(&distroAdvisory).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, cpeName := range cveInfo.CpeNames {
|
||||
cpeName.CveInfoID = cveInfo.ID
|
||||
if err := db.Create(&cpeName).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
db = db.Set("gorm:save_associations", true)
|
||||
cveDetail := cveInfo.CveDetail
|
||||
cveDetail.CveInfoID = cveInfo.ID
|
||||
if err := db.Create(&cveDetail).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
db = db.Set("gorm:save_associations", false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetGormIDs(infos []m.CveInfo) []m.CveInfo {
|
||||
for i := range infos {
|
||||
infos[i].CveDetail.ID = 0
|
||||
// NVD
|
||||
infos[i].CveDetail.Nvd.ID = 0
|
||||
for j := range infos[i].CveDetail.Nvd.Cpes {
|
||||
infos[i].CveDetail.Nvd.Cpes[j].ID = 0
|
||||
}
|
||||
for j := range infos[i].CveDetail.Nvd.References {
|
||||
infos[i].CveDetail.Nvd.References[j].ID = 0
|
||||
}
|
||||
|
||||
// JVN
|
||||
infos[i].CveDetail.Jvn.ID = 0
|
||||
for j := range infos[i].CveDetail.Jvn.Cpes {
|
||||
infos[i].CveDetail.Jvn.Cpes[j].ID = 0
|
||||
}
|
||||
for j := range infos[i].CveDetail.Jvn.References {
|
||||
infos[i].CveDetail.Jvn.References[j].ID = 0
|
||||
}
|
||||
|
||||
//Packages
|
||||
for j := range infos[i].Packages {
|
||||
infos[i].Packages[j].ID = 0
|
||||
infos[i].Packages[j].CveInfoID = 0
|
||||
}
|
||||
}
|
||||
return infos
|
||||
}
|
||||
|
||||
// SelectLatestScanHistory select latest scan history from DB
|
||||
func SelectLatestScanHistory() (m.ScanHistory, error) {
|
||||
scanHistory := m.ScanHistory{}
|
||||
db.Order("scanned_at desc").First(&scanHistory)
|
||||
|
||||
if scanHistory.ID == 0 {
|
||||
return m.ScanHistory{}, fmt.Errorf("No scanHistory records")
|
||||
}
|
||||
|
||||
results := []m.ScanResult{}
|
||||
db.Model(&scanHistory).Related(&results, "ScanResults")
|
||||
scanHistory.ScanResults = results
|
||||
|
||||
for i, r := range results {
|
||||
// nw := []m.NWLink{}
|
||||
// db.Model(&r).Related(&nw, "NWLinks")
|
||||
// scanHistory.ScanResults[i].NWLinks = nw
|
||||
|
||||
knownCves := selectCveInfos(&r, "KnownCves")
|
||||
sort.Sort(m.CveInfos(knownCves))
|
||||
scanHistory.ScanResults[i].KnownCves = knownCves
|
||||
}
|
||||
return scanHistory, nil
|
||||
}
|
||||
|
||||
func selectCveInfos(result *m.ScanResult, fieldName string) []m.CveInfo {
|
||||
cveInfos := []m.CveInfo{}
|
||||
db.Model(&result).Related(&cveInfos, fieldName)
|
||||
|
||||
for i, cveInfo := range cveInfos {
|
||||
cveDetail := cve.CveDetail{}
|
||||
db.Model(&cveInfo).Related(&cveDetail, "CveDetail")
|
||||
id := cveDetail.CveID
|
||||
filledCveDetail := cvedb.Get(id, db)
|
||||
cveInfos[i].CveDetail = filledCveDetail
|
||||
|
||||
packs := []m.PackageInfo{}
|
||||
db.Model(&cveInfo).Related(&packs, "Packages")
|
||||
cveInfos[i].Packages = packs
|
||||
|
||||
advisories := []m.DistroAdvisory{}
|
||||
db.Model(&cveInfo).Related(&advisories, "DistroAdvisories")
|
||||
cveInfos[i].DistroAdvisories = advisories
|
||||
|
||||
names := []m.CpeName{}
|
||||
db.Model(&cveInfo).Related(&names, "CpeNames")
|
||||
cveInfos[i].CpeNames = names
|
||||
}
|
||||
return cveInfos
|
||||
}
|
||||
75
go.mod
Normal file
@@ -0,0 +1,75 @@
|
||||
module github.com/future-architect/vuls
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/MakeNowJust/heredoc v1.0.0
|
||||
github.com/go-redis/redis/v9 v9.0.0-rc.1
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/knqyf263/go-cpe v0.0.0-20201213041631-54f6ab28673f
|
||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
|
||||
github.com/labstack/echo/v4 v4.9.1
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/ulikunitz/xz v0.5.10
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
go.uber.org/zap v1.13.0
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136
|
||||
gorm.io/driver/mysql v1.4.3
|
||||
gorm.io/driver/postgres v1.4.5
|
||||
gorm.io/gorm v1.24.1
|
||||
modernc.org/sqlite v1.19.4
|
||||
oras.land/oras-go/v2 v2.0.0-rc.4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.13.0 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.12.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.17.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/labstack/gommon v0.4.0 // indirect
|
||||
github.com/lib/pq v1.10.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.11 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
go.uber.org/atomic v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.5.0 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect
|
||||
golang.org/x/mod v0.6.0 // indirect
|
||||
golang.org/x/net v0.1.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/tools v0.2.0 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.21.4 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.4.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
)
|
||||
318
go.sum
Normal file
@@ -0,0 +1,318 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-redis/redis/v9 v9.0.0-rc.1 h1:/+bS+yeUnanqAbuD3QwlejzQZ+4eqgfUtFTG4b+QnXs=
|
||||
github.com/go-redis/redis/v9 v9.0.0-rc.1/go.mod h1:8et+z03j0l8N+DvsVnclzjf3Dl/pFHgRk+2Ct1qw66A=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
|
||||
github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
|
||||
github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||
github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
|
||||
github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||
github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
|
||||
github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/knqyf263/go-cpe v0.0.0-20201213041631-54f6ab28673f h1:vZP1dTKPOR7zSAbgqNbnTnYX77+gj3eu0QK+UmANZqE=
|
||||
github.com/knqyf263/go-cpe v0.0.0-20201213041631-54f6ab28673f/go.mod h1:4cVhzV/TndScEg4xMtSo3TTz3cMFhEAvhAA4igAyXZY=
|
||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d h1:X4cedH4Kn3JPupAwwWuo4AzYp16P0OyLO9d7OnMZc/c=
|
||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y=
|
||||
github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo=
|
||||
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
|
||||
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
|
||||
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/gomega v1.21.1 h1:OB/euWYIExnPBohllTicTHmGTrMaqJ67nIu80j0/uEM=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw=
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k=
|
||||
gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
|
||||
gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=
|
||||
gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg=
|
||||
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs=
|
||||
gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/libc v1.21.4 h1:CzTlumWeIbPV5/HVIMzYHNPCRP8uiU/CWiN2gtd/Qu8=
|
||||
modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
|
||||
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.19.4 h1:nlPIDqumn6/mSvs7T5C8MNYEuN73sISzPdKtMdURpUI=
|
||||
modernc.org/sqlite v1.19.4/go.mod h1:x/yZNb3h5+I3zGQSlwIv4REL5eJhiRkUH5MReogAeIc=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
|
||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
||||
oras.land/oras-go/v2 v2.0.0-rc.4 h1:hg/R2znUQ1+qd43gRmL16VeX1GIZ8hQlLalBjYhhKSk=
|
||||
oras.land/oras-go/v2 v2.0.0-rc.4/go.mod h1:YGHvWBGuqRlZgUyXUIoKsR3lcuCOb3DAtG0SEsEw1iY=
|
||||
|
Before Width: | Height: | Size: 247 KiB |
@@ -1,979 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
||||
<!--Created by yEd 3.14.2-->
|
||||
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
||||
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
||||
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
||||
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
||||
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
||||
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
||||
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
||||
<key for="graphml" id="d7" yfiles.type="resources"/>
|
||||
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
||||
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
||||
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
||||
<graph edgedefault="directed" id="G">
|
||||
<data key="d0"/>
|
||||
<node id="n0">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.cloud">
|
||||
<y:Geometry height="50.0" width="80.0" x="269.4041252136233" y="446.4841308593749"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="38.0" y="23.0">
|
||||
<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n1" yfiles.foldertype="group">
|
||||
<data key="d4"/>
|
||||
<data key="d6">
|
||||
<y:ProxyAutoBoundsNode>
|
||||
<y:Realizers active="0">
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="285.54366048177087" width="173.0" x="66.40412521362327" y="347.9090576171874"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="173.0" x="0.0" y="0.0">Vulnerbility Database</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
||||
<y:BorderInsets bottom="54" bottomF="53.63557942708337" left="30" leftF="29.90412521362373" right="28" rightF="28.09587478637627" top="27" topF="27.242065429687557"/>
|
||||
</y:GroupNode>
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 1</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
||||
</y:GroupNode>
|
||||
</y:Realizers>
|
||||
</y:ProxyAutoBoundsNode>
|
||||
</data>
|
||||
<graph edgedefault="directed" id="n1:">
|
||||
<node id="n1::n0">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="70.0" width="85.0" x="111.308250427247" y="494.8171386718749"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="63.279296875" x="10.8603515625" y="18.8671875">JVN
|
||||
(Japanese)<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n1::n1">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="70.0" width="85.0" x="111.308250427247" y="411.81713867187494"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="29.69921875" x="27.650390625" y="25.93359375">NVD<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="n2" yfiles.foldertype="group">
|
||||
<data key="d4"/>
|
||||
<data key="d6">
|
||||
<y:ProxyAutoBoundsNode>
|
||||
<y:Realizers active="0">
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="285.54366048177087" width="137.0" x="1209.345874786376" y="347.9090576171874"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="137.0" x="0.0" y="0.0">Linux Support</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
||||
<y:BorderInsets bottom="4" bottomF="4.059529622395871" left="5" leftF="4.85411262512207" right="8" rightF="8.14588737487793" top="0" topF="0.0"/>
|
||||
</y:GroupNode>
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 2</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
||||
</y:GroupNode>
|
||||
</y:Realizers>
|
||||
</y:ProxyAutoBoundsNode>
|
||||
</data>
|
||||
<graph edgedefault="directed" id="n2:">
|
||||
<node id="n2::n0">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="71.484130859375" width="94.0" x="1229.1999874114981" y="463.7420654296874"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="64.158203125" x="14.9208984375" y="19.6092529296875">apptitude
|
||||
changelog<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n2::n1">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="71.484130859375" width="94.0" x="1229.1999874114981" y="384.5750732421874"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="64.158203125" x="14.9208984375" y="19.6092529296875">yum
|
||||
changelog<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n2::n2">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="71.484130859375" width="94.0" x="1229.1999874114981" y="542.9090576171874"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="92.828125" x="0.5859375" y="19.6092529296875">RHSA (RedHat)
|
||||
ALAS (Amazon)<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="n3">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.cloud">
|
||||
<y:Geometry height="50.0" width="80.0" x="1109.272931098937" y="399.1136678059895"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="38.0" y="23.0">
|
||||
<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n4">
|
||||
<data key="d6">
|
||||
<y:SVGNode>
|
||||
<y:Geometry height="121.666015625" width="137.0" x="942.2729310989371" y="363.2806599934895"/>
|
||||
<y:Fill color="#CCCCFF" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="45.63671875" x="45.681640625" y="-30.475463867187386">servers<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.5" labelRatioY="0.5" nodeRatioX="0.16655736770072993" nodeRatioY="-0.5" offsetX="0.0" offsetY="-12.342651367187386" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="66.5" y="58.8330078125">
|
||||
<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:SVGNodeProperties usingVisualBounds="false"/>
|
||||
<y:SVGModel svgBoundsPolicy="0">
|
||||
<y:SVGContent refid="1"/>
|
||||
</y:SVGModel>
|
||||
</y:SVGNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n5" yfiles.foldertype="group">
|
||||
<data key="d4"/>
|
||||
<data key="d6">
|
||||
<y:ProxyAutoBoundsNode>
|
||||
<y:Realizers active="0">
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="285.54366048177087" width="220.24999999999977" x="662.2499999999998" y="347.9090576171874"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="220.24999999999977" x="0.0" y="0.0">Vuls</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
||||
<y:BorderInsets bottom="12" bottomF="11.710652669270871" left="10" leftF="9.999999999999773" right="0" rightF="0.0" top="31" topF="30.57621256510413"/>
|
||||
</y:GroupNode>
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 3</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
||||
</y:GroupNode>
|
||||
</y:Realizers>
|
||||
</y:ProxyAutoBoundsNode>
|
||||
</data>
|
||||
<graph edgedefault="directed" id="n5:">
|
||||
<node id="n5::n0">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="80.0" x="787.4999999999995" y="495.1512858072915"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="42.595703125" x="18.7021484375" y="15.93359375">Report<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n5::n1">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="80.0" x="787.4999999999995" y="556.7420654296874"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="54.40234375" x="12.798828125" y="15.93359375">TUI View<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n5::n2">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="180.25" x="687.2499999999995" y="415.1512858072915"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="30.68359375" x="74.783203125" y="15.93359375">Scan<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="n6">
|
||||
<data key="d6">
|
||||
<y:SVGNode>
|
||||
<y:Geometry height="64.96826171875" width="56.554100036621094" x="1083.5729436874383" y="568.4844563802083"/>
|
||||
<y:Fill color="#CCCCFF" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="100.890625" x="-22.168262481689453" y="68.96826171875">System Operator<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:SVGNodeProperties usingVisualBounds="true"/>
|
||||
<y:SVGModel svgBoundsPolicy="0">
|
||||
<y:SVGContent refid="2"/>
|
||||
</y:SVGModel>
|
||||
</y:SVGNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n7">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.dataBase">
|
||||
<y:Geometry height="70.0" width="60.5" x="696.9999999999995" y="546.7420654296874"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="28.25" y="33.0">
|
||||
<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="48.4140625" x="6.04296875" y="25.93359375">SQLite3<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n8">
|
||||
<data key="d6">
|
||||
<y:SVGNode>
|
||||
<y:Geometry height="37.0" width="109.57881927490234" x="991.1335277557366" y="532.4466756184895"/>
|
||||
<y:Fill color="#CCCCFF" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="sandwich" modelPosition="s" textColor="#000000" visible="true" width="4.0" x="52.78940963745117" y="41.0"/>
|
||||
<y:SVGNodeProperties usingVisualBounds="true"/>
|
||||
<y:SVGModel svgBoundsPolicy="0">
|
||||
<y:SVGContent refid="3"/>
|
||||
</y:SVGModel>
|
||||
</y:SVGNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n9">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.bpmn.Artifact.withShadow">
|
||||
<y:Geometry height="24.0" width="35.0" x="943.5205974578851" y="538.9466756184895"/>
|
||||
<y:Fill color="#FFFFFFE6" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="15.5" y="28.0">
|
||||
<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:StyleProperties>
|
||||
<y:Property class="java.awt.Color" name="com.yworks.bpmn.icon.line.color" value="#000000"/>
|
||||
<y:Property class="java.awt.Color" name="com.yworks.bpmn.icon.fill2" value="#d4d4d4cc"/>
|
||||
<y:Property class="java.awt.Color" name="com.yworks.bpmn.icon.fill" value="#ffffffe6"/>
|
||||
<y:Property class="com.yworks.yfiles.bpmn.view.BPMNTypeEnum" name="com.yworks.bpmn.type" value="ARTIFACT_TYPE_REQUEST_MESSAGE"/>
|
||||
</y:StyleProperties>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n10" yfiles.foldertype="group">
|
||||
<data key="d4"/>
|
||||
<data key="d6">
|
||||
<y:ProxyAutoBoundsNode>
|
||||
<y:Realizers active="0">
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="285.54366048177087" width="233.0" x="379.4041252136233" y="347.9090576171874"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="233.0" x="0.0" y="0.0">go-cve-dictionary</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
||||
<y:BorderInsets bottom="0" bottomF="0.0" left="2" leftF="1.5" right="5" rightF="5.0" top="62" topF="61.9090576171875"/>
|
||||
</y:GroupNode>
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 4</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
||||
</y:GroupNode>
|
||||
</y:Realizers>
|
||||
</y:ProxyAutoBoundsNode>
|
||||
</data>
|
||||
<graph edgedefault="directed" id="n10:">
|
||||
<node id="n10::n0">
|
||||
<data key="d6">
|
||||
<y:GenericNode configuration="com.yworks.flowchart.dataBase">
|
||||
<y:Geometry height="70.0" width="60.5" x="447.15412521362305" y="548.4527180989583"/>
|
||||
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="28.25" y="33.0">
|
||||
<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="48.4140625" x="6.04296875" y="25.93359375">SQLite3<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n10::n1">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="101.0" x="491.4041252136233" y="446.4841308593749"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="73.943359375" x="13.5283203125" y="15.93359375">HTTP server<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="rectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n10::n2">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="80.0" x="395.9041252136233" y="446.4841308593749"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="46.796875" x="16.6015625" y="15.93359375">Fetcher<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="rectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
</graph>
|
||||
</node>
|
||||
<edge id="n10::e0" source="n10::n1" target="n10::n0">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="none"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e0" source="n10::n2" target="n0">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="none"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="106.240234375" x="-141.12006313536395" y="46.068162980709076">Fetch
|
||||
Vulnerability data<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="62.2009754807092" distanceToCenter="true" position="left" ratio="41.499957391955974" segment="-1"/>
|
||||
</y:ModelParameter>
|
||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||
</y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e1" source="n3" target="n2">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e2" source="n5::n2" target="n10::n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="34.626953125" x="-64.74898719787609" y="-33.01884181664411">HTTP<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
|
||||
</y:ModelParameter>
|
||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||
</y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e3" source="n5::n1" target="n7">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="none"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e4" source="n0" target="n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e5" source="n5::n0" target="n6">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e6" source="n4" target="n3">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="n5::e0" source="n5::n2" target="n5::n0">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e7" source="n5::n2" target="n4">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="25.744140625" x="44.03690814971833" y="17.093924778052497">SSH<y:LabelModel>
|
||||
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="1.0" segment="-1"/>
|
||||
</y:ModelParameter>
|
||||
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||
</y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e8" source="n6" target="n5::n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="n10::e1" source="n10::n2" target="n10::n0">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="none"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e9" source="n5::n2" target="n7">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="none"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
</graph>
|
||||
<data key="d7">
|
||||
<y:Resources>
|
||||
<y:Resource id="1"><?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px" y="0px" width="68px" height="60px" viewBox="-0.435 -0.869 68 60" enable-background="new -0.435 -0.869 68 60"
|
||||
xml:space="preserve">
|
||||
<defs>
|
||||
</defs>
|
||||
<path fill="#666666" d="M52.462,30.881c-0.021,0-0.037,0.01-0.059,0.012c-0.021-0.002-0.037-0.012-0.059-0.012h-18.5v-7.555
|
||||
c0-0.414-0.335-0.75-0.75-0.75c-0.414,0-0.75,0.336-0.75,0.75v7.555h-18.5c-0.02,0-0.037,0.01-0.057,0.012
|
||||
c-0.02-0.002-0.037-0.012-0.057-0.012c-0.414,0-0.75,0.336-0.75,0.75v3.834c0,0.414,0.336,0.75,0.75,0.75s0.75-0.336,0.75-0.75
|
||||
v-3.084H51.71v3.084c0,0.414,0.336,0.75,0.75,0.75s0.75-0.336,0.75-0.75v-3.834C53.212,31.217,52.876,30.881,52.462,30.881z"/>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="130.7236" y1="-184.1631" x2="130.7236" y2="-191.9565" gradientTransform="matrix(1 0 0 -1 -97.6001 -158.6377)">
|
||||
<stop offset="0" style="stop-color:#9CD7FF"/>
|
||||
<stop offset="1" style="stop-color:#3C89C9"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_1_)" d="M36.296,29.976c-0.832,0-1.513-0.681-1.513-1.513v-1.424c0-0.832-0.681-1.513-1.513-1.513h-0.214
|
||||
c-0.832,0-1.513,0.681-1.513,1.513v1.424c0,0.832-0.681,1.513-1.513,1.513h-2.499c-0.832,0-1.513,0.681-1.513,1.513v0.317
|
||||
c0,0.832,0.681,1.513,1.513,1.513h11.187c0.832,0,1.513-0.681,1.513-1.513v-0.317c0-0.833-0.681-1.513-1.513-1.513H36.296z"/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="605.8877" y1="2040.6665" x2="593.1709" y2="2040.6665" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)">
|
||||
<stop offset="0" style="stop-color:#4D4D4D"/>
|
||||
<stop offset="1" style="stop-color:#999999"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_2_)" d="M20.205,57.452c0,0.519-3.619,0.752-6.627,0.752c-2.083,0-5.846-0.186-6.089-0.678
|
||||
c0,0.238,0,0.806,0,0.89c0,0.389,2.573,0.661,6.084,0.661c3.511,0,6.632-0.344,6.632-0.729C20.205,58.264,20.205,57.7,20.205,57.452
|
||||
z"/>
|
||||
<path fill="#808080" d="M13.846,56.806c3.512,0,6.358,0.313,6.358,0.699s-2.846,0.763-6.358,0.763c-3.59,0-6.358-0.375-6.358-0.763
|
||||
S10.335,56.806,13.846,56.806z"/>
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="600.833" y1="2037.4702" x2="598.1563" y2="2037.4702" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)">
|
||||
<stop offset="0" style="stop-color:#999999"/>
|
||||
<stop offset="0.0417" style="stop-color:#8D8D8D"/>
|
||||
<stop offset="0.1617" style="stop-color:#717171"/>
|
||||
<stop offset="0.2821" style="stop-color:#5D5D5D"/>
|
||||
<stop offset="0.4021" style="stop-color:#515151"/>
|
||||
<stop offset="0.5212" style="stop-color:#4D4D4D"/>
|
||||
<stop offset="0.6202" style="stop-color:#565656"/>
|
||||
<stop offset="0.7817" style="stop-color:#6E6E6E"/>
|
||||
<stop offset="0.9844" style="stop-color:#969696"/>
|
||||
<stop offset="1" style="stop-color:#999999"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_3_)" d="M15.215,57.657c0,0-0.792,0.053-1.339,0.053s-1.338-0.053-1.338-0.053v-5.231h2.677V57.657z"/>
|
||||
<radialGradient id="SVGID_4_" cx="465.1113" cy="2023.4497" r="12.8975" gradientTransform="matrix(1.15 0 0 1 -526.6041 -1982.4023)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#F2F2F2"/>
|
||||
<stop offset="1" style="stop-color:#666666"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_4_)" d="M0.065,36.888c0-0.59,0.482-1.071,1.072-1.071H26.98c0.589,0,1.071,0.481,1.071,1.071v16.108
|
||||
c0,0.589-0.482,1.07-1.071,1.07H1.137c-0.59,0.002-1.072-0.481-1.072-1.07V36.888z"/>
|
||||
<path fill="none" stroke="#666666" stroke-width="0.1305" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
|
||||
M0.065,36.888c0-0.59,0.482-1.071,1.072-1.071H26.98c0.589,0,1.071,0.481,1.071,1.071v16.108c0,0.589-0.482,1.07-1.071,1.07H1.137
|
||||
c-0.59,0.002-1.072-0.481-1.072-1.07V36.888z"/>
|
||||
<radialGradient id="SVGID_5_" cx="439.1309" cy="2019.0845" r="28.5715" fx="461.6079" fy="2015.234" gradientTransform="matrix(1.1935 0 0 1 -509.6013 -1982.4023)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#4D4D4D"/>
|
||||
<stop offset="1" style="stop-color:#999999"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_5_)" d="M0.613,37.436c0-0.591,0.482-1.072,1.071-1.072h24.871c0.589,0,1.071,0.481,1.071,1.072v14.893
|
||||
c0,0.59-0.482,1.072-1.071,1.072H1.685c-0.589,0-1.071-0.482-1.071-1.072V37.436z"/>
|
||||
<radialGradient id="SVGID_6_" cx="440.0439" cy="2019.1304" r="18.3134" gradientTransform="matrix(1.1923 0 0 1 -510.0601 -1982.4023)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#9CD7FF"/>
|
||||
<stop offset="1" style="stop-color:#3C89C9"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_6_)" d="M0.917,37.679c0-0.59,0.482-1.071,1.072-1.071h24.262c0.589,0,1.071,0.481,1.071,1.071v14.406
|
||||
c0,0.588-0.482,1.069-1.071,1.069H1.989c-0.59,0-1.072-0.481-1.072-1.069V37.679z"/>
|
||||
<path opacity="0.24" fill="#F2F2F2" d="M0.917,49.11V37.679c0-0.59,0.482-1.071,1.072-1.071h24.262c0.589,0,1.071,0.481,1.071,1.071
|
||||
v7.252l-12.407,2.646c-0.57,0.146-1.52,0.293-2.107,0.326L0.917,49.11z"/>
|
||||
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="644.3887" y1="2040.6665" x2="631.6719" y2="2040.6665" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)">
|
||||
<stop offset="0" style="stop-color:#4D4D4D"/>
|
||||
<stop offset="1" style="stop-color:#999999"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_7_)" d="M58.706,57.452c0,0.518-3.621,0.752-6.627,0.752c-2.084,0-5.848-0.186-6.09-0.678
|
||||
c0,0.237,0,0.805,0,0.889c0,0.389,2.572,0.662,6.084,0.662s6.633-0.344,6.633-0.729C58.706,58.263,58.706,57.7,58.706,57.452z"/>
|
||||
<path fill="#808080" d="M52.347,56.805c3.512,0,6.357,0.313,6.357,0.699s-2.847,0.762-6.357,0.762c-3.59,0-6.357-0.373-6.357-0.762
|
||||
C45.989,57.118,48.837,56.805,52.347,56.805z"/>
|
||||
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="639.333" y1="2037.4683" x2="636.6553" y2="2037.4683" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)">
|
||||
<stop offset="0" style="stop-color:#999999"/>
|
||||
<stop offset="0.0417" style="stop-color:#8D8D8D"/>
|
||||
<stop offset="0.1617" style="stop-color:#717171"/>
|
||||
<stop offset="0.2821" style="stop-color:#5D5D5D"/>
|
||||
<stop offset="0.4021" style="stop-color:#515151"/>
|
||||
<stop offset="0.5212" style="stop-color:#4D4D4D"/>
|
||||
<stop offset="0.6202" style="stop-color:#565656"/>
|
||||
<stop offset="0.7817" style="stop-color:#6E6E6E"/>
|
||||
<stop offset="0.9844" style="stop-color:#969696"/>
|
||||
<stop offset="1" style="stop-color:#999999"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_8_)" d="M53.716,57.657c0,0-0.791,0.052-1.34,0.052c-0.547,0-1.338-0.052-1.338-0.052v-5.232h2.678V57.657z"
|
||||
/>
|
||||
<radialGradient id="SVGID_9_" cx="498.5898" cy="2023.4487" r="12.8975" gradientTransform="matrix(1.15 0 0 1 -526.6041 -1982.4023)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#F2F2F2"/>
|
||||
<stop offset="1" style="stop-color:#666666"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_9_)" d="M38.566,36.887c0-0.59,0.481-1.072,1.071-1.072h25.844c0.589,0,1.07,0.482,1.07,1.072v16.107
|
||||
c0,0.59-0.481,1.072-1.07,1.072H39.638c-0.59,0-1.071-0.482-1.071-1.072V36.887z"/>
|
||||
<path fill="none" stroke="#666666" stroke-width="0.1305" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
|
||||
M38.566,36.887c0-0.59,0.481-1.072,1.071-1.072h25.844c0.589,0,1.07,0.482,1.07,1.072v16.107c0,0.59-0.481,1.072-1.07,1.072H39.638
|
||||
c-0.59,0-1.071-0.482-1.071-1.072V36.887z"/>
|
||||
<radialGradient id="SVGID_10_" cx="471.3896" cy="2019.0845" r="28.5697" fx="493.8652" fy="2015.2343" gradientTransform="matrix(1.1935 0 0 1 -509.6013 -1982.4023)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#4D4D4D"/>
|
||||
<stop offset="1" style="stop-color:#999999"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_10_)" d="M39.114,37.434c0-0.59,0.482-1.072,1.071-1.072h24.87c0.589,0,1.07,0.482,1.07,1.072v14.895
|
||||
c0,0.589-0.481,1.07-1.07,1.07h-24.87c-0.589,0-1.071-0.481-1.071-1.07V37.434z"/>
|
||||
<radialGradient id="SVGID_11_" cx="472.334" cy="2019.1294" r="18.3139" gradientTransform="matrix(1.1923 0 0 1 -510.0601 -1982.4023)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#9CD7FF"/>
|
||||
<stop offset="1" style="stop-color:#3C89C9"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_11_)" d="M39.419,37.678c0-0.59,0.481-1.072,1.07-1.072h24.264c0.588,0,1.07,0.482,1.07,1.072v14.406
|
||||
c0,0.588-0.482,1.07-1.07,1.07H40.489c-0.589,0-1.07-0.482-1.07-1.07V37.678z"/>
|
||||
<path opacity="0.24" fill="#F2F2F2" d="M39.419,49.108v-11.43c0-0.59,0.481-1.072,1.07-1.072h24.264c0.588,0,1.07,0.482,1.07,1.072
|
||||
v7.252l-12.408,2.645c-0.57,0.146-1.52,0.295-2.106,0.326L39.419,49.108z"/>
|
||||
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="624.8936" y1="2004.9155" x2="612.1787" y2="2004.9155" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)">
|
||||
<stop offset="0" style="stop-color:#4D4D4D"/>
|
||||
<stop offset="1" style="stop-color:#999999"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_12_)" d="M39.212,21.701c0,0.518-3.621,0.752-6.626,0.752c-2.083,0-5.847-0.186-6.089-0.678
|
||||
c0,0.238,0,0.805,0,0.889c0,0.389,2.573,0.662,6.084,0.662c3.51,0,6.631-0.344,6.631-0.729
|
||||
C39.212,22.513,39.212,21.949,39.212,21.701z"/>
|
||||
<path fill="#808080" d="M32.854,21.055c3.511,0,6.358,0.313,6.358,0.699c0,0.386-2.848,0.762-6.358,0.762
|
||||
c-3.589,0-6.358-0.374-6.358-0.762C26.496,21.367,29.342,21.055,32.854,21.055z"/>
|
||||
<linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="619.8379" y1="2001.7183" x2="617.1611" y2="2001.7183" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)">
|
||||
<stop offset="0" style="stop-color:#999999"/>
|
||||
<stop offset="0.0417" style="stop-color:#8D8D8D"/>
|
||||
<stop offset="0.1617" style="stop-color:#717171"/>
|
||||
<stop offset="0.2821" style="stop-color:#5D5D5D"/>
|
||||
<stop offset="0.4021" style="stop-color:#515151"/>
|
||||
<stop offset="0.5212" style="stop-color:#4D4D4D"/>
|
||||
<stop offset="0.6202" style="stop-color:#565656"/>
|
||||
<stop offset="0.7817" style="stop-color:#6E6E6E"/>
|
||||
<stop offset="0.9844" style="stop-color:#969696"/>
|
||||
<stop offset="1" style="stop-color:#999999"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_13_)" d="M34.222,21.906c0,0-0.791,0.052-1.338,0.052c-0.547,0-1.338-0.052-1.338-0.052v-5.232h2.677
|
||||
L34.222,21.906L34.222,21.906z"/>
|
||||
<radialGradient id="SVGID_14_" cx="481.6387" cy="1987.6978" r="12.8975" gradientTransform="matrix(1.15 0 0 1 -526.6041 -1982.4023)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#F2F2F2"/>
|
||||
<stop offset="1" style="stop-color:#666666"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_14_)" d="M19.072,1.137c0-0.59,0.482-1.072,1.071-1.072h25.843c0.589,0,1.071,0.482,1.071,1.072v16.108
|
||||
c0,0.589-0.482,1.071-1.071,1.071H20.145c-0.589,0-1.071-0.482-1.071-1.071L19.072,1.137L19.072,1.137z"/>
|
||||
<path fill="none" stroke="#666666" stroke-width="0.1305" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
|
||||
M19.072,1.137c0-0.59,0.482-1.072,1.071-1.072h25.843c0.589,0,1.071,0.482,1.071,1.072v16.108c0,0.589-0.482,1.071-1.071,1.071
|
||||
H20.145c-0.589,0-1.071-0.482-1.071-1.071L19.072,1.137L19.072,1.137z"/>
|
||||
<radialGradient id="SVGID_15_" cx="455.0566" cy="1983.3345" r="28.5689" fx="477.5316" fy="1979.4844" gradientTransform="matrix(1.1935 0 0 1 -509.6013 -1982.4023)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#4D4D4D"/>
|
||||
<stop offset="1" style="stop-color:#999999"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_15_)" d="M19.621,1.685c0-0.59,0.482-1.072,1.072-1.072h24.87c0.589,0,1.071,0.482,1.071,1.072v14.894
|
||||
c0,0.589-0.482,1.071-1.071,1.071h-24.87c-0.589,0-1.072-0.482-1.072-1.071V1.685z"/>
|
||||
<radialGradient id="SVGID_16_" cx="455.9854" cy="1983.3784" r="18.3134" gradientTransform="matrix(1.1923 0 0 1 -510.0601 -1982.4023)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#9CD7FF"/>
|
||||
<stop offset="1" style="stop-color:#3C89C9"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_16_)" d="M19.924,1.928c0-0.59,0.482-1.072,1.072-1.072h24.262c0.589,0,1.07,0.482,1.07,1.072v14.406
|
||||
c0,0.588-0.481,1.07-1.07,1.07H20.997c-0.589,0-1.072-0.482-1.072-1.07V1.928z"/>
|
||||
<path opacity="0.24" fill="#F2F2F2" d="M19.924,13.358V1.928c0-0.59,0.482-1.072,1.072-1.072h24.262c0.589,0,1.07,0.482,1.07,1.072
|
||||
V9.18l-12.408,2.646c-0.569,0.146-1.519,0.294-2.106,0.326L19.924,13.358z"/>
|
||||
</svg>
|
||||
</y:Resource>
|
||||
<y:Resource id="2"><?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve">
|
||||
<g>
|
||||
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)">
|
||||
<stop offset="0.2711" style="stop-color:#FFAB4F"/>
|
||||
<stop offset="1" style="stop-color:#FFD28F"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109
|
||||
V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77
|
||||
c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/>
|
||||
|
||||
<radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#FFD28F"/>
|
||||
<stop offset="1" style="stop-color:#FFAB4F"/>
|
||||
</radialGradient>
|
||||
<path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357
|
||||
c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012
|
||||
C36.627,4.945,43.59,13.158,43.676,23.357z"/>
|
||||
|
||||
<linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)">
|
||||
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/>
|
||||
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/>
|
||||
</linearGradient>
|
||||
<path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386
|
||||
c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247
|
||||
c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/>
|
||||
<path fill="#CC9869" stroke="#99724F" stroke-width="0.9271" stroke-linecap="round" stroke-linejoin="round" d="M28.02,31.921
|
||||
c-6.78,0-6.717,3.708-6.717,3.708c0,8.133,2.985,8.788,6.955,8.788c4.243,0,6.792-0.926,6.792-8.595
|
||||
C35.051,35.822,35.881,31.921,28.02,31.921z M23.989,35.678c0-0.556,1.838-1.005,4.107-1.005c2.27,0,4.107,0.449,4.107,1.005
|
||||
C32.204,36.232,23.989,36.232,23.989,35.678z"/>
|
||||
<path id="hair_x5F_gray_2_" fill="#CC9869" stroke="#99724F" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25
|
||||
c0,0,5.321,7.25,15,3.75c2.729-0.563,9.058,1.035,9.058,1.035S40.68,1.865,27.289,2.744C9.403,4.125,12.058,25.678,12.058,25.678
|
||||
s2.768-0.684,5.036-4.802C18.068,19.106,20.278,13.25,20.278,13.25z"/>
|
||||
|
||||
<radialGradient id="collar_x5F_body_1_" cx="14.9609" cy="3148.9336" r="32.4004" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#B0E8FF"/>
|
||||
<stop offset="1" style="stop-color:#74AEEE"/>
|
||||
</radialGradient>
|
||||
<path id="collar_x5F_body_3_" fill="url(#collar_x5F_body_1_)" stroke="#5491CF" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494
|
||||
h48.51c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146
|
||||
c-1.771,1.655-5.61,2.802-10.063,2.802c-4.453,0-8.292-1.146-10.063-2.802c0,0-5.755,0.586-11.189,6.021
|
||||
C1.378,56.689,0.5,62.768,0.5,62.768z"/>
|
||||
|
||||
<radialGradient id="collar_x5F_r_1_" cx="31.2998" cy="3139.0605" r="9.2823" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#80CCFF"/>
|
||||
<stop offset="1" style="stop-color:#74AEEE"/>
|
||||
</radialGradient>
|
||||
<path id="collar_x5F_r_3_" fill="url(#collar_x5F_r_1_)" stroke="#5491CF" d="M38.159,41.381c0,0-0.574,2.369-3.013,4.441
|
||||
c-2.108,1.795-5.783,2.072-5.783,2.072l3.974,6.217c0,0,2.957-1.637,5.009-3.848c1.922-2.072,1.37-5.479,1.37-5.479L38.159,41.381z
|
||||
"/>
|
||||
|
||||
<radialGradient id="collar_x5F_l_1_" cx="18.9375" cy="3139.1016" r="9.2843" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#80CCFF"/>
|
||||
<stop offset="1" style="stop-color:#74AEEE"/>
|
||||
</radialGradient>
|
||||
<path id="collar_x5F_l_3_" fill="url(#collar_x5F_l_1_)" stroke="#5491CF" d="M18.63,41.422c0,0,0.576,2.369,3.012,4.441
|
||||
c2.109,1.793,5.785,2.072,5.785,2.072l-3.974,6.217c0,0-2.957-1.637-5.007-3.85c-1.922-2.072-1.37-5.48-1.37-5.48L18.63,41.422z"/>
|
||||
|
||||
<radialGradient id="Knob2_1_" cx="27.6895" cy="2375.2871" r="0.9669" gradientTransform="matrix(1 0 0 1 0.2402 -2319.0742)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#80CCFF"/>
|
||||
<stop offset="1" style="stop-color:#74AEEE"/>
|
||||
</radialGradient>
|
||||
<circle id="Knob2_3_" fill="url(#Knob2_1_)" stroke="#5491CF" cx="28.258" cy="56.254" r="0.584"/>
|
||||
|
||||
<radialGradient id="Knob1_1_" cx="27.7275" cy="2381.5283" r="0.9669" gradientTransform="matrix(1 0 0 1 0.2402 -2319.0742)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#80CCFF"/>
|
||||
<stop offset="1" style="stop-color:#74AEEE"/>
|
||||
</radialGradient>
|
||||
<circle id="Knob1_3_" fill="url(#Knob1_1_)" stroke="#5491CF" cx="28.297" cy="62.499" r="0.584"/>
|
||||
<path id="path5135_5_" fill="#D54A30" stroke="#B51A19" d="M27.442,55.23c0,0-1.852,2.057-2.082,6.543c-0.23,4.488,0,4.488,0,4.488
|
||||
h6.546c0,0,0.23,0.063-0.154-4.367c-0.4-4.604-2.389-6.668-2.389-6.668L27.442,55.23L27.442,55.23z"/>
|
||||
<path id="path5131_5_" fill="#D54A30" stroke="#B51A19" d="M28.325,48.688h0.125L31,52.691c0.516,0.953-1.207,1.797-1.457,2.547
|
||||
l-2.277-0.018c-0.242-0.761-2.26-1.369-1.477-2.584L28.325,48.688z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</y:Resource>
|
||||
<y:Resource id="3"><?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 585.16241 167.58249"
|
||||
height="167.58249"
|
||||
width="585.16241"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="Slack CMYK.svg"><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="705"
|
||||
id="namedview3358"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="1.4633713"
|
||||
inkscape:cx="271.33569"
|
||||
inkscape:cy="125.32114"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" /><metadata
|
||||
id="metadata8"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs6"><clipPath
|
||||
id="clipPath20"
|
||||
clipPathUnits="userSpaceOnUse"><path
|
||||
id="path18"
|
||||
d="M 0,1256.87 0,0 l 4388.72,0 0,1256.87 z"
|
||||
inkscape:connector-curvature="0" /></clipPath></defs><g
|
||||
id="g3411"
|
||||
transform="translate(12.322913,-242.28632)"><path
|
||||
d="m 93.133967,257.26632 c -2.4724,-7.60934 -10.644271,-11.77334 -18.253204,-9.30001 -7.608267,2.472 -11.7724,10.64401 -9.300533,18.252 l 37.45734,115.24281 c 2.57133,7.10786 10.24946,11.10266 17.62079,8.98133 7.68814,-2.2136 12.3748,-10.37867 9.92147,-17.93027 -0.0933,-0.2864 -37.445863,-115.24586 -37.445863,-115.24586"
|
||||
style="fill:#e7a213;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path22-7"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 35.095431,276.12365 c -2.4724,-7.608 -10.644267,-11.772 -18.2532,-9.3 -7.6082657,2.472 -11.7723997,10.64426 -9.3005327,18.2532 L 44.99903,400.3195 c 2.571334,7.10834 10.249467,11.1026 17.620267,8.98021 7.688133,-2.21198 12.375599,-10.37761 9.921866,-17.92913 -0.0932,-0.28706 -37.445732,-115.24693 -37.445732,-115.24693"
|
||||
style="fill:#4dc088;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path24-8"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 140.27983,352.71018 c 7.60933,-2.4724 11.772,-10.64427 9.3,-18.2532 -2.472,-7.60827 -10.644,-11.7724 -18.252,-9.30053 L 16.085031,362.61378 c -7.1083997,2.57134 -11.1026657,10.24947 -8.9813327,17.62027 2.213067,7.688 10.3781327,12.37507 17.9302657,9.92133 0.2864,-0.0932 115.245866,-37.4452 115.245866,-37.4452"
|
||||
style="fill:#e10d63;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path26-3"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 40.103697,385.25965 c 7.5016,-2.4376 17.169866,-5.57867 27.543733,-8.94947 -2.436934,-7.50106 -5.579067,-17.17093 -8.950533,-27.5464 l -27.5452,8.95254 8.952,27.54333"
|
||||
style="fill:#3f2543;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path28-4"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 98.142767,366.40125 c 10.413603,-3.38333 20.091733,-6.52813 27.543733,-8.94947 -2.43734,-7.50213 -5.58014,-17.174 -8.95254,-27.5516 l -27.545333,8.95267 8.95414,27.5484"
|
||||
style="fill:#d01e25;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path30-9"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 121.4225,294.67165 c 7.608,-2.4724 11.772,-10.64427 9.3,-18.25334 -2.472,-7.60933 -10.64427,-11.772 -18.2532,-9.3 L -2.7733549,304.57525 c -7.1078131,2.57133 -11.1026001,10.25 -8.9807301,17.62026 2.2130236,7.688 10.3781303,12.3756 17.9296503,9.92187 C 6.4626313,332.02418 121.4225,294.67165 121.4225,294.67165"
|
||||
style="fill:#7cd3dc;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path32-2"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 21.240764,327.22258 c 7.501467,-2.438 17.1724,-5.5796 27.548933,-8.95147 -3.383867,-10.41413 -6.528667,-20.0928 -8.950533,-27.5464 l -27.550533,8.95467 8.952133,27.5432"
|
||||
style="fill:#36987b;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path34-0"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 79.279296,308.36431 c 10.415601,-3.38386 20.095731,-6.5292 27.548934,-8.95106 -3.3844,-10.41667 -6.53026,-20.09787 -8.952133,-27.55227 l -27.551067,8.95533 8.954266,27.548"
|
||||
style="fill:#5a872d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path36-4"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 257.17716,287.60071 c 5.09733,2.224 5.468,3.89227 1.48267,11.49067 -4.076,7.78387 -5.096,8.24733 -10.1,6.20827 -6.30133,-2.68694 -14.364,-4.726 -19.55333,-4.726 -8.524,0 -14.17734,3.0584 -14.17734,7.69173 0,15.29 48.836,7.04213 48.836,39.66094 0,16.40253 -14.08667,27.33746 -35.21333,27.33746 -11.12,0 -24.836,-3.7068 -34.288,-8.526 -4.72533,-2.40893 -5.00267,-3.79906 -0.92667,-11.5828 3.52267,-6.85786 4.63334,-7.59906 9.73067,-5.46773 8.06133,3.52133 18.256,6.2088 25.11333,6.2088 7.784,0 12.97334,-3.15107 12.97334,-7.78387 0,-14.82707 -49.66934,-7.7844 -49.66934,-39.38387 0,-16.7724 13.992,-27.98533 34.93467,-27.98533 9.82267,0 22.24,2.96507 30.85733,6.85773"
|
||||
style="fill:#373d47;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path38-9"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 296.56516,248.86498 0,121.57947 c 0,1.85373 -1.94667,3.79947 -4.72533,3.79947 l -12.78934,0 c -2.78,0 -4.72666,-1.94574 -4.72666,-3.79947 l 0,-121.57947 c 0,-6.02266 1.66933,-6.57866 11.12133,-6.57866 10.74933,0 11.12,0.74133 11.12,6.57866"
|
||||
style="fill:#373d47;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path40-2"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 328.53449,347.55592 c 0,6.20893 5.28267,10.37866 13.252,10.37866 9.63734,0 18.34667,-4.63333 23.444,-12.50986 l 0,-8.15467 c -5.09733,-1.9464 -11.30533,-3.0584 -16.86533,-3.0584 -11.86133,0 -19.83067,5.6532 -19.83067,13.34427 z m 58.936,-30.30201 0,52.35667 c 0,2.77974 -1.94533,4.72614 -4.72533,4.72614 l -12.604,0 c -2.872,0 -4.91067,-2.13134 -4.72533,-5.00374 l 0.18533,-5.65253 c -6.85733,7.59787 -16.68,11.58333 -26.50267,11.58333 -19.08933,0 -31.87733,-11.02813 -31.87733,-27.5224 0,-17.514 14.456,-29.2828 36.14,-29.37547 8.248,0 15.75333,1.4828 21.86933,3.98427 l 0,-6.02333 c 0,-9.63707 -7.59866,-15.3824 -20.47866,-15.3824 -6.024,0 -13.43734,2.41 -19.368,5.83813 -4.54134,2.59467 -5.65334,2.40933 -10.564,-4.91093 -4.81867,-7.32147 -4.63334,-8.71094 0,-11.67667 8.896,-5.74533 20.94266,-9.452 32.06266,-9.452 24.92667,0 40.588,13.5296 40.588,36.51093"
|
||||
style="fill:#373d47;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path42-7"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 474.21182,287.78618 c 4.632,2.68693 4.81867,4.16973 -0.27866,12.0464 -4.81734,7.41347 -5.652,7.87707 -10.748,5.28227 -3.89334,-2.03907 -10.10134,-3.79947 -15.19867,-3.79947 -16.03067,0 -26.688,10.56347 -26.688,26.50253 0,16.58747 10.65733,27.70734 26.688,27.70734 5.56133,0 12.51067,-2.13173 17.05067,-4.63333 4.63333,-2.68747 5.65333,-2.50214 10.564,4.63333 4.448,6.6724 4.356,8.2468 0.37066,11.0276 -7.13466,4.91147 -18.44,8.71093 -28.35466,8.71093 -29.65334,0 -49.48534,-18.99693 -49.48534,-47.44587 0,-28.26293 19.832,-47.07493 49.66934,-47.07493 9.08133,0 19.73866,3.05827 26.41066,7.0432"
|
||||
style="fill:#373d47;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path44-1"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 570.95849,362.75338 c 3.70666,4.72547 2.224,6.39427 -7.04267,9.73014 -9.452,3.42813 -10.74933,3.2428 -14.084,-1.11187 l -26.504,-35.39907 -11.86133,11.49014 0,22.98173 c 0,1.85373 -1.94533,3.79947 -4.72533,3.79947 l -12.788,0 c -2.78,0 -4.72667,-1.94574 -4.72667,-3.79947 l 0,-121.57947 c 0,-6.02266 1.668,-6.57866 11.12,-6.57866 10.74933,0 11.12,0.74133 11.12,6.57866 l 0,69.13 36.32533,-34.84213 c 3.98533,-3.8 6.20933,-3.52134 13.344,1.20466 7.87733,5.0964 8.43333,6.4864 4.63333,10.19267 l -27.05866,26.31773 32.248,41.88547"
|
||||
style="fill:#373d47;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path46-3"
|
||||
inkscape:connector-curvature="0" /></g></svg></y:Resource>
|
||||
</y:Resources>
|
||||
</data>
|
||||
</graphml>
|
||||
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 179 KiB |
@@ -1,265 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
||||
<!--Created by yEd 3.14.2-->
|
||||
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
||||
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
||||
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
||||
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
||||
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
||||
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
||||
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
||||
<key for="graphml" id="d7" yfiles.type="resources"/>
|
||||
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
||||
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
||||
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
||||
<graph edgedefault="directed" id="G">
|
||||
<data key="d0"/>
|
||||
<node id="n0">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="73.25" x="478.6165008544913" y="1358.206868489578"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="28.87890625" x="22.185546875" y="15.93359375">Vuls<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n1">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="73.25" x="711.9623756408686" y="1043.7241210937468"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="38.623046875" x="17.3134765625" y="15.93359375">Nginx<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n2">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="73.25" x="711.9623756408686" y="1287.206868489578"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="42.7890625" x="15.23046875" y="15.93359375">MySQL<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n3" yfiles.foldertype="group">
|
||||
<data key="d4"/>
|
||||
<data key="d6">
|
||||
<y:ProxyAutoBoundsNode>
|
||||
<y:Realizers active="0">
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="101.666015625" width="291.7208747863772" x="602.72693824768" y="1146.2994791666624"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="291.7208747863772" x="0.0" y="0.0">Web/App</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
||||
<y:BorderInsets bottom="0" bottomF="0.0" left="23" leftF="23.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
||||
</y:GroupNode>
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 5</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
||||
</y:GroupNode>
|
||||
</y:Realizers>
|
||||
</y:ProxyAutoBoundsNode>
|
||||
</data>
|
||||
<graph edgedefault="directed" id="n3:">
|
||||
<node id="n3::n0">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="73.25" x="640.72693824768" y="1182.9654947916624"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n3::n1">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="73.25" x="723.4623756408686" y="1182.9654947916624"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n3::n2">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="73.25" x="806.1978130340572" y="1182.9654947916624"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="n4">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="73.25" x="821.1978130340572" y="1287.206868489578"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="35.412109375" x="18.9189453125" y="15.93359375">Redis<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<edge id="e0" source="n3" target="n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="none"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e1" source="n3" target="n2">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="none"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e2" source="n0" target="n3::n0">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e3" source="n0" target="n3::n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e4" source="n0" target="n3::n2">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e5" source="n3" target="n4">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="none"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e6" source="n0" target="n4">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e7" source="n0" target="n1">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e8" source="n0" target="n2">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
</graph>
|
||||
<data key="d7">
|
||||
<y:Resources/>
|
||||
</data>
|
||||
</graphml>
|
||||
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,194 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
||||
<!--Created by yEd 3.14.2-->
|
||||
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
||||
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
||||
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
||||
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
||||
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
||||
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
||||
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
||||
<key for="graphml" id="d7" yfiles.type="resources"/>
|
||||
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
||||
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
||||
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
||||
<graph edgedefault="directed" id="G">
|
||||
<data key="d0"/>
|
||||
<node id="n0">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="73.25" x="508.30825042724564" y="1132.4827473958312"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="28.87890625" x="22.185546875" y="15.93359375">Vuls<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n1">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="73.25" x="749.6541252136229" y="993.2413736979156"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="23.8046875" x="24.72265625" y="15.93359375">ELB<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n2">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="73.25" x="749.6541252136229" y="1236.7241210937468"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="27.0390625" x="23.10546875" y="15.93359375">RDS<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n3" yfiles.foldertype="group">
|
||||
<data key="d4"/>
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ProxyAutoBoundsNode>
|
||||
<y:Realizers active="0">
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="101.666015625" width="291.7208747863772" x="640.4186878204343" y="1095.8167317708312"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="291.7208747863772" x="0.0" y="0.0">Web/App</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
||||
<y:BorderInsets bottom="0" bottomF="0.0" left="23" leftF="23.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
||||
</y:GroupNode>
|
||||
<y:GroupNode>
|
||||
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
||||
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 5</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
||||
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
||||
</y:GroupNode>
|
||||
</y:Realizers>
|
||||
</y:ProxyAutoBoundsNode>
|
||||
</data>
|
||||
<graph edgedefault="directed" id="n3:">
|
||||
<node id="n3::n0">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="73.25" x="678.4186878204343" y="1132.4827473958312"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n3::n1">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="73.25" x="761.1541252136229" y="1132.4827473958312"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n3::n2">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="50.0" width="73.25" x="843.8895626068115" y="1132.4827473958312"/>
|
||||
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
|
||||
<y:SmartNodeLabelModel distance="4.0"/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
<y:Shape type="roundrectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
</graph>
|
||||
</node>
|
||||
<edge id="e0" source="n3" target="n1">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="none"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e1" source="n3" target="n2">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="none"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e2" source="n0" target="n3::n0">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
</graph>
|
||||
<data key="d7">
|
||||
<y:Resources/>
|
||||
</data>
|
||||
</graphml>
|
||||
|
Before Width: | Height: | Size: 5.6 KiB |
44
main.go
@@ -1,44 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/future-architect/vuls/commands"
|
||||
"github.com/google/subcommands"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
subcommands.Register(subcommands.HelpCommand(), "")
|
||||
subcommands.Register(subcommands.FlagsCommand(), "")
|
||||
subcommands.Register(subcommands.CommandsCommand(), "")
|
||||
subcommands.Register(&commands.DiscoverCmd{}, "discover")
|
||||
subcommands.Register(&commands.TuiCmd{}, "tui")
|
||||
subcommands.Register(&commands.ScanCmd{}, "scan")
|
||||
subcommands.Register(&commands.PrepareCmd{}, "prepare")
|
||||
|
||||
flag.Parse()
|
||||
ctx := context.Background()
|
||||
os.Exit(int(subcommands.Execute(ctx)))
|
||||
}
|
||||
244
models/models.go
@@ -1,244 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/jinzhu/gorm"
|
||||
cve "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
)
|
||||
|
||||
// ScanHistory is the history of Scanning.
|
||||
type ScanHistory struct {
|
||||
gorm.Model
|
||||
ScanResults []ScanResult
|
||||
ScannedAt time.Time
|
||||
}
|
||||
|
||||
// ScanResults is slice of ScanResult.
|
||||
type ScanResults []ScanResult
|
||||
|
||||
// FilterByCvssOver is filter function.
|
||||
func (results ScanResults) FilterByCvssOver() (filtered ScanResults) {
|
||||
for _, result := range results {
|
||||
cveInfos := []CveInfo{}
|
||||
for _, cveInfo := range result.KnownCves {
|
||||
if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
|
||||
cveInfos = append(cveInfos, cveInfo)
|
||||
}
|
||||
}
|
||||
result.KnownCves = cveInfos
|
||||
filtered = append(filtered, result)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ScanResult has the result of scanned CVE information.
|
||||
type ScanResult struct {
|
||||
gorm.Model
|
||||
ScanHistoryID uint
|
||||
|
||||
ServerName string // TOML Section key
|
||||
// Hostname string
|
||||
Family string
|
||||
Release string
|
||||
// Fqdn string
|
||||
// NWLinks []NWLink
|
||||
KnownCves []CveInfo
|
||||
UnknownCves []CveInfo
|
||||
}
|
||||
|
||||
// CveSummary summarize the number of CVEs group by CVSSv2 Severity
|
||||
func (r ScanResult) CveSummary() string {
|
||||
var high, middle, low, unknown int
|
||||
cves := append(r.KnownCves, r.UnknownCves...)
|
||||
for _, cveInfo := range cves {
|
||||
score := cveInfo.CveDetail.CvssScore(config.Conf.Lang)
|
||||
switch {
|
||||
case 7.0 < score:
|
||||
high++
|
||||
case 4.0 < score:
|
||||
middle++
|
||||
case 0 < score:
|
||||
low++
|
||||
default:
|
||||
unknown++
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d ?:%d)",
|
||||
high+middle+low+unknown,
|
||||
high, middle, low, unknown,
|
||||
)
|
||||
}
|
||||
|
||||
// NWLink has network link information.
|
||||
type NWLink struct {
|
||||
gorm.Model
|
||||
ScanResultID uint
|
||||
|
||||
IPAddress string
|
||||
Netmask string
|
||||
DevName string
|
||||
LinkState string
|
||||
}
|
||||
|
||||
// CveInfos is for sorting
|
||||
type CveInfos []CveInfo
|
||||
|
||||
func (c CveInfos) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c CveInfos) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
func (c CveInfos) Less(i, j int) bool {
|
||||
lang := config.Conf.Lang
|
||||
return c[i].CveDetail.CvssScore(lang) > c[j].CveDetail.CvssScore(lang)
|
||||
}
|
||||
|
||||
// CveInfo has Cve Information.
|
||||
type CveInfo struct {
|
||||
gorm.Model
|
||||
ScanResultID uint
|
||||
|
||||
CveDetail cve.CveDetail
|
||||
Packages []PackageInfo
|
||||
DistroAdvisories []DistroAdvisory
|
||||
CpeNames []CpeName
|
||||
}
|
||||
|
||||
// CpeName has CPE name
|
||||
type CpeName struct {
|
||||
gorm.Model
|
||||
CveInfoID uint
|
||||
|
||||
Name string
|
||||
}
|
||||
|
||||
// PackageInfoList is slice of PackageInfo
|
||||
type PackageInfoList []PackageInfo
|
||||
|
||||
// Exists returns true if exists the name
|
||||
func (ps PackageInfoList) Exists(name string) bool {
|
||||
for _, p := range ps {
|
||||
if p.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UniqByName be uniq by name.
|
||||
func (ps PackageInfoList) UniqByName() (distincted PackageInfoList) {
|
||||
set := make(map[string]PackageInfo)
|
||||
for _, p := range ps {
|
||||
set[p.Name] = p
|
||||
}
|
||||
//sort by key
|
||||
keys := []string{}
|
||||
for key := range set {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
distincted = append(distincted, set[key])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindByName search PackageInfo by name
|
||||
func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found bool) {
|
||||
for _, p := range ps {
|
||||
if p.Name == name {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
return PackageInfo{}, false
|
||||
}
|
||||
|
||||
// Find search PackageInfo by name-version-release
|
||||
// func (ps PackageInfoList) find(nameVersionRelease string) (PackageInfo, bool) {
|
||||
// for _, p := range ps {
|
||||
// joined := p.Name
|
||||
// if 0 < len(p.Version) {
|
||||
// joined = fmt.Sprintf("%s-%s", joined, p.Version)
|
||||
// }
|
||||
// if 0 < len(p.Release) {
|
||||
// joined = fmt.Sprintf("%s-%s", joined, p.Release)
|
||||
// }
|
||||
// if joined == nameVersionRelease {
|
||||
// return p, true
|
||||
// }
|
||||
// }
|
||||
// return PackageInfo{}, false
|
||||
// }
|
||||
|
||||
// PackageInfo has installed packages.
|
||||
type PackageInfo struct {
|
||||
gorm.Model
|
||||
CveInfoID uint
|
||||
|
||||
Name string
|
||||
Version string
|
||||
Release string
|
||||
|
||||
NewVersion string
|
||||
NewRelease string
|
||||
}
|
||||
|
||||
// ToStringCurrentVersion returns package name-version-release
|
||||
func (p PackageInfo) ToStringCurrentVersion() string {
|
||||
str := p.Name
|
||||
if 0 < len(p.Version) {
|
||||
str = fmt.Sprintf("%s-%s", str, p.Version)
|
||||
}
|
||||
if 0 < len(p.Release) {
|
||||
str = fmt.Sprintf("%s-%s", str, p.Release)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// ToStringNewVersion returns package name-version-release
|
||||
func (p PackageInfo) ToStringNewVersion() string {
|
||||
str := p.Name
|
||||
if 0 < len(p.NewVersion) {
|
||||
str = fmt.Sprintf("%s-%s", str, p.NewVersion)
|
||||
}
|
||||
if 0 < len(p.NewRelease) {
|
||||
str = fmt.Sprintf("%s-%s", str, p.NewRelease)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// DistroAdvisory has Amazon Linux AMI Security Advisory information.
|
||||
//TODO Rename to DistroAdvisory
|
||||
type DistroAdvisory struct {
|
||||
gorm.Model
|
||||
CveInfoID uint
|
||||
|
||||
AdvisoryID string
|
||||
Severity string
|
||||
Issued time.Time
|
||||
Updated time.Time
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package models
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPackageInfosUniqByName(t *testing.T) {
|
||||
var test = struct {
|
||||
in PackageInfoList
|
||||
out PackageInfoList
|
||||
}{
|
||||
PackageInfoList{
|
||||
{
|
||||
Name: "hoge",
|
||||
},
|
||||
{
|
||||
Name: "fuga",
|
||||
},
|
||||
{
|
||||
Name: "hoge",
|
||||
},
|
||||
},
|
||||
PackageInfoList{
|
||||
{
|
||||
Name: "hoge",
|
||||
},
|
||||
{
|
||||
Name: "fuga",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actual := test.in.UniqByName()
|
||||
for i, ePack := range test.out {
|
||||
if actual[i].Name == ePack.Name {
|
||||
t.Errorf("expected %#v, actual %#v", ePack.Name, actual[i].Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
22
pkg/cmd/config/config.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
configInitCmd "github.com/future-architect/vuls/pkg/cmd/config/init"
|
||||
)
|
||||
|
||||
func NewCmdConfig() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "config <subcommand>",
|
||||
Short: "Vuls Config Operation",
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls config init > config.json
|
||||
`),
|
||||
}
|
||||
|
||||
cmd.AddCommand(configInitCmd.NewCmdInit())
|
||||
|
||||
return cmd
|
||||
}
|
||||
101
pkg/cmd/config/init/init.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCmdInit() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "generate vuls config template",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return generateConfigTemplate()
|
||||
},
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls config init > config.json
|
||||
`),
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func generateConfigTemplate() error {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
pwd = os.TempDir()
|
||||
}
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
home = "/home/vuls"
|
||||
}
|
||||
|
||||
create := func(name, t string) *template.Template {
|
||||
return template.Must(template.New(name).Parse(t))
|
||||
}
|
||||
|
||||
t := create("config template",
|
||||
`{
|
||||
"server": {
|
||||
"listen": "127.0.0.1:5515",
|
||||
"path": "{{.dbpath}}"
|
||||
},
|
||||
"hosts": {
|
||||
"local": {
|
||||
"type": "local",
|
||||
"scan": {
|
||||
"ospkg": {
|
||||
"root": false
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"path": "{{.dbpath}}",
|
||||
"result_dir": "{{.results}}"
|
||||
}
|
||||
},
|
||||
"remote": {
|
||||
"type": "remote",
|
||||
"host": "127.0.0.1",
|
||||
"port": "22",
|
||||
"user": "vuls",
|
||||
"ssh_config": "{{.sshconfig}}",
|
||||
"ssh_key": "{{.sshkey}}",
|
||||
"scan": {
|
||||
"ospkg": {
|
||||
"root": false
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"path": "{{.dbpath}}",
|
||||
"result_dir": "{{.results}}"
|
||||
}
|
||||
},
|
||||
"cpe": {
|
||||
"type": "local",
|
||||
"scan": {
|
||||
"cpe": [
|
||||
{
|
||||
"cpe": "cpe:2.3:a:apache:log4j:2.3:*:*:*:*:*:*:*"
|
||||
}
|
||||
]
|
||||
},
|
||||
"detect": {
|
||||
"path": "{{.dbpath}}",
|
||||
"result_dir": "{{.results}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
if err := t.Execute(os.Stdout, map[string]string{"dbpath": filepath.Join(pwd, "vuls.db"), "results": filepath.Join(pwd, "results"), "sshconfig": filepath.Join(home, ".ssh", "config"), "sshkey": filepath.Join(home, ".ssh", "id_rsa")}); err != nil {
|
||||
return errors.Wrap(err, "output config template")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
413
pkg/cmd/db/create/create.go
Normal file
@@ -0,0 +1,413 @@
|
||||
package create
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/cmd/db/create/vulnsrc"
|
||||
"github.com/future-architect/vuls/pkg/db"
|
||||
"github.com/future-architect/vuls/pkg/util"
|
||||
)
|
||||
|
||||
type DBCreateOption struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func NewCmdCreate() *cobra.Command {
|
||||
opts := &DBCreateOption{
|
||||
Path: "vuls.db",
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create Vuls DB",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
return create(args[0], opts.Path)
|
||||
},
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls db create https://github.com/vulsio/vuls-data.git
|
||||
$ vuls db create /home/MaineK00n/.cache/vuls
|
||||
`),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.Path, "path", "p", "vuls.db", "path to create Vuls DB")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func create(src, dbpath string) error {
|
||||
datapath := src
|
||||
if u, err := url.Parse(src); err == nil && u.Scheme != "" {
|
||||
cloneDir := filepath.Join(util.CacheDir(), "clone")
|
||||
if err := exec.Command("git", "clone", "--depth", "1", src, cloneDir).Run(); err != nil {
|
||||
return errors.Wrapf(err, "git clone --depth 1 %s %s", src, cloneDir)
|
||||
}
|
||||
datapath = cloneDir
|
||||
}
|
||||
if _, err := os.Stat(datapath); err != nil {
|
||||
return errors.Wrapf(err, "%s not found", datapath)
|
||||
}
|
||||
|
||||
db, err := db.Open("boltdb", dbpath, false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open db")
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if err := filepath.WalkDir(datapath, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
p := strings.TrimPrefix(strings.TrimPrefix(path, datapath), string(os.PathSeparator))
|
||||
srcType, p, found := strings.Cut(p, string(os.PathSeparator))
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
advType, p, found := strings.Cut(p, string(os.PathSeparator))
|
||||
if !found {
|
||||
return errors.Errorf(`unexpected filepath. expected: "%s/["official", ...]/["vulnerability", "os", "library", "cpe"]/...", actual: "%s"`, datapath, path)
|
||||
}
|
||||
|
||||
bs, err := util.Read(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "read %s", path)
|
||||
}
|
||||
if len(bs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch advType {
|
||||
case "vulnerability":
|
||||
var src vulnsrc.Vulnerability
|
||||
if err := json.Unmarshal(bs, &src); err != nil {
|
||||
return errors.Wrapf(err, "unmarshal json. path: %s", path)
|
||||
}
|
||||
if err := db.PutVulnerability(srcType, fmt.Sprintf("vulnerability:%s", src.ID), vulnsrc.ToVulsVulnerability(src)); err != nil {
|
||||
return errors.Wrap(err, "put vulnerability")
|
||||
}
|
||||
case "os":
|
||||
advType, err := toAdvType(p)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "path to adv type")
|
||||
}
|
||||
bucket, err := toAdvBucket(p)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "path to adv bucket")
|
||||
}
|
||||
|
||||
switch advType {
|
||||
case "redhat_oval":
|
||||
if strings.Contains(p, "repository_to_cpe.json") {
|
||||
var src vulnsrc.RepositoryToCPE
|
||||
if err := json.Unmarshal(bs, &src); err != nil {
|
||||
return errors.Wrapf(err, "unmarshal json. path: %s", path)
|
||||
}
|
||||
if err := db.PutRedHatRepoToCPE(srcType, bucket, vulnsrc.ToVulsRepositoryToCPE(src)); err != nil {
|
||||
return errors.Wrap(err, "put repository to cpe")
|
||||
}
|
||||
break
|
||||
}
|
||||
var src vulnsrc.DetectPackage
|
||||
if err := json.Unmarshal(bs, &src); err != nil {
|
||||
return errors.Wrapf(err, "unmarshal json. path: %s", path)
|
||||
}
|
||||
pkgs, err := vulnsrc.ToVulsPackage(src, advType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "to vuls package")
|
||||
}
|
||||
if err := db.PutPackage(srcType, bucket, pkgs); err != nil {
|
||||
return errors.Wrap(err, "put package")
|
||||
}
|
||||
case "windows":
|
||||
if strings.Contains(p, "supercedence.json") {
|
||||
var supercedences []vulnsrc.Supercedence
|
||||
if err := json.Unmarshal(bs, &supercedences); err != nil {
|
||||
return errors.Wrapf(err, "unnmarshal json. path: %s", path)
|
||||
}
|
||||
if err := db.PutWindowsSupercedence(srcType, bucket, vulnsrc.ToVulsSupercedences(supercedences)); err != nil {
|
||||
return errors.Wrap(err, "put supercedence")
|
||||
}
|
||||
break
|
||||
}
|
||||
var src vulnsrc.DetectPackage
|
||||
if err := json.Unmarshal(bs, &src); err != nil {
|
||||
return errors.Wrapf(err, "unmarshal json. path: %s", path)
|
||||
}
|
||||
pkgs, err := vulnsrc.ToVulsPackage(src, advType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "to vuls package")
|
||||
}
|
||||
if err := db.PutPackage(srcType, bucket, pkgs); err != nil {
|
||||
return errors.Wrap(err, "put package")
|
||||
}
|
||||
default:
|
||||
var src vulnsrc.DetectPackage
|
||||
if err := json.Unmarshal(bs, &src); err != nil {
|
||||
return errors.Wrapf(err, "unmarshal json. path: %s", path)
|
||||
}
|
||||
pkgs, err := vulnsrc.ToVulsPackage(src, advType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "to vuls package")
|
||||
}
|
||||
if err := db.PutPackage(srcType, bucket, pkgs); err != nil {
|
||||
return errors.Wrap(err, "put package")
|
||||
}
|
||||
}
|
||||
case "library":
|
||||
case "cpe":
|
||||
var src vulnsrc.DetectCPE
|
||||
if err := json.Unmarshal(bs, &src); err != nil {
|
||||
return errors.Wrapf(err, "unmarshal json. path: %s", path)
|
||||
}
|
||||
|
||||
advType, err := toAdvType(p)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "path to adv type")
|
||||
}
|
||||
cs, err := vulnsrc.ToVulsCPEConfiguration(src, advType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "to vuls cpe configuration")
|
||||
}
|
||||
bucket, err := toAdvBucket(p)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "path to adv bucket")
|
||||
}
|
||||
|
||||
if err := db.PutCPEConfiguration(srcType, bucket, cs); err != nil {
|
||||
return errors.Wrap(err, "put cpe configuration")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toAdvType(path string) (string, error) {
|
||||
ss := strings.Split(path, string(os.PathSeparator))
|
||||
if len(ss) < 3 && ss[0] != "windows" {
|
||||
return "", errors.Errorf(`unexpected path. accepts: "[<os name>, <library name>, "nvd", "jvn"]/**/*.json*", received: "%s"`, path)
|
||||
}
|
||||
|
||||
switch ss[0] {
|
||||
case "alma", "alpine", "amazon", "epel", "fedora", "oracle", "rocky":
|
||||
return fmt.Sprintf("%s:%s", ss[0], ss[1]), nil
|
||||
case "arch", "freebsd", "gentoo", "windows", "conan", "erlang", "nvd", "jvn":
|
||||
return ss[0], nil
|
||||
case "debian":
|
||||
switch ss[1] {
|
||||
case "oval":
|
||||
return "debian_oval", nil
|
||||
case "tracker":
|
||||
return "debian_security_tracker", nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected debian advisory type. accepts: ["oval", "tracker"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "redhat":
|
||||
switch ss[1] {
|
||||
case "api":
|
||||
return "redhat_security_api", nil
|
||||
case "oval":
|
||||
return "redhat_oval", nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected redhat advisory type. accepts: ["api", "oval"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "suse":
|
||||
switch ss[1] {
|
||||
case "cvrf":
|
||||
return "suse_cvrf", nil
|
||||
case "oval":
|
||||
return "suse_oval", nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected suse advisory type. accepts: ["cvrf", "oval"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "ubuntu":
|
||||
switch ss[1] {
|
||||
case "oval":
|
||||
return "ubuntu_oval", nil
|
||||
case "tracker":
|
||||
return "ubuntu_security_tracker", nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected debian advisory type. accepts: ["oval", "tracker"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "cargo":
|
||||
switch ss[1] {
|
||||
case "db":
|
||||
return "cargo_db", nil
|
||||
case "ghsa":
|
||||
return "cargo_ghsa", nil
|
||||
case "osv":
|
||||
return "cargo_osv", nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected cargo advisory type. accepts: ["db", "ghsa", "osv"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "composer":
|
||||
switch ss[1] {
|
||||
case "db":
|
||||
return "composer_db", nil
|
||||
case "ghsa":
|
||||
return "composer_ghsa", nil
|
||||
case "glsa":
|
||||
return "composer_glsa", nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected composer advisory type. accepts: ["db", "ghsa", "glsa"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "golang":
|
||||
switch ss[1] {
|
||||
case "db":
|
||||
return "golang_db", nil
|
||||
case "ghsa":
|
||||
return "golang_ghsa", nil
|
||||
case "glsa":
|
||||
return "golang_glsa", nil
|
||||
case "govulndb":
|
||||
return "golang_govulndb", nil
|
||||
case "osv":
|
||||
return "golang_osv", nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected golang advisory type. accepts: ["db", "ghsa", "glsa", "govulndb", "osv"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "maven":
|
||||
switch ss[1] {
|
||||
case "ghsa":
|
||||
return "maven_ghsa", nil
|
||||
case "glsa":
|
||||
return "maven_glsa", nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected maven advisory type. accepts: ["ghsa", "glsa"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "npm":
|
||||
switch ss[1] {
|
||||
case "db":
|
||||
return "npm_db", nil
|
||||
case "ghsa":
|
||||
return "npm_ghsa", nil
|
||||
case "glsa":
|
||||
return "npm_glsa", nil
|
||||
case "osv":
|
||||
return "npm_osv", nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected npm advisory type. accepts: ["db", "ghsa", "glsa", "osv"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "nuget":
|
||||
switch ss[1] {
|
||||
case "ghsa":
|
||||
return "nuget_ghsa", nil
|
||||
case "glsa":
|
||||
return "nuget_glsa", nil
|
||||
case "osv":
|
||||
return "nuget_osv", nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected nuget advisory type. accepts: ["ghsa", "glsa", "osv"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "pip":
|
||||
switch ss[1] {
|
||||
case "db":
|
||||
return "pip_db", nil
|
||||
case "ghsa":
|
||||
return "pip_ghsa", nil
|
||||
case "glsa":
|
||||
return "pip_glsa", nil
|
||||
case "osv":
|
||||
return "pip_osv", nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected pip advisory type. accepts: ["db", "ghsa", "glsa", "osv"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "rubygems":
|
||||
switch ss[1] {
|
||||
case "db":
|
||||
return "rubygems_db", nil
|
||||
case "ghsa":
|
||||
return "rubygems_ghsa", nil
|
||||
case "glsa":
|
||||
return "rubygems_glsa", nil
|
||||
case "osv":
|
||||
return "rubygems_osv", nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected rubygems advisory type. accepts: ["db", "ghsa", "glsa", "osv"], received: "%s"`, ss[1])
|
||||
}
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected os or library or cpe. accepts: ["alma", "alpine", "amazon", "arch", "debian", "epel", "fedora", "freebsd", "gentoo", "oracle", "redhat", "rocky", "suse", "ubuntu", "windows", "cargo", "composer", "conan", "erlang", "golang", "maven", "npm", "nuget", "pip", "rubygems", "nvd", "jvn"], received: "%s"`, ss[0])
|
||||
}
|
||||
}
|
||||
|
||||
func toAdvBucket(path string) (string, error) {
|
||||
ss := strings.Split(path, string(os.PathSeparator))
|
||||
if len(ss) < 3 && ss[0] != "windows" {
|
||||
return "", errors.Errorf(`unexpected path. accepts: "[<os name>, <library name>, "nvd", "jvn"]/**/*.json*", received: "%s"`, path)
|
||||
}
|
||||
|
||||
switch ss[0] {
|
||||
case "alma", "alpine", "amazon", "epel", "fedora", "oracle", "rocky":
|
||||
return fmt.Sprintf("%s:%s", ss[0], ss[1]), nil
|
||||
case "arch", "freebsd", "gentoo", "cargo", "composer", "conan", "erlang", "golang", "maven", "npm", "nuget", "pip", "rubygems":
|
||||
return ss[0], nil
|
||||
case "debian":
|
||||
switch ss[1] {
|
||||
case "oval", "tracker":
|
||||
return fmt.Sprintf("%s:%s", ss[0], ss[2]), nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected debian advisory type. accepts: ["oval", "tracker"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "redhat":
|
||||
switch ss[1] {
|
||||
case "api":
|
||||
return fmt.Sprintf("%s:%s", ss[0], ss[2]), nil
|
||||
case "oval":
|
||||
if len(ss) < 4 {
|
||||
return "", errors.Errorf(`unexpected path. accepts: "redhat/oval/<os version>/<stream>/yyyy/*.json*", received: "%s"`, path)
|
||||
}
|
||||
if strings.Contains(path, "repository_to_cpe.json") {
|
||||
return fmt.Sprintf("%s_cpe:%s", ss[0], ss[3]), nil
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", ss[0], ss[3]), nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected redhat advisory type. accepts: ["api", "oval"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "suse":
|
||||
switch ss[1] {
|
||||
case "cvrf", "oval":
|
||||
if len(ss) < 4 {
|
||||
return "", errors.Errorf(`unexpected path. accepts: "suse/[cvrf, oval]/<os>/<version>/yyyy/*.json*", received: "%s"`, path)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", ss[2], ss[3]), nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected suse advisory type. accepts: ["cvrf", "oval"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "ubuntu":
|
||||
switch ss[1] {
|
||||
case "oval", "tracker":
|
||||
return fmt.Sprintf("%s:%s", ss[0], ss[2]), nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected debian advisory type. accepts: ["oval", "tracker"], received: "%s"`, ss[1])
|
||||
}
|
||||
case "windows":
|
||||
if strings.Contains(path, "supercedence.json") {
|
||||
return "windows_supercedence", nil
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", ss[0], ss[1]), nil
|
||||
case "nvd", "jvn":
|
||||
return "cpe", nil
|
||||
default:
|
||||
return "", errors.Errorf(`unexpected os or library or cpe. accepts: ["alma", "alpine", "amazon", "arch", "debian", "epel", "fedora", "freebsd", "gentoo", "oracle", "redhat", "rocky", "suse", "ubuntu", "windows", "cargo", "composer", "conan", "erlang", "golang", "maven", "npm", "nuget", "pip", "rubygems", "nvd", "jvn"], received: "%s"`, ss[0])
|
||||
}
|
||||
}
|
||||
269
pkg/cmd/db/create/vulnsrc/types.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package vulnsrc
|
||||
|
||||
// https://github.com/MaineK00n/vuls-data-update/blob/main/pkg/build/types.go
|
||||
|
||||
import "time"
|
||||
|
||||
type Vulnerability struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Advisory *Advisories `json:"advisory,omitempty"`
|
||||
Title *Titles `json:"title,omitempty"`
|
||||
Description *Descriptions `json:"description,omitempty"`
|
||||
CVSS *CVSSes `json:"cvss,omitempty"`
|
||||
EPSS *EPSS `json:"epss,omitempty"`
|
||||
CWE *CWEs `json:"cwe,omitempty"`
|
||||
Metasploit []Metasploit `json:"metasploit,omitempty"`
|
||||
Exploit *Exploit `json:"exploit,omitempty"`
|
||||
KEV *KEV `json:"kev,omitempty"`
|
||||
Mitigation *Mitigation `json:"mitigation,omitempty"`
|
||||
Published *Publisheds `json:"published,omitempty"`
|
||||
Modified *Modifieds `json:"modified,omitempty"`
|
||||
References *References `json:"references,omitempty"`
|
||||
}
|
||||
|
||||
type Advisories struct {
|
||||
MITRE *Advisory `json:"mitre,omitempty"`
|
||||
NVD *Advisory `json:"nvd,omitempty"`
|
||||
JVN []Advisory `json:"jvn,omitempty"`
|
||||
Alma map[string][]Advisory `json:"alma,omitempty"`
|
||||
Alpine map[string]Advisory `json:"alpine,omitempty"`
|
||||
Amazon map[string][]Advisory `json:"amazon,omitempty"`
|
||||
Arch []Advisory `json:"arch,omitempty"`
|
||||
DebianOVAL map[string][]Advisory `json:"debian_oval,omitempty"`
|
||||
DebianSecurityTracker map[string]Advisory `json:"debian_security_tracker,omitempty"`
|
||||
FreeBSD []Advisory `json:"freebsd,omitempty"`
|
||||
Oracle map[string][]Advisory `json:"oracle,omitempty"`
|
||||
RedHatOVAL map[string][]Advisory `json:"redhat_oval,omitempty"`
|
||||
SUSEOVAL map[string][]Advisory `json:"suse_oval,omitempty"`
|
||||
SUSECVRF *Advisory `json:"suse_cvrf,omitempty"`
|
||||
UbuntuOVAL map[string][]Advisory `json:"ubuntu_oval,omitempty"`
|
||||
UbuntuSecurityTracker *Advisory `json:"ubuntu_security_tracker,omitempty"`
|
||||
}
|
||||
type Advisory struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type Titles struct {
|
||||
MITRE string `json:"mitre,omitempty"`
|
||||
NVD string `json:"nvd,omitempty"`
|
||||
JVN map[string]string `json:"jvn,omitempty"`
|
||||
Alma map[string]map[string]string `json:"alma,omitempty"`
|
||||
Alpine map[string]string `json:"alpine,omitempty"`
|
||||
Amazon map[string]map[string]string `json:"amazon,omitempty"`
|
||||
Arch map[string]string `json:"arch,omitempty"`
|
||||
DebianOVAL map[string]map[string]string `json:"debian_oval,omitempty"`
|
||||
DebianSecurityTracker map[string]string `json:"debian_security_tracker,omitempty"`
|
||||
FreeBSD map[string]string `json:"freebsd,omitempty"`
|
||||
Oracle map[string]map[string]string `json:"oracle,omitempty"`
|
||||
RedHatOVAL map[string]map[string]string `json:"redhat_oval,omitempty"`
|
||||
SUSEOVAL map[string]map[string]string `json:"suse_oval,omitempty"`
|
||||
SUSECVRF string `json:"suse_cvrf,omitempty"`
|
||||
UbuntuOVAL map[string]map[string]string `json:"ubuntu_oval,omitempty"`
|
||||
UbuntuSecurityTracker string `json:"ubuntu_security_tracker,omitempty"`
|
||||
}
|
||||
|
||||
type Descriptions struct {
|
||||
MITRE string `json:"mitre,omitempty"`
|
||||
NVD string `json:"nvd,omitempty"`
|
||||
JVN map[string]string `json:"jvn,omitempty"`
|
||||
Alma map[string]map[string]string `json:"alma,omitempty"`
|
||||
Amazon map[string]map[string]string `json:"amazon,omitempty"`
|
||||
DebianOVAL map[string]map[string]string `json:"debian_oval,omitempty"`
|
||||
DebianSecurityTracker map[string]string `json:"debian_security_tracker,omitempty"`
|
||||
FreeBSD map[string]string `json:"freebsd,omitempty"`
|
||||
Oracle map[string]map[string]string `json:"oracle,omitempty"`
|
||||
RedHatOVAL map[string]map[string]string `json:"redhat_oval,omitempty"`
|
||||
SUSEOVAL map[string]map[string]string `json:"suse_oval,omitempty"`
|
||||
SUSECVRF string `json:"suse_cvrf,omitempty"`
|
||||
UbuntuOVAL map[string]map[string]string `json:"ubuntu_oval,omitempty"`
|
||||
UbuntuSecurityTracker string `json:"ubuntu_security_tracker,omitempty"`
|
||||
}
|
||||
|
||||
type CVSSes struct {
|
||||
NVD []CVSS `json:"nvd,omitempty"`
|
||||
JVN map[string][]CVSS `json:"jvn,omitempty"`
|
||||
Alma map[string]map[string][]CVSS `json:"alma,omitempty"`
|
||||
Amazon map[string]map[string][]CVSS `json:"amazon,omitempty"`
|
||||
Arch map[string][]CVSS `json:"arch,omitempty"`
|
||||
Oracle map[string]map[string][]CVSS `json:"oracle,omitempty"`
|
||||
RedHatOVAL map[string]map[string][]CVSS `json:"redhat_oval,omitempty"`
|
||||
SUSEOVAL map[string]map[string][]CVSS `json:"suse_oval,omitempty"`
|
||||
SUSECVRF []CVSS `json:"suse_cvrf,omitempty"`
|
||||
UbuntuOVAL map[string]map[string][]CVSS `json:"ubuntu_oval,omitempty"`
|
||||
UbuntuSecurityTracker []CVSS `json:"ubuntu_security_tracker,omitempty"`
|
||||
}
|
||||
|
||||
type CVSS struct {
|
||||
Version string `json:"version,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Vector string `json:"vector,omitempty"`
|
||||
Score *float64 `json:"score,omitempty"`
|
||||
Severity string `json:"severity,omitempty"`
|
||||
}
|
||||
|
||||
type EPSS struct {
|
||||
EPSS *float64 `json:"epss,omitempty"`
|
||||
Percentile *float64 `json:"percentile,omitempty"`
|
||||
}
|
||||
|
||||
type CWEs struct {
|
||||
NVD []string `json:"nvd,omitempty"`
|
||||
JVN map[string][]string `json:"jvn,omitempty"`
|
||||
RedHatOVAL map[string]map[string][]string `json:"redhat_oval,omitempty"`
|
||||
}
|
||||
|
||||
type Metasploit struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
}
|
||||
|
||||
type Exploit struct {
|
||||
NVD []string `json:"nvd,omitempty"`
|
||||
ExploitDB []ExploitDB `json:"exploit_db,omitempty"`
|
||||
GitHub []GitHub `json:"github,omitempty"`
|
||||
InTheWild []InTheWild `json:"inthewild,omitempty"`
|
||||
Trickest *Trickest `json:"trickest,omitempty"`
|
||||
}
|
||||
|
||||
type ExploitDB struct {
|
||||
ID string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
FileURL string `json:"file_url,omitempty"`
|
||||
}
|
||||
|
||||
type GitHub struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Stars int `json:"stars"`
|
||||
Forks int `json:"forks"`
|
||||
Watches int `json:"watches"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type InTheWild struct {
|
||||
Source string `json:"source,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type Trickest struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
PoC *TrickestPoc `json:"poc,omitempty"`
|
||||
}
|
||||
|
||||
type TrickestPoc struct {
|
||||
Reference []string `json:"reference,omitempty"`
|
||||
GitHub []string `json:"github,omitempty"`
|
||||
}
|
||||
|
||||
type KEV struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
RequiredAction string `json:"required_action,omitempty"`
|
||||
DueDate *time.Time `json:"due_date,omitempty"`
|
||||
}
|
||||
|
||||
type Mitigation struct {
|
||||
NVD []string `json:"nvd,omitempty"`
|
||||
UbuntuSecurityTracker string `json:"ubuntu_security_tracker,omitempty"`
|
||||
}
|
||||
|
||||
type Publisheds struct {
|
||||
MITRE *time.Time `json:"mitre,omitempty"`
|
||||
NVD *time.Time `json:"nvd,omitempty"`
|
||||
JVN map[string]*time.Time `json:"jvn,omitempty"`
|
||||
Alma map[string]map[string]*time.Time `json:"alma,omitempty"`
|
||||
Amazon map[string]map[string]*time.Time `json:"amazon,omitempty"`
|
||||
FreeBSD map[string]*time.Time `json:"freebsd,omitempty"`
|
||||
Oracle map[string]map[string]*time.Time `json:"oracle,omitempty"`
|
||||
RedHatOVAL map[string]map[string]*time.Time `json:"redhat_oval,omitempty"`
|
||||
SUSEOVAL map[string]map[string]*time.Time `json:"suse_oval,omitempty"`
|
||||
SUSECVRF *time.Time `json:"suse_cvrf,omitempty"`
|
||||
UbuntuOVAL map[string]map[string]*time.Time `json:"ubuntu_oval,omitempty"`
|
||||
UbuntuSecurityTracker *time.Time `json:"ubuntu_security_tracker,omitempty"`
|
||||
}
|
||||
|
||||
type Modifieds struct {
|
||||
MITRE *time.Time `json:"mitre,omitempty"`
|
||||
NVD *time.Time `json:"nvd,omitempty"`
|
||||
JVN map[string]*time.Time `json:"jvn,omitempty"`
|
||||
Alma map[string]map[string]*time.Time `json:"alma,omitempty"`
|
||||
Amazon map[string]map[string]*time.Time `json:"amazon,omitempty"`
|
||||
FreeBSD map[string]*time.Time `json:"freebsd,omitempty"`
|
||||
RedHatOVAL map[string]map[string]*time.Time `json:"redhat_oval,omitempty"`
|
||||
SUSEOVAL map[string]map[string]*time.Time `json:"suse_oval,omitempty"`
|
||||
SUSECVRF *time.Time `json:"suse_cvrf,omitempty"`
|
||||
}
|
||||
|
||||
type References struct {
|
||||
MITRE []Reference `json:"mitre,omitempty"`
|
||||
NVD []Reference `json:"nvd,omitempty"`
|
||||
JVN map[string][]Reference `json:"jvn,omitempty"`
|
||||
Alma map[string]map[string][]Reference `json:"alma,omitempty"`
|
||||
Amazon map[string]map[string][]Reference `json:"amazon,omitempty"`
|
||||
Arch map[string][]Reference `json:"arch,omitempty"`
|
||||
DebianOVAL map[string]map[string][]Reference `json:"debian_oval,omitempty"`
|
||||
DebianSecurityTracker map[string][]Reference `json:"debian_security_tracker,omitempty"`
|
||||
FreeBSD map[string][]Reference `json:"freebsd,omitempty"`
|
||||
Oracle map[string]map[string][]Reference `json:"oracle,omitempty"`
|
||||
RedHatOVAL map[string]map[string][]Reference `json:"redhat_oval,omitempty"`
|
||||
SUSEOVAL map[string]map[string][]Reference `json:"suse_oval,omitempty"`
|
||||
SUSECVRF []Reference `json:"suse_cvrf,omitempty"`
|
||||
UbuntuOVAL map[string]map[string][]Reference `json:"ubuntu_oval,omitempty"`
|
||||
UbuntuSecurityTracker []Reference `json:"ubuntu_security_tracker,omitempty"`
|
||||
}
|
||||
|
||||
type Reference struct {
|
||||
Source string `json:"source,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type DetectCPE struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Configurations map[string][]CPEConfiguration `json:"configurations,omitempty"`
|
||||
}
|
||||
|
||||
type CPEConfiguration struct {
|
||||
Vulnerable []CPE `json:"vulnerable,omitempty"`
|
||||
RunningOn []CPE `json:"running_on,omitempty"`
|
||||
}
|
||||
|
||||
type CPE struct {
|
||||
CPEVersion string `json:"cpe_version,omitempty"`
|
||||
CPE string `json:"cpe,omitempty"`
|
||||
Version []Version `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
type DetectPackage struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Packages map[string][]Package `json:"packages,omitempty"`
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Version [][]Version `json:"version,omitempty"`
|
||||
ModularityLabel string `json:"modularity_label,omitempty"`
|
||||
Arch []string `json:"arch,omitempty"`
|
||||
Repository string `json:"repository,omitempty"`
|
||||
CPE []string `json:"cpe,omitempty"`
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
Operator string `json:"operator,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
type RepositoryToCPE map[string][]string
|
||||
|
||||
type Supercedence struct {
|
||||
KBID string `json:"KBID,omitempty"`
|
||||
Supersededby struct {
|
||||
KBIDs []string `json:"KBIDs,omitempty"`
|
||||
} `json:"Supersededby,omitempty"`
|
||||
}
|
||||
619
pkg/cmd/db/create/vulnsrc/vulnsrc.go
Normal file
@@ -0,0 +1,619 @@
|
||||
package vulnsrc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/knqyf263/go-cpe/common"
|
||||
"github.com/knqyf263/go-cpe/naming"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/db/types"
|
||||
)
|
||||
|
||||
func ToVulsVulnerability(src Vulnerability) types.Vulnerability {
|
||||
return types.Vulnerability{
|
||||
ID: src.ID,
|
||||
Advisory: toVulsAdvisory(src.Advisory),
|
||||
Title: toVulsTitle(src.Title),
|
||||
Description: toVulsDescription(src.Description),
|
||||
CVSS: toVulsCVSS(src.CVSS),
|
||||
EPSS: toVulsEPSS(src.EPSS),
|
||||
CWE: toVulsCWE(src.CWE),
|
||||
Metasploit: toVulsMetasploit(src.Metasploit),
|
||||
Exploit: toVulsExploit(src.Exploit),
|
||||
KEV: src.KEV != nil,
|
||||
Published: toVulsPublished(src.Published),
|
||||
Modified: toVulsModified(src.Modified),
|
||||
Reference: toVulsReference(src.References),
|
||||
}
|
||||
}
|
||||
|
||||
func toVulsAdvisory(src *Advisories) []string {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var advs []string
|
||||
if src.MITRE != nil {
|
||||
advs = append(advs, "mitre")
|
||||
}
|
||||
if src.NVD != nil {
|
||||
advs = append(advs, "nvd")
|
||||
}
|
||||
for _, a := range src.JVN {
|
||||
advs = append(advs, fmt.Sprintf("jvn:%s", a.ID))
|
||||
}
|
||||
for v, as := range src.Alma {
|
||||
for _, a := range as {
|
||||
advs = append(advs, fmt.Sprintf("alma:%s:%s", v, a.ID))
|
||||
}
|
||||
}
|
||||
for v, a := range src.Alpine {
|
||||
advs = append(advs, fmt.Sprintf("alpine:%s:%s", v, a.ID))
|
||||
}
|
||||
for v, as := range src.Amazon {
|
||||
for _, a := range as {
|
||||
advs = append(advs, fmt.Sprintf("amazon:%s:%s", v, a.ID))
|
||||
}
|
||||
}
|
||||
for _, a := range src.Arch {
|
||||
advs = append(advs, fmt.Sprintf("arch:%s", a.ID))
|
||||
}
|
||||
for v, as := range src.DebianOVAL {
|
||||
for _, a := range as {
|
||||
advs = append(advs, fmt.Sprintf("debian_oval:%s:%s", v, a.ID))
|
||||
}
|
||||
}
|
||||
for v, a := range src.DebianSecurityTracker {
|
||||
advs = append(advs, fmt.Sprintf("debian_security_tracker:%s:%s", v, a.ID))
|
||||
}
|
||||
for _, a := range src.FreeBSD {
|
||||
advs = append(advs, fmt.Sprintf("freebsd:%s", a.ID))
|
||||
}
|
||||
for v, as := range src.Oracle {
|
||||
for _, a := range as {
|
||||
advs = append(advs, fmt.Sprintf("oracle:%s:%s", v, a.ID))
|
||||
}
|
||||
}
|
||||
for v, as := range src.RedHatOVAL {
|
||||
for _, a := range as {
|
||||
advs = append(advs, fmt.Sprintf("redhat_oval:%s:%s", v, a.ID))
|
||||
}
|
||||
}
|
||||
for v, as := range src.SUSEOVAL {
|
||||
for _, a := range as {
|
||||
advs = append(advs, fmt.Sprintf("suse_oval:%s:%s", v, a.ID))
|
||||
}
|
||||
}
|
||||
if src.SUSECVRF != nil {
|
||||
advs = append(advs, "suse_cvrf")
|
||||
}
|
||||
for v, as := range src.UbuntuOVAL {
|
||||
for _, a := range as {
|
||||
advs = append(advs, fmt.Sprintf("ubuntu_oval:%s:%s", v, a.ID))
|
||||
}
|
||||
}
|
||||
if src.UbuntuSecurityTracker != nil {
|
||||
advs = append(advs, "ubuntu_security_tracker")
|
||||
}
|
||||
return advs
|
||||
}
|
||||
|
||||
func toVulsTitle(src *Titles) string {
|
||||
if src == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if src.NVD != "" {
|
||||
return src.NVD
|
||||
}
|
||||
if src.MITRE != "" {
|
||||
return src.MITRE
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func toVulsDescription(src *Descriptions) string {
|
||||
if src == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if src.NVD != "" {
|
||||
return src.NVD
|
||||
}
|
||||
if src.MITRE != "" {
|
||||
return src.MITRE
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func toVulsCVSS(src *CVSSes) []types.CVSS {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var cvsses []types.CVSS
|
||||
for _, c := range src.NVD {
|
||||
cvsses = append(cvsses, types.CVSS{
|
||||
Source: "nvd",
|
||||
Version: c.Version,
|
||||
Vector: c.Vector,
|
||||
Score: c.Score,
|
||||
Severity: c.Severity,
|
||||
})
|
||||
}
|
||||
for id, cs := range src.JVN {
|
||||
for _, c := range cs {
|
||||
cvsses = append(cvsses, types.CVSS{
|
||||
Source: fmt.Sprintf("jvn:%s", id),
|
||||
Version: c.Version,
|
||||
Vector: c.Vector,
|
||||
Score: c.Score,
|
||||
Severity: c.Severity,
|
||||
})
|
||||
}
|
||||
}
|
||||
for v, idcs := range src.Alma {
|
||||
for id, cs := range idcs {
|
||||
for _, c := range cs {
|
||||
cvsses = append(cvsses, types.CVSS{
|
||||
Source: fmt.Sprintf("alma:%s:%s", v, id),
|
||||
Version: c.Version,
|
||||
Vector: c.Vector,
|
||||
Score: c.Score,
|
||||
Severity: c.Severity,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for v, idcs := range src.Amazon {
|
||||
for id, cs := range idcs {
|
||||
for _, c := range cs {
|
||||
cvsses = append(cvsses, types.CVSS{
|
||||
Source: fmt.Sprintf("amazon:%s:%s", v, id),
|
||||
Version: c.Version,
|
||||
Vector: c.Vector,
|
||||
Score: c.Score,
|
||||
Severity: c.Severity,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for id, cs := range src.Arch {
|
||||
for _, c := range cs {
|
||||
cvsses = append(cvsses, types.CVSS{
|
||||
Source: fmt.Sprintf("arch:%s", id),
|
||||
Version: c.Version,
|
||||
Vector: c.Vector,
|
||||
Score: c.Score,
|
||||
Severity: c.Severity,
|
||||
})
|
||||
}
|
||||
}
|
||||
for v, idcs := range src.Oracle {
|
||||
for id, cs := range idcs {
|
||||
for _, c := range cs {
|
||||
cvsses = append(cvsses, types.CVSS{
|
||||
Source: fmt.Sprintf("oracle:%s:%s", v, id),
|
||||
Version: c.Version,
|
||||
Vector: c.Vector,
|
||||
Score: c.Score,
|
||||
Severity: c.Severity,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for v, idcs := range src.RedHatOVAL {
|
||||
for id, cs := range idcs {
|
||||
for _, c := range cs {
|
||||
cvsses = append(cvsses, types.CVSS{
|
||||
Source: fmt.Sprintf("redhat_oval:%s:%s", v, id),
|
||||
Version: c.Version,
|
||||
Vector: c.Vector,
|
||||
Score: c.Score,
|
||||
Severity: c.Severity,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for v, idcs := range src.SUSEOVAL {
|
||||
for id, cs := range idcs {
|
||||
for _, c := range cs {
|
||||
cvsses = append(cvsses, types.CVSS{
|
||||
Source: fmt.Sprintf("suse_oval:%s:%s", v, id),
|
||||
Version: c.Version,
|
||||
Vector: c.Vector,
|
||||
Score: c.Score,
|
||||
Severity: c.Severity,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, c := range src.SUSECVRF {
|
||||
cvsses = append(cvsses, types.CVSS{
|
||||
Source: "suse_cvrf",
|
||||
Version: c.Version,
|
||||
Vector: c.Vector,
|
||||
Score: c.Score,
|
||||
Severity: c.Severity,
|
||||
})
|
||||
}
|
||||
for v, idcs := range src.UbuntuOVAL {
|
||||
for id, cs := range idcs {
|
||||
for _, c := range cs {
|
||||
cvsses = append(cvsses, types.CVSS{
|
||||
Source: fmt.Sprintf("ubuntu_oval:%s:%s", v, id),
|
||||
Version: c.Version,
|
||||
Vector: c.Vector,
|
||||
Score: c.Score,
|
||||
Severity: c.Severity,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, c := range src.UbuntuSecurityTracker {
|
||||
cvsses = append(cvsses, types.CVSS{
|
||||
Source: "ubuntu_security_tracker",
|
||||
Version: c.Version,
|
||||
Vector: c.Vector,
|
||||
Score: c.Score,
|
||||
Severity: c.Severity,
|
||||
})
|
||||
}
|
||||
|
||||
return cvsses
|
||||
}
|
||||
|
||||
func toVulsEPSS(src *EPSS) *types.EPSS {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
return &types.EPSS{EPSS: src.EPSS, Percentile: src.Percentile}
|
||||
}
|
||||
|
||||
func toVulsCWE(src *CWEs) []types.CWE {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := map[string][]string{}
|
||||
for _, c := range src.NVD {
|
||||
m[c] = append(m[c], "nvd")
|
||||
}
|
||||
for id, cs := range src.JVN {
|
||||
for _, c := range cs {
|
||||
m[c] = append(m[c], fmt.Sprintf("jvn:%s", id))
|
||||
}
|
||||
}
|
||||
for v, idcs := range src.RedHatOVAL {
|
||||
for id, cs := range idcs {
|
||||
for _, c := range cs {
|
||||
m[c] = append(m[c], fmt.Sprintf("redhat_oval:%s:%s", v, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cwes []types.CWE
|
||||
for id, srcs := range m {
|
||||
cwes = append(cwes, types.CWE{
|
||||
Source: srcs,
|
||||
ID: id,
|
||||
})
|
||||
}
|
||||
return cwes
|
||||
}
|
||||
|
||||
func toVulsMetasploit(src []Metasploit) []types.Metasploit {
|
||||
ms := make([]types.Metasploit, 0, len(src))
|
||||
for _, m := range src {
|
||||
ms = append(ms, types.Metasploit{
|
||||
Title: m.Title,
|
||||
URL: m.URLs[0],
|
||||
})
|
||||
}
|
||||
return ms
|
||||
}
|
||||
|
||||
func toVulsExploit(src *Exploit) []types.Exploit {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := map[string][]string{}
|
||||
for _, e := range src.NVD {
|
||||
m[e] = append(m[e], "nvd")
|
||||
}
|
||||
for _, e := range src.ExploitDB {
|
||||
m[e.URL] = append(m[e.URL], "exploit-db")
|
||||
}
|
||||
for _, e := range src.GitHub {
|
||||
m[e.URL] = append(m[e.URL], "github")
|
||||
}
|
||||
for _, e := range src.InTheWild {
|
||||
m[e.URL] = append(m[e.URL], "inthewild")
|
||||
}
|
||||
if src.Trickest != nil {
|
||||
if src.Trickest.PoC != nil {
|
||||
for _, e := range src.Trickest.PoC.Reference {
|
||||
m[e] = append(m[e], "trickest")
|
||||
}
|
||||
for _, e := range src.Trickest.PoC.GitHub {
|
||||
m[e] = append(m[e], "trickest")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var es []types.Exploit
|
||||
for u, srcs := range m {
|
||||
es = append(es, types.Exploit{
|
||||
Source: srcs,
|
||||
URL: u,
|
||||
})
|
||||
}
|
||||
return es
|
||||
}
|
||||
|
||||
func toVulsPublished(src *Publisheds) *time.Time {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if src.NVD != nil {
|
||||
return src.NVD
|
||||
}
|
||||
if src.MITRE != nil {
|
||||
return src.MITRE
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toVulsModified(src *Modifieds) *time.Time {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if src.NVD != nil {
|
||||
return src.NVD
|
||||
}
|
||||
if src.MITRE != nil {
|
||||
return src.MITRE
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toVulsReference(src *References) []string {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := map[string]struct{}{}
|
||||
for _, r := range src.MITRE {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
for _, r := range src.NVD {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
for _, rs := range src.JVN {
|
||||
for _, r := range rs {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, idrs := range src.Alma {
|
||||
for _, rs := range idrs {
|
||||
for _, r := range rs {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, idrs := range src.Amazon {
|
||||
for _, rs := range idrs {
|
||||
for _, r := range rs {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, rs := range src.Arch {
|
||||
for _, r := range rs {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, idrs := range src.DebianOVAL {
|
||||
for _, rs := range idrs {
|
||||
for _, r := range rs {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, rs := range src.DebianSecurityTracker {
|
||||
for _, r := range rs {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, rs := range src.FreeBSD {
|
||||
for _, r := range rs {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, idrs := range src.Oracle {
|
||||
for _, rs := range idrs {
|
||||
for _, r := range rs {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, idrs := range src.RedHatOVAL {
|
||||
for _, rs := range idrs {
|
||||
for _, r := range rs {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, idrs := range src.SUSEOVAL {
|
||||
for _, rs := range idrs {
|
||||
for _, r := range rs {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, r := range src.SUSECVRF {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
for _, idrs := range src.UbuntuOVAL {
|
||||
for _, rs := range idrs {
|
||||
for _, r := range rs {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, r := range src.UbuntuSecurityTracker {
|
||||
m[r.URL] = struct{}{}
|
||||
}
|
||||
|
||||
return maps.Keys(m)
|
||||
}
|
||||
|
||||
func ToVulsPackage(src DetectPackage, advType string) (map[string]types.Packages, error) {
|
||||
m := map[string]types.Packages{}
|
||||
for id, ps := range src.Packages {
|
||||
id = fmt.Sprintf("%s:%s", advType, id)
|
||||
for _, p := range ps {
|
||||
name, err := toVulsPackageName(p.Name, p.ModularityLabel)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "to vuls package name")
|
||||
}
|
||||
|
||||
base, ok := m[name]
|
||||
if !ok {
|
||||
base = types.Packages{
|
||||
ID: src.ID,
|
||||
Package: map[string]types.Package{},
|
||||
}
|
||||
}
|
||||
|
||||
vers := make([][]types.Version, 0, len(p.Version))
|
||||
for _, vs := range p.Version {
|
||||
vss := make([]types.Version, 0, len(vs))
|
||||
for _, v := range vs {
|
||||
vss = append(vss, types.Version{
|
||||
Operator: v.Operator,
|
||||
Version: v.Version,
|
||||
})
|
||||
}
|
||||
vers = append(vers, vss)
|
||||
}
|
||||
|
||||
base.Package[id] = types.Package{
|
||||
Status: p.Status,
|
||||
Version: vers,
|
||||
Arch: p.Arch,
|
||||
Repository: p.Repository,
|
||||
CPE: p.CPE,
|
||||
}
|
||||
|
||||
m[name] = base
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func toVulsPackageName(name, modularitylabel string) (string, error) {
|
||||
if modularitylabel == "" {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
ss := strings.Split(modularitylabel, ":")
|
||||
if len(ss) < 2 {
|
||||
return name, errors.Errorf(`[WARN] unexpected modularitylabel. accepts: "<module name>:<stream>(:<version>:<context>:<arch>)", received: "%s"`, modularitylabel)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s::%s", ss[0], ss[1], name), nil
|
||||
}
|
||||
|
||||
func ToVulsCPEConfiguration(src DetectCPE, advType string) (map[string]types.CPEConfigurations, error) {
|
||||
m := map[string][]types.CPEConfiguration{}
|
||||
for id, cs := range src.Configurations {
|
||||
id = fmt.Sprintf("%s:%s", advType, id)
|
||||
for _, c := range cs {
|
||||
rs := make([]types.CPE, 0, len(c.RunningOn))
|
||||
for _, r := range c.RunningOn {
|
||||
rs = append(rs, toVulnsrcCPEtoVulsCPE(r))
|
||||
}
|
||||
|
||||
for _, v := range c.Vulnerable {
|
||||
m[id] = append(m[id], types.CPEConfiguration{
|
||||
Vulnerable: toVulnsrcCPEtoVulsCPE(v),
|
||||
RunningOn: rs,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m2 := map[string]types.CPEConfigurations{}
|
||||
for id, cs := range m {
|
||||
for _, c := range cs {
|
||||
pvp, err := toVulsCPEConfigurationName(c.Vulnerable.CPEVersion, c.Vulnerable.CPE)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "to vuls cpe configuration name")
|
||||
}
|
||||
|
||||
base, ok := m2[pvp]
|
||||
if !ok {
|
||||
base = types.CPEConfigurations{
|
||||
ID: src.ID,
|
||||
Configuration: map[string][]types.CPEConfiguration{},
|
||||
}
|
||||
}
|
||||
base.Configuration[id] = append(base.Configuration[id], c)
|
||||
|
||||
m2[pvp] = base
|
||||
}
|
||||
}
|
||||
|
||||
return m2, nil
|
||||
}
|
||||
|
||||
func toVulsCPEConfigurationName(version string, cpe string) (string, error) {
|
||||
var (
|
||||
wfn common.WellFormedName
|
||||
err error
|
||||
)
|
||||
switch version {
|
||||
case "2.3":
|
||||
wfn, err = naming.UnbindFS(cpe)
|
||||
default:
|
||||
wfn, err = naming.UnbindURI(cpe)
|
||||
}
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "unbind %s", cpe)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:%s", wfn.GetString(common.AttributePart), wfn.GetString(common.AttributeVendor), wfn.GetString(common.AttributeProduct)), nil
|
||||
}
|
||||
|
||||
func toVulnsrcCPEtoVulsCPE(s CPE) types.CPE {
|
||||
d := types.CPE{
|
||||
CPEVersion: s.CPEVersion,
|
||||
CPE: s.CPE,
|
||||
}
|
||||
for _, v := range s.Version {
|
||||
d.Version = append(d.Version, types.Version{
|
||||
Operator: v.Operator,
|
||||
Version: v.Version,
|
||||
})
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func ToVulsRepositoryToCPE(src RepositoryToCPE) types.RepositoryToCPE {
|
||||
return types.RepositoryToCPE(src)
|
||||
}
|
||||
|
||||
func ToVulsSupercedences(src []Supercedence) types.Supercedence {
|
||||
ss := types.Supercedence{}
|
||||
for _, s := range src {
|
||||
ss[s.KBID] = s.Supersededby.KBIDs
|
||||
}
|
||||
return ss
|
||||
}
|
||||
37
pkg/cmd/db/db.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
dbCreateCmd "github.com/future-architect/vuls/pkg/cmd/db/create"
|
||||
dbEditCmd "github.com/future-architect/vuls/pkg/cmd/db/edit"
|
||||
dbFetchCmd "github.com/future-architect/vuls/pkg/cmd/db/fetch"
|
||||
dbSearchCmd "github.com/future-architect/vuls/pkg/cmd/db/search"
|
||||
dbUploadCmd "github.com/future-architect/vuls/pkg/cmd/db/upload"
|
||||
)
|
||||
|
||||
func NewCmdDB() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "db <subcommand>",
|
||||
Short: "Vuls DB Operation",
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls db create https://github.com/vulsio/vuls-data.git
|
||||
$ vuls db create /home/MaineK00n/.cache/vuls
|
||||
$ vuls db edit ubuntu 22.04 openssl
|
||||
$ vuls db edit vulnerability CVE-2022-3602
|
||||
$ vuls db fetch
|
||||
$ vuls db fetch ghcr.io/vuls/db
|
||||
$ vuls db search ubuntu 22.04 openssl
|
||||
$ vuls db search vulnerability CVE-2022-3602
|
||||
`),
|
||||
}
|
||||
|
||||
cmd.AddCommand(dbCreateCmd.NewCmdCreate())
|
||||
cmd.AddCommand(dbEditCmd.NewCmdEdit())
|
||||
cmd.AddCommand(dbFetchCmd.NewCmdFetch())
|
||||
cmd.AddCommand(dbSearchCmd.NewCmdSearch())
|
||||
cmd.AddCommand(dbUploadCmd.NewCmdUpload())
|
||||
|
||||
return cmd
|
||||
}
|
||||
23
pkg/cmd/db/edit/edit.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package edit
|
||||
|
||||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCmdEdit() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "edit",
|
||||
Short: "Edit Data in Vuls DB",
|
||||
Args: cobra.RangeArgs(2, 3),
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return nil
|
||||
},
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls db edit ubuntu 22.04 openssl
|
||||
$ vuls db edit vulnerability CVE-2022-3602
|
||||
`),
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
152
pkg/cmd/db/fetch/fetch.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package fetch
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"oras.land/oras-go/v2/content"
|
||||
"oras.land/oras-go/v2/registry/remote"
|
||||
)
|
||||
|
||||
type DBFetchOption struct {
|
||||
Path string
|
||||
PlainHTTP bool
|
||||
}
|
||||
|
||||
const (
|
||||
defaultVulsDBRepository = "ghcr.io/mainek00n/vuls-data/vuls-db"
|
||||
defaultTag = "latest"
|
||||
vulsDBConfigMediaType = "application/vnd.vuls.vuls.db"
|
||||
vulsDBLayerMediaType = "application/vnd.vuls.vuls.db.layer.v1.tar+gzip"
|
||||
)
|
||||
|
||||
func NewCmdFetch() *cobra.Command {
|
||||
opts := &DBFetchOption{
|
||||
Path: "vuls.db",
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "fetch",
|
||||
Short: "Fetch Vuls DB",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
p := defaultVulsDBRepository
|
||||
if len(args) > 0 {
|
||||
p = args[0]
|
||||
}
|
||||
return fetch(context.Background(), p, opts.Path, opts.PlainHTTP)
|
||||
},
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls db fetch
|
||||
$ vuls db fetch ghcr.io/vuls/db
|
||||
`),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.Path, "path", "p", "vuls.db", "path to fetch Vuls DB")
|
||||
cmd.Flags().BoolVarP(&opts.PlainHTTP, "plain-http", "", false, "container registry is provided with plain http")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func fetch(ctx context.Context, ref, dbpath string, plainHTTP bool) error {
|
||||
repo, err := remote.NewRepository(ref)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if plainHTTP {
|
||||
repo.PlainHTTP = true
|
||||
}
|
||||
|
||||
desc, err := repo.Resolve(ctx, defaultTag)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
pulledBlob, err := content.FetchAll(ctx, repo, desc)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
var manifest ocispec.Manifest
|
||||
if err := json.Unmarshal(pulledBlob, &manifest); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if manifest.Config.MediaType != vulsDBConfigMediaType {
|
||||
return errors.New("not vuls repository")
|
||||
}
|
||||
|
||||
for _, l := range manifest.Layers {
|
||||
if l.MediaType != vulsDBLayerMediaType {
|
||||
continue
|
||||
}
|
||||
|
||||
desc, err := repo.Blobs().Resolve(ctx, l.Digest.String())
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
rc, err := repo.Fetch(ctx, desc)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
bs, err := content.ReadAll(rc, desc)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
gr, err := gzip.NewReader(bytes.NewReader(bs))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer gr.Close()
|
||||
|
||||
tr := tar.NewReader(gr)
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return errors.Wrap(err, "Next()")
|
||||
}
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err := os.MkdirAll(filepath.Join(dbpath, header.Name), header.FileInfo().Mode()); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
case tar.TypeReg:
|
||||
if err := func() error {
|
||||
f, err := os.OpenFile(dbpath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := io.Copy(f, tr); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.Errorf("unknown type: %s in %s", header.Typeflag, header.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
23
pkg/cmd/db/search/search.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCmdSearch() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "search",
|
||||
Short: "Search Vulnerabilty/Package in Vuls DB",
|
||||
Args: cobra.RangeArgs(2, 3),
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return nil
|
||||
},
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls db search ubuntu 22.04 openssl
|
||||
$ vuls db search vulnerability CVE-2022-3602
|
||||
`),
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
23
pkg/cmd/db/upload/upload.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package upload
|
||||
|
||||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCmdUpload() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "upload",
|
||||
Short: "Upload Vuls DB",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return nil
|
||||
},
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls db upload
|
||||
$ vuls db upload ghcr.io/vuls/db
|
||||
`),
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
183
pkg/cmd/detect/detect.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package detect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/config"
|
||||
"github.com/future-architect/vuls/pkg/detect"
|
||||
"github.com/future-architect/vuls/pkg/log"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type DetectOptions struct {
|
||||
Config string
|
||||
}
|
||||
|
||||
func NewCmdDetect() *cobra.Command {
|
||||
opts := &DetectOptions{
|
||||
Config: "config.json",
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "detect ([\"host\"])",
|
||||
Short: "Vuls detect vulnerabilities",
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
if err := exec(context.Background(), opts.Config, args); err != nil {
|
||||
return errors.Wrap(err, "failed to detect")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls detect
|
||||
$ vuls detect results/**/host.json
|
||||
`),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.Config, "config", "c", "config.json", "vuls config file path")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func exec(ctx context.Context, path string, args []string) error {
|
||||
logger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create logger")
|
||||
}
|
||||
|
||||
ctx = log.ContextWithLogger(ctx, logger)
|
||||
|
||||
c, err := config.Open(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "open %s as config", path)
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get working direcotry")
|
||||
}
|
||||
|
||||
fs, err := os.ReadDir(filepath.Join(pwd, "results"))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "read %s", filepath.Join(pwd, "results"))
|
||||
}
|
||||
|
||||
var ds []time.Time
|
||||
for _, f := range fs {
|
||||
if !f.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := time.Parse("2006-01-02T150405-0700", f.Name())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ds = append(ds, t)
|
||||
}
|
||||
if len(ds) == 0 {
|
||||
return errors.Wrapf(err, "result dir not found")
|
||||
}
|
||||
|
||||
slices.SortFunc(ds, func(e1, e2 time.Time) bool {
|
||||
return e1.After(e2)
|
||||
})
|
||||
|
||||
args = append(args, filepath.Join(pwd, "results", ds[0].Format("2006-01-02T150405-0700")))
|
||||
}
|
||||
|
||||
type result struct {
|
||||
error string
|
||||
nCVEs int
|
||||
}
|
||||
detectCVEs := map[string]result{}
|
||||
for _, arg := range args {
|
||||
if err := filepath.WalkDir(arg, func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(p, os.O_RDWR, 0644)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "open %s", p)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var host types.Host
|
||||
if err := json.NewDecoder(f).Decode(&host); err != nil {
|
||||
return errors.Wrapf(err, "decode %s", p)
|
||||
}
|
||||
|
||||
hc, ok := c.Hosts[host.Name]
|
||||
if !ok {
|
||||
return errors.Wrapf(err, "not found %s in %s", host.Name, path)
|
||||
}
|
||||
host.Config.Detect = &hc.Detect
|
||||
|
||||
host.ScannedCves = nil
|
||||
host.DetectError = ""
|
||||
|
||||
if err := detect.Detect(ctx, &host); err != nil {
|
||||
host.DetectError = err.Error()
|
||||
}
|
||||
|
||||
name := host.Name
|
||||
if host.Family != "" && host.Release != "" {
|
||||
name = fmt.Sprintf("%s (%s %s)", host.Name, host.Family, host.Release)
|
||||
}
|
||||
errstr := host.DetectError
|
||||
if host.ScanError != "" {
|
||||
errstr = fmt.Sprintf("scan error: %s", host.ScanError)
|
||||
}
|
||||
|
||||
detectCVEs[name] = result{
|
||||
error: errstr,
|
||||
nCVEs: len(host.ScannedCves),
|
||||
}
|
||||
|
||||
if err := f.Truncate(0); err != nil {
|
||||
return errors.Wrap(err, "truncate file")
|
||||
}
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return errors.Wrap(err, "set offset")
|
||||
}
|
||||
enc := json.NewEncoder(f)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(host); err != nil {
|
||||
return errors.Wrap(err, "encode json")
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "walk %s", arg)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Detect Summary")
|
||||
fmt.Println("==============")
|
||||
for name, r := range detectCVEs {
|
||||
if r.error != "" {
|
||||
fmt.Printf("%s : error msg: %s\n", name, r.error)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("%s : success %d CVEs detected\n", name, r.nCVEs)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
245
pkg/cmd/report/report.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/log"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type ReportOptions struct {
|
||||
Format string
|
||||
}
|
||||
|
||||
func NewCmdReport() *cobra.Command {
|
||||
opts := &ReportOptions{
|
||||
Format: "oneline",
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "report (<result path>)",
|
||||
Short: "Vuls report vulnerabilities",
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
if err := exec(context.Background(), opts.Format, args); err != nil {
|
||||
return errors.Wrap(err, "failed to report")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls report
|
||||
$ vuls report results
|
||||
$ vuls report resutls/2022-11-05T01:08:44+09:00/local/localhost.json
|
||||
`),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.Format, "format", "f", "oneline", "stdout format")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
type affectedPackage struct {
|
||||
name string
|
||||
status string
|
||||
source string
|
||||
}
|
||||
type result struct {
|
||||
cveid string
|
||||
cvssVector string
|
||||
cvssScore *float64
|
||||
epss *float64
|
||||
kev bool
|
||||
packages []affectedPackage
|
||||
}
|
||||
|
||||
func exec(ctx context.Context, format string, args []string) error {
|
||||
logger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create logger")
|
||||
}
|
||||
|
||||
ctx = log.ContextWithLogger(ctx, logger)
|
||||
|
||||
if len(args) == 0 {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get working direcotry")
|
||||
}
|
||||
|
||||
fs, err := os.ReadDir(filepath.Join(pwd, "results"))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "read %s", filepath.Join(pwd, "results"))
|
||||
}
|
||||
|
||||
var ds []time.Time
|
||||
for _, f := range fs {
|
||||
if !f.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := time.Parse("2006-01-02T150405-0700", f.Name())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ds = append(ds, t)
|
||||
}
|
||||
if len(ds) == 0 {
|
||||
return errors.Wrapf(err, "result dir not found")
|
||||
}
|
||||
|
||||
slices.SortFunc(ds, func(e1, e2 time.Time) bool {
|
||||
return e1.After(e2)
|
||||
})
|
||||
|
||||
args = append(args, filepath.Join(pwd, "results", ds[0].Format("2006-01-02T150405-0700")))
|
||||
}
|
||||
|
||||
rs := map[string][]result{}
|
||||
for _, arg := range args {
|
||||
if err := filepath.WalkDir(arg, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "open %s", path)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var host types.Host
|
||||
if err := json.NewDecoder(f).Decode(&host); err != nil {
|
||||
return errors.Wrapf(err, "decode %s", path)
|
||||
}
|
||||
|
||||
name := host.Name
|
||||
if host.Family != "" && host.Release != "" {
|
||||
name = fmt.Sprintf("%s (%s %s)", host.Name, host.Family, host.Release)
|
||||
}
|
||||
|
||||
for id, vinfo := range host.ScannedCves {
|
||||
r := result{
|
||||
cveid: id,
|
||||
}
|
||||
|
||||
if officialCont, ok := vinfo.Content["official"]; ok {
|
||||
for _, c := range officialCont.CVSS {
|
||||
if c.Source != "nvd" || strings.HasPrefix(c.Version, "3") {
|
||||
continue
|
||||
}
|
||||
r.cvssVector = c.Vector
|
||||
r.cvssScore = c.Score
|
||||
}
|
||||
if officialCont.EPSS != nil {
|
||||
r.epss = officialCont.EPSS.EPSS
|
||||
}
|
||||
r.kev = officialCont.KEV
|
||||
}
|
||||
|
||||
for _, p := range vinfo.AffectedPackages {
|
||||
r.packages = append(r.packages, affectedPackage{
|
||||
name: p.Name,
|
||||
status: p.Status,
|
||||
source: p.Source,
|
||||
})
|
||||
}
|
||||
|
||||
rs[name] = append(rs[name], r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "walk %s", arg)
|
||||
}
|
||||
}
|
||||
|
||||
switch format {
|
||||
case "oneline":
|
||||
formatOneline(rs)
|
||||
case "list":
|
||||
formatList(rs)
|
||||
default:
|
||||
return errors.Errorf("%s is not implemented format", format)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatOneline(rs map[string][]result) {
|
||||
for name, lines := range rs {
|
||||
fmt.Println(name)
|
||||
fmt.Println(strings.Repeat("=", len(name)))
|
||||
|
||||
status := map[string]int{}
|
||||
for _, l := range lines {
|
||||
for _, p := range l.packages {
|
||||
s := p.status
|
||||
if p.status == "" {
|
||||
s = "(none)"
|
||||
}
|
||||
status[s]++
|
||||
}
|
||||
}
|
||||
|
||||
var ss []string
|
||||
for s, num := range status {
|
||||
ss = append(ss, fmt.Sprintf("%s: %d", s, num))
|
||||
}
|
||||
fmt.Printf("%d CVEs detected. package status: %s\n\n", len(lines), strings.Join(ss, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
func formatList(rs map[string][]result) {
|
||||
for name, lines := range rs {
|
||||
slices.SortFunc(lines, func(l1, l2 result) bool {
|
||||
s1, s2 := 0.0, 0.0
|
||||
if l1.cvssScore != nil {
|
||||
s1 = *l1.cvssScore
|
||||
}
|
||||
if l2.cvssScore != nil {
|
||||
s2 = *l2.cvssScore
|
||||
}
|
||||
return s1 > s2
|
||||
})
|
||||
|
||||
fmt.Println(name)
|
||||
fmt.Println(strings.Repeat("=", len(name)))
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"CVEID", "Vector", "CVSS", "EPSS", "KEV", "Package", "Status", "Source"})
|
||||
table.SetAutoMergeCells(true)
|
||||
table.SetRowLine(true)
|
||||
for _, l := range lines {
|
||||
for _, p := range l.packages {
|
||||
var score string
|
||||
if l.cvssScore != nil {
|
||||
score = fmt.Sprintf("%.1f", *l.cvssScore)
|
||||
}
|
||||
var epss string
|
||||
if l.epss != nil {
|
||||
epss = fmt.Sprintf("%f", *l.epss)
|
||||
}
|
||||
source, _, _ := strings.Cut(p.source, ":")
|
||||
table.Append([]string{l.cveid, l.cvssVector, score, epss, fmt.Sprintf("%v", l.kev), p.name, p.status, source})
|
||||
}
|
||||
}
|
||||
table.Render()
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
44
pkg/cmd/root/root.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package root
|
||||
|
||||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
configCmd "github.com/future-architect/vuls/pkg/cmd/config"
|
||||
dbCmd "github.com/future-architect/vuls/pkg/cmd/db"
|
||||
detectCmd "github.com/future-architect/vuls/pkg/cmd/detect"
|
||||
reportCmd "github.com/future-architect/vuls/pkg/cmd/report"
|
||||
scanCmd "github.com/future-architect/vuls/pkg/cmd/scan"
|
||||
serverCmd "github.com/future-architect/vuls/pkg/cmd/server"
|
||||
tuiCmd "github.com/future-architect/vuls/pkg/cmd/tui"
|
||||
versionCmd "github.com/future-architect/vuls/pkg/cmd/version"
|
||||
)
|
||||
|
||||
func NewCmdRoot() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "vuls <command>",
|
||||
Short: "Vuls",
|
||||
Long: "Vulnerability Scanner: Vuls",
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls config init
|
||||
$ vuls db fetch
|
||||
$ vuls scan
|
||||
$ vuls detect
|
||||
$ vuls report
|
||||
$ vuls tui
|
||||
`),
|
||||
}
|
||||
|
||||
cmd.AddCommand(configCmd.NewCmdConfig())
|
||||
cmd.AddCommand(dbCmd.NewCmdDB())
|
||||
cmd.AddCommand(detectCmd.NewCmdDetect())
|
||||
cmd.AddCommand(reportCmd.NewCmdReport())
|
||||
cmd.AddCommand(scanCmd.NewCmdScan())
|
||||
cmd.AddCommand(serverCmd.NewCmdServer())
|
||||
cmd.AddCommand(tuiCmd.NewCmdTUI())
|
||||
cmd.AddCommand(versionCmd.NewCmdVersion())
|
||||
|
||||
return cmd
|
||||
}
|
||||
136
pkg/cmd/scan/scan.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/config"
|
||||
"github.com/future-architect/vuls/pkg/log"
|
||||
"github.com/future-architect/vuls/pkg/scan"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type ScanOptions struct {
|
||||
Config string
|
||||
}
|
||||
|
||||
func NewCmdScan() *cobra.Command {
|
||||
opts := &ScanOptions{
|
||||
Config: "config.json",
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "scan ([\"host\"])",
|
||||
Short: "Vuls scan your machine information",
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
if err := exec(context.Background(), opts.Config, args); err != nil {
|
||||
return errors.Wrap(err, "failed to scan")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls scan
|
||||
$ vuls scan host
|
||||
`),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.Config, "config", "c", "config.json", "vuls config file path")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func exec(ctx context.Context, path string, args []string) error {
|
||||
logger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create logger")
|
||||
}
|
||||
|
||||
ctx = log.ContextWithLogger(ctx, logger)
|
||||
|
||||
c, err := config.Open(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "open %s as config", path)
|
||||
}
|
||||
|
||||
hosts := []types.Host{}
|
||||
targets := args
|
||||
if len(args) == 0 {
|
||||
targets = maps.Keys(c.Hosts)
|
||||
}
|
||||
for _, t := range targets {
|
||||
h, ok := c.Hosts[t]
|
||||
if !ok {
|
||||
return errors.Errorf("host %s is not defined in config %s", t, path)
|
||||
}
|
||||
|
||||
hosts = append(hosts, types.Host{
|
||||
Name: t,
|
||||
Config: types.Config{
|
||||
Type: h.Type,
|
||||
Host: h.Host,
|
||||
Port: h.Port,
|
||||
User: h.User,
|
||||
SSHConfig: h.SSHConfig,
|
||||
SSHKey: h.SSHKey,
|
||||
Scan: &h.Scan,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for i := range hosts {
|
||||
if err := scan.Scan(ctx, &hosts[i]); err != nil {
|
||||
hosts[i].ScanError = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
for _, h := range hosts {
|
||||
if err := func() error {
|
||||
resultDir := filepath.Join(h.Config.Scan.ResultDir, now.Format("2006-01-02T150405-0700"))
|
||||
if err := os.MkdirAll(resultDir, os.ModePerm); err != nil {
|
||||
return errors.Wrapf(err, "mkdir %s", resultDir)
|
||||
}
|
||||
f, err := os.Create(filepath.Join(resultDir, fmt.Sprintf("%s.json", h.Name)))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s", filepath.Join(resultDir, fmt.Sprintf("%s.json", h.Name)))
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
enc := json.NewEncoder(f)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(h); err != nil {
|
||||
return errors.Wrapf(err, "encode %s result", h.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrapf(err, "write %s result", h.Name)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Scan Summary")
|
||||
fmt.Println("============")
|
||||
for _, h := range hosts {
|
||||
name := h.Name
|
||||
if h.Family != "" && h.Release != "" {
|
||||
name = fmt.Sprintf("%s (%s %s)", h.Name, h.Family, h.Release)
|
||||
}
|
||||
if h.ScanError != "" {
|
||||
fmt.Printf("%s : error msg: %s\n", name, h.ScanError)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("%s: success ospkg: %d, cpe: %d, KB %d installed\n", name, len(h.Packages.OSPkg), len(h.Packages.CPE), len(h.Packages.KB))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
78
pkg/cmd/server/server.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/config"
|
||||
"github.com/future-architect/vuls/pkg/log"
|
||||
"github.com/future-architect/vuls/pkg/server"
|
||||
)
|
||||
|
||||
type Serveroptions struct {
|
||||
Config string
|
||||
}
|
||||
|
||||
func NewCmdServer() *cobra.Command {
|
||||
opts := &Serveroptions{
|
||||
Config: "config.json",
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "Vuls start server mode",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
if err := exec(context.Background(), opts.Config); err != nil {
|
||||
return errors.Wrap(err, "failed to server")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls server
|
||||
`),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.Config, "config", "c", "config.json", "vuls config file path")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func exec(ctx context.Context, path string) error {
|
||||
logger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create logger")
|
||||
}
|
||||
|
||||
ctx = log.ContextWithLogger(ctx, logger)
|
||||
|
||||
c, err := config.Open(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "open %s as config", path)
|
||||
}
|
||||
|
||||
if c.Server == nil {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get working directory")
|
||||
}
|
||||
|
||||
c.Server = &config.Server{
|
||||
Listen: "127.0.0.1:5515",
|
||||
Path: filepath.Join(pwd, "vuls.db"),
|
||||
}
|
||||
}
|
||||
|
||||
e := echo.New()
|
||||
e.POST("/scan", server.Scan())
|
||||
e.POST("/detect", server.Detect(c.Server.Path))
|
||||
|
||||
return e.Start(c.Server.Listen)
|
||||
}
|
||||
33
pkg/cmd/tui/tui.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type TUIOptions struct {
|
||||
Config string
|
||||
}
|
||||
|
||||
func NewCmdTUI() *cobra.Command {
|
||||
opts := &TUIOptions{
|
||||
Config: "config.json",
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "tui (<result path>)",
|
||||
Short: "View vulnerabilities detected by TUI",
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return nil
|
||||
},
|
||||
Example: heredoc.Doc(`
|
||||
$ vuls tui
|
||||
$ vuls tui results
|
||||
$ vuls tui resutls/2022-11-05T01:08:44+09:00/local/localhost.json
|
||||
`),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.Config, "config", "c", "config.json", "vuls config file path")
|
||||
|
||||
return cmd
|
||||
}
|
||||
25
pkg/cmd/version/version.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
Version string
|
||||
Revision string
|
||||
)
|
||||
|
||||
func NewCmdVersion() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version",
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
fmt.Fprintf(os.Stdout, "vuls %s %s\n", Version, Revision)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
59
pkg/config/config.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func Open(path string) (Config, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return Config{}, errors.Wrapf(err, "open %s", path)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var src Config
|
||||
if err := json.NewDecoder(f).Decode(&src); err != nil {
|
||||
return Config{}, errors.Wrap(err, "decode json")
|
||||
}
|
||||
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return Config{}, errors.Wrap(err, "get current user")
|
||||
}
|
||||
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return Config{}, errors.Wrap(err, "get working directory")
|
||||
}
|
||||
|
||||
config := Config{Server: src.Server, Hosts: map[string]Host{}}
|
||||
for n, h := range src.Hosts {
|
||||
c := Host{
|
||||
Type: h.Type,
|
||||
Host: h.Host,
|
||||
Port: h.Port,
|
||||
User: h.User,
|
||||
SSHConfig: h.SSHConfig,
|
||||
SSHKey: h.SSHKey,
|
||||
Scan: h.Scan,
|
||||
Detect: h.Detect,
|
||||
}
|
||||
if c.User == nil {
|
||||
c.User = &u.Name
|
||||
}
|
||||
if c.Scan.ResultDir == "" {
|
||||
c.Scan.ResultDir = filepath.Join(pwd, "results")
|
||||
}
|
||||
if c.Detect.ResultDir == "" {
|
||||
c.Detect.ResultDir = filepath.Join(pwd, "results")
|
||||
}
|
||||
config.Hosts[n] = c
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
41
pkg/config/types.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package config
|
||||
|
||||
type Config struct {
|
||||
Server *Server `json:"server"`
|
||||
Hosts map[string]Host `json:"hosts"`
|
||||
}
|
||||
|
||||
type Scan struct {
|
||||
OSPkg *scanOSPkg `json:"ospkg,omitempty"`
|
||||
CPE []scanCPE `json:"cpe,omitempty"`
|
||||
ResultDir string `json:"result_dir,omitempty"`
|
||||
}
|
||||
|
||||
type scanOSPkg struct {
|
||||
Root bool `json:"root"`
|
||||
}
|
||||
|
||||
type scanCPE struct {
|
||||
CPE string `json:"cpe,omitempty"`
|
||||
RunningOn string `json:"running_on,omitempty"`
|
||||
}
|
||||
|
||||
type Detect struct {
|
||||
Path string `json:"path"`
|
||||
ResultDir string `json:"result_dir"`
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Listen string `json:"listen"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
type Host struct {
|
||||
Type string `json:"type"`
|
||||
Host *string `json:"host"`
|
||||
Port *string `json:"port"`
|
||||
User *string `json:"user"`
|
||||
SSHConfig *string `json:"ssh_config"`
|
||||
SSHKey *string `json:"ssh_key"`
|
||||
Scan Scan `json:"scan"`
|
||||
Detect Detect `json:"detect"`
|
||||
}
|
||||
490
pkg/db/boltdb/boltdb.go
Normal file
@@ -0,0 +1,490 @@
|
||||
package boltdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/db/types"
|
||||
)
|
||||
|
||||
type options struct {
|
||||
}
|
||||
|
||||
type Option interface {
|
||||
apply(*options)
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
conn *bolt.DB
|
||||
}
|
||||
|
||||
func Open(dbPath string, debug bool, opts ...Option) (*DB, error) {
|
||||
db, err := bolt.Open(dbPath, 0666, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "open boltdb")
|
||||
}
|
||||
return &DB{conn: db}, nil
|
||||
}
|
||||
|
||||
func (db *DB) Close() error {
|
||||
if db.conn == nil {
|
||||
return nil
|
||||
}
|
||||
if err := db.conn.Close(); err != nil {
|
||||
return errors.Wrap(err, "close boltdb")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutVulnerability(src, key string, value types.Vulnerability) error {
|
||||
bucket, id, found := strings.Cut(key, ":")
|
||||
if !found {
|
||||
return errors.Errorf(`unexpected key. accepts: "vulnerability:<Vulnerability ID>, received: "%s"`, key)
|
||||
}
|
||||
if err := db.conn.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucketIfNotExists([]byte(bucket))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s bucket", bucket)
|
||||
}
|
||||
|
||||
vb, err := b.CreateBucketIfNotExists([]byte(id))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s/%s bucket", bucket, id)
|
||||
}
|
||||
|
||||
bs, err := json.MarshalIndent(value, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal json")
|
||||
}
|
||||
|
||||
if err := vb.Put([]byte(src), bs); err != nil {
|
||||
return errors.Wrapf(err, "put %%s/%s/%s", bucket, id, src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "update db")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutPackage(src, key string, value map[string]types.Packages) error {
|
||||
if err := db.conn.Update(func(tx *bolt.Tx) error {
|
||||
name, version, found := strings.Cut(key, ":")
|
||||
if !found && name == "" {
|
||||
return errors.Errorf(`unexpected key. accepts: "<osname>(:<version>)", received: "%s"`, key)
|
||||
}
|
||||
|
||||
bucket := name
|
||||
b, err := tx.CreateBucketIfNotExists([]byte(name))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s bucket", name)
|
||||
}
|
||||
switch name {
|
||||
case "arch", "freebsd", "gentoo":
|
||||
case "redhat":
|
||||
if version == "" {
|
||||
return errors.Errorf(`unexpected key. accepts: "<osname>:<version>", received: "%s"`, key)
|
||||
}
|
||||
b, err = b.CreateBucketIfNotExists([]byte(version[:1]))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s/%s bucket", name, version[:1])
|
||||
}
|
||||
b, err = b.CreateBucketIfNotExists([]byte(version))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s/%s/%s bucket", name, version[:1], version)
|
||||
}
|
||||
bucket = fmt.Sprintf("%s/%s/%s", name, version[:1], version)
|
||||
default:
|
||||
if version == "" {
|
||||
return errors.Errorf(`unexpected key. accepts: "<osname>:<version>", received: "%s"`, key)
|
||||
}
|
||||
b, err = b.CreateBucketIfNotExists([]byte(version))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "crate %s/%s bucket", name, version)
|
||||
}
|
||||
bucket = fmt.Sprintf("%s/%s", name, version)
|
||||
}
|
||||
|
||||
for n, v := range value {
|
||||
pb, err := b.CreateBucketIfNotExists([]byte(n))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s/%s bucket", bucket, n)
|
||||
}
|
||||
|
||||
vb, err := pb.CreateBucketIfNotExists([]byte(v.ID))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s/%s/%s bucket", bucket, n, v.ID)
|
||||
}
|
||||
|
||||
var p map[string]types.Package
|
||||
bs := vb.Get([]byte(src))
|
||||
if len(bs) > 0 {
|
||||
if err := json.Unmarshal(bs, &p); err != nil {
|
||||
return errors.Wrap(err, "unmarshal json")
|
||||
}
|
||||
} else {
|
||||
p = map[string]types.Package{}
|
||||
}
|
||||
maps.Copy(p, v.Package)
|
||||
bs, err = json.MarshalIndent(p, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal json")
|
||||
}
|
||||
|
||||
if err := vb.Put([]byte(src), bs); err != nil {
|
||||
return errors.Wrapf(err, "put %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
if name == "windows" {
|
||||
kbToProduct := map[string][]string{}
|
||||
for n, ps := range value {
|
||||
for _, p := range ps.Package {
|
||||
for _, v := range p.Version {
|
||||
if _, err := strconv.Atoi(v[0].Version); err != nil {
|
||||
continue
|
||||
}
|
||||
kbToProduct[v[0].Version] = append(kbToProduct[v[0].Version], n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b, err := tx.CreateBucketIfNotExists([]byte("windows_kb_to_product"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create windows_kb_to_product bucket")
|
||||
}
|
||||
|
||||
if version == "" {
|
||||
return errors.Errorf(`unexpected key. accepts: "<osname>:<version>", received: "%s"`, key)
|
||||
}
|
||||
b, err = b.CreateBucketIfNotExists([]byte(version))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s/%s bucket", name, version)
|
||||
}
|
||||
|
||||
for kb, ps := range kbToProduct {
|
||||
bs, err := json.Marshal(ps)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal json")
|
||||
}
|
||||
b.Put([]byte(kb), bs)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "update db")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutCPEConfiguration(src, key string, value map[string]types.CPEConfigurations) error {
|
||||
if err := db.conn.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucketIfNotExists([]byte(key))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s bucket", key)
|
||||
}
|
||||
|
||||
for pvp, c := range value {
|
||||
pvpb, err := b.CreateBucketIfNotExists([]byte(pvp))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s/%s bucket", key, pvp)
|
||||
}
|
||||
|
||||
vb, err := pvpb.CreateBucketIfNotExists([]byte(c.ID))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s/%s/%s bucket", key, pvp, c.ID)
|
||||
}
|
||||
|
||||
var v map[string][]types.CPEConfiguration
|
||||
bs := vb.Get([]byte(src))
|
||||
if len(bs) > 0 {
|
||||
if err := json.Unmarshal(bs, &v); err != nil {
|
||||
return errors.Wrap(err, "unmarshal json")
|
||||
}
|
||||
} else {
|
||||
v = map[string][]types.CPEConfiguration{}
|
||||
}
|
||||
maps.Copy(v, c.Configuration)
|
||||
bs, err = json.MarshalIndent(v, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal json")
|
||||
}
|
||||
|
||||
if err := vb.Put([]byte(src), bs); err != nil {
|
||||
return errors.Wrapf(err, "put %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "update db")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutRedHatRepoToCPE(src, key string, value types.RepositoryToCPE) error {
|
||||
if err := db.conn.Update(func(tx *bolt.Tx) error {
|
||||
name, version, found := strings.Cut(key, ":")
|
||||
if !found && name == "" {
|
||||
return errors.Errorf(`unexpected key. accepts: "redhat_cpe:<version>", received: "%s"`, key)
|
||||
}
|
||||
|
||||
b, err := tx.CreateBucketIfNotExists([]byte(name))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s bucket", name)
|
||||
}
|
||||
|
||||
b, err = b.CreateBucketIfNotExists([]byte(version[:1]))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s/%s bucket", name, version[:1])
|
||||
}
|
||||
|
||||
b, err = b.CreateBucketIfNotExists([]byte(version))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s/%s/%s bucket", name, version[:1], version)
|
||||
}
|
||||
|
||||
for repo, cpes := range value {
|
||||
rb, err := b.CreateBucketIfNotExists([]byte(repo))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s/%s/%s/%s bucket", name, version[:1], version, repo)
|
||||
}
|
||||
|
||||
bs, err := json.MarshalIndent(cpes, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal json")
|
||||
}
|
||||
|
||||
if err := rb.Put([]byte(src), bs); err != nil {
|
||||
return errors.Wrapf(err, "put %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "update db")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutWindowsSupercedence(src, key string, value types.Supercedence) error {
|
||||
if err := db.conn.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucketIfNotExists([]byte(key))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s bucket", key)
|
||||
}
|
||||
for kb, supercedences := range value {
|
||||
kbb, err := b.CreateBucketIfNotExists([]byte(kb))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create %s/%s bucket", key, kb)
|
||||
}
|
||||
bs, err := json.Marshal(supercedences)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal json")
|
||||
}
|
||||
|
||||
if err := kbb.Put([]byte(src), bs); err != nil {
|
||||
return errors.Wrapf(err, "put %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "update db")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) GetVulnerability(ids []string) (map[string]map[string]types.Vulnerability, error) {
|
||||
r := map[string]map[string]types.Vulnerability{}
|
||||
if err := db.conn.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte("vulnerability"))
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
for _, id := range ids {
|
||||
vb := b.Bucket([]byte(id))
|
||||
if vb == nil {
|
||||
return nil
|
||||
}
|
||||
r[string(id)] = map[string]types.Vulnerability{}
|
||||
if err := vb.ForEach(func(src, bs []byte) error {
|
||||
var v types.Vulnerability
|
||||
if err := json.Unmarshal(bs, &v); err != nil {
|
||||
return errors.Wrapf(err, "decode %s/%s", string(id), string(src))
|
||||
}
|
||||
r[string(id)][string(src)] = v
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetPackage(family, release string, name string) (map[string]map[string]map[string]types.Package, error) {
|
||||
r := map[string]map[string]map[string]types.Package{}
|
||||
if err := db.conn.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte(family))
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
switch family {
|
||||
case "debian", "ubuntu", "windows":
|
||||
b = b.Bucket([]byte(release))
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
b = b.Bucket([]byte(name))
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := b.ForEach(func(cveid, _ []byte) error {
|
||||
vb := b.Bucket(cveid)
|
||||
r[string(cveid)] = map[string]map[string]types.Package{}
|
||||
if err := vb.ForEach(func(src, bs []byte) error {
|
||||
var v map[string]types.Package
|
||||
if err := json.Unmarshal(bs, &v); err != nil {
|
||||
return errors.Wrapf(err, "decode %s/%s", string(cveid), string(src))
|
||||
}
|
||||
r[string(cveid)][string(src)] = v
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetCPEConfiguration(partvendorproduct string) (map[string]map[string]map[string][]types.CPEConfiguration, error) {
|
||||
r := map[string]map[string]map[string][]types.CPEConfiguration{}
|
||||
if err := db.conn.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte("cpe"))
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
b = b.Bucket([]byte(partvendorproduct))
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := b.ForEach(func(cveid, _ []byte) error {
|
||||
vb := b.Bucket(cveid)
|
||||
r[string(cveid)] = map[string]map[string][]types.CPEConfiguration{}
|
||||
if err := vb.ForEach(func(src, bs []byte) error {
|
||||
var v map[string][]types.CPEConfiguration
|
||||
if err := json.Unmarshal(bs, &v); err != nil {
|
||||
return errors.Wrapf(err, "decode cpe/%s/%s/%s", partvendorproduct, string(cveid), string(src))
|
||||
}
|
||||
r[string(cveid)][string(src)] = v
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetSupercedence(kbs []string) (map[string][]string, error) {
|
||||
r := map[string][]string{}
|
||||
if err := db.conn.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte("windows_supercedence"))
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
for _, kb := range kbs {
|
||||
kbb := b.Bucket([]byte(kb))
|
||||
if kbb == nil {
|
||||
continue
|
||||
}
|
||||
if err := kbb.ForEach(func(_, v []byte) error {
|
||||
var ss []string
|
||||
if err := json.Unmarshal(v, &ss); err != nil {
|
||||
return errors.Wrapf(err, "decode windows_supercedence/%s", kb)
|
||||
}
|
||||
r[kb] = append(r[kb], ss...)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetKBtoProduct(release string, kbs []string) ([]string, error) {
|
||||
var r []string
|
||||
if err := db.conn.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte("windows_kb_to_product"))
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
b = b.Bucket([]byte(release))
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
for _, kb := range kbs {
|
||||
if bs := b.Get([]byte(kb)); len(bs) > 0 {
|
||||
var ps []string
|
||||
if err := json.Unmarshal(bs, &ps); err != nil {
|
||||
return errors.Wrapf(err, "decode windows_kb_to_product/%s/%s", release, kb)
|
||||
}
|
||||
r = append(r, ps...)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
149
pkg/db/db.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/db/boltdb"
|
||||
"github.com/future-architect/vuls/pkg/db/rdb"
|
||||
"github.com/future-architect/vuls/pkg/db/redis"
|
||||
"github.com/future-architect/vuls/pkg/db/types"
|
||||
)
|
||||
|
||||
type options struct {
|
||||
}
|
||||
|
||||
type Option interface {
|
||||
apply(*options)
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
name string
|
||||
driver Driver
|
||||
}
|
||||
|
||||
type Driver interface {
|
||||
Close() error
|
||||
|
||||
PutVulnerability(string, string, types.Vulnerability) error
|
||||
PutPackage(string, string, map[string]types.Packages) error
|
||||
PutCPEConfiguration(string, string, map[string]types.CPEConfigurations) error
|
||||
PutRedHatRepoToCPE(string, string, types.RepositoryToCPE) error
|
||||
PutWindowsSupercedence(string, string, types.Supercedence) error
|
||||
|
||||
GetVulnerability([]string) (map[string]map[string]types.Vulnerability, error)
|
||||
GetPackage(string, string, string) (map[string]map[string]map[string]types.Package, error)
|
||||
GetCPEConfiguration(string) (map[string]map[string]map[string][]types.CPEConfiguration, error)
|
||||
GetSupercedence([]string) (map[string][]string, error)
|
||||
GetKBtoProduct(string, []string) ([]string, error)
|
||||
}
|
||||
|
||||
func (db *DB) Name() string {
|
||||
return db.name
|
||||
}
|
||||
|
||||
func Open(dbType, dbPath string, debug bool, opts ...Option) (*DB, error) {
|
||||
switch dbType {
|
||||
case "boltdb":
|
||||
d, err := boltdb.Open(dbPath, debug)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "open boltdb")
|
||||
}
|
||||
return &DB{name: dbType, driver: d}, nil
|
||||
case "sqlite3", "mysql", "postgres":
|
||||
d, err := rdb.Open(dbType, dbPath, debug)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "open rdb")
|
||||
}
|
||||
return &DB{name: dbType, driver: d}, nil
|
||||
case "redis":
|
||||
d, err := redis.Open(dbPath, debug)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "open rdb")
|
||||
}
|
||||
return &DB{name: dbType, driver: d}, nil
|
||||
default:
|
||||
return nil, errors.Errorf(`unexpected dbType. accepts: ["boltdb", "sqlite3", "mysql", "postgres", "redis"], received: "%s"`, dbType)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) Close() error {
|
||||
if err := db.driver.Close(); err != nil {
|
||||
return errors.Wrapf(err, "close %s", db.name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutVulnerability(src, key string, value types.Vulnerability) error {
|
||||
if err := db.driver.PutVulnerability(src, key, value); err != nil {
|
||||
return errors.Wrapf(err, "put vulnerability")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutPackage(src, key string, value map[string]types.Packages) error {
|
||||
if err := db.driver.PutPackage(src, key, value); err != nil {
|
||||
return errors.Wrapf(err, "put package")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutCPEConfiguration(src, key string, value map[string]types.CPEConfigurations) error {
|
||||
if err := db.driver.PutCPEConfiguration(src, key, value); err != nil {
|
||||
return errors.Wrapf(err, "put cpe configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutRedHatRepoToCPE(src, key string, value types.RepositoryToCPE) error {
|
||||
if err := db.driver.PutRedHatRepoToCPE(src, key, value); err != nil {
|
||||
return errors.Wrap(err, "put repository to cpe")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutWindowsSupercedence(src, key string, value types.Supercedence) error {
|
||||
if err := db.driver.PutWindowsSupercedence(src, key, value); err != nil {
|
||||
return errors.Wrap(err, "put supercedence")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) GetVulnerability(ids []string) (map[string]map[string]types.Vulnerability, error) {
|
||||
rs, err := db.driver.GetVulnerability(ids)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "get vulnerability")
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetPackage(family, release string, name string) (map[string]map[string]map[string]types.Package, error) {
|
||||
rs, err := db.driver.GetPackage(family, release, name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "get package")
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetCPEConfiguration(partvendorproduct string) (map[string]map[string]map[string][]types.CPEConfiguration, error) {
|
||||
rs, err := db.driver.GetCPEConfiguration(partvendorproduct)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "get cpe configuration")
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetSupercedence(kb []string) (map[string][]string, error) {
|
||||
rs, err := db.driver.GetSupercedence(kb)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get supercedence")
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetKBtoProduct(release string, kb []string) ([]string, error) {
|
||||
rs, err := db.driver.GetKBtoProduct(release, kb)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get product from kb")
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
110
pkg/db/rdb/rdb.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package rdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
_ "modernc.org/sqlite"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/db/types"
|
||||
)
|
||||
|
||||
type options struct {
|
||||
}
|
||||
|
||||
type Option interface {
|
||||
apply(*options)
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
conn *gorm.DB
|
||||
}
|
||||
|
||||
func Open(dbType, dbPath string, debug bool, opts ...Option) (*DB, error) {
|
||||
switch dbType {
|
||||
case "sqlite3":
|
||||
// db, err := gorm.Open(sqlite.Open(dbPath))
|
||||
db := &gorm.DB{}
|
||||
conn, err := sql.Open("sqlite", dbPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "open sqlite3")
|
||||
}
|
||||
db.ConnPool = conn
|
||||
return &DB{conn: db}, nil
|
||||
case "mysql":
|
||||
db, err := gorm.Open(mysql.Open(dbPath))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "open mysql")
|
||||
}
|
||||
return &DB{conn: db}, nil
|
||||
case "postgres":
|
||||
db, err := gorm.Open(postgres.Open(dbPath))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "open postgres")
|
||||
}
|
||||
return &DB{conn: db}, nil
|
||||
default:
|
||||
return nil, errors.Errorf(`unexpected dbType. accepts: ["sqlite3", "mysql", "postgres"], received: "%s"`, dbType)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) Close() error {
|
||||
if db.conn == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
sqlDB *sql.DB
|
||||
err error
|
||||
)
|
||||
if sqlDB, err = db.conn.DB(); err != nil {
|
||||
return errors.Wrap(err, "get *sql.DB")
|
||||
}
|
||||
if err := sqlDB.Close(); err != nil {
|
||||
return errors.Wrap(err, "close *sql.DB")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutVulnerability(src, key string, value types.Vulnerability) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutPackage(src, key string, value map[string]types.Packages) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutCPEConfiguration(src, key string, value map[string]types.CPEConfigurations) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutRedHatRepoToCPE(src, key string, value types.RepositoryToCPE) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutWindowsSupercedence(src, key string, value types.Supercedence) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) GetVulnerability(ids []string) (map[string]map[string]types.Vulnerability, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetPackage(family, release string, name string) (map[string]map[string]map[string]types.Package, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetCPEConfiguration(partvendorproduct string) (map[string]map[string]map[string][]types.CPEConfiguration, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetSupercedence(kb []string) (map[string][]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetKBtoProduct(elease string, kb []string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
77
pkg/db/redis/redis.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"github.com/go-redis/redis/v9"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/db/types"
|
||||
)
|
||||
|
||||
type options struct {
|
||||
}
|
||||
|
||||
type Option interface {
|
||||
apply(*options)
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
conn *redis.Client
|
||||
}
|
||||
|
||||
func Open(dbPath string, debug bool, opts ...Option) (*DB, error) {
|
||||
redisOpts, err := redis.ParseURL(dbPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse redis URL")
|
||||
}
|
||||
return &DB{conn: redis.NewClient(redisOpts)}, nil
|
||||
}
|
||||
|
||||
func (db *DB) Close() error {
|
||||
if db.conn == nil {
|
||||
return nil
|
||||
}
|
||||
if err := db.conn.Close(); err != nil {
|
||||
return errors.Wrap(err, "close redis")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutVulnerability(src, key string, value types.Vulnerability) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutPackage(src, key string, value map[string]types.Packages) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutCPEConfiguration(src, key string, value map[string]types.CPEConfigurations) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutRedHatRepoToCPE(src, key string, value types.RepositoryToCPE) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) PutWindowsSupercedence(src, key string, value types.Supercedence) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) GetVulnerability(ids []string) (map[string]map[string]types.Vulnerability, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetPackage(family, release string, name string) (map[string]map[string]map[string]types.Package, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetCPEConfiguration(partvendorproduct string) (map[string]map[string]map[string][]types.CPEConfiguration, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetSupercedence(kb []string) (map[string][]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetKBtoProduct(release string, kb []string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
85
pkg/db/types/types.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
type Vulnerability struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Advisory []string `json:"advisory,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
CVSS []CVSS `json:"cvss,omitempty"`
|
||||
EPSS *EPSS `json:"epss,omitempty"`
|
||||
CWE []CWE `json:"cwe,omitempty"`
|
||||
Metasploit []Metasploit `json:"metasploit,omitempty"`
|
||||
Exploit []Exploit `json:"exploit,omitempty"`
|
||||
KEV bool `json:"kev,omitempty"`
|
||||
Published *time.Time `json:"published,omitempty"`
|
||||
Modified *time.Time `json:"modified,omitempty"`
|
||||
Reference []string `json:"reference,omitempty"`
|
||||
}
|
||||
|
||||
type CVSS struct {
|
||||
Source string `json:"source,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Vector string `json:"vector,omitempty"`
|
||||
Score *float64 `json:"score,omitempty"`
|
||||
Severity string `json:"severity,omitempty"`
|
||||
}
|
||||
|
||||
type EPSS struct {
|
||||
EPSS *float64 `json:"epss,omitempty"`
|
||||
Percentile *float64 `json:"percentile,omitempty"`
|
||||
}
|
||||
|
||||
type CWE struct {
|
||||
Source []string `json:"source,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
type Metasploit struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type Exploit struct {
|
||||
Source []string `json:"source,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type CPEConfigurations struct {
|
||||
ID string `json:"-,omitempty"`
|
||||
Configuration map[string][]CPEConfiguration `json:"configuration,omitempty"`
|
||||
}
|
||||
|
||||
type CPEConfiguration struct {
|
||||
Vulnerable CPE `json:"vulnerable,omitempty"`
|
||||
RunningOn []CPE `json:"running_on,omitempty"`
|
||||
}
|
||||
|
||||
type CPE struct {
|
||||
CPEVersion string `json:"cpe_version,omitempty"`
|
||||
CPE string `json:"cpe,omitempty"`
|
||||
Version []Version `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
type Packages struct {
|
||||
ID string `json:"-,omitempty"`
|
||||
Package map[string]Package `json:"package,omitempty"`
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
Version [][]Version `json:"version,omitempty"`
|
||||
Arch []string `json:"arch,omitempty"`
|
||||
Repository string `json:"repository,omitempty"`
|
||||
CPE []string `json:"cpe,omitempty"`
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
Operator string `json:"operator,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
type RepositoryToCPE map[string][]string
|
||||
|
||||
type Supercedence map[string][]string
|
||||
1
pkg/db/util/util.go
Normal file
@@ -0,0 +1 @@
|
||||
package util
|
||||
179
pkg/detect/cpe/cpe.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/knqyf263/go-cpe/common"
|
||||
"github.com/knqyf263/go-cpe/matching"
|
||||
"github.com/knqyf263/go-cpe/naming"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/db"
|
||||
dbTypes "github.com/future-architect/vuls/pkg/db/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
"github.com/future-architect/vuls/pkg/util"
|
||||
)
|
||||
|
||||
type Detector struct{}
|
||||
|
||||
func (d Detector) Name() string {
|
||||
return "cpe detector"
|
||||
}
|
||||
|
||||
func (d Detector) Detect(ctx context.Context, host *types.Host) error {
|
||||
if host.ScannedCves == nil {
|
||||
host.ScannedCves = map[string]types.VulnInfo{}
|
||||
}
|
||||
|
||||
vulndb, err := db.Open("boltdb", host.Config.Detect.Path, false)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "open %s", host.Config.Detect.Path)
|
||||
}
|
||||
defer vulndb.Close()
|
||||
|
||||
for key, cpe := range host.Packages.CPE {
|
||||
installed, err := naming.UnbindFS(cpe.CPE)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unbind %s", cpe.CPE)
|
||||
}
|
||||
var runningOn common.WellFormedName
|
||||
if cpe.RunningOn != "" {
|
||||
runningOn, err = naming.UnbindFS(cpe.RunningOn)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unbind %s", cpe.RunningOn)
|
||||
}
|
||||
}
|
||||
|
||||
cpes, err := vulndb.GetCPEConfiguration(fmt.Sprintf("%s:%s:%s", installed.GetString(common.AttributePart), installed.GetString(common.AttributeVendor), installed.GetString(common.AttributeProduct)))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get cpe configuration")
|
||||
}
|
||||
|
||||
for cveid, datasrcs := range cpes {
|
||||
for datasrc, orcs := range datasrcs {
|
||||
for id, andcs := range orcs {
|
||||
for _, c := range andcs {
|
||||
affected, err := compare(installed, &runningOn, c)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "compare")
|
||||
}
|
||||
if affected {
|
||||
vinfo, ok := host.ScannedCves[cveid]
|
||||
if !ok {
|
||||
host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
|
||||
}
|
||||
vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
|
||||
Name: key,
|
||||
Source: fmt.Sprintf("%s:%s", datasrc, id),
|
||||
})
|
||||
vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
|
||||
host.ScannedCves[cveid] = vinfo
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vulns, err := vulndb.GetVulnerability(maps.Keys(host.ScannedCves))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get vulnerability")
|
||||
}
|
||||
for cveid, datasrcs := range vulns {
|
||||
vinfo := host.ScannedCves[cveid]
|
||||
vinfo.Content = map[string]dbTypes.Vulnerability{}
|
||||
for src, v := range datasrcs {
|
||||
vinfo.Content[src] = v
|
||||
}
|
||||
host.ScannedCves[cveid] = vinfo
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func compare(installedCPE common.WellFormedName, installedRunningOn *common.WellFormedName, target dbTypes.CPEConfiguration) (bool, error) {
|
||||
var (
|
||||
wfn common.WellFormedName
|
||||
err error
|
||||
)
|
||||
|
||||
if target.Vulnerable.CPEVersion == "2.3" {
|
||||
wfn, err = naming.UnbindFS(target.Vulnerable.CPE)
|
||||
} else {
|
||||
wfn, err = naming.UnbindURI(target.Vulnerable.CPE)
|
||||
}
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "unbind %s", target.Vulnerable.CPE)
|
||||
}
|
||||
if !matching.IsEqual(installedCPE, wfn) && !matching.IsSubset(installedCPE, wfn) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
for _, runningOn := range target.RunningOn {
|
||||
if runningOn.CPEVersion == "2.3" {
|
||||
wfn, err = naming.UnbindFS(runningOn.CPE)
|
||||
} else {
|
||||
wfn, err = naming.UnbindURI(runningOn.CPE)
|
||||
}
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "unbind %s", runningOn.CPE)
|
||||
}
|
||||
if !matching.IsEqual(*installedRunningOn, wfn) && !matching.IsSubset(*installedRunningOn, wfn) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(target.Vulnerable.Version) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
attrver := installedCPE.GetString(common.AttributeVersion)
|
||||
switch attrver {
|
||||
case "ANY":
|
||||
return true, nil
|
||||
case "NA":
|
||||
return false, nil
|
||||
default:
|
||||
v, err := version.NewVersion(strings.ReplaceAll(attrver, "\\", ""))
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "parse version in %s", installedCPE.GetString(common.AttributeVersion))
|
||||
}
|
||||
for _, vconf := range target.Vulnerable.Version {
|
||||
vconfv, err := version.NewVersion(vconf.Version)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "parse version in %s", vconf.Version)
|
||||
}
|
||||
|
||||
switch vconf.Operator {
|
||||
case "eq":
|
||||
if !v.Equal(vconfv) {
|
||||
return false, nil
|
||||
}
|
||||
case "lt":
|
||||
if !v.LessThan(vconfv) {
|
||||
return false, nil
|
||||
}
|
||||
case "le":
|
||||
if !v.LessThanOrEqual(vconfv) {
|
||||
return false, nil
|
||||
}
|
||||
case "gt":
|
||||
if !v.GreaterThan(vconfv) {
|
||||
return false, nil
|
||||
}
|
||||
case "ge":
|
||||
if !v.GreaterThanOrEqual(vconfv) {
|
||||
return false, nil
|
||||
}
|
||||
default:
|
||||
return false, errors.New("not supported operator")
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
151
pkg/detect/debian/debian.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package debian
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
version "github.com/knqyf263/go-deb-version"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/db"
|
||||
dbTypes "github.com/future-architect/vuls/pkg/db/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
"github.com/future-architect/vuls/pkg/util"
|
||||
)
|
||||
|
||||
type Detector struct{}
|
||||
|
||||
func (d Detector) Name() string {
|
||||
return "debian detector"
|
||||
}
|
||||
|
||||
func (d Detector) Detect(ctx context.Context, host *types.Host) error {
|
||||
if host.ScannedCves == nil {
|
||||
host.ScannedCves = map[string]types.VulnInfo{}
|
||||
}
|
||||
|
||||
vulndb, err := db.Open("boltdb", host.Config.Detect.Path, false)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "open %s", host.Config.Detect.Path)
|
||||
}
|
||||
defer vulndb.Close()
|
||||
|
||||
srcpkgs := map[string]string{}
|
||||
for _, p := range host.Packages.OSPkg {
|
||||
srcpkgs[p.SrcName] = p.SrcVersion
|
||||
}
|
||||
|
||||
for srcname, srcver := range srcpkgs {
|
||||
pkgs, err := vulndb.GetPackage(host.Family, host.Release, srcname)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get package")
|
||||
}
|
||||
|
||||
for cveid, datasrcs := range pkgs {
|
||||
for datasrc, ps := range datasrcs {
|
||||
for id, p := range ps {
|
||||
switch p.Status {
|
||||
case "fixed":
|
||||
for _, andVs := range p.Version {
|
||||
affected := true
|
||||
for _, v := range andVs {
|
||||
r, err := compare(v.Operator, srcver, v.Version)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "compare")
|
||||
}
|
||||
if !r {
|
||||
affected = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if affected {
|
||||
vinfo, ok := host.ScannedCves[cveid]
|
||||
if !ok {
|
||||
host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
|
||||
}
|
||||
vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
|
||||
Name: srcname,
|
||||
Source: fmt.Sprintf("%s:%s", datasrc, id),
|
||||
Status: p.Status,
|
||||
})
|
||||
vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
|
||||
host.ScannedCves[cveid] = vinfo
|
||||
}
|
||||
}
|
||||
case "open":
|
||||
vinfo, ok := host.ScannedCves[cveid]
|
||||
if !ok {
|
||||
host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
|
||||
}
|
||||
vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
|
||||
Name: srcname,
|
||||
Source: fmt.Sprintf("%s:%s", datasrc, id),
|
||||
Status: p.Status,
|
||||
})
|
||||
vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
|
||||
host.ScannedCves[cveid] = vinfo
|
||||
case "not affected":
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vulns, err := vulndb.GetVulnerability(maps.Keys(host.ScannedCves))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get vulnerability")
|
||||
}
|
||||
for cveid, datasrcs := range vulns {
|
||||
vinfo := host.ScannedCves[cveid]
|
||||
vinfo.Content = map[string]dbTypes.Vulnerability{}
|
||||
for src, v := range datasrcs {
|
||||
vinfo.Content[src] = v
|
||||
}
|
||||
host.ScannedCves[cveid] = vinfo
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func compare(operator, srcver, ver string) (bool, error) {
|
||||
v1, err := version.NewVersion(srcver)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "parse version")
|
||||
}
|
||||
v2, err := version.NewVersion(ver)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "parse version")
|
||||
}
|
||||
|
||||
r := v1.Compare(v2)
|
||||
switch operator {
|
||||
case "eq":
|
||||
if r == 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
case "lt":
|
||||
if r < 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
case "le":
|
||||
if r <= 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
case "gt":
|
||||
if r > 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
case "ge":
|
||||
if r >= 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
default:
|
||||
return false, errors.New("not supported operator")
|
||||
}
|
||||
}
|
||||
63
pkg/detect/detect.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package detect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/cmd/version"
|
||||
"github.com/future-architect/vuls/pkg/detect/cpe"
|
||||
"github.com/future-architect/vuls/pkg/detect/debian"
|
||||
detectTypes "github.com/future-architect/vuls/pkg/detect/types"
|
||||
"github.com/future-architect/vuls/pkg/detect/ubuntu"
|
||||
"github.com/future-architect/vuls/pkg/detect/windows"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
func Detect(ctx context.Context, host *types.Host) error {
|
||||
if host.ScanError != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var detectors []detectTypes.Detector
|
||||
if len(host.Packages.OSPkg) > 0 {
|
||||
switch host.Family {
|
||||
case "debian":
|
||||
detectors = append(detectors, debian.Detector{})
|
||||
case "ubuntu":
|
||||
detectors = append(detectors, ubuntu.Detector{})
|
||||
default:
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
}
|
||||
if len(host.Packages.KB) > 0 {
|
||||
detectors = append(detectors, windows.Detector{})
|
||||
}
|
||||
if len(host.Packages.CPE) > 0 {
|
||||
detectors = append(detectors, cpe.Detector{})
|
||||
}
|
||||
|
||||
var err error
|
||||
for {
|
||||
if len(detectors) == 0 {
|
||||
break
|
||||
}
|
||||
d := detectors[0]
|
||||
if err = d.Detect(ctx, host); err != nil {
|
||||
break
|
||||
}
|
||||
detectors = detectors[1:]
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
host.DetecteddAt = &t
|
||||
host.DetectedVersion = version.Version
|
||||
host.DetectedRevision = version.Revision
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "detect %s", host.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
12
pkg/detect/types/types.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type Detector interface {
|
||||
Name() string
|
||||
Detect(context.Context, *types.Host) error
|
||||
}
|
||||
151
pkg/detect/ubuntu/ubuntu.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package ubuntu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
version "github.com/knqyf263/go-deb-version"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/db"
|
||||
dbTypes "github.com/future-architect/vuls/pkg/db/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
"github.com/future-architect/vuls/pkg/util"
|
||||
)
|
||||
|
||||
type Detector struct{}
|
||||
|
||||
func (d Detector) Name() string {
|
||||
return "ubuntu detector"
|
||||
}
|
||||
|
||||
func (d Detector) Detect(ctx context.Context, host *types.Host) error {
|
||||
if host.ScannedCves == nil {
|
||||
host.ScannedCves = map[string]types.VulnInfo{}
|
||||
}
|
||||
|
||||
vulndb, err := db.Open("boltdb", host.Config.Detect.Path, false)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "open %s", host.Config.Detect.Path)
|
||||
}
|
||||
defer vulndb.Close()
|
||||
|
||||
srcpkgs := map[string]string{}
|
||||
for _, p := range host.Packages.OSPkg {
|
||||
srcpkgs[p.SrcName] = p.SrcVersion
|
||||
}
|
||||
|
||||
for srcname, srcver := range srcpkgs {
|
||||
pkgs, err := vulndb.GetPackage(host.Family, host.Release, srcname)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get package")
|
||||
}
|
||||
|
||||
for cveid, datasrcs := range pkgs {
|
||||
for datasrc, ps := range datasrcs {
|
||||
for id, p := range ps {
|
||||
switch p.Status {
|
||||
case "released":
|
||||
for _, andVs := range p.Version {
|
||||
affected := true
|
||||
for _, v := range andVs {
|
||||
r, err := compare(v.Operator, srcver, v.Version)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "compare")
|
||||
}
|
||||
if !r {
|
||||
affected = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if affected {
|
||||
vinfo, ok := host.ScannedCves[cveid]
|
||||
if !ok {
|
||||
host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
|
||||
}
|
||||
vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
|
||||
Name: srcname,
|
||||
Source: fmt.Sprintf("%s:%s", datasrc, id),
|
||||
Status: p.Status,
|
||||
})
|
||||
vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
|
||||
host.ScannedCves[cveid] = vinfo
|
||||
}
|
||||
}
|
||||
case "needed", "deferred", "pending":
|
||||
vinfo, ok := host.ScannedCves[cveid]
|
||||
if !ok {
|
||||
host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
|
||||
}
|
||||
vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
|
||||
Name: srcname,
|
||||
Source: fmt.Sprintf("%s:%s", datasrc, id),
|
||||
Status: p.Status,
|
||||
})
|
||||
vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
|
||||
host.ScannedCves[cveid] = vinfo
|
||||
case "not-affected", "DNE":
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vulns, err := vulndb.GetVulnerability(maps.Keys(host.ScannedCves))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get vulnerability")
|
||||
}
|
||||
for cveid, datasrcs := range vulns {
|
||||
vinfo := host.ScannedCves[cveid]
|
||||
vinfo.Content = map[string]dbTypes.Vulnerability{}
|
||||
for src, v := range datasrcs {
|
||||
vinfo.Content[src] = v
|
||||
}
|
||||
host.ScannedCves[cveid] = vinfo
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func compare(operator, srcver, ver string) (bool, error) {
|
||||
v1, err := version.NewVersion(srcver)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "parse version")
|
||||
}
|
||||
v2, err := version.NewVersion(ver)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "parse version")
|
||||
}
|
||||
|
||||
r := v1.Compare(v2)
|
||||
switch operator {
|
||||
case "eq":
|
||||
if r == 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
case "lt":
|
||||
if r < 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
case "le":
|
||||
if r <= 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
case "gt":
|
||||
if r > 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
case "ge":
|
||||
if r >= 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
default:
|
||||
return false, errors.New("not supported operator")
|
||||
}
|
||||
}
|
||||
120
pkg/detect/windows/windows.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package windows
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/db"
|
||||
dbTypes "github.com/future-architect/vuls/pkg/db/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
"github.com/future-architect/vuls/pkg/util"
|
||||
)
|
||||
|
||||
type Detector struct{}
|
||||
|
||||
func (d Detector) Name() string {
|
||||
return "windows detector"
|
||||
}
|
||||
|
||||
func (d Detector) Detect(ctx context.Context, host *types.Host) error {
|
||||
if host.ScannedCves == nil {
|
||||
host.ScannedCves = map[string]types.VulnInfo{}
|
||||
}
|
||||
|
||||
vulndb, err := db.Open("boltdb", host.Config.Detect.Path, false)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "open %s", host.Config.Detect.Path)
|
||||
}
|
||||
defer vulndb.Close()
|
||||
|
||||
supercedences, err := vulndb.GetSupercedence(host.Packages.KB)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get supercedence")
|
||||
}
|
||||
|
||||
var unapplied []string
|
||||
for _, kbs := range supercedences {
|
||||
var applied bool
|
||||
for _, kb := range kbs {
|
||||
if slices.Contains(host.Packages.KB, kb) {
|
||||
applied = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !applied {
|
||||
unapplied = append(unapplied, kbs...)
|
||||
}
|
||||
}
|
||||
unapplied = util.Unique(unapplied)
|
||||
|
||||
products, err := vulndb.GetKBtoProduct(host.Release, append(host.Packages.KB, unapplied...))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get product from kb")
|
||||
}
|
||||
if !slices.Contains(products, host.Release) {
|
||||
products = append(products, host.Release)
|
||||
}
|
||||
|
||||
for _, product := range util.Unique(products) {
|
||||
pkgs, err := vulndb.GetPackage(host.Family, host.Release, product)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get package")
|
||||
}
|
||||
|
||||
for cveid, datasrcs := range pkgs {
|
||||
for datasrc, ps := range datasrcs {
|
||||
for id, p := range ps {
|
||||
switch p.Status {
|
||||
case "fixed":
|
||||
for _, v := range p.Version {
|
||||
if slices.Contains(unapplied, v[0].Version) {
|
||||
vinfo, ok := host.ScannedCves[cveid]
|
||||
if !ok {
|
||||
host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
|
||||
}
|
||||
vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
|
||||
Name: fmt.Sprintf("%s: KB%s", product, v[0].Version),
|
||||
Source: fmt.Sprintf("%s:%s", datasrc, id),
|
||||
Status: p.Status,
|
||||
})
|
||||
vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
|
||||
host.ScannedCves[cveid] = vinfo
|
||||
}
|
||||
}
|
||||
case "unfixed":
|
||||
vinfo, ok := host.ScannedCves[cveid]
|
||||
if !ok {
|
||||
host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
|
||||
}
|
||||
vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
|
||||
Name: product,
|
||||
Source: fmt.Sprintf("%s:%s", datasrc, id),
|
||||
Status: p.Status,
|
||||
})
|
||||
vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
|
||||
host.ScannedCves[cveid] = vinfo
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vulns, err := vulndb.GetVulnerability(maps.Keys(host.ScannedCves))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get vulnerability")
|
||||
}
|
||||
for cveid, datasrcs := range vulns {
|
||||
vinfo := host.ScannedCves[cveid]
|
||||
vinfo.Content = map[string]dbTypes.Vulnerability{}
|
||||
for src, v := range datasrcs {
|
||||
vinfo.Content[src] = v
|
||||
}
|
||||
host.ScannedCves[cveid] = vinfo
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
22
pkg/log/log.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ctxLogger struct{}
|
||||
|
||||
// ContextWithLogger adds logger to context
|
||||
func ContextWithLogger(ctx context.Context, l *zap.Logger) context.Context {
|
||||
return context.WithValue(ctx, ctxLogger{}, l)
|
||||
}
|
||||
|
||||
// LoggerFromContext returns logger from context
|
||||
func LoggerFromContext(ctx context.Context) *zap.Logger {
|
||||
if l, ok := ctx.Value(ctxLogger{}).(*zap.Logger); ok {
|
||||
return l
|
||||
}
|
||||
return zap.L()
|
||||
}
|
||||
44
pkg/scan/cpe/cpe.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/knqyf263/go-cpe/naming"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
scanTypes "github.com/future-architect/vuls/pkg/scan/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
}
|
||||
|
||||
func (a Analyzer) Name() string {
|
||||
return "cpe analyzer"
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
|
||||
ah.Host.Packages.CPE = map[string]types.CPE{}
|
||||
for _, c := range ah.Host.Config.Scan.CPE {
|
||||
if _, err := naming.UnbindFS(c.CPE); err != nil {
|
||||
return errors.Wrapf(err, "unbind %s", c.CPE)
|
||||
}
|
||||
key := c.CPE
|
||||
|
||||
if c.RunningOn != "" {
|
||||
if _, err := naming.UnbindFS(c.RunningOn); err != nil {
|
||||
return errors.Wrapf(err, "unbind %s", c.RunningOn)
|
||||
}
|
||||
key = fmt.Sprintf("%s_on_%s", c.CPE, c.RunningOn)
|
||||
}
|
||||
|
||||
ah.Host.Packages.CPE[key] = types.CPE{
|
||||
CPE: c.CPE,
|
||||
RunningOn: c.RunningOn,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
93
pkg/scan/os/os.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package os
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/scan/ospkg/apk"
|
||||
"github.com/future-architect/vuls/pkg/scan/ospkg/dpkg"
|
||||
"github.com/future-architect/vuls/pkg/scan/ospkg/rpm"
|
||||
"github.com/future-architect/vuls/pkg/scan/types"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
}
|
||||
|
||||
func (a Analyzer) Name() string {
|
||||
return "os analyzer"
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(ctx context.Context, ah *types.AnalyzerHost) error {
|
||||
status, stdout, stderr, err := ah.Host.Exec(ctx, "cat /etc/os-release", false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, `exec "cat /etc/os-release"`)
|
||||
}
|
||||
if stderr != "" {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
if status != 0 {
|
||||
return errors.Errorf("exit status is %d", status)
|
||||
}
|
||||
|
||||
ah.Host.Family, ah.Host.Release, err = ParseOSRelease(stdout)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse /etc/os-release")
|
||||
}
|
||||
|
||||
switch ah.Host.Family {
|
||||
case "debian", "ubuntu":
|
||||
ah.Analyzers = append(ah.Analyzers, dpkg.Analyzer{})
|
||||
case "redhat", "centos", "alma", "rocky", "fedora", "opensuse", "opensuse.tumbleweed", "opensuse.leap", "suse.linux.enterprise.server", "suse.linux.enterprise.desktop":
|
||||
ah.Analyzers = append(ah.Analyzers, rpm.Analyzer{})
|
||||
case "alpine":
|
||||
ah.Analyzers = append(ah.Analyzers, apk.Analyzer{})
|
||||
case "":
|
||||
return errors.New("family is unknown")
|
||||
default:
|
||||
return errors.New("not supported OS")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseOSRelease(stdout string) (string, string, error) {
|
||||
var family, versionID string
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
ss := strings.SplitN(line, "=", 2)
|
||||
if len(ss) != 2 {
|
||||
continue
|
||||
}
|
||||
key, value := strings.TrimSpace(ss[0]), strings.TrimSpace(ss[1])
|
||||
|
||||
switch key {
|
||||
case "ID":
|
||||
switch id := strings.Trim(value, `"'`); id {
|
||||
case "almalinux":
|
||||
family = "alma"
|
||||
case "opensuse-leap", "opensuse-tumbleweed":
|
||||
family = strings.ReplaceAll(id, "-", ".")
|
||||
case "sles":
|
||||
family = "suse.linux.enterprise.server"
|
||||
case "sled":
|
||||
family = "suse.linux.enterprise.desktop"
|
||||
default:
|
||||
family = strings.ToLower(id)
|
||||
}
|
||||
case "VERSION_ID":
|
||||
versionID = strings.Trim(value, `"'`)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if family == "" {
|
||||
return "", "", errors.New("family is unknown")
|
||||
}
|
||||
return family, versionID, nil
|
||||
}
|
||||
71
pkg/scan/ospkg/apk/apk.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package apk
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
scanTypes "github.com/future-architect/vuls/pkg/scan/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
}
|
||||
|
||||
func (a Analyzer) Name() string {
|
||||
return "apk analyzer"
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
|
||||
status, stdout, stderr, err := ah.Host.Exec(ctx, "apk info -v", false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, `exec "apk info -v"`)
|
||||
}
|
||||
if stderr != "" {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
if status != 0 {
|
||||
return errors.Errorf("exit status is %d", status)
|
||||
}
|
||||
|
||||
ah.Host.Packages.OSPkg, err = ParseInstalledPackage(stdout)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse installed package")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseInstalledPackage(stdout string) (map[string]types.Package, error) {
|
||||
pkgs := map[string]types.Package{}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
name, version, err := parseApkInfo(scanner.Text())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse apk info line")
|
||||
}
|
||||
if name == "" || version == "" {
|
||||
continue
|
||||
}
|
||||
pkgs[name] = types.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
func parseApkInfo(line string) (string, string, error) {
|
||||
ss := strings.Split(line, "-")
|
||||
if len(ss) < 3 {
|
||||
if strings.Contains(ss[0], "WARNING") {
|
||||
return "", "", nil
|
||||
}
|
||||
return "", "", errors.Errorf(`unexpected package line format. accepts: "<package name>-<version>-<release>", received: "%s"`, line)
|
||||
}
|
||||
return strings.Join(ss[:len(ss)-2], "-"), strings.Join(ss[len(ss)-2:], "-"), nil
|
||||
}
|
||||
95
pkg/scan/ospkg/dpkg/dpkg.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package dpkg
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
scanTypes "github.com/future-architect/vuls/pkg/scan/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
}
|
||||
|
||||
func (a Analyzer) Name() string {
|
||||
return "dpkg analyzer"
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
|
||||
status, stdout, stderr, err := ah.Host.Exec(ctx, `dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${Architecture},\${source:Package},\${source:Version}\n"`, false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, `exec "dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${Architecture},\${source:Package},\${source:Version}\n"`)
|
||||
}
|
||||
if stderr != "" {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
if status != 0 {
|
||||
return errors.Errorf("exit status is %d", status)
|
||||
}
|
||||
|
||||
ah.Host.Packages.OSPkg, err = ParseInstalledPackage(stdout)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse installed package")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseInstalledPackage(stdout string) (map[string]types.Package, error) {
|
||||
pkgs := map[string]types.Package{}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
|
||||
name, status, version, arch, srcName, srcVersion, err := parseDPKGQueryLine(trimmed)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse dpkq query line")
|
||||
}
|
||||
|
||||
packageStatus := status[1]
|
||||
// Package status:
|
||||
// n = Not-installed
|
||||
// c = Config-files
|
||||
// H = Half-installed
|
||||
// U = Unpacked
|
||||
// F = Half-configured
|
||||
// W = Triggers-awaiting
|
||||
// t = Triggers-pending
|
||||
// i = Installed
|
||||
if packageStatus != 'i' {
|
||||
continue
|
||||
}
|
||||
pkgs[name] = types.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Arch: arch,
|
||||
SrcName: srcName,
|
||||
SrcVersion: srcVersion,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
func parseDPKGQueryLine(line string) (string, string, string, string, string, string, error) {
|
||||
ss := strings.Split(line, ",")
|
||||
if len(ss) == 6 {
|
||||
// remove :amd64, i386...
|
||||
name, _, _ := strings.Cut(ss[0], ":")
|
||||
status := strings.TrimSpace(ss[1])
|
||||
if len(status) < 2 {
|
||||
return "", "", "", "", "", "", errors.Errorf(`unexpected db:Status-Abbrev format. accepts: "ii", received: "%s"`, status)
|
||||
}
|
||||
version := ss[2]
|
||||
arch := ss[3]
|
||||
srcName, _, _ := strings.Cut(ss[4], " ")
|
||||
srcVersion := ss[5]
|
||||
return name, status, version, arch, srcName, srcVersion, nil
|
||||
}
|
||||
return "", "", "", "", "", "", errors.Errorf(`unexpected package line format. accepts: "<bin name>,<status>,<bin version>,<arch>,<src name>,<src version>", received: "%s"`, line)
|
||||
}
|
||||
114
pkg/scan/ospkg/rpm/rpm.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
scanTypes "github.com/future-architect/vuls/pkg/scan/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
}
|
||||
|
||||
func (a Analyzer) Name() string {
|
||||
return "rpm analyzer"
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
|
||||
status, stdout, stderr, err := ah.Host.Exec(ctx, `rpm --version`, false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, `exec "rpm --version"`)
|
||||
}
|
||||
if stderr != "" {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
if status != 0 {
|
||||
return errors.Errorf("exit status is %d", status)
|
||||
}
|
||||
|
||||
cmd := `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH} %{VENDOR}\n"`
|
||||
rpmver, err := version.NewVersion(strings.TrimPrefix(strings.TrimSpace(stdout), "RPM version "))
|
||||
rpmModukaritylabel, err := version.NewVersion("4.15.0")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse rpm version for modularitylabel")
|
||||
}
|
||||
rpmEpochNum, err := version.NewVersion("4.8.0")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse rpm version for epochnum")
|
||||
}
|
||||
if rpmver.GreaterThanOrEqual(rpmModukaritylabel) {
|
||||
cmd = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH} %{VENDOR} %{MODULARITYLABEL}\n"`
|
||||
} else if rpmver.GreaterThanOrEqual(rpmEpochNum) {
|
||||
cmd = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH} %{VENDOR}\n"`
|
||||
}
|
||||
|
||||
status, stdout, stderr, err = ah.Host.Exec(ctx, cmd, false)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, `exec "%s"`, cmd)
|
||||
}
|
||||
if stderr != "" {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
if status != 0 {
|
||||
return errors.Errorf("exit status is %d", status)
|
||||
}
|
||||
|
||||
ah.Host.Packages.OSPkg, err = ParseInstalledPackage(stdout)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse installed package")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseInstalledPackage(stdout string) (map[string]types.Package, error) {
|
||||
pkgs := map[string]types.Package{}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
|
||||
name, version, release, arch, vendor, modularitylabel, err := parseRpmQaLine(trimmed)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse rpm -qa line")
|
||||
}
|
||||
|
||||
pkgs[name] = types.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Release: release,
|
||||
Arch: arch,
|
||||
Vendor: vendor,
|
||||
ModularityLabel: modularitylabel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
func parseRpmQaLine(line string) (string, string, string, string, string, string, error) {
|
||||
ss := strings.Fields(line)
|
||||
if len(ss) < 6 {
|
||||
return "", "", "", "", "", "", errors.Errorf(`unexpected rpm -qa line format. accepts: "<name> <epoch> <version> <release> <arch> <vendor>( <modularitylabel>)", received: "%s"`, line)
|
||||
}
|
||||
|
||||
ver := ss[2]
|
||||
epoch := ss[1]
|
||||
if epoch != "0" && epoch != "(none)" {
|
||||
ver = fmt.Sprintf("%s:%s", epoch, ss[2])
|
||||
}
|
||||
|
||||
var modularitylabel string
|
||||
if len(ss) == 7 {
|
||||
modularitylabel = ss[5]
|
||||
}
|
||||
|
||||
return ss[0], ver, ss[3], ss[4], ss[5], modularitylabel, nil
|
||||
}
|
||||
54
pkg/scan/scan.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/cmd/version"
|
||||
"github.com/future-architect/vuls/pkg/scan/cpe"
|
||||
"github.com/future-architect/vuls/pkg/scan/os"
|
||||
"github.com/future-architect/vuls/pkg/scan/systeminfo"
|
||||
scanTypes "github.com/future-architect/vuls/pkg/scan/types"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
func Scan(ctx context.Context, host *types.Host) error {
|
||||
ah := scanTypes.AnalyzerHost{Host: host}
|
||||
if ah.Host.Config.Scan.OSPkg != nil {
|
||||
if runtime.GOOS == "windows" {
|
||||
ah.Analyzers = append(ah.Analyzers, systeminfo.Analyzer{})
|
||||
} else {
|
||||
ah.Analyzers = append(ah.Analyzers, os.Analyzer{})
|
||||
}
|
||||
}
|
||||
if len(ah.Host.Config.Scan.CPE) > 0 {
|
||||
ah.Analyzers = append(ah.Analyzers, cpe.Analyzer{})
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
)
|
||||
for {
|
||||
if len(ah.Analyzers) == 0 {
|
||||
break
|
||||
}
|
||||
a := ah.Analyzers[0]
|
||||
if err = a.Analyze(ctx, &ah); err != nil {
|
||||
break
|
||||
}
|
||||
ah.Analyzers = ah.Analyzers[1:]
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
ah.Host.ScannedAt = &t
|
||||
ah.Host.ScannedVersion = version.Version
|
||||
ah.Host.ScannedRevision = version.Revision
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "analyze %s", ah.Host.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
480
pkg/scan/systeminfo/systeminfo.go
Normal file
@@ -0,0 +1,480 @@
|
||||
package systeminfo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/scan/types"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
}
|
||||
|
||||
func (a Analyzer) Name() string {
|
||||
return "systeminfo analyzer"
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(ctx context.Context, ah *types.AnalyzerHost) error {
|
||||
status, stdout, stderr, err := ah.Host.Exec(ctx, "systeminfo", false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, `exec "systeminfo"`)
|
||||
}
|
||||
if stderr != "" {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
if status != 0 {
|
||||
return errors.Errorf("exit status is %d", status)
|
||||
}
|
||||
|
||||
ah.Host.Family, ah.Host.Release, ah.Host.Packages.KB, err = ParseSysteminfo(stdout)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse systeminfo")
|
||||
}
|
||||
|
||||
if ah.Host.Family == "" {
|
||||
return errors.New("family is unknown")
|
||||
}
|
||||
if ah.Host.Release == "" {
|
||||
return errors.New("release is unknown")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseSysteminfo(stdout string) (string, string, []string, error) {
|
||||
var (
|
||||
o osInfo
|
||||
kbs []string
|
||||
)
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(line, "OS Name:"):
|
||||
o.productName = strings.TrimSpace(strings.TrimPrefix(line, "OS Name:"))
|
||||
case strings.HasPrefix(line, "OS Version:"):
|
||||
s := strings.TrimSpace(strings.TrimPrefix(line, "OS Version:"))
|
||||
lhs, build, _ := strings.Cut(s, " Build ")
|
||||
vb, sp, _ := strings.Cut(lhs, " ")
|
||||
o.version = strings.TrimSuffix(vb, fmt.Sprintf(".%s", build))
|
||||
o.build = build
|
||||
if sp != "N/A" {
|
||||
o.servicePack = sp
|
||||
}
|
||||
case strings.HasPrefix(line, "System Type:"):
|
||||
o.arch = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "System Type:"), "PC"))
|
||||
case strings.HasPrefix(line, "OS Configuration:"):
|
||||
switch {
|
||||
case strings.Contains(line, "Server"):
|
||||
o.installationType = "Server"
|
||||
case strings.Contains(line, "Workstation"):
|
||||
o.installationType = "Client"
|
||||
default:
|
||||
return "", "", nil, errors.Errorf(`installation type not found from "%s"`, line)
|
||||
}
|
||||
case strings.HasPrefix(line, "Hotfix(s):"):
|
||||
nKB, err := strconv.Atoi(strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "Hotfix(s):"), " Hotfix(s) Installed.")))
|
||||
if err != nil {
|
||||
return "", "", nil, errors.Errorf(`number of installed hotfix from "%s"`, line)
|
||||
}
|
||||
for i := 0; i < nKB; i++ {
|
||||
scanner.Scan()
|
||||
line := scanner.Text()
|
||||
_, rhs, found := strings.Cut(line, ":")
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
s := strings.TrimSpace(rhs)
|
||||
if strings.HasPrefix(s, "KB") {
|
||||
kbs = append(kbs, strings.TrimPrefix(s, "KB"))
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
release, err := detectOSName(o)
|
||||
if err != nil {
|
||||
return "", "", nil, errors.Wrap(err, "detect os name")
|
||||
}
|
||||
|
||||
return "windows", release, kbs, nil
|
||||
}
|
||||
|
||||
type osInfo struct {
|
||||
productName string
|
||||
version string
|
||||
build string
|
||||
edition string
|
||||
servicePack string
|
||||
arch string
|
||||
installationType string
|
||||
}
|
||||
|
||||
func detectOSName(osInfo osInfo) (string, error) {
|
||||
osName, err := detectOSNameFromOSInfo(osInfo)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "detect OS Name from OSInfo: %#v", osInfo)
|
||||
}
|
||||
return osName, nil
|
||||
}
|
||||
|
||||
func detectOSNameFromOSInfo(osInfo osInfo) (string, error) {
|
||||
switch osInfo.version {
|
||||
case "5.0":
|
||||
switch osInfo.installationType {
|
||||
case "Client":
|
||||
if osInfo.servicePack != "" {
|
||||
return fmt.Sprintf("Microsoft Windows 2000 %s", osInfo.servicePack), nil
|
||||
}
|
||||
return "Microsoft Windows 2000", nil
|
||||
case "Server":
|
||||
if osInfo.servicePack != "" {
|
||||
return fmt.Sprintf("Microsoft Windows 2000 Server %s", osInfo.servicePack), nil
|
||||
}
|
||||
return "Microsoft Windows 2000 Server", nil
|
||||
}
|
||||
case "5.1":
|
||||
switch osInfo.installationType {
|
||||
case "Client":
|
||||
var n string
|
||||
switch osInfo.edition {
|
||||
case "Professional":
|
||||
n = "Microsoft Windows XP Professional"
|
||||
case "Media Center":
|
||||
n = "Microsoft Windows XP Media Center Edition 2005"
|
||||
case "Tablet PC":
|
||||
n = "Microsoft Windows XP Tablet PC Edition 2005"
|
||||
default:
|
||||
n = "Microsoft Windows XP"
|
||||
}
|
||||
switch osInfo.arch {
|
||||
case "x64":
|
||||
n = fmt.Sprintf("%s x64 Edition", n)
|
||||
}
|
||||
if osInfo.servicePack != "" {
|
||||
return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
case "5.2":
|
||||
switch osInfo.installationType {
|
||||
case "Client":
|
||||
var n string
|
||||
switch osInfo.edition {
|
||||
case "Professional":
|
||||
n = "Microsoft Windows XP Professional"
|
||||
case "Media Center":
|
||||
n = "Microsoft Windows XP Media Center Edition 2005"
|
||||
case "Tablet PC":
|
||||
n = "Microsoft Windows XP Tablet PC Edition 2005"
|
||||
default:
|
||||
n = "Microsoft Windows XP"
|
||||
}
|
||||
switch osInfo.arch {
|
||||
case "x64":
|
||||
n = fmt.Sprintf("%s x64 Edition", n)
|
||||
}
|
||||
if osInfo.servicePack != "" {
|
||||
return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil
|
||||
}
|
||||
return n, nil
|
||||
case "Server":
|
||||
n := "Microsoft Windows Server 2003"
|
||||
if strings.Contains(osInfo.productName, "R2") {
|
||||
n = "Microsoft Windows Server 2003 R2"
|
||||
}
|
||||
switch osInfo.arch {
|
||||
case "x64":
|
||||
n = fmt.Sprintf("%s x64 Edition", n)
|
||||
case "IA64":
|
||||
if osInfo.edition == "Enterprise" {
|
||||
n = fmt.Sprintf("%s, Enterprise Edition for Itanium-based Systems", n)
|
||||
} else {
|
||||
n = fmt.Sprintf("%s for Itanium-based Systems", n)
|
||||
}
|
||||
}
|
||||
if osInfo.servicePack != "" {
|
||||
return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
case "6.0":
|
||||
switch osInfo.installationType {
|
||||
case "Client":
|
||||
var n string
|
||||
switch osInfo.arch {
|
||||
case "x64":
|
||||
n = "Windows Vista x64 Editions"
|
||||
default:
|
||||
n = "Windows Vista"
|
||||
}
|
||||
if osInfo.servicePack != "" {
|
||||
return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil
|
||||
}
|
||||
return n, nil
|
||||
case "Server":
|
||||
arch, err := formatArch(osInfo.arch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if osInfo.servicePack != "" {
|
||||
return fmt.Sprintf("Windows Server 2008 for %s Systems %s", arch, osInfo.servicePack), nil
|
||||
}
|
||||
return fmt.Sprintf("Windows Server 2008 for %s Systems", arch), nil
|
||||
case "Server Core":
|
||||
arch, err := formatArch(osInfo.arch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if osInfo.servicePack != "" {
|
||||
return fmt.Sprintf("Windows Server 2008 for %s Systems %s (Server Core installation)", arch, osInfo.servicePack), nil
|
||||
}
|
||||
return fmt.Sprintf("Windows Server 2008 for %s Systems (Server Core installation)", arch), nil
|
||||
}
|
||||
case "6.1":
|
||||
switch osInfo.installationType {
|
||||
case "Client":
|
||||
arch, err := formatArch(osInfo.arch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if osInfo.servicePack != "" {
|
||||
return fmt.Sprintf("Windows 7 for %s Systems %s", arch, osInfo.servicePack), nil
|
||||
}
|
||||
return fmt.Sprintf("Windows 7 for %s Systems", arch), nil
|
||||
case "Server":
|
||||
arch, err := formatArch(osInfo.arch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if osInfo.servicePack != "" {
|
||||
return fmt.Sprintf("Windows Server 2008 R2 for %s Systems %s", arch, osInfo.servicePack), nil
|
||||
}
|
||||
return fmt.Sprintf("Windows Server 2008 R2 for %s Systems", arch), nil
|
||||
case "Server Core":
|
||||
arch, err := formatArch(osInfo.arch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if osInfo.servicePack != "" {
|
||||
return fmt.Sprintf("Windows Server 2008 R2 for %s Systems %s (Server Core installation)", arch, osInfo.servicePack), nil
|
||||
}
|
||||
return fmt.Sprintf("Windows Server 2008 R2 for %s Systems (Server Core installation)", arch), nil
|
||||
}
|
||||
case "6.2":
|
||||
switch osInfo.installationType {
|
||||
case "Client":
|
||||
arch, err := formatArch(osInfo.arch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("Windows 8 for %s Systems", arch), nil
|
||||
case "Server":
|
||||
return "Windows Server 2012", nil
|
||||
case "Server Core":
|
||||
return "Windows Server 2012 (Server Core installation)", nil
|
||||
}
|
||||
case "6.3":
|
||||
switch osInfo.installationType {
|
||||
case "Client":
|
||||
arch, err := formatArch(osInfo.arch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("Windows 8.1 for %s Systems", arch), nil
|
||||
case "Server":
|
||||
return "Windows Server 2012 R2", nil
|
||||
case "Server Core":
|
||||
return "Windows Server 2012 R2 (Server Core installation)", nil
|
||||
}
|
||||
case "10.0":
|
||||
switch osInfo.installationType {
|
||||
case "Client":
|
||||
if strings.Contains(osInfo.productName, "Windows 10") {
|
||||
arch, err := formatArch(osInfo.arch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name, err := formatNamebyBuild("10", osInfo.build)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s for %s Systems", name, arch), nil
|
||||
}
|
||||
if strings.Contains(osInfo.productName, "Windows 11") {
|
||||
arch, err := formatArch(osInfo.arch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name, err := formatNamebyBuild("11", osInfo.build)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s for %s Systems", name, arch), nil
|
||||
}
|
||||
case "Server":
|
||||
return formatNamebyBuild("Server", osInfo.build)
|
||||
case "Server Core":
|
||||
name, err := formatNamebyBuild("Server", osInfo.build)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s (Server Core installation)", name), nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("OS Name not found")
|
||||
}
|
||||
|
||||
func formatArch(arch string) (string, error) {
|
||||
switch arch {
|
||||
case "x64-based":
|
||||
return "x64-based", nil
|
||||
case "ARM64-based":
|
||||
return "ARM64-based", nil
|
||||
case "Itanium-based":
|
||||
return "Itanium-based", nil
|
||||
case "X86-based":
|
||||
return "32-bit", nil
|
||||
default:
|
||||
return "", errors.New("CPU Architecture not found")
|
||||
}
|
||||
}
|
||||
|
||||
type buildNumber struct {
|
||||
build string
|
||||
name string
|
||||
}
|
||||
|
||||
var (
|
||||
winBuilds = map[string][]buildNumber{
|
||||
"10": {
|
||||
{
|
||||
build: "10240",
|
||||
name: "Windows 10", // not "Windows 10 Version 1507"
|
||||
},
|
||||
{
|
||||
build: "10586",
|
||||
name: "Windows 10 Version 1511",
|
||||
},
|
||||
{
|
||||
build: "14393",
|
||||
name: "Windows 10 Version 1607",
|
||||
},
|
||||
{
|
||||
build: "15063",
|
||||
name: "Windows 10 Version 1703",
|
||||
},
|
||||
{
|
||||
build: "16299",
|
||||
name: "Windows 10 Version 1709",
|
||||
},
|
||||
{
|
||||
build: "17134",
|
||||
name: "Windows 10 Version 1803",
|
||||
},
|
||||
{
|
||||
build: "17763",
|
||||
name: "Windows 10 Version 1809",
|
||||
},
|
||||
{
|
||||
build: "18362",
|
||||
name: "Windows 10 Version 1903",
|
||||
},
|
||||
{
|
||||
build: "18363",
|
||||
name: "Windows 10 Version 1909",
|
||||
},
|
||||
{
|
||||
build: "19041",
|
||||
name: "Windows 10 Version 2004",
|
||||
},
|
||||
{
|
||||
build: "19042",
|
||||
name: "Windows 10 Version 20H2",
|
||||
},
|
||||
{
|
||||
build: "19043",
|
||||
name: "Windows 10 Version 21H1",
|
||||
},
|
||||
{
|
||||
build: "19044",
|
||||
name: "Windows 10 Version 21H2",
|
||||
},
|
||||
// It seems that there are cases where the Product Name is Windows 10 even though it is Windows 11
|
||||
// ref: https://docs.microsoft.com/en-us/answers/questions/586548/in-the-official-version-of-windows-11-why-the-key.html
|
||||
{
|
||||
build: "22000",
|
||||
name: "Windows 11",
|
||||
},
|
||||
},
|
||||
"11": {
|
||||
{
|
||||
build: "22000",
|
||||
name: "Windows 11", // not "Windows 11 Version 21H2"
|
||||
},
|
||||
},
|
||||
"Server": {
|
||||
{
|
||||
build: "14393",
|
||||
name: "Windows Server 2016",
|
||||
},
|
||||
{
|
||||
build: "16299",
|
||||
name: "Windows Server, Version 1709",
|
||||
},
|
||||
{
|
||||
build: "17134",
|
||||
name: "Windows Server, Version 1809",
|
||||
},
|
||||
{
|
||||
build: "17763",
|
||||
name: "Windows Server 2019",
|
||||
},
|
||||
{
|
||||
build: "18362",
|
||||
name: "Windows Server, Version 1903",
|
||||
},
|
||||
{
|
||||
build: "18363",
|
||||
name: "Windows Server, Version 1909",
|
||||
},
|
||||
{
|
||||
build: "19041",
|
||||
name: "Windows Server, Version 2004",
|
||||
},
|
||||
{
|
||||
build: "19042",
|
||||
name: "Windows Server, Version 20H2",
|
||||
},
|
||||
{
|
||||
build: "20348",
|
||||
name: "Windows Server 2022",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func formatNamebyBuild(osType string, mybuild string) (string, error) {
|
||||
builds, ok := winBuilds[osType]
|
||||
if !ok {
|
||||
return "", errors.New("OS Type not found")
|
||||
}
|
||||
|
||||
v := builds[0].name
|
||||
for _, b := range builds {
|
||||
if mybuild == b.build {
|
||||
return b.name, nil
|
||||
}
|
||||
if mybuild < b.build {
|
||||
break
|
||||
}
|
||||
v = b.name
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
17
pkg/scan/types/types.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type Analyzer interface {
|
||||
Name() string
|
||||
Analyze(context.Context, *AnalyzerHost) error
|
||||
}
|
||||
|
||||
type AnalyzerHost struct {
|
||||
Host *types.Host
|
||||
Analyzers []Analyzer
|
||||
}
|
||||
108
pkg/server/server.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/cmd/version"
|
||||
"github.com/future-architect/vuls/pkg/config"
|
||||
"github.com/future-architect/vuls/pkg/detect"
|
||||
"github.com/future-architect/vuls/pkg/scan/os"
|
||||
"github.com/future-architect/vuls/pkg/scan/ospkg/apk"
|
||||
"github.com/future-architect/vuls/pkg/scan/ospkg/dpkg"
|
||||
"github.com/future-architect/vuls/pkg/scan/ospkg/rpm"
|
||||
"github.com/future-architect/vuls/pkg/scan/systeminfo"
|
||||
"github.com/future-architect/vuls/pkg/types"
|
||||
)
|
||||
|
||||
type scanContents struct {
|
||||
Contents []struct {
|
||||
ContentType string `json:"type,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
} `json:"contents,omitempty"`
|
||||
}
|
||||
|
||||
func Scan() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
s := new(scanContents)
|
||||
if err := c.Bind(s); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, "bad request")
|
||||
}
|
||||
|
||||
h := types.Host{Name: uuid.NewString()}
|
||||
|
||||
for _, cont := range s.Contents {
|
||||
switch cont.ContentType {
|
||||
case "os-release":
|
||||
family, release, err := os.ParseOSRelease(cont.Content)
|
||||
if err != nil {
|
||||
h.ScanError = err.Error()
|
||||
return c.JSON(http.StatusInternalServerError, h)
|
||||
}
|
||||
h.Family = family
|
||||
h.Release = release
|
||||
case "systeminfo":
|
||||
family, release, kbs, err := systeminfo.ParseSysteminfo(cont.Content)
|
||||
if err != nil {
|
||||
h.ScanError = err.Error()
|
||||
return c.JSON(http.StatusInternalServerError, h)
|
||||
}
|
||||
h.Family = family
|
||||
h.Release = release
|
||||
h.Packages.KB = kbs
|
||||
case "apk":
|
||||
pkgs, err := apk.ParseInstalledPackage(cont.Content)
|
||||
if err != nil {
|
||||
h.ScanError = err.Error()
|
||||
return c.JSON(http.StatusInternalServerError, h)
|
||||
}
|
||||
h.Packages.OSPkg = pkgs
|
||||
case "dpkg":
|
||||
pkgs, err := dpkg.ParseInstalledPackage(cont.Content)
|
||||
if err != nil {
|
||||
h.ScanError = err.Error()
|
||||
return c.JSON(http.StatusInternalServerError, h)
|
||||
}
|
||||
h.Packages.OSPkg = pkgs
|
||||
case "rpm":
|
||||
pkgs, err := rpm.ParseInstalledPackage(cont.Content)
|
||||
if err != nil {
|
||||
h.ScanError = err.Error()
|
||||
return c.JSON(http.StatusInternalServerError, h)
|
||||
}
|
||||
h.Packages.OSPkg = pkgs
|
||||
}
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
h.ScannedAt = &t
|
||||
h.ScannedVersion = version.Version
|
||||
h.ScannedRevision = version.Revision
|
||||
return c.JSON(http.StatusOK, h)
|
||||
}
|
||||
}
|
||||
|
||||
func Detect(dbpath string) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
h := new(types.Host)
|
||||
if err := c.Bind(h); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, "bad request")
|
||||
}
|
||||
|
||||
if h.Config.Detect == nil {
|
||||
h.Config.Detect = &config.Detect{}
|
||||
}
|
||||
h.Config.Detect.Path = dbpath
|
||||
|
||||
if err := detect.Detect(context.Background(), h); err != nil {
|
||||
h.DetectError = err.Error()
|
||||
return c.JSON(http.StatusInternalServerError, h)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, h)
|
||||
}
|
||||
}
|
||||
182
pkg/types/types.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/future-architect/vuls/pkg/config"
|
||||
"github.com/future-architect/vuls/pkg/db/types"
|
||||
)
|
||||
|
||||
type Host struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Family string `json:"family,omitempty"`
|
||||
Release string `json:"release,omitempty"`
|
||||
|
||||
ScannedAt *time.Time `json:"scanned_at,omitempty"`
|
||||
ScannedVersion string `json:"scanned_version,omitempty"`
|
||||
ScannedRevision string `json:"scanned_revision,omitempty"`
|
||||
ScanError string `json:"scan_error,omitempty"`
|
||||
|
||||
DetecteddAt *time.Time `json:"detectedd_at,omitempty"`
|
||||
DetectedVersion string `json:"detected_version,omitempty"`
|
||||
DetectedRevision string `json:"detected_revision,omitempty"`
|
||||
DetectError string `json:"detect_error,omitempty"`
|
||||
|
||||
ReportedAt *time.Time `json:"reported_at,omitempty"`
|
||||
ReportedVersion string `json:"reported_version,omitempty"`
|
||||
ReportedRevision string `json:"reported_revision,omitempty"`
|
||||
|
||||
Packages Packages `json:"packages,omitempty"`
|
||||
ScannedCves map[string]VulnInfo `json:"scanned_cves,omitempty"`
|
||||
|
||||
Config Config `json:"config,omitempty"`
|
||||
}
|
||||
|
||||
func (h *Host) Exec(ctx context.Context, cmd string, sudo bool) (int, string, string, error) {
|
||||
if sudo {
|
||||
cmd = fmt.Sprintf("sudo -S %s", cmd)
|
||||
}
|
||||
switch h.Config.Type {
|
||||
case "local":
|
||||
execCmd := exec.CommandContext(ctx, "/bin/sh", "-c", cmd)
|
||||
if runtime.GOOS == "windows" {
|
||||
execCmd = exec.CommandContext(ctx, cmd)
|
||||
}
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
execCmd.Stdout = &stdoutBuf
|
||||
execCmd.Stderr = &stderrBuf
|
||||
if err := execCmd.Run(); err != nil {
|
||||
if e, ok := err.(*exec.ExitError); ok {
|
||||
if s, ok := e.Sys().(syscall.WaitStatus); ok {
|
||||
return s.ExitStatus(), stdoutBuf.String(), stderrBuf.String(), nil
|
||||
} else {
|
||||
return 998, stdoutBuf.String(), stderrBuf.String(), nil
|
||||
}
|
||||
} else {
|
||||
return 999, stdoutBuf.String(), stderrBuf.String(), nil
|
||||
}
|
||||
} else {
|
||||
return 0, stdoutBuf.String(), stderrBuf.String(), nil
|
||||
}
|
||||
case "remote":
|
||||
sshBinPath, err := exec.LookPath("ssh")
|
||||
if err != nil {
|
||||
return 0, "", "", errors.Wrap(err, "look path to ssh")
|
||||
}
|
||||
|
||||
args := []string{"-tt"}
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return 0, "", "", errors.Wrap(err, "find %s home directory")
|
||||
}
|
||||
args = append(args,
|
||||
"-o", "StrictHostKeyChecking=yes",
|
||||
"-o", "LogLevel=quiet",
|
||||
"-o", "ConnectionAttempts=3",
|
||||
"-o", "ConnectTimeout=10",
|
||||
"-o", "ControlMaster=auto",
|
||||
"-o", fmt.Sprintf("ControlPath=%s", filepath.Join(home, ".vuls", fmt.Sprintf("controlmaster-%%r-%s.%%p", h.Name))),
|
||||
"-o", "Controlpersist=10m",
|
||||
"-l", *h.Config.User,
|
||||
)
|
||||
if h.Config.Port != nil {
|
||||
args = append(args, "-p", *h.Config.Port)
|
||||
}
|
||||
if h.Config.SSHKey != nil {
|
||||
args = append(args, "-i", *h.Config.SSHKey, "-o", "PasswordAuthentication=no")
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
args = append(args, *h.Config.Host, cmd)
|
||||
} else {
|
||||
args = append(args, *h.Config.Host, fmt.Sprintf("stty cols 1000; %s", cmd))
|
||||
}
|
||||
|
||||
execCmd := exec.CommandContext(ctx, sshBinPath, args...)
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
execCmd.Stdout = &stdoutBuf
|
||||
execCmd.Stderr = &stderrBuf
|
||||
if err := execCmd.Run(); err != nil {
|
||||
if e, ok := err.(*exec.ExitError); ok {
|
||||
if s, ok := e.Sys().(syscall.WaitStatus); ok {
|
||||
return s.ExitStatus(), stdoutBuf.String(), stderrBuf.String(), nil
|
||||
} else {
|
||||
return 998, stdoutBuf.String(), stderrBuf.String(), nil
|
||||
}
|
||||
} else {
|
||||
return 999, stdoutBuf.String(), stderrBuf.String(), nil
|
||||
}
|
||||
} else {
|
||||
return 0, stdoutBuf.String(), stderrBuf.String(), nil
|
||||
}
|
||||
default:
|
||||
return 0, "", "", errors.Errorf("%s is not implemented", h.Config.Type)
|
||||
}
|
||||
}
|
||||
|
||||
type Packages struct {
|
||||
Kernel Kernel `json:"kernel,omitempty"`
|
||||
OSPkg map[string]Package `json:"ospkg,omitempty"`
|
||||
CPE map[string]CPE `json:"cpe,omitempty"`
|
||||
KB []string `json:"kb,omitempty"`
|
||||
}
|
||||
|
||||
type Kernel struct {
|
||||
Version string `json:"version,omitempty"`
|
||||
Release string `json:"release,omitempty"`
|
||||
RebootRrequired bool `json:"reboot_rrequired,omitempty"`
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Release string `json:"release,omitempty"`
|
||||
NewVersion string `json:"new_version,omitempty"`
|
||||
NewRelease string `json:"new_release,omitempty"`
|
||||
Arch string `json:"arch,omitempty"`
|
||||
Vendor string `json:"vendor,omitempty"`
|
||||
Repository string `json:"repository,omitempty"`
|
||||
ModularityLabel string `json:"modularity_label,omitempty"`
|
||||
|
||||
SrcName string `json:"src_name,omitempty"`
|
||||
SrcVersion string `json:"src_version,omitempty"`
|
||||
SrcArch string `json:"src_arch,omitempty"`
|
||||
}
|
||||
|
||||
type CPE struct {
|
||||
CPE string `json:"cpe,omitempty"`
|
||||
RunningOn string `json:"running_on,omitempty"`
|
||||
}
|
||||
|
||||
type VulnInfo struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Content map[string]types.Vulnerability `json:"content,omitempty"`
|
||||
AffectedPackages []AffectedPackage `json:"affected_packages,omitempty"`
|
||||
}
|
||||
|
||||
type AffectedPackage struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Host *string `json:"host,omitempty"`
|
||||
Port *string `json:"port,omitempty"`
|
||||
User *string `json:"user,omitempty"`
|
||||
SSHConfig *string `json:"ssh_config,omitempty"`
|
||||
SSHKey *string `json:"ssh_key,omitempty"`
|
||||
Scan *config.Scan `json:"scan,omitempty"`
|
||||
Detect *config.Detect `json:"detect,omitempty"`
|
||||
}
|
||||
76
pkg/util/util.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/ulikunitz/xz"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func CacheDir() string {
|
||||
cacheDir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
cacheDir = os.TempDir()
|
||||
}
|
||||
dir := filepath.Join(cacheDir, "vuls")
|
||||
return dir
|
||||
}
|
||||
|
||||
func Unique[T comparable](s []T) []T {
|
||||
m := map[T]struct{}{}
|
||||
for _, v := range s {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return maps.Keys(m)
|
||||
}
|
||||
|
||||
func Read(path string) ([]byte, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "open %s", path)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
switch filepath.Ext(path) {
|
||||
case ".gz":
|
||||
gr, err := gzip.NewReader(f)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create gzip reader")
|
||||
}
|
||||
defer gr.Close()
|
||||
|
||||
bs, err := io.ReadAll(gr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read data")
|
||||
}
|
||||
return bs, nil
|
||||
case ".bz2":
|
||||
bs, err := io.ReadAll(bzip2.NewReader(f))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read data")
|
||||
}
|
||||
return bs, nil
|
||||
case ".xz":
|
||||
xr, err := xz.NewReader(f)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create xz reader")
|
||||
}
|
||||
|
||||
bs, err := io.ReadAll(xr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read data")
|
||||
}
|
||||
return bs, nil
|
||||
default:
|
||||
bs, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read data")
|
||||
}
|
||||
return bs, nil
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
// JSONWriter writes report as JSON format
|
||||
type JSONWriter struct{}
|
||||
|
||||
func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
|
||||
var j []byte
|
||||
if j, err = json.MarshalIndent(scanResults, "", " "); err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(string(j))
|
||||
return nil
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/future-architect/vuls/models"
|
||||
formatter "github.com/kotakanbe/logrus-prefixed-formatter"
|
||||
)
|
||||
|
||||
// LogrusWriter write to logfile
|
||||
type LogrusWriter struct {
|
||||
}
|
||||
|
||||
func (w LogrusWriter) Write(scanResults []models.ScanResult) error {
|
||||
path := "/var/log/vuls/report.log"
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log := logrus.New()
|
||||
log.Formatter = &formatter.TextFormatter{}
|
||||
log.Out = f
|
||||
log.Level = logrus.InfoLevel
|
||||
|
||||
for _, s := range scanResults {
|
||||
text, err := toPlainText(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof(text)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"gopkg.in/gomail.v2"
|
||||
)
|
||||
|
||||
// MailWriter send mail
|
||||
type MailWriter struct{}
|
||||
|
||||
func (w MailWriter) Write(scanResults []models.ScanResult) (err error) {
|
||||
conf := config.Conf
|
||||
for _, s := range scanResults {
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", conf.Mail.From)
|
||||
m.SetHeader("To", conf.Mail.To...)
|
||||
m.SetHeader("Cc", conf.Mail.Cc...)
|
||||
|
||||
subject := fmt.Sprintf("%s%s %s",
|
||||
conf.Mail.SubjectPrefix,
|
||||
s.ServerName,
|
||||
s.CveSummary(),
|
||||
)
|
||||
m.SetHeader("Subject", subject)
|
||||
|
||||
var body string
|
||||
if body, err = toPlainText(s); err != nil {
|
||||
return err
|
||||
}
|
||||
m.SetBody("text/plain", body)
|
||||
port, _ := strconv.Atoi(conf.Mail.SMTPPort)
|
||||
d := gomail.NewPlainDialer(
|
||||
conf.Mail.SMTPAddr,
|
||||
port,
|
||||
conf.Mail.User,
|
||||
conf.Mail.Password,
|
||||
)
|
||||
|
||||
d.TLSConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
if err := d.DialAndSend(m); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
236
report/slack.go
@@ -1,236 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
)
|
||||
|
||||
type field struct {
|
||||
Title string `json:"title"`
|
||||
Value string `json:"value"`
|
||||
Short bool `json:"short"`
|
||||
}
|
||||
type attachment struct {
|
||||
Title string `json:"title"`
|
||||
TitleLink string `json:"title_link"`
|
||||
Fallback string `json:"fallback"`
|
||||
Text string `json:"text"`
|
||||
Pretext string `json:"pretext"`
|
||||
Color string `json:"color"`
|
||||
Fields []*field `json:"fields"`
|
||||
MrkdwnIn []string `json:"mrkdwn_in"`
|
||||
}
|
||||
type message struct {
|
||||
Text string `json:"text"`
|
||||
Username string `json:"username"`
|
||||
IconEmoji string `json:"icon_emoji"`
|
||||
Channel string `json:"channel"`
|
||||
Attachments []*attachment `json:"attachments"`
|
||||
}
|
||||
|
||||
// SlackWriter send report to slack
|
||||
type SlackWriter struct{}
|
||||
|
||||
func (w SlackWriter) Write(scanResults []models.ScanResult) error {
|
||||
conf := config.Conf.Slack
|
||||
for _, s := range scanResults {
|
||||
|
||||
channel := conf.Channel
|
||||
if channel == "${servername}" {
|
||||
channel = fmt.Sprintf("#%s", s.ServerName)
|
||||
}
|
||||
|
||||
msg := message{
|
||||
Text: msgText(s),
|
||||
Username: conf.AuthUser,
|
||||
IconEmoji: conf.IconEmoji,
|
||||
Channel: channel,
|
||||
Attachments: toSlackAttachments(s),
|
||||
}
|
||||
|
||||
bytes, _ := json.Marshal(msg)
|
||||
jsonBody := string(bytes)
|
||||
f := func() (err error) {
|
||||
resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).
|
||||
Send(string(jsonBody)).End()
|
||||
if resp.StatusCode != 200 {
|
||||
log.Errorf("Resonse body: %s", body)
|
||||
if len(errs) > 0 {
|
||||
return errs[0]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, t time.Duration) {
|
||||
log.Warn("Retrying in ", t)
|
||||
}
|
||||
if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
|
||||
return fmt.Errorf("HTTP Error: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func msgText(r models.ScanResult) string {
|
||||
|
||||
notifyUsers := ""
|
||||
if 0 < len(r.KnownCves) || 0 < len(r.UnknownCves) {
|
||||
notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers)
|
||||
}
|
||||
|
||||
hostinfo := fmt.Sprintf(
|
||||
"*%s* (%s %s)",
|
||||
r.ServerName,
|
||||
r.Family,
|
||||
r.Release,
|
||||
)
|
||||
return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, hostinfo, r.CveSummary())
|
||||
}
|
||||
|
||||
func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
|
||||
|
||||
scanResult.KnownCves = append(scanResult.KnownCves, scanResult.UnknownCves...)
|
||||
for _, cveInfo := range scanResult.KnownCves {
|
||||
cveID := cveInfo.CveDetail.CveID
|
||||
|
||||
curentPackages := []string{}
|
||||
for _, p := range cveInfo.Packages {
|
||||
curentPackages = append(curentPackages, p.ToStringCurrentVersion())
|
||||
}
|
||||
for _, cpename := range cveInfo.CpeNames {
|
||||
curentPackages = append(curentPackages, cpename.Name)
|
||||
}
|
||||
|
||||
newPackages := []string{}
|
||||
for _, p := range cveInfo.Packages {
|
||||
newPackages = append(newPackages, p.ToStringNewVersion())
|
||||
}
|
||||
|
||||
a := attachment{
|
||||
Title: cveID,
|
||||
TitleLink: fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID),
|
||||
Text: attachmentText(cveInfo, scanResult.Family),
|
||||
MrkdwnIn: []string{"text", "pretext"},
|
||||
Fields: []*field{
|
||||
{
|
||||
// Title: "Current Package/CPE",
|
||||
Title: "Installed",
|
||||
Value: strings.Join(curentPackages, "\n"),
|
||||
Short: true,
|
||||
},
|
||||
{
|
||||
Title: "Candidate",
|
||||
Value: strings.Join(newPackages, "\n"),
|
||||
Short: true,
|
||||
},
|
||||
},
|
||||
Color: color(cveInfo.CveDetail.CvssScore(config.Conf.Lang)),
|
||||
}
|
||||
attaches = append(attaches, &a)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// https://api.slack.com/docs/attachments
|
||||
func color(cvssScore float64) string {
|
||||
switch {
|
||||
case 7 <= cvssScore:
|
||||
return "danger"
|
||||
case 4 <= cvssScore && cvssScore < 7:
|
||||
return "warning"
|
||||
case cvssScore < 0:
|
||||
return "#C0C0C0"
|
||||
default:
|
||||
return "good"
|
||||
}
|
||||
}
|
||||
|
||||
func attachmentText(cveInfo models.CveInfo, osFamily string) string {
|
||||
|
||||
linkText := links(cveInfo, osFamily)
|
||||
|
||||
switch {
|
||||
case config.Conf.Lang == "ja" &&
|
||||
cveInfo.CveDetail.Jvn.ID != 0 &&
|
||||
0 < cveInfo.CveDetail.CvssScore("ja"):
|
||||
|
||||
jvn := cveInfo.CveDetail.Jvn
|
||||
return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
|
||||
cveInfo.CveDetail.CvssScore(config.Conf.Lang),
|
||||
jvn.Severity,
|
||||
fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, jvn.Vector),
|
||||
jvn.Vector,
|
||||
jvn.Title,
|
||||
linkText,
|
||||
)
|
||||
|
||||
case 0 < cveInfo.CveDetail.CvssScore("en"):
|
||||
nvd := cveInfo.CveDetail.Nvd
|
||||
return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
|
||||
cveInfo.CveDetail.CvssScore(config.Conf.Lang),
|
||||
nvd.Severity(),
|
||||
fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, nvd.CvssVector()),
|
||||
nvd.CvssVector(),
|
||||
nvd.Summary,
|
||||
linkText,
|
||||
)
|
||||
default:
|
||||
nvd := cveInfo.CveDetail.Nvd
|
||||
return fmt.Sprintf("?\n%s\n%s", nvd.Summary, linkText)
|
||||
}
|
||||
}
|
||||
|
||||
func links(cveInfo models.CveInfo, osFamily string) string {
|
||||
links := []string{}
|
||||
cveID := cveInfo.CveDetail.CveID
|
||||
if config.Conf.Lang == "ja" && 0 < len(cveInfo.CveDetail.Jvn.Link()) {
|
||||
jvn := fmt.Sprintf("<%s|JVN>", cveInfo.CveDetail.Jvn.Link())
|
||||
links = append(links, jvn)
|
||||
}
|
||||
links = append(links, fmt.Sprintf("<%s|CVEDetails>",
|
||||
fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID)))
|
||||
links = append(links, fmt.Sprintf("<%s|MITRE>",
|
||||
fmt.Sprintf("%s%s", mitreBaseURL, cveID)))
|
||||
|
||||
dlinks := distroLinks(cveInfo, osFamily)
|
||||
for _, link := range dlinks {
|
||||
links = append(links,
|
||||
fmt.Sprintf("<%s|%s>", link.url, link.title))
|
||||
}
|
||||
|
||||
return strings.Join(links, " / ")
|
||||
}
|
||||
|
||||
// See testcase
|
||||
func getNotifyUsers(notifyUsers []string) string {
|
||||
slackStyleTexts := []string{}
|
||||
for _, username := range notifyUsers {
|
||||
slackStyleTexts = append(slackStyleTexts, fmt.Sprintf("<%s>", username))
|
||||
}
|
||||
return strings.Join(slackStyleTexts, " ")
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package report
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGetNotifyUsers(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
[]string{"@user1", "@user2"},
|
||||
"<@user1> <@user2>",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
actual := getNotifyUsers(tt.in)
|
||||
if tt.expected != actual {
|
||||
t.Errorf("expected %s, actual %s", tt.expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
// TextWriter write to stdout
|
||||
type TextWriter struct{}
|
||||
|
||||
func (w TextWriter) Write(scanResults []models.ScanResult) error {
|
||||
for _, s := range scanResults {
|
||||
text, err := toPlainText(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(text)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
772
report/tui.go
@@ -1,772 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/db"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/google/subcommands"
|
||||
"github.com/gosuri/uitable"
|
||||
"github.com/jroimartin/gocui"
|
||||
cve "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
)
|
||||
|
||||
var scanHistory models.ScanHistory
|
||||
var currentScanResult models.ScanResult
|
||||
var currentCveInfo int
|
||||
var currentDetailLimitY int
|
||||
|
||||
// RunTui execute main logic
|
||||
func RunTui() subcommands.ExitStatus {
|
||||
var err error
|
||||
scanHistory, err = latestScanHistory()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
g := gocui.NewGui()
|
||||
if err := g.Init(); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
if err := keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
g.SelBgColor = gocui.ColorGreen
|
||||
g.SelFgColor = gocui.ColorBlack
|
||||
g.Cursor = true
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
func latestScanHistory() (latest models.ScanHistory, err error) {
|
||||
if err := db.OpenDB(); err != nil {
|
||||
return latest, fmt.Errorf(
|
||||
"Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
|
||||
}
|
||||
latest, err = db.SelectLatestScanHistory()
|
||||
return
|
||||
}
|
||||
|
||||
func keybindings(g *gocui.Gui) (err error) {
|
||||
errs := []error{}
|
||||
|
||||
// Move beetween views
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyTab, gocui.ModNone, nextView))
|
||||
// errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlH, gocui.ModNone, previousView))
|
||||
// errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlL, gocui.ModNone, nextView))
|
||||
// errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowRight, gocui.ModAlt, nextView))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeySpace, gocui.ModNone, cursorPageDown))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlN, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlP, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, nextView))
|
||||
|
||||
// errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
|
||||
// errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, showMsg))
|
||||
|
||||
// summary
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyTab, gocui.ModNone, nextView))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlQ, gocui.ModNone, previousView))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlH, gocui.ModNone, previousView))
|
||||
// errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlL, gocui.ModNone, nextView))
|
||||
// errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowLeft, gocui.ModAlt, previousView))
|
||||
// errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModAlt, nextView))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeySpace, gocui.ModNone, cursorPageDown))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
|
||||
// errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyEnter, gocui.ModNone, nextView))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
|
||||
|
||||
// detail
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyTab, gocui.ModNone, nextView))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlQ, gocui.ModNone, previousView))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlH, gocui.ModNone, nextView))
|
||||
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlL, gocui.ModNone, nextView))
|
||||
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModAlt, previousView))
|
||||
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowLeft, gocui.ModAlt, nextView))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeySpace, gocui.ModNone, cursorPageDown))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
|
||||
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, nextView))
|
||||
|
||||
// errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
|
||||
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, showMsg))
|
||||
|
||||
//TODO Help Ctrl-h
|
||||
|
||||
errs = append(errs, g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit))
|
||||
// errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, getLine))
|
||||
// errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
|
||||
|
||||
for _, e := range errs {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nextView(g *gocui.Gui, v *gocui.View) error {
|
||||
if v == nil {
|
||||
return g.SetCurrentView("side")
|
||||
}
|
||||
switch v.Name() {
|
||||
case "side":
|
||||
return g.SetCurrentView("summary")
|
||||
case "summary":
|
||||
return g.SetCurrentView("detail")
|
||||
case "detail":
|
||||
return g.SetCurrentView("side")
|
||||
default:
|
||||
return g.SetCurrentView("summary")
|
||||
}
|
||||
}
|
||||
|
||||
func previousView(g *gocui.Gui, v *gocui.View) error {
|
||||
if v == nil {
|
||||
return g.SetCurrentView("side")
|
||||
}
|
||||
switch v.Name() {
|
||||
case "side":
|
||||
return g.SetCurrentView("side")
|
||||
case "summary":
|
||||
return g.SetCurrentView("side")
|
||||
case "detail":
|
||||
return g.SetCurrentView("summary")
|
||||
default:
|
||||
return g.SetCurrentView("side")
|
||||
}
|
||||
}
|
||||
|
||||
func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
|
||||
switch v.Name() {
|
||||
case "side":
|
||||
yLimit = len(scanHistory.ScanResults) - 1
|
||||
if yLimit < nextY {
|
||||
return false, yLimit
|
||||
}
|
||||
return true, yLimit
|
||||
case "summary":
|
||||
yLimit = len(currentScanResult.KnownCves) - 1
|
||||
if yLimit < nextY {
|
||||
return false, yLimit
|
||||
}
|
||||
return true, yLimit
|
||||
case "detail":
|
||||
if currentDetailLimitY < nextY {
|
||||
return false, currentDetailLimitY
|
||||
}
|
||||
return true, currentDetailLimitY
|
||||
default:
|
||||
return true, 0
|
||||
}
|
||||
}
|
||||
|
||||
func pageUpDownJumpCount(v *gocui.View) int {
|
||||
var jump int
|
||||
switch v.Name() {
|
||||
case "side", "summary":
|
||||
jump = 8
|
||||
case "detail":
|
||||
jump = 30
|
||||
default:
|
||||
jump = 8
|
||||
}
|
||||
return jump
|
||||
}
|
||||
|
||||
// redraw views
|
||||
func onMovingCursorRedrawView(g *gocui.Gui, v *gocui.View) error {
|
||||
switch v.Name() {
|
||||
case "summary":
|
||||
if err := redrawDetail(g); err != nil {
|
||||
return err
|
||||
}
|
||||
case "side":
|
||||
if err := changeHost(g, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorDown(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
cx, cy := v.Cursor()
|
||||
ox, oy := v.Origin()
|
||||
// ok, := movable(v, oy+cy+1)
|
||||
// _, maxY := v.Size()
|
||||
ok, _ := movable(v, oy+cy+1)
|
||||
// log.Info(cy, oy, maxY, yLimit)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if err := v.SetCursor(cx, cy+1); err != nil {
|
||||
if err := v.SetOrigin(ox, oy+1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorMoveTop(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
cx, _ := v.Cursor()
|
||||
v.SetCursor(cx, 0)
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorMoveBottom(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
_, maxY := v.Size()
|
||||
cx, _ := v.Cursor()
|
||||
v.SetCursor(cx, maxY-1)
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorMoveMiddle(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
_, maxY := v.Size()
|
||||
cx, _ := v.Cursor()
|
||||
v.SetCursor(cx, maxY/2)
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorPageDown(g *gocui.Gui, v *gocui.View) error {
|
||||
jump := pageUpDownJumpCount(v)
|
||||
|
||||
if v != nil {
|
||||
cx, cy := v.Cursor()
|
||||
ox, oy := v.Origin()
|
||||
ok, yLimit := movable(v, oy+cy+jump)
|
||||
_, maxY := v.Size()
|
||||
|
||||
if !ok {
|
||||
if yLimit < maxY {
|
||||
v.SetCursor(cx, yLimit)
|
||||
} else {
|
||||
v.SetCursor(cx, maxY-1)
|
||||
v.SetOrigin(ox, yLimit-maxY+1)
|
||||
}
|
||||
} else if yLimit < oy+jump+maxY {
|
||||
if yLimit < maxY {
|
||||
v.SetCursor(cx, yLimit)
|
||||
} else {
|
||||
v.SetOrigin(ox, yLimit-maxY+1)
|
||||
v.SetCursor(cx, maxY-1)
|
||||
}
|
||||
} else {
|
||||
v.SetCursor(cx, cy)
|
||||
v.SetOrigin(ox, oy+jump)
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorUp(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
ox, oy := v.Origin()
|
||||
cx, cy := v.Cursor()
|
||||
if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
|
||||
if err := v.SetOrigin(ox, oy-1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorPageUp(g *gocui.Gui, v *gocui.View) error {
|
||||
jump := pageUpDownJumpCount(v)
|
||||
if v != nil {
|
||||
cx, _ := v.Cursor()
|
||||
ox, oy := v.Origin()
|
||||
if err := v.SetOrigin(ox, oy-jump); err != nil {
|
||||
v.SetOrigin(ox, 0)
|
||||
v.SetCursor(cx, 0)
|
||||
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func previousSummary(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
// cursor to summary
|
||||
if err := g.SetCurrentView("summary"); err != nil {
|
||||
return err
|
||||
}
|
||||
// move next line
|
||||
if err := cursorUp(g, g.CurrentView()); err != nil {
|
||||
return err
|
||||
}
|
||||
// cursor to detail
|
||||
if err := g.SetCurrentView("detail"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nextSummary(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
// cursor to summary
|
||||
if err := g.SetCurrentView("summary"); err != nil {
|
||||
return err
|
||||
}
|
||||
// move next line
|
||||
if err := cursorDown(g, g.CurrentView()); err != nil {
|
||||
return err
|
||||
}
|
||||
// cursor to detail
|
||||
if err := g.SetCurrentView("detail"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func changeHost(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
if err := g.DeleteView("summary"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.DeleteView("detail"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, cy := v.Cursor()
|
||||
l, err := v.Line(cy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serverName := strings.TrimSpace(l)
|
||||
|
||||
for _, r := range scanHistory.ScanResults {
|
||||
if serverName == r.ServerName {
|
||||
currentScanResult = r
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := setSummaryLayout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setDetailLayout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func redrawDetail(g *gocui.Gui) error {
|
||||
if err := g.DeleteView("detail"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setDetailLayout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getLine(g *gocui.Gui, v *gocui.View) error {
|
||||
var l string
|
||||
var err error
|
||||
|
||||
_, cy := v.Cursor()
|
||||
if l, err = v.Line(cy); err != nil {
|
||||
l = ""
|
||||
}
|
||||
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, l)
|
||||
if err := g.SetCurrentView("msg"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func showMsg(g *gocui.Gui, v *gocui.View) error {
|
||||
jump := 8
|
||||
_, cy := v.Cursor()
|
||||
_, oy := v.Origin()
|
||||
ok, yLimit := movable(v, oy+cy+jump)
|
||||
// maxX, maxY := v.Size()
|
||||
_, maxY := v.Size()
|
||||
|
||||
l := fmt.Sprintf("cy: %d, oy: %d, maxY: %d, yLimit: %d, curCve %d, ok: %v", cy, oy, maxY, yLimit, currentCveInfo, ok)
|
||||
// if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
|
||||
if v, err := g.SetView("msg", 10, maxY/2, 10+50, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, l)
|
||||
if err := g.SetCurrentView("msg"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func delMsg(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := g.DeleteView("msg"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetCurrentView("summary"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
if err := setSideLayout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setSummaryLayout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setDetailLayout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSideLayout(g *gocui.Gui) error {
|
||||
_, maxY := g.Size()
|
||||
if v, err := g.SetView("side", -1, -1, 30, maxY); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Highlight = true
|
||||
|
||||
for _, result := range scanHistory.ScanResults {
|
||||
fmt.Fprintln(v, result.ServerName)
|
||||
}
|
||||
currentScanResult = scanHistory.ScanResults[0]
|
||||
if err := g.SetCurrentView("side"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSummaryLayout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("summary", 30, -1, maxX, int(float64(maxY)*0.2)); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
|
||||
lines := summaryLines(currentScanResult)
|
||||
fmt.Fprintf(v, lines)
|
||||
|
||||
v.Highlight = true
|
||||
v.Editable = false
|
||||
v.Wrap = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func summaryLines(data models.ScanResult) string {
|
||||
stable := uitable.New()
|
||||
stable.MaxColWidth = 1000
|
||||
stable.Wrap = false
|
||||
|
||||
indexFormat := ""
|
||||
if len(data.KnownCves) < 10 {
|
||||
indexFormat = "[%1d]"
|
||||
} else if len(data.KnownCves) < 100 {
|
||||
indexFormat = "[%2d]"
|
||||
} else {
|
||||
indexFormat = "[%3d]"
|
||||
}
|
||||
|
||||
for i, d := range data.KnownCves {
|
||||
var cols []string
|
||||
// packs := []string{}
|
||||
// for _, pack := range d.Packages {
|
||||
// packs = append(packs, pack.Name)
|
||||
// }
|
||||
if config.Conf.Lang == "ja" && 0 < d.CveDetail.Jvn.CvssScore() {
|
||||
summary := d.CveDetail.Jvn.Title
|
||||
cols = []string{
|
||||
fmt.Sprintf(indexFormat, i+1),
|
||||
d.CveDetail.CveID,
|
||||
fmt.Sprintf("| %-4.1f(%s)",
|
||||
d.CveDetail.CvssScore(config.Conf.Lang),
|
||||
d.CveDetail.Jvn.Severity,
|
||||
),
|
||||
// strings.Join(packs, ","),
|
||||
summary,
|
||||
}
|
||||
} else {
|
||||
summary := d.CveDetail.Nvd.Summary
|
||||
|
||||
var cvssScore string
|
||||
if d.CveDetail.CvssScore("en") <= 0 {
|
||||
cvssScore = "| ?"
|
||||
} else {
|
||||
cvssScore = fmt.Sprintf("| %-4.1f(%s)",
|
||||
d.CveDetail.CvssScore(config.Conf.Lang),
|
||||
d.CveDetail.Nvd.Severity(),
|
||||
)
|
||||
}
|
||||
|
||||
cols = []string{
|
||||
fmt.Sprintf(indexFormat, i+1),
|
||||
d.CveDetail.CveID,
|
||||
cvssScore,
|
||||
summary,
|
||||
}
|
||||
}
|
||||
|
||||
icols := make([]interface{}, len(cols))
|
||||
for j := range cols {
|
||||
icols[j] = cols[j]
|
||||
}
|
||||
stable.AddRow(icols...)
|
||||
}
|
||||
return fmt.Sprintf("%s", stable)
|
||||
}
|
||||
|
||||
func setDetailLayout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
|
||||
summaryView, err := g.View("summary")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, cy := summaryView.Cursor()
|
||||
_, oy := summaryView.Origin()
|
||||
currentCveInfo = cy + oy
|
||||
|
||||
if v, err := g.SetView("detail", 30, int(float64(maxY)*0.2), maxX, maxY); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
// text := report.ToPlainTextDetailsLangEn(
|
||||
// currentScanResult.KnownCves[currentCveInfo],
|
||||
// currentScanResult.Family)
|
||||
|
||||
text, err := detailLines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(v, text)
|
||||
v.Editable = false
|
||||
v.Wrap = true
|
||||
|
||||
currentDetailLimitY = len(strings.Split(text, "\n")) - 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type dataForTmpl struct {
|
||||
CveID string
|
||||
CvssScore string
|
||||
CvssVector string
|
||||
CvssSeverity string
|
||||
Summary string
|
||||
VulnSiteLinks []string
|
||||
References []cve.Reference
|
||||
Packages []string
|
||||
CpeNames []models.CpeName
|
||||
PublishedDate time.Time
|
||||
LastModifiedDate time.Time
|
||||
}
|
||||
|
||||
func detailLines() (string, error) {
|
||||
if len(currentScanResult.KnownCves) == 0 {
|
||||
return "No vulnerable packages", nil
|
||||
}
|
||||
|
||||
cveInfo := currentScanResult.KnownCves[currentCveInfo]
|
||||
cveID := cveInfo.CveDetail.CveID
|
||||
|
||||
tmpl, err := template.New("detail").Parse(detailTemplate())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var cvssSeverity, cvssVector, summary string
|
||||
var refs []cve.Reference
|
||||
switch {
|
||||
case config.Conf.Lang == "ja" &&
|
||||
0 < cveInfo.CveDetail.Jvn.CvssScore():
|
||||
jvn := cveInfo.CveDetail.Jvn
|
||||
cvssSeverity = jvn.Severity
|
||||
cvssVector = jvn.Vector
|
||||
summary = fmt.Sprintf("%s\n%s", jvn.Title, jvn.Summary)
|
||||
refs = jvn.References
|
||||
default:
|
||||
nvd := cveInfo.CveDetail.Nvd
|
||||
cvssSeverity = nvd.Severity()
|
||||
cvssVector = nvd.CvssVector()
|
||||
summary = nvd.Summary
|
||||
refs = nvd.References
|
||||
}
|
||||
|
||||
links := []string{
|
||||
fmt.Sprintf("[NVD]( %s )", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID)),
|
||||
fmt.Sprintf("[MITRE]( %s )", fmt.Sprintf("%s%s", mitreBaseURL, cveID)),
|
||||
fmt.Sprintf("[CveDetais]( %s )", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID)),
|
||||
fmt.Sprintf("[CVSSv2 Caluclator]( %s )", fmt.Sprintf(cvssV2CalcURLTemplate, cveID, cvssVector)),
|
||||
}
|
||||
dlinks := distroLinks(cveInfo, currentScanResult.Family)
|
||||
for _, link := range dlinks {
|
||||
links = append(links, fmt.Sprintf("[%s]( %s )", link.title, link.url))
|
||||
}
|
||||
|
||||
var cvssScore string
|
||||
if cveInfo.CveDetail.CvssScore(config.Conf.Lang) == -1 {
|
||||
cvssScore = "?"
|
||||
} else {
|
||||
cvssScore = fmt.Sprintf("%4.1f", cveInfo.CveDetail.CvssScore(config.Conf.Lang))
|
||||
}
|
||||
|
||||
packages := []string{}
|
||||
for _, pack := range cveInfo.Packages {
|
||||
packages = append(packages,
|
||||
fmt.Sprintf(
|
||||
"%s -> %s",
|
||||
pack.ToStringCurrentVersion(),
|
||||
pack.ToStringNewVersion()))
|
||||
}
|
||||
|
||||
data := dataForTmpl{
|
||||
CveID: cveID,
|
||||
CvssScore: cvssScore,
|
||||
CvssSeverity: cvssSeverity,
|
||||
CvssVector: cvssVector,
|
||||
Summary: summary,
|
||||
VulnSiteLinks: links,
|
||||
References: refs,
|
||||
Packages: packages,
|
||||
CpeNames: cveInfo.CpeNames,
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil) // create empty buffer
|
||||
if err := tmpl.Execute(buf, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(buf.Bytes()), nil
|
||||
}
|
||||
|
||||
// * {{.Name}}-{{.Version}}-{{.Release}}
|
||||
|
||||
func detailTemplate() string {
|
||||
return `
|
||||
{{.CveID}}
|
||||
==============
|
||||
|
||||
CVSS Score
|
||||
--------------
|
||||
|
||||
{{.CvssScore}} ({{.CvssSeverity}}) {{.CvssVector}}
|
||||
|
||||
Summary
|
||||
--------------
|
||||
|
||||
{{.Summary }}
|
||||
|
||||
Package/CPE
|
||||
--------------
|
||||
|
||||
{{range $pack := .Packages -}}
|
||||
* {{$pack}}
|
||||
{{end -}}
|
||||
{{range .CpeNames -}}
|
||||
* {{.Name}}
|
||||
{{end}}
|
||||
Links
|
||||
--------------
|
||||
|
||||
{{range $link := .VulnSiteLinks -}}
|
||||
* {{$link}}
|
||||
{{end}}
|
||||
References
|
||||
--------------
|
||||
|
||||
{{range .References -}}
|
||||
* [{{.Source}}]( {{.Link}} )
|
||||
{{end}}
|
||||
|
||||
`
|
||||
}
|
||||
334
report/util.go
@@ -1,334 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/gosuri/uitable"
|
||||
)
|
||||
|
||||
func toPlainText(scanResult models.ScanResult) (string, error) {
|
||||
hostinfo := fmt.Sprintf(
|
||||
"%s (%s %s)",
|
||||
scanResult.ServerName,
|
||||
scanResult.Family,
|
||||
scanResult.Release,
|
||||
)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
for i := 0; i < len(hostinfo); i++ {
|
||||
buffer.WriteString("=")
|
||||
}
|
||||
header := fmt.Sprintf("%s\n%s", hostinfo, buffer.String())
|
||||
|
||||
if len(scanResult.KnownCves) == 0 && len(scanResult.UnknownCves) == 0 {
|
||||
return fmt.Sprintf(`
|
||||
%s
|
||||
No unsecure packages.
|
||||
`, header), nil
|
||||
}
|
||||
|
||||
summary := ToPlainTextSummary(scanResult)
|
||||
scoredReport, unscoredReport := []string{}, []string{}
|
||||
scoredReport, unscoredReport = toPlainTextDetails(scanResult, scanResult.Family)
|
||||
|
||||
scored := strings.Join(scoredReport, "\n\n")
|
||||
unscored := strings.Join(unscoredReport, "\n\n")
|
||||
detail := fmt.Sprintf(`
|
||||
%s
|
||||
|
||||
%s
|
||||
`,
|
||||
scored,
|
||||
unscored,
|
||||
)
|
||||
text := fmt.Sprintf("%s\n%s\n%s\n", header, summary, detail)
|
||||
|
||||
return text, nil
|
||||
}
|
||||
|
||||
// ToPlainTextSummary format summary for plain text.
|
||||
func ToPlainTextSummary(r models.ScanResult) string {
|
||||
stable := uitable.New()
|
||||
stable.MaxColWidth = 84
|
||||
stable.Wrap = true
|
||||
cves := append(r.KnownCves, r.UnknownCves...)
|
||||
for _, d := range cves {
|
||||
var scols []string
|
||||
|
||||
switch {
|
||||
case config.Conf.Lang == "ja" &&
|
||||
d.CveDetail.Jvn.ID != 0 &&
|
||||
0 < d.CveDetail.CvssScore("ja"):
|
||||
|
||||
summary := d.CveDetail.Jvn.Title
|
||||
scols = []string{
|
||||
d.CveDetail.CveID,
|
||||
fmt.Sprintf("%-4.1f (%s)",
|
||||
d.CveDetail.CvssScore(config.Conf.Lang),
|
||||
d.CveDetail.Jvn.Severity,
|
||||
),
|
||||
summary,
|
||||
}
|
||||
case 0 < d.CveDetail.CvssScore("en"):
|
||||
summary := d.CveDetail.Nvd.Summary
|
||||
scols = []string{
|
||||
d.CveDetail.CveID,
|
||||
fmt.Sprintf("%-4.1f",
|
||||
d.CveDetail.CvssScore(config.Conf.Lang),
|
||||
),
|
||||
summary,
|
||||
}
|
||||
default:
|
||||
scols = []string{
|
||||
d.CveDetail.CveID,
|
||||
"?",
|
||||
d.CveDetail.Nvd.Summary,
|
||||
}
|
||||
}
|
||||
|
||||
cols := make([]interface{}, len(scols))
|
||||
for i := range cols {
|
||||
cols[i] = scols[i]
|
||||
}
|
||||
stable.AddRow(cols...)
|
||||
}
|
||||
return fmt.Sprintf("%s", stable)
|
||||
}
|
||||
|
||||
//TODO Distro Advisory
|
||||
func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
|
||||
for _, cve := range data.KnownCves {
|
||||
switch config.Conf.Lang {
|
||||
case "en":
|
||||
if cve.CveDetail.Nvd.ID != 0 {
|
||||
scoredReport = append(
|
||||
scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
|
||||
} else {
|
||||
scoredReport = append(
|
||||
scoredReport, toPlainTextUnknownCve(cve, osFamily))
|
||||
}
|
||||
case "ja":
|
||||
if cve.CveDetail.Jvn.ID != 0 {
|
||||
scoredReport = append(
|
||||
scoredReport, toPlainTextDetailsLangJa(cve, osFamily))
|
||||
} else if cve.CveDetail.Nvd.ID != 0 {
|
||||
scoredReport = append(
|
||||
scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
|
||||
} else {
|
||||
scoredReport = append(
|
||||
scoredReport, toPlainTextUnknownCve(cve, osFamily))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, cve := range data.UnknownCves {
|
||||
unscoredReport = append(
|
||||
unscoredReport, toPlainTextUnknownCve(cve, osFamily))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
|
||||
cveID := cveInfo.CveDetail.CveID
|
||||
dtable := uitable.New()
|
||||
dtable.MaxColWidth = 100
|
||||
dtable.Wrap = true
|
||||
dtable.AddRow(cveID)
|
||||
dtable.AddRow("-------------")
|
||||
dtable.AddRow("Score", "?")
|
||||
dtable.AddRow("NVD",
|
||||
fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
|
||||
dtable.AddRow("CVE Details",
|
||||
fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
|
||||
|
||||
dlinks := distroLinks(cveInfo, osFamily)
|
||||
for _, link := range dlinks {
|
||||
dtable.AddRow(link.title, link.url)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s", dtable)
|
||||
}
|
||||
|
||||
func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
|
||||
|
||||
cveDetail := cveInfo.CveDetail
|
||||
cveID := cveDetail.CveID
|
||||
jvn := cveDetail.Jvn
|
||||
|
||||
dtable := uitable.New()
|
||||
//TODO resize
|
||||
dtable.MaxColWidth = 100
|
||||
dtable.Wrap = true
|
||||
dtable.AddRow(cveID)
|
||||
dtable.AddRow("-------------")
|
||||
if score := cveDetail.Jvn.CvssScore(); 0 < score {
|
||||
dtable.AddRow("Score",
|
||||
fmt.Sprintf("%4.1f (%s)",
|
||||
cveDetail.Jvn.CvssScore(),
|
||||
jvn.Severity,
|
||||
))
|
||||
} else {
|
||||
dtable.AddRow("Score", "?")
|
||||
}
|
||||
dtable.AddRow("Vector", jvn.Vector)
|
||||
dtable.AddRow("Title", jvn.Title)
|
||||
dtable.AddRow("Description", jvn.Summary)
|
||||
|
||||
dtable.AddRow("JVN", jvn.Link())
|
||||
dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
|
||||
dtable.AddRow("MITRE", fmt.Sprintf("%s%s", mitreBaseURL, cveID))
|
||||
dtable.AddRow("CVE Details", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
|
||||
dtable.AddRow("CVSS Claculator", cveDetail.CvssV2CalculatorLink("ja"))
|
||||
|
||||
dlinks := distroLinks(cveInfo, osFamily)
|
||||
for _, link := range dlinks {
|
||||
dtable.AddRow(link.title, link.url)
|
||||
}
|
||||
|
||||
dtable = addPackageInfos(dtable, cveInfo.Packages)
|
||||
dtable = addCpeNames(dtable, cveInfo.CpeNames)
|
||||
|
||||
return fmt.Sprintf("%s", dtable)
|
||||
}
|
||||
|
||||
func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
|
||||
cveDetail := d.CveDetail
|
||||
cveID := cveDetail.CveID
|
||||
nvd := cveDetail.Nvd
|
||||
|
||||
dtable := uitable.New()
|
||||
//TODO resize
|
||||
dtable.MaxColWidth = 100
|
||||
dtable.Wrap = true
|
||||
dtable.AddRow(cveID)
|
||||
dtable.AddRow("-------------")
|
||||
|
||||
if score := cveDetail.Nvd.CvssScore(); 0 < score {
|
||||
dtable.AddRow("Score",
|
||||
fmt.Sprintf("%4.1f (%s)",
|
||||
cveDetail.Nvd.CvssScore(),
|
||||
nvd.Severity(),
|
||||
))
|
||||
} else {
|
||||
dtable.AddRow("Score", "?")
|
||||
}
|
||||
|
||||
dtable.AddRow("Vector", nvd.CvssVector())
|
||||
dtable.AddRow("Summary", nvd.Summary)
|
||||
dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
|
||||
dtable.AddRow("MITRE", fmt.Sprintf("%s%s", mitreBaseURL, cveID))
|
||||
dtable.AddRow("CVE Details", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
|
||||
dtable.AddRow("CVSS Claculator", cveDetail.CvssV2CalculatorLink("en"))
|
||||
|
||||
links := distroLinks(d, osFamily)
|
||||
for _, link := range links {
|
||||
dtable.AddRow(link.title, link.url)
|
||||
}
|
||||
dtable = addPackageInfos(dtable, d.Packages)
|
||||
dtable = addCpeNames(dtable, d.CpeNames)
|
||||
|
||||
return fmt.Sprintf("%s\n", dtable)
|
||||
}
|
||||
|
||||
type distroLink struct {
|
||||
title string
|
||||
url string
|
||||
}
|
||||
|
||||
// addVendorSite add Vendor site of the CVE to table
|
||||
func distroLinks(cveInfo models.CveInfo, osFamily string) []distroLink {
|
||||
cveID := cveInfo.CveDetail.CveID
|
||||
switch osFamily {
|
||||
case "rhel", "centos":
|
||||
links := []distroLink{
|
||||
{
|
||||
"RHEL-CVE",
|
||||
fmt.Sprintf("%s/%s", redhatSecurityBaseURL, cveID),
|
||||
},
|
||||
}
|
||||
for _, advisory := range cveInfo.DistroAdvisories {
|
||||
aidURL := strings.Replace(advisory.AdvisoryID, ":", "-", -1)
|
||||
links = append(links, distroLink{
|
||||
// "RHEL-errata",
|
||||
advisory.AdvisoryID,
|
||||
fmt.Sprintf(redhatRHSABaseBaseURL, aidURL),
|
||||
})
|
||||
}
|
||||
return links
|
||||
case "amazon":
|
||||
links := []distroLink{
|
||||
{
|
||||
"RHEL-CVE",
|
||||
fmt.Sprintf("%s/%s", redhatSecurityBaseURL, cveID),
|
||||
},
|
||||
}
|
||||
for _, advisory := range cveInfo.DistroAdvisories {
|
||||
links = append(links, distroLink{
|
||||
// "Amazon-ALAS",
|
||||
advisory.AdvisoryID,
|
||||
fmt.Sprintf(amazonSecurityBaseURL, advisory.AdvisoryID),
|
||||
})
|
||||
}
|
||||
return links
|
||||
case "ubuntu":
|
||||
return []distroLink{
|
||||
{
|
||||
"Ubuntu-CVE",
|
||||
fmt.Sprintf("%s/%s", ubuntuSecurityBaseURL, cveID),
|
||||
},
|
||||
//TODO Ubuntu USN
|
||||
}
|
||||
case "debian":
|
||||
return []distroLink{
|
||||
{
|
||||
"Debian-CVE",
|
||||
fmt.Sprintf("%s/%s", debianTrackerBaseURL, cveID),
|
||||
},
|
||||
// TODO Debian dsa
|
||||
}
|
||||
default:
|
||||
return []distroLink{}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO
|
||||
// addPackageInfos add package information related the CVE to table
|
||||
func addPackageInfos(table *uitable.Table, packs []models.PackageInfo) *uitable.Table {
|
||||
for i, p := range packs {
|
||||
var title string
|
||||
if i == 0 {
|
||||
title = "Package/CPE"
|
||||
}
|
||||
ver := fmt.Sprintf(
|
||||
"%s -> %s", p.ToStringCurrentVersion(), p.ToStringNewVersion())
|
||||
table.AddRow(title, ver)
|
||||
}
|
||||
return table
|
||||
}
|
||||
|
||||
func addCpeNames(table *uitable.Table, names []models.CpeName) *uitable.Table {
|
||||
for _, p := range names {
|
||||
table.AddRow("CPE", fmt.Sprintf("%s", p.Name))
|
||||
}
|
||||
return table
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import "github.com/future-architect/vuls/models"
|
||||
|
||||
const (
|
||||
nvdBaseURL = "https://web.nvd.nist.gov/view/vuln/detail"
|
||||
mitreBaseURL = "https://cve.mitre.org/cgi-bin/cvename.cgi?name="
|
||||
cveDetailsBaseURL = "http://www.cvedetails.com/cve"
|
||||
cvssV2CalcURLTemplate = "https://nvd.nist.gov/cvss/v2-calculator?name=%s&vector=%s"
|
||||
|
||||
redhatSecurityBaseURL = "https://access.redhat.com/security/cve"
|
||||
redhatRHSABaseBaseURL = "https://rhn.redhat.com/errata/%s.html"
|
||||
amazonSecurityBaseURL = "https://alas.aws.amazon.com/%s.html"
|
||||
|
||||
ubuntuSecurityBaseURL = "http://people.ubuntu.com/~ubuntu-security/cve"
|
||||
debianTrackerBaseURL = "https://security-tracker.debian.org/tracker"
|
||||
)
|
||||
|
||||
// ResultWriter Interface
|
||||
type ResultWriter interface {
|
||||
Write([]models.ScanResult) error
|
||||
}
|
||||
655
scan/debian.go
@@ -1,655 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
// inherit OsTypeInterface
|
||||
type debian struct {
|
||||
linux
|
||||
}
|
||||
|
||||
// NewDebian is constructor
|
||||
func newDebian(c config.ServerInfo) *debian {
|
||||
d := &debian{}
|
||||
d.log = util.NewCustomLogger(c)
|
||||
return d
|
||||
}
|
||||
|
||||
// Ubuntu, Debian
|
||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/debian.rb
|
||||
func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface) {
|
||||
|
||||
deb = newDebian(c)
|
||||
|
||||
// set sudo option flag
|
||||
c.SudoOpt = config.SudoOption{ExecBySudo: true}
|
||||
deb.setServerInfo(c)
|
||||
|
||||
if r := sshExec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
|
||||
Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
|
||||
return false, deb
|
||||
}
|
||||
|
||||
if r := sshExec(c, "lsb_release -ir", noSudo); r.isSuccess() {
|
||||
// e.g.
|
||||
// root@fa3ec524be43:/# lsb_release -ir
|
||||
// Distributor ID: Ubuntu
|
||||
// Release: 14.04
|
||||
re, _ := regexp.Compile(
|
||||
`(?s)^Distributor ID:\s*(.+?)\n*Release:\s*(.+?)$`)
|
||||
result := re.FindStringSubmatch(trim(r.Stdout))
|
||||
|
||||
if len(result) == 0 {
|
||||
deb.setDistributionInfo("debian/ubuntu", "unknown")
|
||||
Log.Warnf(
|
||||
"Unknown Debian/Ubuntu version. lsb_release -ir: %s, Host: %s:%s",
|
||||
r.Stdout, c.Host, c.Port)
|
||||
} else {
|
||||
distro := strings.ToLower(trim(result[1]))
|
||||
deb.setDistributionInfo(distro, trim(result[2]))
|
||||
}
|
||||
return true, deb
|
||||
}
|
||||
|
||||
if r := sshExec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
|
||||
// e.g.
|
||||
// DISTRIB_ID=Ubuntu
|
||||
// DISTRIB_RELEASE=14.04
|
||||
// DISTRIB_CODENAME=trusty
|
||||
// DISTRIB_DESCRIPTION="Ubuntu 14.04.2 LTS"
|
||||
re, _ := regexp.Compile(
|
||||
`(?s)^DISTRIB_ID=(.+?)\n*DISTRIB_RELEASE=(.+?)\n.*$`)
|
||||
result := re.FindStringSubmatch(trim(r.Stdout))
|
||||
if len(result) == 0 {
|
||||
Log.Warnf(
|
||||
"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s, Host: %s:%s",
|
||||
r.Stdout, c.Host, c.Port)
|
||||
deb.setDistributionInfo("debian/ubuntu", "unknown")
|
||||
} else {
|
||||
distro := strings.ToLower(trim(result[1]))
|
||||
deb.setDistributionInfo(distro, trim(result[2]))
|
||||
}
|
||||
return true, deb
|
||||
}
|
||||
|
||||
// Debian
|
||||
cmd := "cat /etc/debian_version"
|
||||
if r := sshExec(c, cmd, noSudo); r.isSuccess() {
|
||||
deb.setDistributionInfo("debian", trim(r.Stdout))
|
||||
return true, deb
|
||||
}
|
||||
|
||||
Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
|
||||
return false, deb
|
||||
}
|
||||
|
||||
func trim(str string) string {
|
||||
return strings.TrimSpace(str)
|
||||
}
|
||||
|
||||
func (o *debian) install() error {
|
||||
|
||||
// apt-get update
|
||||
o.log.Infof("apt-get update...")
|
||||
cmd := util.PrependProxyEnv("apt-get update")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
if o.Family == "debian" {
|
||||
// install aptitude
|
||||
cmd = util.PrependProxyEnv("apt-get install --force-yes -y aptitude")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
o.log.Infof("Installed: aptitude")
|
||||
}
|
||||
|
||||
// install unattended-upgrades
|
||||
if !config.Conf.UseUnattendedUpgrades {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r := o.ssh("type unattended-upgrade", noSudo); r.isSuccess() {
|
||||
o.log.Infof(
|
||||
"Ignored: unattended-upgrade already installed")
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd = util.PrependProxyEnv(
|
||||
"apt-get install --force-yes -y unattended-upgrades")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
o.log.Infof("Installed: unattended-upgrades")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *debian) scanPackages() error {
|
||||
var err error
|
||||
var packs []models.PackageInfo
|
||||
if packs, err = o.scanInstalledPackages(); err != nil {
|
||||
o.log.Errorf("Failed to scan installed packages")
|
||||
return err
|
||||
}
|
||||
o.setPackages(packs)
|
||||
|
||||
var unsecurePacks []CvePacksInfo
|
||||
if unsecurePacks, err = o.scanUnsecurePackages(packs); err != nil {
|
||||
o.log.Errorf("Failed to scan vulnerable packages")
|
||||
return err
|
||||
}
|
||||
o.setUnsecurePackages(unsecurePacks)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error) {
|
||||
r := o.ssh("dpkg-query -W", noSudo)
|
||||
if !r.isSuccess() {
|
||||
return packs, fmt.Errorf(
|
||||
"Failed to scan packages. status: %d, stdout:%s, stderr: %s",
|
||||
r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
|
||||
// e.g.
|
||||
// curl 7.19.7-40.el6_6.4
|
||||
// openldap 2.4.39-8.el6
|
||||
lines := strings.Split(r.Stdout, "\n")
|
||||
for _, line := range lines {
|
||||
if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
|
||||
name, version, err := o.parseScanedPackagesLine(trimmed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Debian: Failed to parse package line: %s", line)
|
||||
}
|
||||
packs = append(packs, models.PackageInfo{
|
||||
Name: name,
|
||||
Version: version,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *debian) parseScanedPackagesLine(line string) (name, version string, err error) {
|
||||
re, _ := regexp.Compile(`^([^\t']+)\t(.+)$`)
|
||||
result := re.FindStringSubmatch(line)
|
||||
if len(result) == 3 {
|
||||
// remove :amd64, i386...
|
||||
name = regexp.MustCompile(":.+").ReplaceAllString(result[1], "")
|
||||
version = result[2]
|
||||
return
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("Unknown format: %s", line)
|
||||
}
|
||||
|
||||
// unattended-upgrade command need to check security upgrades).
|
||||
func (o *debian) checkRequiredPackagesInstalled() error {
|
||||
|
||||
if o.Family == "debian" {
|
||||
if r := o.ssh("test -f /usr/bin/aptitude", sudo); !r.isSuccess() {
|
||||
msg := "aptitude is not installed"
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
}
|
||||
|
||||
if !config.Conf.UseUnattendedUpgrades {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r := o.ssh("type unattended-upgrade", noSudo); !r.isSuccess() {
|
||||
msg := "unattended-upgrade is not installed"
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//TODO return whether already expired.
|
||||
func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInfo, error) {
|
||||
// cmd := prependProxyEnv(conf.HTTPProxy, "apt-get update | cat; echo 1")
|
||||
cmd := util.PrependProxyEnv("apt-get update")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr,
|
||||
)
|
||||
}
|
||||
|
||||
var upgradablePackNames []string
|
||||
var err error
|
||||
if config.Conf.UseUnattendedUpgrades {
|
||||
upgradablePackNames, err = o.GetUnsecurePackNamesUsingUnattendedUpgrades()
|
||||
if err != nil {
|
||||
return []CvePacksInfo{}, err
|
||||
}
|
||||
} else {
|
||||
upgradablePackNames, err = o.GetUpgradablePackNames()
|
||||
if err != nil {
|
||||
return []CvePacksInfo{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Convert package name to PackageInfo struct
|
||||
var unsecurePacks []models.PackageInfo
|
||||
for _, name := range upgradablePackNames {
|
||||
for _, pack := range packs {
|
||||
if pack.Name == name {
|
||||
unsecurePacks = append(unsecurePacks, pack)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsecurePacks, err = o.fillCandidateVersion(unsecurePacks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Collect CVE information of upgradable packages
|
||||
cvePacksInfos, err := o.scanPackageCveInfos(unsecurePacks)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
|
||||
}
|
||||
|
||||
return cvePacksInfos, nil
|
||||
}
|
||||
|
||||
func (o *debian) fillCandidateVersion(packs []models.PackageInfo) ([]models.PackageInfo, error) {
|
||||
reqChan := make(chan models.PackageInfo, len(packs))
|
||||
resChan := make(chan models.PackageInfo, len(packs))
|
||||
errChan := make(chan error, len(packs))
|
||||
defer close(resChan)
|
||||
defer close(errChan)
|
||||
defer close(reqChan)
|
||||
|
||||
go func() {
|
||||
for _, pack := range packs {
|
||||
reqChan <- pack
|
||||
}
|
||||
}()
|
||||
|
||||
timeout := time.After(5 * 60 * time.Second)
|
||||
concurrency := 5
|
||||
tasks := util.GenWorkers(concurrency)
|
||||
for range packs {
|
||||
tasks <- func() {
|
||||
select {
|
||||
case pack := <-reqChan:
|
||||
func(p models.PackageInfo) {
|
||||
cmd := fmt.Sprintf("apt-cache policy %s", p.Name)
|
||||
r := o.ssh(cmd, sudo)
|
||||
if !r.isSuccess() {
|
||||
errChan <- fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
return
|
||||
}
|
||||
ver, err := o.parseAptCachePolicy(r.Stdout, p.Name)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("Failed to parse %s", err)
|
||||
}
|
||||
p.NewVersion = ver.Candidate
|
||||
resChan <- p
|
||||
}(pack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := []models.PackageInfo{}
|
||||
for i := 0; i < len(packs); i++ {
|
||||
select {
|
||||
case pack := <-resChan:
|
||||
result = append(result, pack)
|
||||
o.log.Infof("(%d/%d) Upgradable: %s-%s -> %s",
|
||||
i+1, len(packs), pack.Name, pack.Version, pack.NewVersion)
|
||||
case err := <-errChan:
|
||||
return nil, err
|
||||
case <-timeout:
|
||||
return nil, fmt.Errorf("Timeout fillCandidateVersion")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (o *debian) GetUnsecurePackNamesUsingUnattendedUpgrades() (packNames []string, err error) {
|
||||
cmd := util.PrependProxyEnv("unattended-upgrades --dry-run -d 2>&1 ")
|
||||
release, err := strconv.ParseFloat(o.Release, 64)
|
||||
if err != nil {
|
||||
return packNames, fmt.Errorf(
|
||||
"OS Release Version is invalid, %s, %s", o.Family, o.Release)
|
||||
}
|
||||
switch {
|
||||
case release < 12:
|
||||
return packNames, fmt.Errorf(
|
||||
"Support expired. %s, %s", o.Family, o.Release)
|
||||
|
||||
case 12 < release && release < 14:
|
||||
cmd += `| grep 'pkgs that look like they should be upgraded:' |
|
||||
sed -e 's/pkgs that look like they should be upgraded://g'`
|
||||
|
||||
case 14 < release:
|
||||
cmd += `| grep 'Packages that will be upgraded:' |
|
||||
sed -e 's/Packages that will be upgraded://g'`
|
||||
|
||||
default:
|
||||
return packNames, fmt.Errorf(
|
||||
"Not supported yet. %s, %s", o.Family, o.Release)
|
||||
}
|
||||
|
||||
r := o.ssh(cmd, sudo)
|
||||
if r.isSuccess(0, 1) {
|
||||
packNames = strings.Split(strings.TrimSpace(r.Stdout), " ")
|
||||
return packNames, nil
|
||||
}
|
||||
|
||||
return packNames, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
|
||||
func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
|
||||
cmd := util.PrependProxyEnv("apt-get upgrade --dry-run")
|
||||
r := o.ssh(cmd, sudo)
|
||||
if r.isSuccess(0, 1) {
|
||||
return o.parseAptGetUpgrade(r.Stdout)
|
||||
}
|
||||
return packNames, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
|
||||
func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, err error) {
|
||||
startRe, _ := regexp.Compile(`The following packages will be upgraded:`)
|
||||
stopRe, _ := regexp.Compile(`^(\d+) upgraded.*`)
|
||||
startLineFound, stopLineFound := false, false
|
||||
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
if !startLineFound {
|
||||
if matche := startRe.MatchString(line); matche {
|
||||
startLineFound = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
result := stopRe.FindStringSubmatch(line)
|
||||
if len(result) == 2 {
|
||||
numUpgradablePacks, err := strconv.Atoi(result[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to scan upgradable packages number. line: %s", line)
|
||||
}
|
||||
if numUpgradablePacks != len(upgradableNames) {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to scan upgradable packages, expected: %s, detected: %d",
|
||||
result[1], len(upgradableNames))
|
||||
}
|
||||
stopLineFound = true
|
||||
o.log.Debugf("Found the stop line. line: %s", line)
|
||||
break
|
||||
}
|
||||
upgradableNames = append(upgradableNames, strings.Fields(line)...)
|
||||
}
|
||||
if !startLineFound {
|
||||
// no upgrades
|
||||
return
|
||||
}
|
||||
if !stopLineFound {
|
||||
// There are upgrades, but not found the stop line.
|
||||
return nil, fmt.Errorf("Failed to scan upgradable packages")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePacksList CvePacksList, err error) {
|
||||
|
||||
// { CVE ID: [packageInfo] }
|
||||
cvePackages := make(map[string][]models.PackageInfo)
|
||||
|
||||
type strarray []string
|
||||
resChan := make(chan struct {
|
||||
models.PackageInfo
|
||||
strarray
|
||||
}, len(unsecurePacks))
|
||||
errChan := make(chan error, len(unsecurePacks))
|
||||
reqChan := make(chan models.PackageInfo, len(unsecurePacks))
|
||||
defer close(resChan)
|
||||
defer close(errChan)
|
||||
defer close(reqChan)
|
||||
|
||||
go func() {
|
||||
for _, pack := range unsecurePacks {
|
||||
reqChan <- pack
|
||||
}
|
||||
}()
|
||||
|
||||
timeout := time.After(30 * 60 * time.Second)
|
||||
|
||||
concurrency := 10
|
||||
tasks := util.GenWorkers(concurrency)
|
||||
for range unsecurePacks {
|
||||
tasks <- func() {
|
||||
select {
|
||||
case pack := <-reqChan:
|
||||
func(p models.PackageInfo) {
|
||||
if cveIds, err := o.scanPackageCveIds(p); err != nil {
|
||||
errChan <- err
|
||||
} else {
|
||||
resChan <- struct {
|
||||
models.PackageInfo
|
||||
strarray
|
||||
}{p, cveIds}
|
||||
}
|
||||
}(pack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(unsecurePacks); i++ {
|
||||
select {
|
||||
case pair := <-resChan:
|
||||
pack := pair.PackageInfo
|
||||
cveIds := pair.strarray
|
||||
for _, cveID := range cveIds {
|
||||
cvePackages[cveID] = appendPackIfMissing(cvePackages[cveID], pack)
|
||||
}
|
||||
o.log.Infof("(%d/%d) Scanned %s-%s : %s",
|
||||
i+1, len(unsecurePacks), pair.Name, pair.PackageInfo.Version, cveIds)
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case <-timeout:
|
||||
return nil, fmt.Errorf("Timeout scanPackageCveIds")
|
||||
}
|
||||
}
|
||||
|
||||
var cveIds []string
|
||||
for k := range cvePackages {
|
||||
cveIds = append(cveIds, k)
|
||||
}
|
||||
|
||||
o.log.Debugf("%d Cves are found. cves: %v", len(cveIds), cveIds)
|
||||
|
||||
o.log.Info("Fetching CVE details...")
|
||||
cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.log.Info("Done")
|
||||
|
||||
for _, detail := range cveDetails {
|
||||
cvePacksList = append(cvePacksList, CvePacksInfo{
|
||||
CveID: detail.CveID,
|
||||
CveDetail: detail,
|
||||
Packs: cvePackages[detail.CveID],
|
||||
// CvssScore: cinfo.CvssScore(conf.Lang),
|
||||
})
|
||||
}
|
||||
sort.Sort(CvePacksList(cvePacksList))
|
||||
return
|
||||
}
|
||||
|
||||
func (o *debian) scanPackageCveIds(pack models.PackageInfo) (cveIds []string, err error) {
|
||||
cmd := ""
|
||||
switch o.Family {
|
||||
case "ubuntu":
|
||||
cmd = fmt.Sprintf(`apt-get changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
|
||||
case "debian":
|
||||
cmd = fmt.Sprintf(`aptitude changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
|
||||
}
|
||||
cmd = util.PrependProxyEnv(cmd)
|
||||
|
||||
r := o.ssh(cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
o.log.Warnf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
// Ignore this Error.
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
cveIds, err = o.getCveIDParsingChangelog(r.Stdout, pack.Name, pack.Version)
|
||||
if err != nil {
|
||||
trimUbuntu := strings.Split(pack.Version, "ubuntu")[0]
|
||||
return o.getCveIDParsingChangelog(r.Stdout, pack.Name, trimUbuntu)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *debian) getCveIDParsingChangelog(changelog string,
|
||||
packName string, versionOrLater string) (cveIDs []string, err error) {
|
||||
|
||||
cveIDs, err = o.parseChangelog(changelog, packName, versionOrLater)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ver := strings.Split(versionOrLater, "ubuntu")[0]
|
||||
cveIDs, err = o.parseChangelog(changelog, packName, ver)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
splittedByColon := strings.Split(versionOrLater, ":")
|
||||
if 1 < len(splittedByColon) {
|
||||
ver = splittedByColon[1]
|
||||
}
|
||||
cveIDs, err = o.parseChangelog(changelog, packName, ver)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO report as unable to parse changelog.
|
||||
o.log.Warn(err)
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// Collect CVE-IDs included in the changelog.
|
||||
// The version which specified in argument(versionOrLater) is excluded.
|
||||
func (o *debian) parseChangelog(changelog string,
|
||||
packName string, versionOrLater string) (cveIDs []string, err error) {
|
||||
|
||||
cveRe, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`)
|
||||
stopRe, _ := regexp.Compile(fmt.Sprintf(`\(%s\)`, regexp.QuoteMeta(versionOrLater)))
|
||||
stopLineFound := false
|
||||
lines := strings.Split(changelog, "\n")
|
||||
for _, line := range lines {
|
||||
if matche := stopRe.MatchString(line); matche {
|
||||
o.log.Debugf("Found the stop line. line: %s", line)
|
||||
stopLineFound = true
|
||||
break
|
||||
} else if matches := cveRe.FindAllString(line, -1); len(matches) > 0 {
|
||||
for _, m := range matches {
|
||||
cveIDs = util.AppendIfMissing(cveIDs, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !stopLineFound {
|
||||
return []string{}, fmt.Errorf(
|
||||
"Failed to scan CVE IDs. The version is not in changelog. name: %s, version: %s",
|
||||
packName,
|
||||
versionOrLater,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type packCandidateVer struct {
|
||||
Name string
|
||||
Installed string
|
||||
Candidate string
|
||||
}
|
||||
|
||||
// parseAptCachePolicy the stdout of parse pat-get cache policy
|
||||
func (o *debian) parseAptCachePolicy(stdout, name string) (packCandidateVer, error) {
|
||||
ver := packCandidateVer{Name: name}
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 2 {
|
||||
continue
|
||||
}
|
||||
switch fields[0] {
|
||||
case "Installed:":
|
||||
ver.Installed = fields[1]
|
||||
case "Candidate:":
|
||||
ver.Candidate = fields[1]
|
||||
return ver, nil
|
||||
default:
|
||||
// nop
|
||||
}
|
||||
}
|
||||
return ver, fmt.Errorf("Unknown Format: %s", stdout)
|
||||
}
|
||||
|
||||
func appendPackIfMissing(slice []models.PackageInfo, s models.PackageInfo) []models.PackageInfo {
|
||||
for _, ele := range slice {
|
||||
if ele.Name == s.Name &&
|
||||
ele.Version == s.Version &&
|
||||
ele.Release == s.Release {
|
||||
return slice
|
||||
}
|
||||
}
|
||||
return append(slice, s)
|
||||
}
|
||||
@@ -1,601 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/k0kubun/pp"
|
||||
)
|
||||
|
||||
func TestParseScanedPackagesLineDebian(t *testing.T) {
|
||||
|
||||
var packagetests = []struct {
|
||||
in string
|
||||
name string
|
||||
version string
|
||||
}{
|
||||
{"base-passwd 3.5.33", "base-passwd", "3.5.33"},
|
||||
{"bzip2 1.0.6-5", "bzip2", "1.0.6-5"},
|
||||
{"adduser 3.113+nmu3ubuntu3", "adduser", "3.113+nmu3ubuntu3"},
|
||||
{"bash 4.3-7ubuntu1.5", "bash", "4.3-7ubuntu1.5"},
|
||||
{"bsdutils 1:2.20.1-5.1ubuntu20.4", "bsdutils", "1:2.20.1-5.1ubuntu20.4"},
|
||||
{"ca-certificates 20141019ubuntu0.14.04.1", "ca-certificates", "20141019ubuntu0.14.04.1"},
|
||||
{"apt 1.0.1ubuntu2.8", "apt", "1.0.1ubuntu2.8"},
|
||||
}
|
||||
|
||||
d := newDebian(config.ServerInfo{})
|
||||
for _, tt := range packagetests {
|
||||
n, v, _ := d.parseScanedPackagesLine(tt.in)
|
||||
if n != tt.name {
|
||||
t.Errorf("name: expected %s, actual %s", tt.name, n)
|
||||
}
|
||||
if v != tt.version {
|
||||
t.Errorf("version: expected %s, actual %s", tt.version, v)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestgetCveIDParsingChangelog(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
in []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
// verubuntu1
|
||||
[]string{
|
||||
"systemd",
|
||||
"228-4ubuntu1",
|
||||
`systemd (229-2) unstable; urgency=medium
|
||||
systemd (229-1) unstable; urgency=medium
|
||||
systemd (228-6) unstable; urgency=medium
|
||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
|
||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
|
||||
systemd (228-5) unstable; urgency=medium
|
||||
systemd (228-4) unstable; urgency=medium
|
||||
systemd (228-3) unstable; urgency=medium
|
||||
systemd (228-2) unstable; urgency=medium
|
||||
systemd (228-1) unstable; urgency=medium
|
||||
systemd (227-3) unstable; urgency=medium
|
||||
systemd (227-2) unstable; urgency=medium
|
||||
systemd (227-1) unstable; urgency=medium`,
|
||||
},
|
||||
[]string{
|
||||
"CVE-2015-2325",
|
||||
"CVE-2015-2326",
|
||||
"CVE-2015-3210",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
// ver
|
||||
[]string{
|
||||
"libpcre3",
|
||||
"2:8.38-1ubuntu1",
|
||||
`pcre3 (2:8.38-2) unstable; urgency=low
|
||||
pcre3 (2:8.38-1) unstable; urgency=low
|
||||
pcre3 (2:8.35-8) unstable; urgency=low
|
||||
pcre3 (2:8.35-7.4) unstable; urgency=medium
|
||||
pcre3 (2:8.35-7.3) unstable; urgency=medium
|
||||
pcre3 (2:8.35-7.2) unstable; urgency=low
|
||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
|
||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
|
||||
pcre3 (2:8.35-7.1) unstable; urgency=medium
|
||||
pcre3 (2:8.35-7) unstable; urgency=medium`,
|
||||
},
|
||||
[]string{
|
||||
"CVE-2015-2325",
|
||||
"CVE-2015-2326",
|
||||
"CVE-2015-3210",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
// ver-ubuntu3
|
||||
[]string{
|
||||
"sysvinit",
|
||||
"2.88dsf-59.2ubuntu3",
|
||||
`sysvinit (2.88dsf-59.3ubuntu1) xenial; urgency=low
|
||||
sysvinit (2.88dsf-59.3) unstable; urgency=medium
|
||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
|
||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
|
||||
sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium
|
||||
sysvinit (2.88dsf-59.2ubuntu2) wily; urgency=medium
|
||||
sysvinit (2.88dsf-59.2ubuntu1) wily; urgency=medium
|
||||
CVE-2015-2321: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
sysvinit (2.88dsf-59.2) unstable; urgency=medium
|
||||
sysvinit (2.88dsf-59.1ubuntu3) wily; urgency=medium
|
||||
CVE-2015-2322: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
sysvinit (2.88dsf-59.1ubuntu2) wily; urgency=medium
|
||||
sysvinit (2.88dsf-59.1ubuntu1) wily; urgency=medium
|
||||
sysvinit (2.88dsf-59.1) unstable; urgency=medium
|
||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
sysvinit (2.88dsf-59) unstable; urgency=medium
|
||||
sysvinit (2.88dsf-58) unstable; urgency=low
|
||||
sysvinit (2.88dsf-57) unstable; urgency=low`,
|
||||
},
|
||||
[]string{
|
||||
"CVE-2015-2325",
|
||||
"CVE-2015-2326",
|
||||
"CVE-2015-3210",
|
||||
},
|
||||
},
|
||||
{
|
||||
// 1:ver-ubuntu3
|
||||
[]string{
|
||||
"bsdutils",
|
||||
"1:2.27.1-1ubuntu3",
|
||||
` util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
|
||||
util-linux (2.27.1-3) unstable; urgency=medium
|
||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
|
||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
|
||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
|
||||
util-linux (2.27.1-2) unstable; urgency=medium
|
||||
util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
|
||||
util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
|
||||
util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
|
||||
util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
|
||||
util-linux (2.27.1-1) unstable; urgency=medium
|
||||
util-linux (2.27-3ubuntu1) xenial; urgency=medium
|
||||
util-linux (2.27-3) unstable; urgency=medium
|
||||
util-linux (2.27-2) unstable; urgency=medium
|
||||
util-linux (2.27-1) unstable; urgency=medium
|
||||
util-linux (2.27~rc2-2) experimental; urgency=medium
|
||||
util-linux (2.27~rc2-1) experimental; urgency=medium
|
||||
util-linux (2.27~rc1-1) experimental; urgency=medium
|
||||
util-linux (2.26.2-9) unstable; urgency=medium
|
||||
util-linux (2.26.2-8) experimental; urgency=medium
|
||||
util-linux (2.26.2-7) experimental; urgency=medium
|
||||
util-linux (2.26.2-6ubuntu3) wily; urgency=medium
|
||||
CVE-2015-2329: heap buffer overflow in compile_branch(). (Closes: #781795)
|
||||
util-linux (2.26.2-6ubuntu2) wily; urgency=medium
|
||||
util-linux (2.26.2-6ubuntu1) wily; urgency=medium
|
||||
util-linux (2.26.2-6) unstable; urgency=medium`,
|
||||
},
|
||||
[]string{
|
||||
"CVE-2015-2325",
|
||||
"CVE-2015-2326",
|
||||
"CVE-2015-3210",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
d := newDebian(config.ServerInfo{})
|
||||
for _, tt := range tests {
|
||||
actual, _ := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], tt.in[1])
|
||||
if len(actual) != len(tt.expected) {
|
||||
t.Errorf("Len of return array are'nt same. expected %#v, actual %#v", tt.expected, actual)
|
||||
continue
|
||||
}
|
||||
for i := range tt.expected {
|
||||
if actual[i] != tt.expected[i] {
|
||||
t.Errorf("expected %s, actual %s", tt.expected[i], actual[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
_, err := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], "version number do'nt match case")
|
||||
if err != nil {
|
||||
t.Errorf("Returning error is unexpected")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUpdatablePackNames(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
expected []string
|
||||
}{
|
||||
{ // Ubuntu 12.04
|
||||
`Reading package lists... Done
|
||||
Building dependency tree
|
||||
Reading state information... Done
|
||||
The following packages will be upgraded:
|
||||
apt ca-certificates cpio dpkg e2fslibs e2fsprogs gnupg gpgv libc-bin libc6 libcomerr2 libpcre3
|
||||
libpng12-0 libss2 libssl1.0.0 libudev0 multiarch-support openssl tzdata udev upstart
|
||||
21 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
|
||||
Inst dpkg [1.16.1.2ubuntu7.5] (1.16.1.2ubuntu7.7 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf dpkg (1.16.1.2ubuntu7.7 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst upstart [1.5-0ubuntu7.2] (1.5-0ubuntu7.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libc-bin [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64]) [libc6:amd64 ]
|
||||
Conf libc-bin (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64]) [libc6:amd64 ]
|
||||
Inst libc6 [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf libc6 (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libudev0 [175-0ubuntu9.9] (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst tzdata [2015a-0ubuntu0.12.04] (2015g-0ubuntu0.12.04 Ubuntu:12.04/precise-updates [all])
|
||||
Conf tzdata (2015g-0ubuntu0.12.04 Ubuntu:12.04/precise-updates [all])
|
||||
Inst e2fslibs [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64]) [e2fsprogs:amd64 on e2fslibs:amd64] [e2fsprogs:amd64 ]
|
||||
Conf e2fslibs (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64]) [e2fsprogs:amd64 ]
|
||||
Inst e2fsprogs [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf e2fsprogs (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst gpgv [1.4.11-3ubuntu2.7] (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf gpgv (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst gnupg [1.4.11-3ubuntu2.7] (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf gnupg (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst apt [0.8.16~exp12ubuntu10.22] (0.8.16~exp12ubuntu10.26 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf apt (0.8.16~exp12ubuntu10.26 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libcomerr2 [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf libcomerr2 (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libss2 [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf libss2 (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libssl1.0.0 [1.0.1-4ubuntu5.21] (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf libssl1.0.0 (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libpcre3 [8.12-4] (8.12-4ubuntu0.1 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst libpng12-0 [1.2.46-3ubuntu4] (1.2.46-3ubuntu4.2 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst multiarch-support [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf multiarch-support (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst cpio [2.11-7ubuntu3.1] (2.11-7ubuntu3.2 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst udev [175-0ubuntu9.9] (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst openssl [1.0.1-4ubuntu5.33] (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
|
||||
Inst ca-certificates [20141019ubuntu0.12.04.1] (20160104ubuntu0.12.04.1 Ubuntu:12.04/precise-updates [all])
|
||||
Conf libudev0 (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf upstart (1.5-0ubuntu7.3 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf libpcre3 (8.12-4ubuntu0.1 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf libpng12-0 (1.2.46-3ubuntu4.2 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf cpio (2.11-7ubuntu3.2 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf udev (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf openssl (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
|
||||
Conf ca-certificates (20160104ubuntu0.12.04.1 Ubuntu:12.04/precise-updates [all])`,
|
||||
[]string{
|
||||
"apt",
|
||||
"ca-certificates",
|
||||
"cpio",
|
||||
"dpkg",
|
||||
"e2fslibs",
|
||||
"e2fsprogs",
|
||||
"gnupg",
|
||||
"gpgv",
|
||||
"libc-bin",
|
||||
"libc6",
|
||||
"libcomerr2",
|
||||
"libpcre3",
|
||||
"libpng12-0",
|
||||
"libss2",
|
||||
"libssl1.0.0",
|
||||
"libudev0",
|
||||
"multiarch-support",
|
||||
"openssl",
|
||||
"tzdata",
|
||||
"udev",
|
||||
"upstart",
|
||||
},
|
||||
},
|
||||
{ // Ubuntu 14.04
|
||||
`Reading package lists... Done
|
||||
Building dependency tree
|
||||
Reading state information... Done
|
||||
Calculating upgrade... Done
|
||||
The following packages will be upgraded:
|
||||
apt apt-utils base-files bsdutils coreutils cpio dh-python dpkg e2fslibs
|
||||
e2fsprogs gcc-4.8-base gcc-4.9-base gnupg gpgv ifupdown initscripts iproute2
|
||||
isc-dhcp-client isc-dhcp-common libapt-inst1.5 libapt-pkg4.12 libblkid1
|
||||
libc-bin libc6 libcgmanager0 libcomerr2 libdrm2 libexpat1 libffi6 libgcc1
|
||||
libgcrypt11 libgnutls-openssl27 libgnutls26 libmount1 libpcre3 libpng12-0
|
||||
libpython3.4-minimal libpython3.4-stdlib libsqlite3-0 libss2 libssl1.0.0
|
||||
libstdc++6 libtasn1-6 libudev1 libuuid1 login mount multiarch-support
|
||||
ntpdate passwd python3.4 python3.4-minimal rsyslog sudo sysv-rc
|
||||
sysvinit-utils tzdata udev util-linux
|
||||
59 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
|
||||
Inst base-files [7.2ubuntu5.2] (7.2ubuntu5.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf base-files (7.2ubuntu5.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst coreutils [8.21-1ubuntu5.1] (8.21-1ubuntu5.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf coreutils (8.21-1ubuntu5.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst dpkg [1.17.5ubuntu5.3] (1.17.5ubuntu5.5 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf dpkg (1.17.5ubuntu5.5 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libc-bin [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libc6 [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libgcc1 [1:4.9.1-0ubuntu1] (1:4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst gcc-4.9-base [4.9.1-0ubuntu1] (4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf gcc-4.9-base (4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libgcc1 (1:4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libc6 (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libc-bin (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst e2fslibs [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64]) [e2fsprogs:amd64 on e2fslibs:amd64] [e2fsprogs:amd64 ]
|
||||
Conf e2fslibs (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64]) [e2fsprogs:amd64 ]
|
||||
Inst e2fsprogs [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf e2fsprogs (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst login [1:4.1.5.1-1ubuntu9] (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf login (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst mount [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf mount (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst tzdata [2015a-0ubuntu0.14.04] (2015g-0ubuntu0.14.04 Ubuntu:14.04/trusty-updates [all])
|
||||
Conf tzdata (2015g-0ubuntu0.14.04 Ubuntu:14.04/trusty-updates [all])
|
||||
Inst sysvinit-utils [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst sysv-rc [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [all])
|
||||
Conf sysv-rc (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [all])
|
||||
Conf sysvinit-utils (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst util-linux [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf util-linux (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst gcc-4.8-base [4.8.2-19ubuntu1] (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64]) [libstdc++6:amd64 ]
|
||||
Conf gcc-4.8-base (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64]) [libstdc++6:amd64 ]
|
||||
Inst libstdc++6 [4.8.2-19ubuntu1] (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libstdc++6 (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libapt-pkg4.12 [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libapt-pkg4.12 (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst gpgv [1.4.16-1ubuntu2.1] (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf gpgv (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst gnupg [1.4.16-1ubuntu2.1] (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf gnupg (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst apt [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf apt (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst bsdutils [1:2.20.1-5.1ubuntu20.4] (1:2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf bsdutils (1:2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst passwd [1:4.1.5.1-1ubuntu9] (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf passwd (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libuuid1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libuuid1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libblkid1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libblkid1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libcomerr2 [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libcomerr2 (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libmount1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libmount1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libpcre3 [1:8.31-2ubuntu2] (1:8.31-2ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libpcre3 (1:8.31-2ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libss2 [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libss2 (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libapt-inst1.5 [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libexpat1 [2.1.0-4ubuntu1] (2.1.0-4ubuntu1.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libffi6 [3.1~rc1+r3.0.13-12] (3.1~rc1+r3.0.13-12ubuntu0.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libgcrypt11 [1.5.3-2ubuntu4.1] (1.5.3-2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libtasn1-6 [3.4-3ubuntu0.1] (3.4-3ubuntu0.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libgnutls-openssl27 [2.12.23-12ubuntu2.1] (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst libgnutls26 [2.12.23-12ubuntu2.1] (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libsqlite3-0 [3.8.2-1ubuntu2] (3.8.2-1ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst python3.4 [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst libpython3.4-stdlib [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst python3.4-minimal [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst libssl1.0.0 [1.0.1f-1ubuntu2.8] (1.0.1f-1ubuntu2.16 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst libpython3.4-minimal [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst ntpdate [1:4.2.6.p5+dfsg-3ubuntu2.14.04.2] (1:4.2.6.p5+dfsg-3ubuntu2.14.04.8 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libdrm2 [2.4.56-1~ubuntu2] (2.4.64-1~ubuntu14.04.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libpng12-0 [1.2.50-1ubuntu2] (1.2.50-1ubuntu2.14.04.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst initscripts [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst libcgmanager0 [0.24-0ubuntu7.3] (0.24-0ubuntu7.5 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst udev [204-5ubuntu20.10] (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst libudev1 [204-5ubuntu20.10] (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst multiarch-support [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf multiarch-support (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst apt-utils [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst dh-python [1.20140128-1ubuntu8] (1.20140128-1ubuntu8.2 Ubuntu:14.04/trusty-updates [all])
|
||||
Inst iproute2 [3.12.0-2] (3.12.0-2ubuntu1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst ifupdown [0.7.47.2ubuntu4.1] (0.7.47.2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst isc-dhcp-client [4.2.4-7ubuntu12] (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64]) []
|
||||
Inst isc-dhcp-common [4.2.4-7ubuntu12] (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst rsyslog [7.4.4-1ubuntu2.5] (7.4.4-1ubuntu2.6 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst sudo [1.8.9p5-1ubuntu1] (1.8.9p5-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Inst cpio [2.11+dfsg-1ubuntu1.1] (2.11+dfsg-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libapt-inst1.5 (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libexpat1 (2.1.0-4ubuntu1.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libffi6 (3.1~rc1+r3.0.13-12ubuntu0.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libgcrypt11 (1.5.3-2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libtasn1-6 (3.4-3ubuntu0.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libgnutls26 (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libgnutls-openssl27 (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libsqlite3-0 (3.8.2-1ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libssl1.0.0 (1.0.1f-1ubuntu2.16 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libpython3.4-minimal (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf python3.4-minimal (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libpython3.4-stdlib (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf python3.4 (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf ntpdate (1:4.2.6.p5+dfsg-3ubuntu2.14.04.8 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libdrm2 (2.4.64-1~ubuntu14.04.1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libpng12-0 (1.2.50-1ubuntu2.14.04.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf initscripts (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libcgmanager0 (0.24-0ubuntu7.5 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf libudev1 (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf udev (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf apt-utils (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf dh-python (1.20140128-1ubuntu8.2 Ubuntu:14.04/trusty-updates [all])
|
||||
Conf iproute2 (3.12.0-2ubuntu1 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf ifupdown (0.7.47.2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf isc-dhcp-common (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf isc-dhcp-client (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf rsyslog (7.4.4-1ubuntu2.6 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf sudo (1.8.9p5-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
Conf cpio (2.11+dfsg-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
|
||||
`,
|
||||
[]string{
|
||||
"apt",
|
||||
"apt-utils",
|
||||
"base-files",
|
||||
"bsdutils",
|
||||
"coreutils",
|
||||
"cpio",
|
||||
"dh-python",
|
||||
"dpkg",
|
||||
"e2fslibs",
|
||||
"e2fsprogs",
|
||||
"gcc-4.8-base",
|
||||
"gcc-4.9-base",
|
||||
"gnupg",
|
||||
"gpgv",
|
||||
"ifupdown",
|
||||
"initscripts",
|
||||
"iproute2",
|
||||
"isc-dhcp-client",
|
||||
"isc-dhcp-common",
|
||||
"libapt-inst1.5",
|
||||
"libapt-pkg4.12",
|
||||
"libblkid1",
|
||||
"libc-bin",
|
||||
"libc6",
|
||||
"libcgmanager0",
|
||||
"libcomerr2",
|
||||
"libdrm2",
|
||||
"libexpat1",
|
||||
"libffi6",
|
||||
"libgcc1",
|
||||
"libgcrypt11",
|
||||
"libgnutls-openssl27",
|
||||
"libgnutls26",
|
||||
"libmount1",
|
||||
"libpcre3",
|
||||
"libpng12-0",
|
||||
"libpython3.4-minimal",
|
||||
"libpython3.4-stdlib",
|
||||
"libsqlite3-0",
|
||||
"libss2",
|
||||
"libssl1.0.0",
|
||||
"libstdc++6",
|
||||
"libtasn1-6",
|
||||
"libudev1",
|
||||
"libuuid1",
|
||||
"login",
|
||||
"mount",
|
||||
"multiarch-support",
|
||||
"ntpdate",
|
||||
"passwd",
|
||||
"python3.4",
|
||||
"python3.4-minimal",
|
||||
"rsyslog",
|
||||
"sudo",
|
||||
"sysv-rc",
|
||||
"sysvinit-utils",
|
||||
"tzdata",
|
||||
"udev",
|
||||
"util-linux",
|
||||
},
|
||||
},
|
||||
{
|
||||
//Ubuntu12.04
|
||||
`Reading package lists... Done
|
||||
Building dependency tree
|
||||
Reading state information... Done
|
||||
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
//Ubuntu14.04
|
||||
`Reading package lists... Done
|
||||
Building dependency tree
|
||||
Reading state information... Done
|
||||
Calculating upgrade... Done
|
||||
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
|
||||
[]string{},
|
||||
},
|
||||
}
|
||||
|
||||
d := newDebian(config.ServerInfo{})
|
||||
for _, tt := range tests {
|
||||
actual, err := d.parseAptGetUpgrade(tt.in)
|
||||
if err != nil {
|
||||
t.Errorf("Returning error is unexpected")
|
||||
}
|
||||
if len(tt.expected) != len(actual) {
|
||||
t.Errorf("Result length is not as same as expected. expected: %d, actual: %d", len(tt.expected), len(actual))
|
||||
pp.Println(tt.expected)
|
||||
pp.Println(actual)
|
||||
return
|
||||
}
|
||||
for i := range tt.expected {
|
||||
if tt.expected[i] != actual[i] {
|
||||
t.Errorf("[%d] expected %s, actual %s", i, tt.expected[i], actual[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAptCachePolicy(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
stdout string
|
||||
name string
|
||||
expected packCandidateVer
|
||||
}{
|
||||
{
|
||||
// Ubuntu 16.04
|
||||
`openssl:
|
||||
Installed: 1.0.2f-2ubuntu1
|
||||
Candidate: 1.0.2g-1ubuntu2
|
||||
Version table:
|
||||
1.0.2g-1ubuntu2 500
|
||||
500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages
|
||||
*** 1.0.2f-2ubuntu1 100
|
||||
100 /var/lib/dpkg/status`,
|
||||
"openssl",
|
||||
packCandidateVer{
|
||||
Name: "openssl",
|
||||
Installed: "1.0.2f-2ubuntu1",
|
||||
Candidate: "1.0.2g-1ubuntu2",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Ubuntu 14.04
|
||||
`openssl:
|
||||
Installed: 1.0.1f-1ubuntu2.16
|
||||
Candidate: 1.0.1f-1ubuntu2.17
|
||||
Version table:
|
||||
1.0.1f-1ubuntu2.17 0
|
||||
500 http://archive.ubuntu.com/ubuntu/ trusty-updates/main amd64 Packages
|
||||
500 http://archive.ubuntu.com/ubuntu/ trusty-security/main amd64 Packages
|
||||
*** 1.0.1f-1ubuntu2.16 0
|
||||
100 /var/lib/dpkg/status
|
||||
1.0.1f-1ubuntu2 0
|
||||
500 http://archive.ubuntu.com/ubuntu/ trusty/main amd64 Packages`,
|
||||
"openssl",
|
||||
packCandidateVer{
|
||||
Name: "openssl",
|
||||
Installed: "1.0.1f-1ubuntu2.16",
|
||||
Candidate: "1.0.1f-1ubuntu2.17",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Ubuntu 12.04
|
||||
`openssl:
|
||||
Installed: 1.0.1-4ubuntu5.33
|
||||
Candidate: 1.0.1-4ubuntu5.34
|
||||
Version table:
|
||||
1.0.1-4ubuntu5.34 0
|
||||
500 http://archive.ubuntu.com/ubuntu/ precise-updates/main amd64 Packages
|
||||
500 http://archive.ubuntu.com/ubuntu/ precise-security/main amd64 Packages
|
||||
*** 1.0.1-4ubuntu5.33 0
|
||||
100 /var/lib/dpkg/status
|
||||
1.0.1-4ubuntu3 0
|
||||
500 http://archive.ubuntu.com/ubuntu/ precise/main amd64 Packages`,
|
||||
"openssl",
|
||||
packCandidateVer{
|
||||
Name: "openssl",
|
||||
Installed: "1.0.1-4ubuntu5.33",
|
||||
Candidate: "1.0.1-4ubuntu5.34",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
d := newDebian(config.ServerInfo{})
|
||||
for _, tt := range tests {
|
||||
actual, err := d.parseAptCachePolicy(tt.stdout, tt.name)
|
||||
if err != nil {
|
||||
t.Errorf("Error has occurred: %s, actual: %#v", err, actual)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.expected, actual) {
|
||||
e := pp.Sprintf("%v", tt.expected)
|
||||
a := pp.Sprintf("%v", actual)
|
||||
t.Errorf("expected %s, actual %s", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
134
scan/linux.go
@@ -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 scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
type linux struct {
|
||||
ServerInfo config.ServerInfo
|
||||
|
||||
Family string
|
||||
Release string
|
||||
osPackages
|
||||
log *logrus.Entry
|
||||
}
|
||||
|
||||
func (l *linux) ssh(cmd string, sudo bool) sshResult {
|
||||
return sshExec(l.ServerInfo, cmd, sudo, l.log)
|
||||
}
|
||||
|
||||
func (l *linux) setServerInfo(c config.ServerInfo) {
|
||||
l.ServerInfo = c
|
||||
}
|
||||
|
||||
func (l *linux) getServerInfo() config.ServerInfo {
|
||||
return l.ServerInfo
|
||||
}
|
||||
|
||||
func (l *linux) setDistributionInfo(fam, rel string) {
|
||||
l.Family = fam
|
||||
l.Release = rel
|
||||
}
|
||||
|
||||
func (l *linux) getDistributionInfo() string {
|
||||
return fmt.Sprintf("%s %s", l.Family, l.Release)
|
||||
}
|
||||
|
||||
func (l *linux) convertToModel() (models.ScanResult, error) {
|
||||
var cves, unknownScoreCves []models.CveInfo
|
||||
for _, p := range l.UnsecurePackages {
|
||||
if p.CveDetail.CvssScore(config.Conf.Lang) < 0 {
|
||||
unknownScoreCves = append(unknownScoreCves, models.CveInfo{
|
||||
CveDetail: p.CveDetail,
|
||||
Packages: p.Packs,
|
||||
DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
cpenames := []models.CpeName{}
|
||||
for _, cpename := range p.CpeNames {
|
||||
cpenames = append(cpenames,
|
||||
models.CpeName{Name: cpename})
|
||||
}
|
||||
|
||||
cve := models.CveInfo{
|
||||
CveDetail: p.CveDetail,
|
||||
Packages: p.Packs,
|
||||
DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
|
||||
CpeNames: cpenames,
|
||||
}
|
||||
cves = append(cves, cve)
|
||||
}
|
||||
|
||||
return models.ScanResult{
|
||||
ServerName: l.ServerInfo.ServerName,
|
||||
Family: l.Family,
|
||||
Release: l.Release,
|
||||
KnownCves: cves,
|
||||
UnknownCves: unknownScoreCves,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
|
||||
func (l *linux) scanVulnByCpeName() error {
|
||||
unsecurePacks := CvePacksList{}
|
||||
|
||||
serverInfo := l.getServerInfo()
|
||||
cpeNames := serverInfo.CpeNames
|
||||
|
||||
// remove duplicate
|
||||
set := map[string]CvePacksInfo{}
|
||||
|
||||
for _, name := range cpeNames {
|
||||
details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, detail := range details {
|
||||
if val, ok := set[detail.CveID]; ok {
|
||||
names := val.CpeNames
|
||||
names = append(names, name)
|
||||
val.CpeNames = names
|
||||
set[detail.CveID] = val
|
||||
} else {
|
||||
set[detail.CveID] = CvePacksInfo{
|
||||
CveID: detail.CveID,
|
||||
CveDetail: detail,
|
||||
CpeNames: []string{name},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key := range set {
|
||||
unsecurePacks = append(unsecurePacks, set[key])
|
||||
}
|
||||
unsecurePacks = append(unsecurePacks, l.UnsecurePackages...)
|
||||
sort.Sort(CvePacksList(unsecurePacks))
|
||||
l.setUnsecurePackages(unsecurePacks)
|
||||
return nil
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
861
scan/redhat.go
@@ -1,861 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
|
||||
"github.com/k0kubun/pp"
|
||||
)
|
||||
|
||||
// inherit OsTypeInterface
|
||||
type redhat struct {
|
||||
linux
|
||||
}
|
||||
|
||||
// NewRedhat is constructor
|
||||
func newRedhat(c config.ServerInfo) *redhat {
|
||||
r := &redhat{}
|
||||
r.log = util.NewCustomLogger(c)
|
||||
return r
|
||||
}
|
||||
|
||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/redhat.rb
|
||||
func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
|
||||
|
||||
red = newRedhat(c)
|
||||
|
||||
// set sudo option flag
|
||||
c.SudoOpt = config.SudoOption{ExecBySudo: true}
|
||||
red.setServerInfo(c)
|
||||
|
||||
if r := sshExec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
|
||||
red.setDistributionInfo("fedora", "unknown")
|
||||
Log.Warn("Fedora not tested yet. Host: %s:%s", c.Host, c.Port)
|
||||
return true, red
|
||||
}
|
||||
|
||||
if r := sshExec(c, "ls /etc/redhat-release", noSudo); r.isSuccess() {
|
||||
// https://www.rackaid.com/blog/how-to-determine-centos-or-red-hat-version/
|
||||
// e.g.
|
||||
// $ cat /etc/redhat-release
|
||||
// CentOS release 6.5 (Final)
|
||||
if r := sshExec(c, "cat /etc/redhat-release", noSudo); r.isSuccess() {
|
||||
re, _ := regexp.Compile(`(.*) release (\d[\d.]*)`)
|
||||
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
|
||||
if len(result) != 3 {
|
||||
Log.Warn(
|
||||
"Failed to parse RedHat/CentOS version. stdout: %s, Host: %s:%s",
|
||||
r.Stdout, c.Host, c.Port)
|
||||
return true, red
|
||||
}
|
||||
|
||||
release := result[2]
|
||||
switch strings.ToLower(result[1]) {
|
||||
case "centos", "centos linux":
|
||||
red.setDistributionInfo("centos", release)
|
||||
default:
|
||||
red.setDistributionInfo("rhel", release)
|
||||
}
|
||||
return true, red
|
||||
}
|
||||
return true, red
|
||||
}
|
||||
|
||||
if r := sshExec(c, "ls /etc/system-release", noSudo); r.isSuccess() {
|
||||
family := "amazon"
|
||||
release := "unknown"
|
||||
if r := sshExec(c, "cat /etc/system-release", noSudo); r.isSuccess() {
|
||||
fields := strings.Fields(r.Stdout)
|
||||
if len(fields) == 5 {
|
||||
release = fields[4]
|
||||
}
|
||||
}
|
||||
red.setDistributionInfo(family, release)
|
||||
return true, red
|
||||
}
|
||||
|
||||
Log.Debugf("Not RedHat like Linux. Host: %s:%s", c.Host, c.Port)
|
||||
return false, red
|
||||
}
|
||||
|
||||
// CentOS 5 ... yum-plugin-security, yum-changelog
|
||||
// CentOS 6 ... yum-plugin-security, yum-plugin-changelog
|
||||
// CentOS 7 ... yum-plugin-security, yum-plugin-changelog
|
||||
// RHEL, Amazon ... no additinal packages needed
|
||||
func (o *redhat) install() error {
|
||||
|
||||
switch o.Family {
|
||||
case "rhel", "amazon":
|
||||
o.log.Infof("Nothing to do")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := o.installYumPluginSecurity(); err != nil {
|
||||
return err
|
||||
}
|
||||
return o.installYumChangelog()
|
||||
}
|
||||
|
||||
func (o *redhat) installYumPluginSecurity() error {
|
||||
|
||||
if r := o.ssh("rpm -q yum-plugin-security", noSudo); r.isSuccess() {
|
||||
o.log.Infof("Ignored: yum-plugin-security already installed")
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := util.PrependProxyEnv("yum install -y yum-plugin-security")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
return fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) installYumChangelog() error {
|
||||
o.log.Info("Installing yum-plugin-security...")
|
||||
|
||||
if o.Family == "centos" {
|
||||
var majorVersion int
|
||||
if 0 < len(o.Release) {
|
||||
majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
|
||||
} else {
|
||||
return fmt.Errorf(
|
||||
"Not implemented yet. family: %s, release: %s",
|
||||
o.Family, o.Release)
|
||||
}
|
||||
|
||||
var packName = ""
|
||||
if majorVersion < 6 {
|
||||
packName = "yum-changelog"
|
||||
} else {
|
||||
packName = "yum-plugin-changelog"
|
||||
}
|
||||
|
||||
cmd := "rpm -q " + packName
|
||||
if r := o.ssh(cmd, noSudo); r.isSuccess() {
|
||||
o.log.Infof("Ignored: %s already installed", packName)
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd = util.PrependProxyEnv("yum install -y " + packName)
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
return fmt.Errorf(
|
||||
"Failed to install %s. status: %d, stdout: %s, stderr: %s",
|
||||
packName, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
o.log.Infof("Installed: %s", packName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) checkRequiredPackagesInstalled() error {
|
||||
if config.Conf.UseYumPluginSecurity {
|
||||
// check if yum-plugin-security is installed.
|
||||
// Amazon Linux, REHL can execute 'yum updateinfo --security updates' without yum-plugin-security
|
||||
cmd := "rpm -q yum-plugin-security"
|
||||
if o.Family == "centos" {
|
||||
if r := o.ssh(cmd, noSudo); !r.isSuccess() {
|
||||
msg := "yum-plugin-security is not installed"
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if o.Family == "centos" {
|
||||
var majorVersion int
|
||||
if 0 < len(o.Release) {
|
||||
majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
|
||||
} else {
|
||||
msg := fmt.Sprintf("Not implemented yet. family: %s, release: %s", o.Family, o.Release)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
var packName = ""
|
||||
if majorVersion < 6 {
|
||||
packName = "yum-changelog"
|
||||
} else {
|
||||
packName = "yum-plugin-changelog"
|
||||
}
|
||||
|
||||
cmd := "rpm -q " + packName
|
||||
if r := o.ssh(cmd, noSudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("%s is not installed", packName)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) scanPackages() error {
|
||||
var err error
|
||||
var packs []models.PackageInfo
|
||||
if packs, err = o.scanInstalledPackages(); err != nil {
|
||||
o.log.Errorf("Failed to scan installed packages")
|
||||
return err
|
||||
}
|
||||
o.setPackages(packs)
|
||||
|
||||
var unsecurePacks []CvePacksInfo
|
||||
if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
|
||||
o.log.Errorf("Failed to scan vulnerable packages")
|
||||
return err
|
||||
}
|
||||
o.setUnsecurePackages(unsecurePacks)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) scanInstalledPackages() (installedPackages models.PackageInfoList, err error) {
|
||||
cmd := "rpm -qa --queryformat '%{NAME}\t%{VERSION}\t%{RELEASE}\n'"
|
||||
r := o.ssh(cmd, noSudo)
|
||||
if r.isSuccess() {
|
||||
// e.g.
|
||||
// openssl 1.0.1e 30.el6.11
|
||||
lines := strings.Split(r.Stdout, "\n")
|
||||
for _, line := range lines {
|
||||
if trimed := strings.TrimSpace(line); len(trimed) != 0 {
|
||||
var packinfo models.PackageInfo
|
||||
if packinfo, err = o.parseScanedPackagesLine(line); err != nil {
|
||||
return
|
||||
}
|
||||
installedPackages = append(installedPackages, packinfo)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return installedPackages, fmt.Errorf(
|
||||
"Scan packages failed. status: %d, stdout: %s, stderr: %s",
|
||||
r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
|
||||
func (o *redhat) parseScanedPackagesLine(line string) (pack models.PackageInfo, err error) {
|
||||
re, _ := regexp.Compile(`^([^\t']+)\t([^\t]+)\t(.+)$`)
|
||||
result := re.FindStringSubmatch(line)
|
||||
if len(result) == 4 {
|
||||
pack.Name = result[1]
|
||||
pack.Version = result[2]
|
||||
pack.Release = strings.TrimSpace(result[3])
|
||||
} else {
|
||||
err = fmt.Errorf("redhat: Failed to parse package line: %s", line)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
|
||||
if o.Family != "centos" || config.Conf.UseYumPluginSecurity {
|
||||
// Amazon, RHEL has yum updateinfo as default
|
||||
// yum updateinfo can collenct vendor advisory information.
|
||||
return o.scanUnsecurePackagesUsingYumPluginSecurity()
|
||||
}
|
||||
// CentOS does not have security channel...
|
||||
// So, yum check-update then parse chnagelog.
|
||||
return o.scanUnsecurePackagesUsingYumCheckUpdate()
|
||||
}
|
||||
|
||||
//TODO return whether already expired.
|
||||
func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error) {
|
||||
|
||||
cmd := "yum check-update"
|
||||
r := o.ssh(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess(0, 100) {
|
||||
//returns an exit code of 100 if there are available updates.
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
|
||||
// get Updateble package name, installed, candidate version.
|
||||
packInfoList, err := o.parseYumCheckUpdateLines(r.Stdout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
|
||||
}
|
||||
o.log.Debugf("%s", pp.Sprintf("%s", packInfoList))
|
||||
|
||||
// Collect CVE-IDs in changelog
|
||||
type PackInfoCveIDs struct {
|
||||
PackInfo models.PackageInfo
|
||||
CveIDs []string
|
||||
}
|
||||
var results []PackInfoCveIDs
|
||||
for i, packInfo := range packInfoList {
|
||||
changelog, err := o.getChangelog(packInfo.Name)
|
||||
if err != nil {
|
||||
o.log.Errorf("Failed to collect CVE. err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Collect unique set of CVE-ID in each changelog
|
||||
uniqueCveIDMap := make(map[string]bool)
|
||||
lines := strings.Split(changelog, "\n")
|
||||
for _, line := range lines {
|
||||
cveIDs := o.parseYumUpdateinfoLineToGetCveIDs(line)
|
||||
for _, c := range cveIDs {
|
||||
uniqueCveIDMap[c] = true
|
||||
}
|
||||
}
|
||||
|
||||
// keys
|
||||
var cveIDs []string
|
||||
for k := range uniqueCveIDMap {
|
||||
cveIDs = append(cveIDs, k)
|
||||
}
|
||||
p := PackInfoCveIDs{
|
||||
PackInfo: packInfo,
|
||||
CveIDs: cveIDs,
|
||||
}
|
||||
results = append(results, p)
|
||||
|
||||
o.log.Infof("(%d/%d) Scanned %s-%s-%s -> %s-%s : %s",
|
||||
i+1,
|
||||
len(packInfoList),
|
||||
p.PackInfo.Name,
|
||||
p.PackInfo.Version,
|
||||
p.PackInfo.Release,
|
||||
p.PackInfo.NewVersion,
|
||||
p.PackInfo.NewRelease,
|
||||
p.CveIDs)
|
||||
}
|
||||
|
||||
// transform datastructure
|
||||
// - From
|
||||
// [
|
||||
// {
|
||||
// PackInfo: models.PackageInfo,
|
||||
// CveIDs: []string,
|
||||
// },
|
||||
// ]
|
||||
// - To
|
||||
// map {
|
||||
// CveID: []models.PackageInfo
|
||||
// }
|
||||
cveIDPackInfoMap := make(map[string][]models.PackageInfo)
|
||||
for _, res := range results {
|
||||
for _, cveID := range res.CveIDs {
|
||||
// packInfo, found := o.Packages.FindByName(res.Packname)
|
||||
// if !found {
|
||||
// return CvePacksList{}, fmt.Errorf(
|
||||
// "Faild to transform data structure: %v", res.Packname)
|
||||
// }
|
||||
cveIDPackInfoMap[cveID] = append(cveIDPackInfoMap[cveID], res.PackInfo)
|
||||
}
|
||||
}
|
||||
|
||||
var uniqueCveIDs []string
|
||||
for cveID := range cveIDPackInfoMap {
|
||||
uniqueCveIDs = append(uniqueCveIDs, cveID)
|
||||
}
|
||||
|
||||
// cveIDs => []cve.CveInfo
|
||||
o.log.Info("Fetching CVE details...")
|
||||
cveDetails, err := cveapi.CveClient.FetchCveDetails(uniqueCveIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.log.Info("Done")
|
||||
|
||||
cvePacksList := []CvePacksInfo{}
|
||||
for _, detail := range cveDetails {
|
||||
// Amazon, RHEL do not use this method, so VendorAdvisory do not set.
|
||||
cvePacksList = append(cvePacksList, CvePacksInfo{
|
||||
CveID: detail.CveID,
|
||||
CveDetail: detail,
|
||||
Packs: cveIDPackInfoMap[detail.CveID],
|
||||
// CvssScore: cinfo.CvssScore(conf.Lang),
|
||||
})
|
||||
}
|
||||
sort.Sort(CvePacksList(cvePacksList))
|
||||
return cvePacksList, nil
|
||||
}
|
||||
|
||||
// parseYumCheckUpdateLines parse yum check-update to get package name, candidate version
|
||||
func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.PackageInfoList, err error) {
|
||||
needToParse := false
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
// update information of packages begin after blank line.
|
||||
if trimed := strings.TrimSpace(line); len(trimed) == 0 {
|
||||
needToParse = true
|
||||
continue
|
||||
}
|
||||
if needToParse {
|
||||
candidate, err := o.parseYumCheckUpdateLine(line)
|
||||
if err != nil {
|
||||
return models.PackageInfoList{}, err
|
||||
}
|
||||
|
||||
installed, found := o.Packages.FindByName(candidate.Name)
|
||||
if !found {
|
||||
return models.PackageInfoList{}, fmt.Errorf(
|
||||
"Failed to parse yum check update line: %s-%s-%s",
|
||||
candidate.Name, candidate.Version, candidate.Release)
|
||||
}
|
||||
installed.NewVersion = candidate.NewVersion
|
||||
installed.NewRelease = candidate.NewRelease
|
||||
results = append(results, installed)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumCheckUpdateLine(line string) (models.PackageInfo, error) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 3 {
|
||||
return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line)
|
||||
}
|
||||
splitted := strings.Split(fields[0], ".")
|
||||
packName := ""
|
||||
if len(splitted) == 1 {
|
||||
packName = fields[0]
|
||||
} else {
|
||||
packName = strings.Join(strings.Split(fields[0], ".")[0:(len(splitted)-1)], ".")
|
||||
}
|
||||
|
||||
fields = strings.Split(fields[1], "-")
|
||||
if len(fields) != 2 {
|
||||
return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line)
|
||||
}
|
||||
version := fields[0]
|
||||
release := fields[1]
|
||||
return models.PackageInfo{
|
||||
Name: packName,
|
||||
NewVersion: version,
|
||||
NewRelease: release,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *redhat) getChangelog(packageNames string) (stdout string, err error) {
|
||||
command := "echo N | "
|
||||
if 0 < len(config.Conf.HTTPProxy) {
|
||||
command += util.ProxyEnv()
|
||||
}
|
||||
command += fmt.Sprintf(" yum update --changelog %s | grep CVE", packageNames)
|
||||
|
||||
r := o.ssh(command, sudo)
|
||||
if !r.isSuccess(0, 1) {
|
||||
return "", fmt.Errorf(
|
||||
"Failed to get changelog. status: %d, stdout: %s, stderr: %s",
|
||||
r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
return r.Stdout, nil
|
||||
}
|
||||
|
||||
type distroAdvisoryCveIDs struct {
|
||||
DistroAdvisory models.DistroAdvisory
|
||||
CveIDs []string
|
||||
}
|
||||
|
||||
// Scaning unsecure packages using yum-plugin-security.
|
||||
//TODO return whether already expired.
|
||||
func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, error) {
|
||||
if o.Family == "centos" {
|
||||
// CentOS has no security channel.
|
||||
// So use yum check-update && parse changelog
|
||||
return CvePacksList{}, fmt.Errorf(
|
||||
"yum updateinfo is not suppported on CentOS")
|
||||
}
|
||||
|
||||
cmd := "yum repolist"
|
||||
r := o.ssh(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
|
||||
// get advisoryID(RHSA, ALAS) - package name,version
|
||||
cmd = "yum updateinfo list available --security"
|
||||
r = o.ssh(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
advIDPackNamesList, err := o.parseYumUpdateinfoListAvailable(r.Stdout)
|
||||
|
||||
// get package name, version, rel to be upgrade.
|
||||
cmd = "yum check-update --security"
|
||||
r = o.ssh(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess(0, 100) {
|
||||
//returns an exit code of 100 if there are available updates.
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
vulnerablePackInfoList, err := o.parseYumCheckUpdateLines(r.Stdout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
|
||||
}
|
||||
o.log.Debugf("%s", pp.Sprintf("%v", vulnerablePackInfoList))
|
||||
for i, packInfo := range vulnerablePackInfoList {
|
||||
installedPack, found := o.Packages.FindByName(packInfo.Name)
|
||||
if !found {
|
||||
return nil, fmt.Errorf(
|
||||
"Parsed package not found. packInfo: %#v", packInfo)
|
||||
}
|
||||
vulnerablePackInfoList[i].Version = installedPack.Version
|
||||
vulnerablePackInfoList[i].Release = installedPack.Release
|
||||
}
|
||||
|
||||
dict := map[string][]models.PackageInfo{}
|
||||
for _, advIDPackNames := range advIDPackNamesList {
|
||||
packInfoList := models.PackageInfoList{}
|
||||
for _, packName := range advIDPackNames.PackNames {
|
||||
packInfo, found := vulnerablePackInfoList.FindByName(packName)
|
||||
if !found {
|
||||
return nil, fmt.Errorf(
|
||||
"PackInfo not found. packInfo: %#v", packName)
|
||||
}
|
||||
packInfoList = append(packInfoList, packInfo)
|
||||
continue
|
||||
}
|
||||
dict[advIDPackNames.AdvisoryID] = packInfoList
|
||||
}
|
||||
|
||||
// get advisoryID(RHSA, ALAS) - CVE IDs
|
||||
cmd = "yum updateinfo --security update"
|
||||
r = o.ssh(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
advisoryCveIDsList, err := o.parseYumUpdateinfo(r.Stdout)
|
||||
if err != nil {
|
||||
return CvePacksList{}, err
|
||||
}
|
||||
// pp.Println(advisoryCveIDsList)
|
||||
|
||||
// All information collected.
|
||||
// Convert to CvePacksList.
|
||||
o.log.Info("Fetching CVE details...")
|
||||
result := CvePacksList{}
|
||||
for _, advIDCveIDs := range advisoryCveIDsList {
|
||||
cveDetails, err :=
|
||||
cveapi.CveClient.FetchCveDetails(advIDCveIDs.CveIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, cveDetail := range cveDetails {
|
||||
found := false
|
||||
for i, p := range result {
|
||||
if cveDetail.CveID == p.CveID {
|
||||
advAppended := append(p.DistroAdvisories, advIDCveIDs.DistroAdvisory)
|
||||
result[i].DistroAdvisories = advAppended
|
||||
|
||||
packs := dict[advIDCveIDs.DistroAdvisory.AdvisoryID]
|
||||
result[i].Packs = append(result[i].Packs, packs...)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
cpinfo := CvePacksInfo{
|
||||
CveID: cveDetail.CveID,
|
||||
CveDetail: cveDetail,
|
||||
DistroAdvisories: []models.DistroAdvisory{advIDCveIDs.DistroAdvisory},
|
||||
Packs: dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
|
||||
}
|
||||
result = append(result, cpinfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
o.log.Info("Done")
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveIDs, err error) {
|
||||
sectionState := Outside
|
||||
lines := strings.Split(stdout, "\n")
|
||||
lines = append(lines, "=============")
|
||||
|
||||
// Amazon Linux AMI Security Information
|
||||
advisory := models.DistroAdvisory{}
|
||||
|
||||
cveIDsSetInThisSection := make(map[string]bool)
|
||||
|
||||
// use this flag to Collect CVE IDs in CVEs field.
|
||||
var inDesctiption = false
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
// find the new section pattern
|
||||
if match, _ := o.isHorizontalRule(line); match {
|
||||
|
||||
// set previous section's result to return-variable
|
||||
if sectionState == Content {
|
||||
|
||||
foundCveIDs := []string{}
|
||||
for cveID := range cveIDsSetInThisSection {
|
||||
foundCveIDs = append(foundCveIDs, cveID)
|
||||
}
|
||||
sort.Strings(foundCveIDs)
|
||||
result = append(result, distroAdvisoryCveIDs{
|
||||
DistroAdvisory: advisory,
|
||||
CveIDs: foundCveIDs,
|
||||
})
|
||||
|
||||
// reset for next section.
|
||||
cveIDsSetInThisSection = make(map[string]bool)
|
||||
inDesctiption = false
|
||||
}
|
||||
|
||||
// Go to next section
|
||||
sectionState = o.changeSectionState(sectionState)
|
||||
continue
|
||||
}
|
||||
|
||||
switch sectionState {
|
||||
case Header:
|
||||
switch o.Family {
|
||||
case "centos":
|
||||
// CentOS has no security channel.
|
||||
// So use yum check-update && parse changelog
|
||||
return result, fmt.Errorf(
|
||||
"yum updateinfo is not suppported on CentOS")
|
||||
case "rhel", "amazon":
|
||||
// nop
|
||||
}
|
||||
|
||||
case Content:
|
||||
if found := o.isDescriptionLine(line); found {
|
||||
inDesctiption = true
|
||||
}
|
||||
|
||||
// severity
|
||||
severity, found := o.parseYumUpdateinfoToGetSeverity(line)
|
||||
if found {
|
||||
advisory.Severity = severity
|
||||
}
|
||||
|
||||
// No need to parse in description except severity
|
||||
if inDesctiption {
|
||||
continue
|
||||
}
|
||||
|
||||
cveIDs := o.parseYumUpdateinfoLineToGetCveIDs(line)
|
||||
for _, cveID := range cveIDs {
|
||||
cveIDsSetInThisSection[cveID] = true
|
||||
}
|
||||
|
||||
advisoryID, found := o.parseYumUpdateinfoToGetAdvisoryID(line)
|
||||
if found {
|
||||
advisory.AdvisoryID = advisoryID
|
||||
}
|
||||
|
||||
issued, found := o.parseYumUpdateinfoLineToGetIssued(line)
|
||||
if found {
|
||||
advisory.Issued = issued
|
||||
}
|
||||
|
||||
updated, found := o.parseYumUpdateinfoLineToGetUpdated(line)
|
||||
if found {
|
||||
advisory.Updated = updated
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// state
|
||||
const (
|
||||
Outside = iota
|
||||
Header = iota
|
||||
Content = iota
|
||||
)
|
||||
|
||||
func (o *redhat) changeSectionState(state int) (newState int) {
|
||||
switch state {
|
||||
case Outside, Content:
|
||||
newState = Header
|
||||
case Header:
|
||||
newState = Content
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
||||
func (o *redhat) isHorizontalRule(line string) (bool, error) {
|
||||
return regexp.MatchString("^=+$", line)
|
||||
}
|
||||
|
||||
// see test case
|
||||
func (o *redhat) parseYumUpdateinfoHeaderCentOS(line string) (packs []models.PackageInfo, err error) {
|
||||
pkgs := strings.Split(strings.TrimSpace(line), ",")
|
||||
for _, pkg := range pkgs {
|
||||
packs = append(packs, models.PackageInfo{})
|
||||
s := strings.Split(pkg, "-")
|
||||
if len(s) == 3 {
|
||||
packs[len(packs)-1].Name = s[0]
|
||||
packs[len(packs)-1].Version = s[1]
|
||||
packs[len(packs)-1].Release = s[2]
|
||||
} else {
|
||||
return packs, fmt.Errorf("CentOS: Unknown Header format: %s", line)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoHeaderAmazon(line string) (a models.DistroAdvisory, names []string, err error) {
|
||||
re, _ := regexp.Compile(`(ALAS-.+): (.+) priority package update for (.+)$`)
|
||||
result := re.FindStringSubmatch(line)
|
||||
if len(result) == 4 {
|
||||
a.AdvisoryID = result[1]
|
||||
a.Severity = result[2]
|
||||
spaceSeparatedPacknames := result[3]
|
||||
names = strings.Fields(spaceSeparatedPacknames)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("Amazon Linux: Unknown Header Format. %s", line)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoLineToGetCveIDs(line string) []string {
|
||||
re, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`)
|
||||
return re.FindAllString(line, -1)
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoToGetAdvisoryID(line string) (advisoryID string, found bool) {
|
||||
re, _ := regexp.Compile(`^ *Update ID : (.*)$`)
|
||||
result := re.FindStringSubmatch(line)
|
||||
if len(result) != 2 {
|
||||
return "", false
|
||||
}
|
||||
return strings.TrimSpace(result[1]), true
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoLineToGetIssued(line string) (date time.Time, found bool) {
|
||||
return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Issued : (\d{4}-\d{2}-\d{2})`)
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoLineToGetUpdated(line string) (date time.Time, found bool) {
|
||||
return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Updated : (\d{4}-\d{2}-\d{2})`)
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoLineToGetDate(line, regexpFormat string) (date time.Time, found bool) {
|
||||
re, _ := regexp.Compile(regexpFormat)
|
||||
result := re.FindStringSubmatch(line)
|
||||
if len(result) != 2 {
|
||||
return date, false
|
||||
}
|
||||
t, err := time.Parse("2006-01-02", result[1])
|
||||
if err != nil {
|
||||
return date, false
|
||||
}
|
||||
return t, true
|
||||
}
|
||||
|
||||
func (o *redhat) isDescriptionLine(line string) bool {
|
||||
re, _ := regexp.Compile(`^\s*Description : `)
|
||||
return re.MatchString(line)
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoToGetSeverity(line string) (severity string, found bool) {
|
||||
re, _ := regexp.Compile(`^ *Severity : (.*)$`)
|
||||
result := re.FindStringSubmatch(line)
|
||||
if len(result) != 2 {
|
||||
return "", false
|
||||
}
|
||||
return strings.TrimSpace(result[1]), true
|
||||
}
|
||||
|
||||
type advisoryIDPacks struct {
|
||||
AdvisoryID string
|
||||
PackNames []string
|
||||
}
|
||||
|
||||
type advisoryIDPacksList []advisoryIDPacks
|
||||
|
||||
func (list advisoryIDPacksList) find(advisoryID string) (advisoryIDPacks, bool) {
|
||||
for _, a := range list {
|
||||
if a.AdvisoryID == advisoryID {
|
||||
return a, true
|
||||
}
|
||||
}
|
||||
return advisoryIDPacks{}, false
|
||||
}
|
||||
func (o *redhat) extractPackNameVerRel(nameVerRel string) (name, ver, rel string) {
|
||||
fields := strings.Split(nameVerRel, ".")
|
||||
archTrimed := strings.Join(fields[0:len(fields)-1], ".")
|
||||
|
||||
fields = strings.Split(archTrimed, "-")
|
||||
rel = fields[len(fields)-1]
|
||||
ver = fields[len(fields)-2]
|
||||
name = strings.Join(fields[0:(len(fields)-2)], "-")
|
||||
return
|
||||
}
|
||||
|
||||
// parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS), packages
|
||||
func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacksList, error) {
|
||||
|
||||
result := []advisoryIDPacks{}
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
|
||||
if !(strings.HasPrefix(line, "RHSA") ||
|
||||
strings.HasPrefix(line, "ALAS")) {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 3 {
|
||||
return []advisoryIDPacks{}, fmt.Errorf(
|
||||
"Unknown format. line: %s", line)
|
||||
}
|
||||
|
||||
// extract fields
|
||||
advisoryID := fields[0]
|
||||
packVersion := fields[2]
|
||||
packName, _, _ := o.extractPackNameVerRel(packVersion)
|
||||
|
||||
found := false
|
||||
for i, s := range result {
|
||||
if s.AdvisoryID == advisoryID {
|
||||
names := s.PackNames
|
||||
names = append(names, packName)
|
||||
result[i].PackNames = names
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
result = append(result, advisoryIDPacks{
|
||||
AdvisoryID: advisoryID,
|
||||
PackNames: []string{packName},
|
||||
})
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,843 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/k0kubun/pp"
|
||||
)
|
||||
|
||||
// func unixtimeNoerr(s string) time.Time {
|
||||
// t, _ := unixtime(s)
|
||||
// return t
|
||||
// }
|
||||
|
||||
func TestParseScanedPackagesLineRedhat(t *testing.T) {
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
|
||||
var packagetests = []struct {
|
||||
in string
|
||||
pack models.PackageInfo
|
||||
}{
|
||||
{
|
||||
"openssl 1.0.1e 30.el6.11",
|
||||
models.PackageInfo{
|
||||
Name: "openssl",
|
||||
Version: "1.0.1e",
|
||||
Release: "30.el6.11",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range packagetests {
|
||||
p, _ := r.parseScanedPackagesLine(tt.in)
|
||||
if p.Name != tt.pack.Name {
|
||||
t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
|
||||
}
|
||||
if p.Version != tt.pack.Version {
|
||||
t.Errorf("version: expected %s, actual %s", tt.pack.Version, p.Version)
|
||||
}
|
||||
if p.Release != tt.pack.Release {
|
||||
t.Errorf("release: expected %s, actual %s", tt.pack.Release, p.Release)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestChangeSectionState(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
oldState int
|
||||
newState int
|
||||
}{
|
||||
{Outside, Header},
|
||||
{Header, Content},
|
||||
{Content, Header},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if n := r.changeSectionState(tt.oldState); n != tt.newState {
|
||||
t.Errorf("expected %d, actual %d", tt.newState, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoHeader(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []models.PackageInfo
|
||||
}{
|
||||
{
|
||||
" nodejs-0.10.36-3.el6,libuv-0.10.34-1.el6,v8-3.14.5.10-17.el6 ",
|
||||
[]models.PackageInfo{
|
||||
{
|
||||
Name: "nodejs",
|
||||
Version: "0.10.36",
|
||||
Release: "3.el6",
|
||||
},
|
||||
{
|
||||
Name: "libuv",
|
||||
Version: "0.10.34",
|
||||
Release: "1.el6",
|
||||
},
|
||||
{
|
||||
Name: "v8",
|
||||
Version: "3.14.5.10",
|
||||
Release: "17.el6",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if a, err := r.parseYumUpdateinfoHeaderCentOS(tt.in); err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
} else {
|
||||
if !reflect.DeepEqual(a, tt.out) {
|
||||
e := pp.Sprintf("%#v", tt.out)
|
||||
a := pp.Sprintf("%#v", a)
|
||||
t.Errorf("expected %s, actual %s", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoLineToGetCveIDs(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []string
|
||||
}{
|
||||
{
|
||||
"Bugs : 1194651 - CVE-2015-0278 libuv:",
|
||||
[]string{"CVE-2015-0278"},
|
||||
},
|
||||
{
|
||||
": 1195457 - nodejs-0.10.35 causes undefined symbolsCVE-2015-0278, CVE-2015-0278, CVE-2015-0277",
|
||||
[]string{
|
||||
"CVE-2015-0278",
|
||||
"CVE-2015-0278",
|
||||
"CVE-2015-0277",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
act := r.parseYumUpdateinfoLineToGetCveIDs(tt.in)
|
||||
for i, s := range act {
|
||||
if s != tt.out[i] {
|
||||
t.Errorf("expected %s, actual %s", tt.out[i], s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoToGetAdvisoryID(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out string
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
"Update ID : RHSA-2015:2315",
|
||||
"RHSA-2015:2315",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Update ID : ALAS-2015-620",
|
||||
"ALAS-2015-620",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Issued : 2015-11-19 00:00:00",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
advisoryID, found := r.parseYumUpdateinfoToGetAdvisoryID(tt.in)
|
||||
if tt.out != advisoryID {
|
||||
t.Errorf("expected %s, actual %s", tt.out, advisoryID)
|
||||
}
|
||||
if tt.found != found {
|
||||
t.Errorf("expected %t, actual %t", tt.found, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoLineToGetIssued(t *testing.T) {
|
||||
|
||||
date, _ := time.Parse("2006-01-02", "2015-12-15")
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out time.Time
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
"Issued : 2015-12-15 00:00:00",
|
||||
date,
|
||||
true,
|
||||
},
|
||||
{
|
||||
" Issued : 2015-12-15 00:00:00 ",
|
||||
date,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Type : security",
|
||||
time.Time{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
d, found := r.parseYumUpdateinfoLineToGetIssued(tt.in)
|
||||
if tt.found != found {
|
||||
t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
|
||||
}
|
||||
if tt.out != d {
|
||||
t.Errorf("[%d] line: %s, expected %v, actual %v", i, tt.in, tt.out, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoLineToGetUpdated(t *testing.T) {
|
||||
|
||||
date, _ := time.Parse("2006-01-02", "2015-12-15")
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out time.Time
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
"Updated : 2015-12-15 00:00:00 Bugs : 1286966 - CVE-2015-8370 grub2: buffer overflow when checking password entered during bootup",
|
||||
date,
|
||||
true,
|
||||
},
|
||||
{
|
||||
|
||||
"Updated : 2015-12-15 14:16 CVEs : CVE-2015-7981",
|
||||
date,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Type : security",
|
||||
time.Time{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
d, found := r.parseYumUpdateinfoLineToGetUpdated(tt.in)
|
||||
if tt.found != found {
|
||||
t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
|
||||
}
|
||||
if tt.out != d {
|
||||
t.Errorf("[%d] line: %s, expected %v, actual %v", i, tt.in, tt.out, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDescriptionLine(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
"Description : Package updates are available for Amazon Linux AMI that fix the",
|
||||
true,
|
||||
},
|
||||
{
|
||||
" Description : Package updates are available for Amazon Linux AMI that fix the",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Status : final",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
found := r.isDescriptionLine(tt.in)
|
||||
if tt.found != found {
|
||||
t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoToGetSeverity(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out string
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
"Severity : Moderate",
|
||||
"Moderate",
|
||||
true,
|
||||
},
|
||||
{
|
||||
" Severity : medium",
|
||||
"medium",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Status : final",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
out, found := r.parseYumUpdateinfoToGetSeverity(tt.in)
|
||||
if tt.found != found {
|
||||
t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
|
||||
}
|
||||
if tt.out != out {
|
||||
t.Errorf("[%d] line: %s, expected %v, actual %v", i, tt.in, tt.out, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoRHEL(t *testing.T) {
|
||||
|
||||
stdout := `===============================================================================
|
||||
Important: bind security update
|
||||
===============================================================================
|
||||
Update ID : RHSA-2015:1705
|
||||
Release :
|
||||
Type : security
|
||||
Status : final
|
||||
Issued : 2015-09-03 00:00:00
|
||||
Bugs : 1259087 - CVE-2015-5722 bind: malformed DNSSEC key failed assertion denial of service
|
||||
CVEs : CVE-2015-5722
|
||||
Description : The Berkeley Internet Name Domain (BIND) is an implementation of
|
||||
: the Domain Name System (DNS) protocols. BIND
|
||||
: includes a DNS server (named); a resolver library
|
||||
: (routines for applications to use when interfacing
|
||||
: with DNS); and tools for verifying that the DNS
|
||||
: server is operating correctly.
|
||||
:
|
||||
Severity : Important
|
||||
|
||||
===============================================================================
|
||||
Important: bind security update
|
||||
===============================================================================
|
||||
Update ID : RHSA-2015:2655
|
||||
Release :
|
||||
Type : security
|
||||
Status : final
|
||||
Issued : 2015-09-03 01:00:00
|
||||
Updated : 2015-09-04 00:00:00
|
||||
Bugs : 1291176 - CVE-2015-8000 bind: responses with a malformed class attribute can trigger an assertion failure in db.c
|
||||
CVEs : CVE-2015-8000
|
||||
: CVE-2015-8001
|
||||
Description : The Berkeley Internet Name Domain (BIND) is an implementation of
|
||||
: the Domain Name System (DNS) protocols. BIND
|
||||
: includes a DNS server (named); a resolver library
|
||||
: (routines for applications to use when interfacing
|
||||
: with DNS); and tools for verifying that the DNS
|
||||
: server is operating correctly.
|
||||
:
|
||||
Severity : Low
|
||||
|
||||
===============================================================================
|
||||
Moderate: bind security update
|
||||
===============================================================================
|
||||
Update ID : RHSA-2016:0073
|
||||
Release :
|
||||
Type : security
|
||||
Status : final
|
||||
Issued : 2015-09-03 02:00:00
|
||||
Bugs : 1299364 - CVE-2015-8704 bind: specific APL data could trigger an INSIST in apl_42.c CVEs : CVE-2015-8704
|
||||
: CVE-2015-8705
|
||||
Description : The Berkeley Internet Name Domain (BIND) is an implementation of
|
||||
: the Domain Name System (DNS) protocols. BIND
|
||||
: includes a DNS server (named); a resolver library
|
||||
: (routines for applications to use when interfacing
|
||||
: with DNS); and tools for verifying that the DNS
|
||||
: server is operating correctly.
|
||||
:
|
||||
Severity : Moderate
|
||||
|
||||
`
|
||||
issued, _ := time.Parse("2006-01-02", "2015-09-03")
|
||||
updated, _ := time.Parse("2006-01-02", "2015-09-04")
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r.Family = "redhat"
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []distroAdvisoryCveIDs
|
||||
}{
|
||||
{
|
||||
stdout,
|
||||
[]distroAdvisoryCveIDs{
|
||||
{
|
||||
DistroAdvisory: models.DistroAdvisory{
|
||||
AdvisoryID: "RHSA-2015:1705",
|
||||
Severity: "Important",
|
||||
Issued: issued,
|
||||
},
|
||||
CveIDs: []string{"CVE-2015-5722"},
|
||||
},
|
||||
{
|
||||
DistroAdvisory: models.DistroAdvisory{
|
||||
AdvisoryID: "RHSA-2015:2655",
|
||||
Severity: "Low",
|
||||
Issued: issued,
|
||||
Updated: updated,
|
||||
},
|
||||
CveIDs: []string{
|
||||
"CVE-2015-8000",
|
||||
"CVE-2015-8001",
|
||||
},
|
||||
},
|
||||
{
|
||||
DistroAdvisory: models.DistroAdvisory{
|
||||
AdvisoryID: "RHSA-2016:0073",
|
||||
Severity: "Moderate",
|
||||
Issued: issued,
|
||||
Updated: updated,
|
||||
},
|
||||
CveIDs: []string{
|
||||
"CVE-2015-8704",
|
||||
"CVE-2015-8705",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
actual, _ := r.parseYumUpdateinfo(tt.in)
|
||||
for i, advisoryCveIDs := range actual {
|
||||
if !reflect.DeepEqual(tt.out[i], advisoryCveIDs) {
|
||||
e := pp.Sprintf("%v", tt.out[i])
|
||||
a := pp.Sprintf("%v", advisoryCveIDs)
|
||||
t.Errorf("[%d] Alas is not same. \nexpected: %s\nactual: %s",
|
||||
i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoAmazon(t *testing.T) {
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r.Family = "amazon"
|
||||
|
||||
issued, _ := time.Parse("2006-01-02", "2015-12-15")
|
||||
updated, _ := time.Parse("2006-01-02", "2015-12-16")
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []distroAdvisoryCveIDs
|
||||
}{
|
||||
{
|
||||
`===============================================================================
|
||||
Amazon Linux AMI 2014.03 - ALAS-2016-644: medium priority package update for python-rsa
|
||||
===============================================================================
|
||||
Update ID : ALAS-2016-644
|
||||
Release :
|
||||
Type : security
|
||||
Status : final
|
||||
Issued : 2015-12-15 13:30
|
||||
CVEs : CVE-2016-1494
|
||||
Description : Package updates are available for Amazon Linux AMI that fix the
|
||||
: following vulnerabilities: CVE-2016-1494:
|
||||
: 1295869:
|
||||
: CVE-2016-1494 python-rsa: Signature forgery using
|
||||
: Bleichenbacher'06 attack
|
||||
Severity : medium
|
||||
|
||||
===============================================================================
|
||||
Amazon Linux AMI 2014.03 - ALAS-2015-614: medium priority package update for openssl
|
||||
===============================================================================
|
||||
Update ID : ALAS-2015-614
|
||||
Release :
|
||||
Type : security
|
||||
Status : final
|
||||
Issued : 2015-12-15 10:00
|
||||
Updated : 2015-12-16 14:15 CVEs : CVE-2015-3194
|
||||
: CVE-2015-3195
|
||||
: CVE-2015-3196
|
||||
Description : Package updates are available for Amazon Linux AMI that fix the
|
||||
: following vulnerabilities: CVE-2015-3196:
|
||||
: 1288326:
|
||||
: CVE-2015-3196 OpenSSL: Race condition handling PSK
|
||||
: identify hint A race condition flaw, leading to a
|
||||
: double free, was found in the way OpenSSL handled
|
||||
: pre-shared keys (PSKs). A remote attacker could
|
||||
: use this flaw to crash a multi-threaded SSL/TLS
|
||||
: client.
|
||||
:
|
||||
Severity : medium`,
|
||||
|
||||
[]distroAdvisoryCveIDs{
|
||||
{
|
||||
DistroAdvisory: models.DistroAdvisory{
|
||||
AdvisoryID: "ALAS-2016-644",
|
||||
Severity: "medium",
|
||||
Issued: issued,
|
||||
},
|
||||
CveIDs: []string{"CVE-2016-1494"},
|
||||
},
|
||||
{
|
||||
DistroAdvisory: models.DistroAdvisory{
|
||||
AdvisoryID: "ALAS-2015-614",
|
||||
Severity: "medium",
|
||||
Issued: issued,
|
||||
Updated: updated,
|
||||
},
|
||||
CveIDs: []string{
|
||||
"CVE-2015-3194",
|
||||
"CVE-2015-3195",
|
||||
"CVE-2015-3196",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
actual, _ := r.parseYumUpdateinfo(tt.in)
|
||||
for i, advisoryCveIDs := range actual {
|
||||
if !reflect.DeepEqual(tt.out[i], advisoryCveIDs) {
|
||||
e := pp.Sprintf("%v", tt.out[i])
|
||||
a := pp.Sprintf("%v", advisoryCveIDs)
|
||||
t.Errorf("[%d] Alas is not same. expected %s, actual %s",
|
||||
i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumCheckUpdateLines(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r.Family = "centos"
|
||||
stdout := `Loaded plugins: changelog, fastestmirror, keys, protect-packages, protectbase, security
|
||||
Loading mirror speeds from cached hostfile
|
||||
* base: mirror.fairway.ne.jp
|
||||
* epel: epel.mirror.srv.co.ge
|
||||
* extras: mirror.fairway.ne.jp
|
||||
* updates: mirror.fairway.ne.jp
|
||||
0 packages excluded due to repository protections
|
||||
|
||||
audit-libs.x86_64 2.3.7-5.el6 base
|
||||
bash.x86_64 4.1.2-33.el6_7.1 updates
|
||||
`
|
||||
r.Packages = []models.PackageInfo{
|
||||
{
|
||||
Name: "audit-libs",
|
||||
Version: "2.3.6",
|
||||
Release: "4.el6",
|
||||
},
|
||||
{
|
||||
Name: "bash",
|
||||
Version: "4.1.1",
|
||||
Release: "33",
|
||||
},
|
||||
}
|
||||
var tests = []struct {
|
||||
in string
|
||||
out models.PackageInfoList
|
||||
}{
|
||||
{
|
||||
stdout,
|
||||
models.PackageInfoList{
|
||||
{
|
||||
Name: "audit-libs",
|
||||
Version: "2.3.6",
|
||||
Release: "4.el6",
|
||||
NewVersion: "2.3.7",
|
||||
NewRelease: "5.el6",
|
||||
},
|
||||
{
|
||||
Name: "bash",
|
||||
Version: "4.1.1",
|
||||
Release: "33",
|
||||
NewVersion: "4.1.2",
|
||||
NewRelease: "33.el6_7.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
packInfoList, err := r.parseYumCheckUpdateLines(tt.in)
|
||||
if err != nil {
|
||||
t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
|
||||
return
|
||||
}
|
||||
for i, ePackInfo := range tt.out {
|
||||
if !reflect.DeepEqual(ePackInfo, packInfoList[i]) {
|
||||
e := pp.Sprintf("%v", ePackInfo)
|
||||
a := pp.Sprintf("%v", packInfoList[i])
|
||||
t.Errorf("[%d] expected %s, actual %s", i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumCheckUpdateLinesAmazon(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r.Family = "amzon"
|
||||
stdout := `Loaded plugins: priorities, update-motd, upgrade-helper
|
||||
34 package(s) needed for security, out of 71 available
|
||||
|
||||
bind-libs.x86_64 32:9.8.2-0.37.rc1.45.amzn1 amzn-main
|
||||
java-1.7.0-openjdk.x86_64 1:1.7.0.95-2.6.4.0.65.amzn1 amzn-main
|
||||
if-not-architecture 100-200 amzn-main
|
||||
`
|
||||
r.Packages = []models.PackageInfo{
|
||||
{
|
||||
Name: "bind-libs",
|
||||
Version: "32:9.8.0",
|
||||
Release: "0.33.rc1.45.amzn1",
|
||||
},
|
||||
{
|
||||
Name: "java-1.7.0-openjdk",
|
||||
Version: "1:1.7.0.0",
|
||||
Release: "2.6.4.0.0.amzn1",
|
||||
},
|
||||
{
|
||||
Name: "if-not-architecture",
|
||||
Version: "10",
|
||||
Release: "20",
|
||||
},
|
||||
}
|
||||
var tests = []struct {
|
||||
in string
|
||||
out models.PackageInfoList
|
||||
}{
|
||||
{
|
||||
stdout,
|
||||
models.PackageInfoList{
|
||||
{
|
||||
Name: "bind-libs",
|
||||
Version: "32:9.8.0",
|
||||
Release: "0.33.rc1.45.amzn1",
|
||||
NewVersion: "32:9.8.2",
|
||||
NewRelease: "0.37.rc1.45.amzn1",
|
||||
},
|
||||
{
|
||||
Name: "java-1.7.0-openjdk",
|
||||
Version: "1:1.7.0.0",
|
||||
Release: "2.6.4.0.0.amzn1",
|
||||
NewVersion: "1:1.7.0.95",
|
||||
NewRelease: "2.6.4.0.65.amzn1",
|
||||
},
|
||||
{
|
||||
Name: "if-not-architecture",
|
||||
Version: "10",
|
||||
Release: "20",
|
||||
NewVersion: "100",
|
||||
NewRelease: "200",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
packInfoList, err := r.parseYumCheckUpdateLines(tt.in)
|
||||
if err != nil {
|
||||
t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
|
||||
return
|
||||
}
|
||||
for i, ePackInfo := range tt.out {
|
||||
if !reflect.DeepEqual(ePackInfo, packInfoList[i]) {
|
||||
e := pp.Sprintf("%v", ePackInfo)
|
||||
a := pp.Sprintf("%v", packInfoList[i])
|
||||
t.Errorf("[%d] expected %s, actual %s", i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoAmazonLinuxHeader(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out models.DistroAdvisory
|
||||
}{
|
||||
{
|
||||
"Amazon Linux AMI 2014.03 - ALAS-2015-598: low priority package update for grep",
|
||||
models.DistroAdvisory{
|
||||
AdvisoryID: "ALAS-2015-598",
|
||||
Severity: "low",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
a, _, _ := r.parseYumUpdateinfoHeaderAmazon(tt.in)
|
||||
if !reflect.DeepEqual(a, tt.out) {
|
||||
e := pp.Sprintf("%v", tt.out)
|
||||
a := pp.Sprintf("%v", a)
|
||||
t.Errorf("expected %s, actual %s", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoListAvailable(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
rhelStdout := `RHSA-2015:2315 Moderate/Sec. NetworkManager-1:1.0.6-27.el7.x86_64
|
||||
RHSA-2015:2315 Moderate/Sec. NetworkManager-config-server-1:1.0.6-27.el7.x86_64
|
||||
RHSA-2015:1705 Important/Sec. bind-libs-lite-32:9.9.4-18.el7_1.5.x86_64
|
||||
RHSA-2016:0176 Critical/Sec. glibc-2.17-106.el7_2.4.x86_64
|
||||
RHSA-2015:2401 Low/Sec. grub2-1:2.02-0.29.el7.x86_64
|
||||
RHSA-2015:2401 Low/Sec. grub2-tools-1:2.02-0.29.el7.x86_64
|
||||
updateinfo list done`
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []advisoryIDPacks
|
||||
}{
|
||||
{
|
||||
rhelStdout,
|
||||
[]advisoryIDPacks{
|
||||
{
|
||||
AdvisoryID: "RHSA-2015:2315",
|
||||
PackNames: []string{
|
||||
"NetworkManager",
|
||||
"NetworkManager-config-server",
|
||||
},
|
||||
},
|
||||
{
|
||||
AdvisoryID: "RHSA-2015:1705",
|
||||
PackNames: []string{
|
||||
"bind-libs-lite",
|
||||
},
|
||||
},
|
||||
{
|
||||
AdvisoryID: "RHSA-2016:0176",
|
||||
PackNames: []string{
|
||||
"glibc",
|
||||
},
|
||||
},
|
||||
{
|
||||
AdvisoryID: "RHSA-2015:2401",
|
||||
PackNames: []string{
|
||||
"grub2",
|
||||
"grub2-tools",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
actual, err := r.parseYumUpdateinfoListAvailable(tt.in)
|
||||
if err != nil {
|
||||
t.Errorf("Error has occurred: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
for i := range actual {
|
||||
if !reflect.DeepEqual(actual[i], tt.out[i]) {
|
||||
e := pp.Sprintf("%v", tt.out)
|
||||
a := pp.Sprintf("%v", actual)
|
||||
t.Errorf("[%d] expected: %s\nactual: %s", i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoToGetUpdateID(t *testing.T) {
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
|
||||
var packagetests = []struct {
|
||||
in string
|
||||
pack models.PackageInfo
|
||||
}{
|
||||
{
|
||||
"openssl 1.0.1e 30.el6.11",
|
||||
models.PackageInfo{
|
||||
Name: "openssl",
|
||||
Version: "1.0.1e",
|
||||
Release: "30.el6.11",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range packagetests {
|
||||
p, _ := r.parseScanedPackagesLine(tt.in)
|
||||
if p.Name != tt.pack.Name {
|
||||
t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
|
||||
}
|
||||
if p.Version != tt.pack.Version {
|
||||
t.Errorf("version: expected %s, actual %s", tt.pack.Version, p.Version)
|
||||
}
|
||||
if p.Release != tt.pack.Release {
|
||||
t.Errorf("release: expected %s, actual %s", tt.pack.Release, p.Release)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestExtractPackNameVerRel(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []string
|
||||
}{
|
||||
{
|
||||
"openssh-server-6.2p2-8.45.amzn1.x86_64",
|
||||
[]string{"openssh-server", "6.2p2", "8.45.amzn1"},
|
||||
},
|
||||
{
|
||||
"bind-libs-lite-32:9.9.4-29.el7_2.1.x86_64",
|
||||
[]string{"bind-libs-lite", "32:9.9.4", "29.el7_2.1"},
|
||||
},
|
||||
{
|
||||
"glibc-2.17-106.el7_2.1.x86_64",
|
||||
[]string{"glibc", "2.17", "106.el7_2.1"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
name, ver, rel := r.extractPackNameVerRel(tt.in)
|
||||
if tt.out[0] != name {
|
||||
t.Errorf("name: expected %s, actual %s", tt.out[0], name)
|
||||
}
|
||||
if tt.out[1] != ver {
|
||||
t.Errorf("ver: expected %s, actual %s", tt.out[1], ver)
|
||||
}
|
||||
if tt.out[2] != rel {
|
||||
t.Errorf("ver: expected %s, actual %s", tt.out[2], rel)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/k0kubun/pp"
|
||||
cve "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
)
|
||||
|
||||
// Log for localhsot
|
||||
var Log *logrus.Entry
|
||||
|
||||
var servers []osTypeInterface
|
||||
|
||||
// Base Interface of redhat, debian
|
||||
type osTypeInterface interface {
|
||||
setServerInfo(config.ServerInfo)
|
||||
getServerInfo() config.ServerInfo
|
||||
setDistributionInfo(string, string)
|
||||
getDistributionInfo() string
|
||||
checkRequiredPackagesInstalled() error
|
||||
scanPackages() error
|
||||
scanVulnByCpeName() error
|
||||
install() error
|
||||
convertToModel() (models.ScanResult, error)
|
||||
}
|
||||
|
||||
// osPackages included by linux struct
|
||||
type osPackages struct {
|
||||
// installed packages
|
||||
Packages models.PackageInfoList
|
||||
|
||||
// unsecure packages
|
||||
UnsecurePackages CvePacksList
|
||||
}
|
||||
|
||||
func (p *osPackages) setPackages(pi models.PackageInfoList) {
|
||||
p.Packages = pi
|
||||
}
|
||||
|
||||
func (p *osPackages) setUnsecurePackages(pi []CvePacksInfo) {
|
||||
p.UnsecurePackages = pi
|
||||
}
|
||||
|
||||
// CvePacksList have CvePacksInfo list, getter/setter, sortable methods.
|
||||
type CvePacksList []CvePacksInfo
|
||||
|
||||
// CvePacksInfo hold the CVE information.
|
||||
type CvePacksInfo struct {
|
||||
CveID string
|
||||
CveDetail cve.CveDetail
|
||||
Packs []models.PackageInfo
|
||||
DistroAdvisories []models.DistroAdvisory // for Aamazon, RHEL
|
||||
CpeNames []string
|
||||
// CvssScore float64
|
||||
}
|
||||
|
||||
// FindByCveID find by CVEID
|
||||
func (s CvePacksList) FindByCveID(cveID string) (pi CvePacksInfo, found bool) {
|
||||
for _, p := range s {
|
||||
if cveID == p.CveID {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
return CvePacksInfo{CveID: cveID}, false
|
||||
}
|
||||
|
||||
// immutable
|
||||
func (s CvePacksList) set(cveID string, cvePacksInfo CvePacksInfo) CvePacksList {
|
||||
for i, p := range s {
|
||||
if cveID == p.CveID {
|
||||
s[i] = cvePacksInfo
|
||||
return s
|
||||
}
|
||||
}
|
||||
return append(s, cvePacksInfo)
|
||||
}
|
||||
|
||||
// Len implement Sort Interface
|
||||
func (s CvePacksList) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Swap implement Sort Interface
|
||||
func (s CvePacksList) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// Less implement Sort Interface
|
||||
func (s CvePacksList) Less(i, j int) bool {
|
||||
return s[i].CveDetail.CvssScore("en") > s[j].CveDetail.CvssScore("en")
|
||||
}
|
||||
|
||||
func detectOs(c config.ServerInfo) (osType osTypeInterface) {
|
||||
var itsMe bool
|
||||
itsMe, osType = detectDebian(c)
|
||||
if itsMe {
|
||||
return
|
||||
}
|
||||
itsMe, osType = detectRedhat(c)
|
||||
return
|
||||
}
|
||||
|
||||
// InitServers detect the kind of OS distribution of target servers
|
||||
func InitServers(localLogger *logrus.Entry) (err error) {
|
||||
Log = localLogger
|
||||
if servers, err = detectServersOS(); err != nil {
|
||||
err = fmt.Errorf("Failed to detect the type of OS. err: %s", err)
|
||||
} else {
|
||||
Log.Debugf("%s", pp.Sprintf("%s", servers))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func detectServersOS() (osi []osTypeInterface, err error) {
|
||||
osTypeChan := make(chan osTypeInterface, len(config.Conf.Servers))
|
||||
defer close(osTypeChan)
|
||||
for _, s := range config.Conf.Servers {
|
||||
go func(s config.ServerInfo) {
|
||||
osTypeChan <- detectOs(s)
|
||||
}(s)
|
||||
}
|
||||
|
||||
timeout := time.After(60 * time.Second)
|
||||
for i := 0; i < len(config.Conf.Servers); i++ {
|
||||
select {
|
||||
case res := <-osTypeChan:
|
||||
Log.Infof("(%d/%d) Successfully detected. %s: %s",
|
||||
i+1, len(config.Conf.Servers),
|
||||
res.getServerInfo().ServerName,
|
||||
res.getDistributionInfo())
|
||||
osi = append(osi, res)
|
||||
case <-timeout:
|
||||
Log.Error("Timeout occured while detecting")
|
||||
err = fmt.Errorf("Timeout")
|
||||
for servername := range config.Conf.Servers {
|
||||
found := false
|
||||
for _, o := range osi {
|
||||
if servername == o.getServerInfo().ServerName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
Log.Errorf("Failed to detect. servername: %s", servername)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Prepare installs requred packages to scan vulnerabilities.
|
||||
func Prepare() []error {
|
||||
return parallelSSHExec(func(o osTypeInterface) error {
|
||||
if err := o.install(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Scan scan
|
||||
func Scan() []error {
|
||||
if len(servers) == 0 {
|
||||
return []error{fmt.Errorf("No server defined. Check the configuration")}
|
||||
}
|
||||
|
||||
Log.Info("Check required packages for scanning...")
|
||||
if errs := checkRequiredPackagesInstalled(); errs != nil {
|
||||
Log.Error("Please execute with [prepare] subcommand to install required packages before scanning")
|
||||
return errs
|
||||
}
|
||||
|
||||
Log.Info("Scanning vulnerable OS packages...")
|
||||
if errs := scanPackages(); errs != nil {
|
||||
return errs
|
||||
}
|
||||
|
||||
Log.Info("Scanning vulnerable software specified in the CPE...")
|
||||
if errs := scanVulnByCpeName(); errs != nil {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkRequiredPackagesInstalled() []error {
|
||||
timeoutSec := 30 * 60
|
||||
return parallelSSHExec(func(o osTypeInterface) error {
|
||||
return o.checkRequiredPackagesInstalled()
|
||||
}, timeoutSec)
|
||||
}
|
||||
|
||||
func scanPackages() []error {
|
||||
timeoutSec := 30 * 60
|
||||
return parallelSSHExec(func(o osTypeInterface) error {
|
||||
return o.scanPackages()
|
||||
}, timeoutSec)
|
||||
|
||||
}
|
||||
|
||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
|
||||
func scanVulnByCpeName() []error {
|
||||
timeoutSec := 30 * 60
|
||||
return parallelSSHExec(func(o osTypeInterface) error {
|
||||
return o.scanVulnByCpeName()
|
||||
}, timeoutSec)
|
||||
|
||||
}
|
||||
|
||||
// GetScanResults returns Scan Resutls
|
||||
func GetScanResults() (results models.ScanResults, err error) {
|
||||
for _, s := range servers {
|
||||
r, err := s.convertToModel()
|
||||
if err != nil {
|
||||
return results, fmt.Errorf("Failed converting to model: %s", err)
|
||||
}
|
||||
results = append(results, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package scan
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPackageCveInfosSetGet(t *testing.T) {
|
||||
var test = struct {
|
||||
in []string
|
||||
out []string
|
||||
}{
|
||||
[]string{
|
||||
"CVE1",
|
||||
"CVE2",
|
||||
"CVE3",
|
||||
"CVE1",
|
||||
"CVE1",
|
||||
"CVE2",
|
||||
"CVE3",
|
||||
},
|
||||
[]string{
|
||||
"CVE1",
|
||||
"CVE2",
|
||||
"CVE3",
|
||||
},
|
||||
}
|
||||
|
||||
// var ps packageCveInfos
|
||||
var ps CvePacksList
|
||||
for _, cid := range test.in {
|
||||
ps = ps.set(cid, CvePacksInfo{CveID: cid})
|
||||
}
|
||||
|
||||
if len(test.out) != len(ps) {
|
||||
t.Errorf("length: expected %d, actual %d", len(test.out), len(ps))
|
||||
}
|
||||
|
||||
for i, expectedCid := range test.out {
|
||||
if expectedCid != ps[i].CveID {
|
||||
t.Errorf("expected %s, actual %s", expectedCid, ps[i].CveID)
|
||||
}
|
||||
}
|
||||
for _, cid := range test.in {
|
||||
p, _ := ps.FindByCveID(cid)
|
||||
if p.CveID != cid {
|
||||
t.Errorf("expected %s, actual %s", cid, p.CveID)
|
||||
}
|
||||
}
|
||||
}
|
||||
311
scan/sshutil.go
@@ -1,311 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
conf "github.com/future-architect/vuls/config"
|
||||
"github.com/k0kubun/pp"
|
||||
)
|
||||
|
||||
type sshResult struct {
|
||||
Host string
|
||||
Port string
|
||||
Stdout string
|
||||
Stderr string
|
||||
ExitStatus int
|
||||
}
|
||||
|
||||
func (s sshResult) isSuccess(expectedStatusCodes ...int) bool {
|
||||
if len(expectedStatusCodes) == 0 {
|
||||
return s.ExitStatus == 0
|
||||
}
|
||||
for _, code := range expectedStatusCodes {
|
||||
if code == s.ExitStatus {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Sudo is Const value for sudo mode
|
||||
const sudo = true
|
||||
|
||||
// NoSudo is Const value for normal user mode
|
||||
const noSudo = false
|
||||
|
||||
func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []error) {
|
||||
errChan := make(chan error, len(servers))
|
||||
defer close(errChan)
|
||||
for _, s := range servers {
|
||||
go func(s osTypeInterface) {
|
||||
if err := fn(s); err != nil {
|
||||
errChan <- fmt.Errorf("%s@%s:%s: %s",
|
||||
s.getServerInfo().User,
|
||||
s.getServerInfo().Host,
|
||||
s.getServerInfo().Port,
|
||||
err,
|
||||
)
|
||||
} else {
|
||||
errChan <- nil
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
|
||||
var timeout int
|
||||
if len(timeoutSec) == 0 {
|
||||
timeout = 10 * 60
|
||||
} else {
|
||||
timeout = timeoutSec[0]
|
||||
}
|
||||
|
||||
for i := 0; i < len(servers); i++ {
|
||||
select {
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
logrus.Debug("Parallel SSH Success")
|
||||
}
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
logrus.Errorf("Parallel SSH Timeout")
|
||||
errs = append(errs, fmt.Errorf("Timed out"))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result sshResult) {
|
||||
// Setup Logger
|
||||
var logger *logrus.Entry
|
||||
if len(log) == 0 {
|
||||
level := logrus.InfoLevel
|
||||
if conf.Conf.Debug == true {
|
||||
level = logrus.DebugLevel
|
||||
}
|
||||
l := &logrus.Logger{
|
||||
Out: os.Stderr,
|
||||
Formatter: new(logrus.TextFormatter),
|
||||
Hooks: make(logrus.LevelHooks),
|
||||
Level: level,
|
||||
}
|
||||
logger = logrus.NewEntry(l)
|
||||
} else {
|
||||
logger = log[0]
|
||||
}
|
||||
|
||||
var err error
|
||||
if sudo && c.User != "root" {
|
||||
switch {
|
||||
case c.SudoOpt.ExecBySudo:
|
||||
cmd = fmt.Sprintf("echo %s | sudo -S %s", c.Password, cmd)
|
||||
case c.SudoOpt.ExecBySudoSh:
|
||||
cmd = fmt.Sprintf("echo %s | sudo sh -c '%s'", c.Password, cmd)
|
||||
default:
|
||||
logger.Panicf("sudoOpt is invalid. SudoOpt: %v", c.SudoOpt)
|
||||
}
|
||||
}
|
||||
// set pipefail option.
|
||||
// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
|
||||
cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
|
||||
logger.Debugf("Command: %s", strings.Replace(cmd, "\n", "", -1))
|
||||
|
||||
var client *ssh.Client
|
||||
client, err = sshConnect(c)
|
||||
defer client.Close()
|
||||
|
||||
var session *ssh.Session
|
||||
if session, err = client.NewSession(); err != nil {
|
||||
logger.Errorf("Failed to new session. err: %s, c: %s",
|
||||
err,
|
||||
pp.Sprintf("%v", c))
|
||||
result.ExitStatus = 999
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// http://blog.ralch.com/tutorial/golang-ssh-connection/
|
||||
modes := ssh.TerminalModes{
|
||||
ssh.ECHO: 0, // disable echoing
|
||||
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
|
||||
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
||||
}
|
||||
if err = session.RequestPty("xterm", 400, 120, modes); err != nil {
|
||||
logger.Errorf("Failed to request for pseudo terminal. err: %s, c: %s",
|
||||
err,
|
||||
pp.Sprintf("%v", c))
|
||||
|
||||
result.ExitStatus = 999
|
||||
return
|
||||
}
|
||||
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
session.Stdout = &stdoutBuf
|
||||
session.Stderr = &stderrBuf
|
||||
|
||||
if err := session.Run(cmd); err != nil {
|
||||
if exitErr, ok := err.(*ssh.ExitError); ok {
|
||||
result.ExitStatus = exitErr.ExitStatus()
|
||||
} else {
|
||||
result.ExitStatus = 999
|
||||
}
|
||||
} else {
|
||||
result.ExitStatus = 0
|
||||
}
|
||||
|
||||
result.Stdout = stdoutBuf.String()
|
||||
result.Stderr = stderrBuf.String()
|
||||
result.Host = c.Host
|
||||
result.Port = c.Port
|
||||
|
||||
logger.Debugf(
|
||||
"SSH executed. cmd: %s, status: %#v\nstdout: \n%s\nstderr: \n%s",
|
||||
cmd, err, result.Stdout, result.Stderr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getAgentAuth() (auth ssh.AuthMethod, ok bool) {
|
||||
if sock := os.Getenv("SSH_AUTH_SOCK"); len(sock) > 0 {
|
||||
if agconn, err := net.Dial("unix", sock); err == nil {
|
||||
ag := agent.NewClient(agconn)
|
||||
auth = ssh.PublicKeysCallback(ag.Signers)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func tryAgentConnect(c conf.ServerInfo) *ssh.Client {
|
||||
if auth, ok := getAgentAuth(); ok {
|
||||
config := &ssh.ClientConfig{
|
||||
User: c.User,
|
||||
Auth: []ssh.AuthMethod{auth},
|
||||
}
|
||||
client, _ := ssh.Dial("tcp", c.Host+":"+c.Port, config)
|
||||
return client
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
|
||||
|
||||
if client = tryAgentConnect(c); client != nil {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
var auths = []ssh.AuthMethod{}
|
||||
if auths, err = addKeyAuth(auths, c.KeyPath, c.KeyPassword); err != nil {
|
||||
logrus.Fatalf("Failed to add keyAuth. err: %s", err)
|
||||
}
|
||||
|
||||
if c.Password != "" {
|
||||
auths = append(auths, ssh.Password(c.Password))
|
||||
}
|
||||
|
||||
// http://blog.ralch.com/tutorial/golang-ssh-connection/
|
||||
config := &ssh.ClientConfig{
|
||||
User: c.User,
|
||||
Auth: auths,
|
||||
}
|
||||
// log.Debugf("config: %s", pp.Sprintf("%v", config))
|
||||
|
||||
notifyFunc := func(e error, t time.Duration) {
|
||||
logrus.Warnf("Failed to ssh %s@%s:%s. err: %s, Retrying in %s...",
|
||||
c.User, c.Host, c.Port, e, t)
|
||||
logrus.Debugf("sshConInfo: %s", pp.Sprintf("%v", c))
|
||||
}
|
||||
err = backoff.RetryNotify(func() error {
|
||||
if client, err = ssh.Dial("tcp", c.Host+":"+c.Port, config); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, backoff.NewExponentialBackOff(), notifyFunc)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// https://github.com/rapidloop/rtop/blob/ba5b35e964135d50e0babedf0bd69b2fcb5dbcb4/src/sshhelper.go#L100
|
||||
func addKeyAuth(auths []ssh.AuthMethod, keypath string, keypassword string) ([]ssh.AuthMethod, error) {
|
||||
if len(keypath) == 0 {
|
||||
return auths, nil
|
||||
}
|
||||
|
||||
// read the file
|
||||
pemBytes, err := ioutil.ReadFile(keypath)
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
|
||||
// get first pem block
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return auths, fmt.Errorf("no key found in %s", keypath)
|
||||
}
|
||||
|
||||
// handle plain and encrypted keyfiles
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
block.Bytes, err = x509.DecryptPEMBlock(block, []byte(keypassword))
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
key, err := parsePemBlock(block)
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
signer, err := ssh.NewSignerFromKey(key)
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
return append(auths, ssh.PublicKeys(signer)), nil
|
||||
}
|
||||
|
||||
signer, err := ssh.ParsePrivateKey(pemBytes)
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
return append(auths, ssh.PublicKeys(signer)), nil
|
||||
}
|
||||
|
||||
// ref golang.org/x/crypto/ssh/keys.go#ParseRawPrivateKey.
|
||||
func parsePemBlock(block *pem.Block) (interface{}, error) {
|
||||
switch block.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
case "EC PRIVATE KEY":
|
||||
return x509.ParseECPrivateKey(block.Bytes)
|
||||
case "DSA PRIVATE KEY":
|
||||
return ssh.ParseDSAPrivateKey(block.Bytes)
|
||||
default:
|
||||
return nil, fmt.Errorf("rtop: unsupported key type %q", block.Type)
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/rifflock/lfshook"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
formatter "github.com/kotakanbe/logrus-prefixed-formatter"
|
||||
)
|
||||
|
||||
// NewCustomLogger creates logrus
|
||||
func NewCustomLogger(c config.ServerInfo) *logrus.Entry {
|
||||
log := logrus.New()
|
||||
log.Formatter = &formatter.TextFormatter{MsgAnsiColor: c.LogMsgAnsiColor}
|
||||
log.Out = os.Stderr
|
||||
log.Level = logrus.InfoLevel
|
||||
if config.Conf.Debug {
|
||||
log.Level = logrus.DebugLevel
|
||||
}
|
||||
|
||||
// File output
|
||||
logDir := "/var/log/vuls"
|
||||
if _, err := os.Stat(logDir); os.IsNotExist(err) {
|
||||
if err := os.Mkdir(logDir, 0666); err != nil {
|
||||
logrus.Errorf("Failed to create log directory: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
whereami := "localhost"
|
||||
if 0 < len(c.ServerName) {
|
||||
whereami = fmt.Sprintf("%s:%s", c.ServerName, c.Port)
|
||||
|
||||
}
|
||||
if _, err := os.Stat(logDir); err == nil {
|
||||
path := fmt.Sprintf("%s/%s.log", logDir, whereami)
|
||||
log.Hooks.Add(lfshook.NewHook(lfshook.PathMap{
|
||||
logrus.DebugLevel: path,
|
||||
logrus.InfoLevel: path,
|
||||
logrus.WarnLevel: path,
|
||||
logrus.ErrorLevel: path,
|
||||
logrus.FatalLevel: path,
|
||||
logrus.PanicLevel: path,
|
||||
}))
|
||||
}
|
||||
|
||||
fields := logrus.Fields{"prefix": whereami}
|
||||
return log.WithFields(fields)
|
||||
}
|
||||
120
util/util.go
@@ -1,120 +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 util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
)
|
||||
|
||||
// GenWorkers generates goroutine
|
||||
// http://qiita.com/na-o-ys/items/65373132b1c5bc973cca
|
||||
func GenWorkers(num int) chan<- func() {
|
||||
tasks := make(chan func())
|
||||
for i := 0; i < num; i++ {
|
||||
go func() {
|
||||
for f := range tasks {
|
||||
f()
|
||||
}
|
||||
}()
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
// AppendIfMissing append to the slice if missing
|
||||
func AppendIfMissing(slice []string, s string) []string {
|
||||
for _, ele := range slice {
|
||||
if ele == s {
|
||||
return slice
|
||||
}
|
||||
}
|
||||
return append(slice, s)
|
||||
}
|
||||
|
||||
// URLPathJoin make URL
|
||||
func URLPathJoin(baseURL string, paths ...string) (string, error) {
|
||||
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||
trimedPaths := []string{}
|
||||
for _, path := range paths {
|
||||
trimed := strings.Trim(path, " /")
|
||||
if len(trimed) != 0 {
|
||||
trimedPaths = append(trimedPaths, trimed)
|
||||
}
|
||||
}
|
||||
var url *url.URL
|
||||
url, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
url.Path += strings.Join(trimedPaths, "/")
|
||||
return url.String(), nil
|
||||
}
|
||||
|
||||
// URLPathParamJoin make URL
|
||||
func URLPathParamJoin(baseURL string, paths []string, params map[string]string) (string, error) {
|
||||
urlPath, err := URLPathJoin(baseURL, paths...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
u, err := url.Parse(urlPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
parameters := url.Values{}
|
||||
for key := range params {
|
||||
parameters.Add(key, params[key])
|
||||
}
|
||||
u.RawQuery = parameters.Encode()
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
// ProxyEnv returns shell environment variables to set proxy
|
||||
func ProxyEnv() string {
|
||||
httpProxyEnv := "env"
|
||||
keys := []string{
|
||||
"http_proxy",
|
||||
"https_proxy",
|
||||
"HTTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
}
|
||||
for _, key := range keys {
|
||||
httpProxyEnv += fmt.Sprintf(
|
||||
` %s="%s"`, key, config.Conf.HTTPProxy)
|
||||
}
|
||||
return httpProxyEnv
|
||||
}
|
||||
|
||||
// PrependProxyEnv prepends proxy enviroment variable
|
||||
func PrependProxyEnv(cmd string) string {
|
||||
if config.Conf.HTTPProxy == "" {
|
||||
return cmd
|
||||
}
|
||||
return fmt.Sprintf("%s %s", ProxyEnv(), cmd)
|
||||
}
|
||||
|
||||
// func unixtime(s string) (time.Time, error) {
|
||||
// i, err := strconv.ParseInt(s, 10, 64)
|
||||
// if err != nil {
|
||||
// return time.Time{}, err
|
||||
// }
|
||||
// return time.Unix(i, 0), nil
|
||||
// }
|
||||