mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
Compare commits
851 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b167435c4d | ||
|
|
9b29321a53 | ||
|
|
466a11fa1c | ||
|
|
cfe022ff1d | ||
|
|
e0eeb691e6 | ||
|
|
dc65bd4ccc | ||
|
|
02790da144 | ||
|
|
b97f50ffb5 | ||
|
|
0841d1d483 | ||
|
|
fbef268f22 | ||
|
|
427dccadd3 | ||
|
|
01bb19bf6e | ||
|
|
c0d4bb45eb | ||
|
|
222c1ec866 | ||
|
|
dc49218c7c | ||
|
|
3b4585a827 | ||
|
|
7f79bc2d1d | ||
|
|
3623e55433 | ||
|
|
2f7841b5a2 | ||
|
|
f70d81d7c4 | ||
|
|
bd49251234 | ||
|
|
57addd493f | ||
|
|
8f009d4698 | ||
|
|
7c0e38072d | ||
|
|
aa9a610c4c | ||
|
|
25bd51e8b4 | ||
|
|
2759beece5 | ||
|
|
6ce0121a03 | ||
|
|
09aa1ab866 | ||
|
|
0ec188b23d | ||
|
|
090820ba04 | ||
|
|
0cf24d058f | ||
|
|
c32e665809 | ||
|
|
82ec11b207 | ||
|
|
32a15acdea | ||
|
|
837a50c903 | ||
|
|
bd00d153e9 | ||
|
|
306050046d | ||
|
|
413db87e85 | ||
|
|
4d3b3efb9a | ||
|
|
7ca609d39f | ||
|
|
872c0c9fab | ||
|
|
9353eb5b54 | ||
|
|
aa62fbea68 | ||
|
|
08d964b631 | ||
|
|
75fb07efde | ||
|
|
9445e0aa01 | ||
|
|
ea12643a3c | ||
|
|
0c42b41dcc | ||
|
|
351f957083 | ||
|
|
9d876b14e9 | ||
|
|
895233630f | ||
|
|
423d9c5c1f | ||
|
|
3f3681a4cd | ||
|
|
d6ccc37640 | ||
|
|
3b6bc00b03 | ||
|
|
8984f941ab | ||
|
|
46eb266064 | ||
|
|
0f2125817b | ||
|
|
1225540590 | ||
|
|
0e4ff13276 | ||
|
|
5fed9cc507 | ||
|
|
06241fce03 | ||
|
|
2b91023c6b | ||
|
|
082edf52d9 | ||
|
|
be250ff090 | ||
|
|
d74803af28 | ||
|
|
893bb86035 | ||
|
|
314a74b817 | ||
|
|
997bc2d23b | ||
|
|
5d1699291a | ||
|
|
bfca19bf25 | ||
|
|
1b94d27fd6 | ||
|
|
acf7ad04ed | ||
|
|
0f5775065e | ||
|
|
66fbca8f24 | ||
|
|
1f8de23a65 | ||
|
|
89478eabcc | ||
|
|
6be9aec5b0 | ||
|
|
32551275ba | ||
|
|
4ee6238244 | ||
|
|
54dda8bf31 | ||
|
|
270b3b320d | ||
|
|
fa17ca26e1 | ||
|
|
66e970a3dc | ||
|
|
d10d08c02b | ||
|
|
8d7c595a76 | ||
|
|
621ffd3ead | ||
|
|
5dee6d0e4f | ||
|
|
f516853af8 | ||
|
|
0d01329683 | ||
|
|
5a0f5f98c1 | ||
|
|
771fc4acca | ||
|
|
68a9d0cf60 | ||
|
|
d2bc957500 | ||
|
|
d20ec9e471 | ||
|
|
d3824028c5 | ||
|
|
5013f91814 | ||
|
|
41e47c3ad3 | ||
|
|
acfe986863 | ||
|
|
abafa9eafa | ||
|
|
dce1ee4dc6 | ||
|
|
62a143326c | ||
|
|
d72a6005bb | ||
|
|
adb9b80442 | ||
|
|
cb7cca7b44 | ||
|
|
f38bec9314 | ||
|
|
fff663bed4 | ||
|
|
6a72851182 | ||
|
|
e4962fe934 | ||
|
|
dac3af19a3 | ||
|
|
0b44e94b67 | ||
|
|
df37457504 | ||
|
|
f88a374b6d | ||
|
|
47442f954c | ||
|
|
efbb8e8367 | ||
|
|
60d7276de3 | ||
|
|
c05427ff38 | ||
|
|
3e245da02b | ||
|
|
cc7aae470f | ||
|
|
8d59a6074e | ||
|
|
1f7dd6e5f5 | ||
|
|
bf5ca3c1f0 | ||
|
|
a8574c61ea | ||
|
|
6f9c0ae85f | ||
|
|
be2c74e48a | ||
|
|
68da73855f | ||
|
|
5b3f2d0ff9 | ||
|
|
02637c7a8e | ||
|
|
7d5b374f9d | ||
|
|
1dd6d7a1b3 | ||
|
|
6b80b85555 | ||
|
|
d88bc067e2 | ||
|
|
ba78527c80 | ||
|
|
4c8692bf8c | ||
|
|
742e3bb67f | ||
|
|
a39f36c9fb | ||
|
|
9bc29032e1 | ||
|
|
a4a290a3ce | ||
|
|
379a3fbc27 | ||
|
|
a46098c034 | ||
|
|
440f39ba3e | ||
|
|
b6a4e282f9 | ||
|
|
8deff34d12 | ||
|
|
acc9b54b2b | ||
|
|
1ffa29fbaa | ||
|
|
2ae30a8162 | ||
|
|
0ca5378c6b | ||
|
|
f51a1281f7 | ||
|
|
5469d8bc04 | ||
|
|
bd7c0c580e | ||
|
|
154fec1385 | ||
|
|
5c2275e32a | ||
|
|
2da4736201 | ||
|
|
aefafeae6f | ||
|
|
1772b38b8c | ||
|
|
c844f42208 | ||
|
|
b86d051998 | ||
|
|
aaa8d1ed35 | ||
|
|
441d16aa08 | ||
|
|
b33f1c8cc7 | ||
|
|
4929af510e | ||
|
|
f28bb11c55 | ||
|
|
8bff4a02e1 | ||
|
|
33d1e018ec | ||
|
|
0c74599314 | ||
|
|
c23b85cc84 | ||
|
|
aab10d14a2 | ||
|
|
2fcbe54e4e | ||
|
|
078d154ab8 | ||
|
|
cc9dcf827e | ||
|
|
76943d05fb | ||
|
|
621ac111cb | ||
|
|
3f80bce811 | ||
|
|
cc6895fc50 | ||
|
|
7d9d8e4b59 | ||
|
|
f8d4bf515d | ||
|
|
774ebe4a5f | ||
|
|
45a07a8046 | ||
|
|
ff96edae4d | ||
|
|
34b82cad27 | ||
|
|
1a4c979ab8 | ||
|
|
3481af4a5b | ||
|
|
71dc7a702c | ||
|
|
6d92389285 | ||
|
|
bd0be45c0b | ||
|
|
7ebf078d0c | ||
|
|
1bd729cf83 | ||
|
|
88b9b22bca | ||
|
|
182162d521 | ||
|
|
1c02191bb1 | ||
|
|
ca66ccb33d | ||
|
|
07eda20b88 | ||
|
|
108c84d97d | ||
|
|
35e7fa2b94 | ||
|
|
abb7917b29 | ||
|
|
31ba56a0cf | ||
|
|
b3efe4d003 | ||
|
|
5faade2b66 | ||
|
|
79207f66be | ||
|
|
af39f9a7ef | ||
|
|
482b7c1f67 | ||
|
|
82e2fd0be2 | ||
|
|
6eec751027 | ||
|
|
4a6480c8b4 | ||
|
|
a51bfa4c3e | ||
|
|
2a48af3c17 | ||
|
|
ffeb4577e3 | ||
|
|
b5c7422355 | ||
|
|
e41b5d77a0 | ||
|
|
5afaae8847 | ||
|
|
011fc0689d | ||
|
|
db30020c95 | ||
|
|
c5341a356b | ||
|
|
85a7f57373 | ||
|
|
cd9ebdf08f | ||
|
|
bc602a78ab | ||
|
|
a2361fd155 | ||
|
|
aa8d41fc2e | ||
|
|
5bd4beb41f | ||
|
|
dbf21dee37 | ||
|
|
be49d9b7be | ||
|
|
7a5699fba3 | ||
|
|
1f8afecea8 | ||
|
|
3ebb1d749e | ||
|
|
f80c9d947d | ||
|
|
03b76ff4aa | ||
|
|
01531b6276 | ||
|
|
aedfe1c4c0 | ||
|
|
d2bedc1d2b | ||
|
|
35288e7b85 | ||
|
|
cd046fa695 | ||
|
|
407b8be08f | ||
|
|
b211fe9148 | ||
|
|
525e51d68e | ||
|
|
daabd6c81a | ||
|
|
5b351d5eec | ||
|
|
a5b607ae2e | ||
|
|
fec51b00ba | ||
|
|
4f9809eec1 | ||
|
|
c0c25c3430 | ||
|
|
6ed3e408be | ||
|
|
6042818a71 | ||
|
|
b37c20aed9 | ||
|
|
3de8204c43 | ||
|
|
d5bd3708b8 | ||
|
|
2bd686131e | ||
|
|
1ea4e0c304 | ||
|
|
b3251306d0 | ||
|
|
91ecdaba4e | ||
|
|
fa05dcd00d | ||
|
|
1c2c928732 | ||
|
|
69ac490006 | ||
|
|
b67b9f3af2 | ||
|
|
628ed4a374 | ||
|
|
a6fe34b466 | ||
|
|
2e9406d96a | ||
|
|
6b1bf07f7f | ||
|
|
318c2c7ae6 | ||
|
|
45f60b6fe0 | ||
|
|
20557bc721 | ||
|
|
d8bfb27bc3 | ||
|
|
9776691816 | ||
|
|
80e419df24 | ||
|
|
143f831f5b | ||
|
|
a4897304e8 | ||
|
|
80d1165e2c | ||
|
|
31ed7d5160 | ||
|
|
d62e9ce207 | ||
|
|
3b10443ff5 | ||
|
|
218e3914b1 | ||
|
|
2b07fbe782 | ||
|
|
0655d0496b | ||
|
|
02bf31fbc9 | ||
|
|
4d8a3f51e3 | ||
|
|
43d29f7b8b | ||
|
|
ee0d4cba98 | ||
|
|
d860c0234a | ||
|
|
f13ded61bf | ||
|
|
fe8fc700f4 | ||
|
|
47b670637b | ||
|
|
97c83a652b | ||
|
|
a089831720 | ||
|
|
e4f1720a0c | ||
|
|
233eb2134c | ||
|
|
06da926455 | ||
|
|
37f6193fe0 | ||
|
|
27ac036b7d | ||
|
|
321d335b39 | ||
|
|
91b7d8fc2b | ||
|
|
2b28911db0 | ||
|
|
667e5e8258 | ||
|
|
728b341048 | ||
|
|
75b295d579 | ||
|
|
75298eabf2 | ||
|
|
2458f2ceb9 | ||
|
|
f57948ad97 | ||
|
|
d0befc5f16 | ||
|
|
5d4bd2e94e | ||
|
|
ae37fdc295 | ||
|
|
3dd95ff3a3 | ||
|
|
daadb5b804 | ||
|
|
d250017faf | ||
|
|
835bcbeb12 | ||
|
|
2e4f7c4477 | ||
|
|
66bf93eb0c | ||
|
|
3a036ed0e3 | ||
|
|
fe7dad4560 | ||
|
|
fb36b09f3a | ||
|
|
e71b0c75a9 | ||
|
|
d615099ce1 | ||
|
|
f265b91939 | ||
|
|
825694ade1 | ||
|
|
979a30aea7 | ||
|
|
39c4aa4faa | ||
|
|
475b672a7a | ||
|
|
815c87b532 | ||
|
|
82120f9d31 | ||
|
|
0545818f82 | ||
|
|
046da1940c | ||
|
|
a31154897f | ||
|
|
199c57be30 | ||
|
|
7d55c79f11 | ||
|
|
ee76364371 | ||
|
|
4f2c7ac1de | ||
|
|
00340827be | ||
|
|
708fe64240 | ||
|
|
8985bbe3a9 | ||
|
|
1ffca5648e | ||
|
|
76b1ecb022 | ||
|
|
fc69a3692e | ||
|
|
e159458129 | ||
|
|
b259f117ff | ||
|
|
13cf34bffd | ||
|
|
0300fee38b | ||
|
|
d61d641e81 | ||
|
|
2added0f7c | ||
|
|
b6f6573ed8 | ||
|
|
4215771134 | ||
|
|
fd37446e1b | ||
|
|
351498aac5 | ||
|
|
2005010568 | ||
|
|
e16c4cc9b4 | ||
|
|
544ba9831a | ||
|
|
b6c919feb1 | ||
|
|
1c3b2831a2 | ||
|
|
8a19a73bb1 | ||
|
|
d966b0acbc | ||
|
|
14ffe35437 | ||
|
|
985d72e5fb | ||
|
|
70a9380966 | ||
|
|
f706d126f5 | ||
|
|
600f19406e | ||
|
|
d7ebf3239b | ||
|
|
5e0b25b04a | ||
|
|
98fe2347fa | ||
|
|
9b22d3284e | ||
|
|
9544e9cd66 | ||
|
|
1ed1bb11f2 | ||
|
|
b8ca1fcbce | ||
|
|
326a3e4c63 | ||
|
|
b348acd291 | ||
|
|
4fc3eacf7b | ||
|
|
d6030a9c03 | ||
|
|
e87bf7b723 | ||
|
|
4ef0b27ccf | ||
|
|
219582b92a | ||
|
|
07ed8c61f1 | ||
|
|
c585abc21a | ||
|
|
08696c583a | ||
|
|
7d94dc74bb | ||
|
|
570369a66f | ||
|
|
97f24920e8 | ||
|
|
f57305280f | ||
|
|
53c134cbc3 | ||
|
|
3c3a1838e3 | ||
|
|
adfd09a9d4 | ||
|
|
43ac47ec51 | ||
|
|
ec715ab68b | ||
|
|
fbff5873f7 | ||
|
|
a81eab0a1a | ||
|
|
cfc52856b3 | ||
|
|
5707d7f7e4 | ||
|
|
4f3ef49f99 | ||
|
|
a9ac880356 | ||
|
|
761d4c6ff4 | ||
|
|
bbb2aafc7e | ||
|
|
7735087937 | ||
|
|
5b9c6491de | ||
|
|
b0e3744140 | ||
|
|
0451cdb345 | ||
|
|
4546465f4a | ||
|
|
52c564b2a4 | ||
|
|
0abc81003e | ||
|
|
817d4902ff | ||
|
|
5553a1adf0 | ||
|
|
e95352d31e | ||
|
|
5655051a95 | ||
|
|
90c359533f | ||
|
|
7013d83aa8 | ||
|
|
c4935671fe | ||
|
|
e67ef1c54d | ||
|
|
5d6b9a5e83 | ||
|
|
a6752a5a6d | ||
|
|
6f47f8dae0 | ||
|
|
d82e8daa25 | ||
|
|
1cad446fe6 | ||
|
|
12c6f34fe7 | ||
|
|
79c2dd9a2b | ||
|
|
f64d5a03af | ||
|
|
e27d1581f1 | ||
|
|
99985e3235 | ||
|
|
27782afeac | ||
|
|
0e846b2dc5 | ||
|
|
45fb0f207e | ||
|
|
b7c91c238d | ||
|
|
76635a0705 | ||
|
|
c91d69e7fd | ||
|
|
5fd7096d67 | ||
|
|
f4189cb5ec | ||
|
|
278ca5b8ae | ||
|
|
729efcb8c3 | ||
|
|
406031d4e9 | ||
|
|
9e1d0d2cd6 | ||
|
|
f3e78f9408 | ||
|
|
eea4cc0b49 | ||
|
|
1127f44c10 | ||
|
|
377509fab8 | ||
|
|
c21e2f3147 | ||
|
|
0b4c5db939 | ||
|
|
6d490fc501 | ||
|
|
dbb71ba066 | ||
|
|
d5b8532e40 | ||
|
|
db396b26f8 | ||
|
|
1242259331 | ||
|
|
ad0e50898a | ||
|
|
3cf45cffd8 | ||
|
|
ac0d982531 | ||
|
|
99e22efe7b | ||
|
|
aedeb8f9cb | ||
|
|
824e76200e | ||
|
|
8342f96a62 | ||
|
|
b824d52345 | ||
|
|
11b6567db4 | ||
|
|
c7d3105ca5 | ||
|
|
f1c15cd2b5 | ||
|
|
7507f58306 | ||
|
|
48ad56a2ef | ||
|
|
2fdec20b28 | ||
|
|
2d77ea7b62 | ||
|
|
eacd559c34 | ||
|
|
c56e5799d7 | ||
|
|
ae5744f54e | ||
|
|
c649cc66a5 | ||
|
|
7db735ade6 | ||
|
|
456145e240 | ||
|
|
382a2f03c8 | ||
|
|
44ebf59d76 | ||
|
|
0688e3620b | ||
|
|
ab534b0346 | ||
|
|
09420a41a7 | ||
|
|
e93eb942a8 | ||
|
|
12f87b2710 | ||
|
|
d6dc8f219c | ||
|
|
fb3376d305 | ||
|
|
ef2ded1933 | ||
|
|
e9f1d4085a | ||
|
|
51a9707d24 | ||
|
|
a4058eac62 | ||
|
|
f2b621134c | ||
|
|
58ce50e751 | ||
|
|
2bbedc99dd | ||
|
|
78794990d7 | ||
|
|
a7127c0b27 | ||
|
|
01505406a6 | ||
|
|
e1fe7cda50 | ||
|
|
f0bc2845cf | ||
|
|
c2c521b715 | ||
|
|
2d5ea3e789 | ||
|
|
137fe81701 | ||
|
|
f293606f81 | ||
|
|
d6d2315ad0 | ||
|
|
65aa28dd38 | ||
|
|
15e55e011c | ||
|
|
0ee98351c0 | ||
|
|
f52056a879 | ||
|
|
840162c865 | ||
|
|
160709eabf | ||
|
|
7f9f6d35f7 | ||
|
|
b2b37f6abc | ||
|
|
0863d845e1 | ||
|
|
da6faa3df0 | ||
|
|
3cbd2c458d | ||
|
|
629451dd33 | ||
|
|
29a313e708 | ||
|
|
38896ccd24 | ||
|
|
834623762d | ||
|
|
c937ed16f4 | ||
|
|
ea5f72af4e | ||
|
|
beb5a4d43e | ||
|
|
77e21d5e94 | ||
|
|
3fd7bf40cc | ||
|
|
18e0a227e1 | ||
|
|
060c17b480 | ||
|
|
e67a2e9d1c | ||
|
|
dfa5f1037e | ||
|
|
a15fc066e1 | ||
|
|
effc57dfda | ||
|
|
4b5c2dfed4 | ||
|
|
f39d4efd62 | ||
|
|
97ce466fbd | ||
|
|
a94dc85e14 | ||
|
|
7811b0a4a6 | ||
|
|
f9cc9b5b28 | ||
|
|
2f208c0866 | ||
|
|
97e4ca749b | ||
|
|
9521cf1974 | ||
|
|
8ec56976c5 | ||
|
|
5993f2db3a | ||
|
|
d0abfb4ae7 | ||
|
|
bd35d521f2 | ||
|
|
533c0392d4 | ||
|
|
e0f2944fc8 | ||
|
|
4c9cacecfe | ||
|
|
6ee6a78a75 | ||
|
|
e754ecff4f | ||
|
|
bda7a17f41 | ||
|
|
dee6ed96f8 | ||
|
|
0d1de027c9 | ||
|
|
22c85b5e3b | ||
|
|
d27284b6f6 | ||
|
|
4bde684d8a | ||
|
|
8cf735f84c | ||
|
|
e1db7f3704 | ||
|
|
fd64a068aa | ||
|
|
1945d3dfaa | ||
|
|
42670c7a9f | ||
|
|
81a9ca4254 | ||
|
|
00c0a205d6 | ||
|
|
374d8be96f | ||
|
|
70daff7cec | ||
|
|
22fc14ae50 | ||
|
|
d9736d7d56 | ||
|
|
574763ccfc | ||
|
|
a8cc411945 | ||
|
|
0576548bbe | ||
|
|
0477f8cb03 | ||
|
|
9a2d58faa0 | ||
|
|
b9fd60b395 | ||
|
|
d975f8e64a | ||
|
|
a2bd504e36 | ||
|
|
8a671b9658 | ||
|
|
b7a4f82968 | ||
|
|
0ee121a08f | ||
|
|
708bf4477a | ||
|
|
56a9d9a7f3 | ||
|
|
337fb96e3f | ||
|
|
c5fa53c00f | ||
|
|
e2dc7d24f9 | ||
|
|
d13dd9b3a7 | ||
|
|
aa0f13e348 | ||
|
|
725eab67f9 | ||
|
|
6efa37a14d | ||
|
|
f05ab61421 | ||
|
|
5217ad21ec | ||
|
|
55e570a2b2 | ||
|
|
f64617c88c | ||
|
|
ad4996e553 | ||
|
|
46febea6d3 | ||
|
|
da022a1cf0 | ||
|
|
9d11f2d881 | ||
|
|
d0521b83ae | ||
|
|
d8d6ab96df | ||
|
|
abefe8c21a | ||
|
|
0e0e1ed6fb | ||
|
|
05ec28be48 | ||
|
|
f5e110c212 | ||
|
|
2429d2b89d | ||
|
|
cf75cc3a7a | ||
|
|
f1c34efa97 | ||
|
|
f0c3a568f0 | ||
|
|
7fd1396cff | ||
|
|
522cc3a454 | ||
|
|
cbdbd7433b | ||
|
|
15b63401b7 | ||
|
|
7f5d513e6b | ||
|
|
e87f3399c7 | ||
|
|
fad8f2b272 | ||
|
|
bc33f10d0a | ||
|
|
0033dc881e | ||
|
|
9549e0dcde | ||
|
|
809146c070 | ||
|
|
546b982891 | ||
|
|
f8ac5af31b | ||
|
|
20babdc071 | ||
|
|
533edc6d05 | ||
|
|
3362a38d09 | ||
|
|
ac6c5ca570 | ||
|
|
ea7a8bef94 | ||
|
|
e74ceec858 | ||
|
|
e6150d405e | ||
|
|
86331c6abd | ||
|
|
3f866683a4 | ||
|
|
3affee539f | ||
|
|
513bfa91be | ||
|
|
e644b6cc74 | ||
|
|
10f0a04a6e | ||
|
|
6c5c1265d5 | ||
|
|
18df5dc5ed | ||
|
|
05903e34ff | ||
|
|
9b05fb60e4 | ||
|
|
d01a7977ae | ||
|
|
121e4ccad4 | ||
|
|
f1eaf09570 | ||
|
|
179befbcd5 | ||
|
|
036b7e25fa | ||
|
|
cf8c00fb54 | ||
|
|
5fd7529c90 | ||
|
|
be5efbb3ef | ||
|
|
3e287db1f9 | ||
|
|
71fac79876 | ||
|
|
f430120ff4 | ||
|
|
8844f8144b | ||
|
|
1b59b6a35f | ||
|
|
6a6e4181bd | ||
|
|
5e41d7db1e | ||
|
|
5e9fbf05f1 | ||
|
|
b4f58f3a6d | ||
|
|
2ec3e47f0a | ||
|
|
b6030c0bc5 | ||
|
|
4b8786bcaa | ||
|
|
bdefcd2442 | ||
|
|
4e4a642673 | ||
|
|
3634499e11 | ||
|
|
9f5d4f97df | ||
|
|
a0e6ebe0e0 | ||
|
|
65a557db90 | ||
|
|
d15a837139 | ||
|
|
d4cb97905e | ||
|
|
4208ed1ca6 | ||
|
|
4adb1da4d2 | ||
|
|
3ba1e9c187 | ||
|
|
81eec99b51 | ||
|
|
3ba3af8244 | ||
|
|
8ca6f71d57 | ||
|
|
10278a9088 | ||
|
|
155889a479 | ||
|
|
06d9c9d31c | ||
|
|
f309d54b08 | ||
|
|
0d2a667299 | ||
|
|
1dd8022d6a | ||
|
|
e29e6a5e8a | ||
|
|
e27237047e | ||
|
|
1b260f60cc | ||
|
|
882a8404d3 | ||
|
|
b797413ef0 | ||
|
|
298e30e857 | ||
|
|
6ba727b721 | ||
|
|
362557a964 | ||
|
|
c119911914 | ||
|
|
de2e86dc6e | ||
|
|
a2aa52f4cb | ||
|
|
a382a2c5a3 | ||
|
|
dd5a18c8fa | ||
|
|
e4e795c6dc | ||
|
|
2c65d92d69 | ||
|
|
d1b78856ca | ||
|
|
289e055014 | ||
|
|
81ba550043 | ||
|
|
8e5d8e5c96 | ||
|
|
5789f1f0fa | ||
|
|
9a523f4a01 | ||
|
|
fb3357fba4 | ||
|
|
69362ae415 | ||
|
|
de799d7b75 | ||
|
|
bb17e1de78 | ||
|
|
23013d6fe6 | ||
|
|
27d2fe8f27 | ||
|
|
504891f519 | ||
|
|
d1606c5e39 | ||
|
|
952beddcc3 | ||
|
|
9630adc74b | ||
|
|
27c171d09c | ||
|
|
bd79fe4d8d | ||
|
|
08f3756608 | ||
|
|
7cda7346b7 | ||
|
|
8cd0bddf6f | ||
|
|
8fa7fc922d | ||
|
|
6a2e48ac74 | ||
|
|
b68cfbed67 | ||
|
|
0bfbb87285 | ||
|
|
491ed09f6c | ||
|
|
658855aaee | ||
|
|
5ed8e180b3 | ||
|
|
e4477eaac4 | ||
|
|
64351f93be | ||
|
|
70a010976e | ||
|
|
d653530ba2 | ||
|
|
5242e8c4b0 | ||
|
|
efd2f7e77f | ||
|
|
16c632950d | ||
|
|
070d8544cd | ||
|
|
b562c1d730 | ||
|
|
fdeadda704 | ||
|
|
5ef720bfee | ||
|
|
0bc2b33e99 | ||
|
|
b9bcb6bbbf | ||
|
|
620f9b2717 | ||
|
|
2ff1512ed0 | ||
|
|
ca57f66b8b | ||
|
|
770f660db5 | ||
|
|
f54c2ee822 | ||
|
|
5172ce30d1 | ||
|
|
4e24ad87dd | ||
|
|
c49f9c88e2 | ||
|
|
4bf3783677 | ||
|
|
139a89770f | ||
|
|
c84a8a7dea | ||
|
|
2fb4efa531 | ||
|
|
1d2993e83e | ||
|
|
68f5ae7ed2 | ||
|
|
803b8dc5a4 | ||
|
|
b0913b2a4f | ||
|
|
8f9824a426 | ||
|
|
28baac78fb | ||
|
|
210b5dac33 | ||
|
|
2d31472fa1 | ||
|
|
a3b4d60dfb | ||
|
|
50bd74a173 | ||
|
|
9ea4b0dd93 | ||
|
|
ba3e416eb8 | ||
|
|
0af0f2a229 | ||
|
|
bca14ea369 | ||
|
|
6f1919bbe2 | ||
|
|
11401c755a | ||
|
|
69bbf7f72e | ||
|
|
524b6f2b1d | ||
|
|
2adb72be8e | ||
|
|
43ba550f72 | ||
|
|
3d606245f2 | ||
|
|
85da52ebbd | ||
|
|
9e7eb6243a | ||
|
|
9611fb631b | ||
|
|
44ddbc6ae5 | ||
|
|
f86fa99316 | ||
|
|
2603f04cfc | ||
|
|
029b4c2677 | ||
|
|
84d4ff7cfe | ||
|
|
a76e02cb8a | ||
|
|
f351b3b333 | ||
|
|
dfd13aea6f | ||
|
|
660a9801a4 | ||
|
|
9fda098f70 | ||
|
|
c02c8bf7e2 | ||
|
|
aa45a874b9 | ||
|
|
94f6261055 | ||
|
|
9c38c1a090 | ||
|
|
1d7519c3b7 | ||
|
|
6cf03bd679 | ||
|
|
f3670ca629 | ||
|
|
3ce838e344 | ||
|
|
e8228c149a | ||
|
|
ef3bda9972 | ||
|
|
66df4412b0 | ||
|
|
db1c4afcd6 | ||
|
|
5ea09516ef | ||
|
|
a0911d8752 | ||
|
|
47d81ce721 | ||
|
|
6fe6dbb333 | ||
|
|
53f45e599a | ||
|
|
6b4ef219c9 | ||
|
|
d496485f75 | ||
|
|
d6bb70ba4a | ||
|
|
1f0cbad800 | ||
|
|
362375a733 | ||
|
|
92d39c5abc | ||
|
|
c4f0e6e46b | ||
|
|
6fcfe7f4e5 | ||
|
|
633024f8c5 | ||
|
|
92a4c1f64a | ||
|
|
1f43de06f8 | ||
|
|
5d5ac5c5d5 | ||
|
|
04b06d875b | ||
|
|
5d795edd31 | ||
|
|
fd390bbd37 | ||
|
|
3b78169f8c | ||
|
|
ba7317b4eb | ||
|
|
85b8648724 | ||
|
|
55162829e7 | ||
|
|
27590f623f | ||
|
|
bc2fc83599 | ||
|
|
cb78723a96 | ||
|
|
a513c27dce | ||
|
|
f814d1df19 | ||
|
|
5455855e65 | ||
|
|
eff7f36866 | ||
|
|
12056f4cad | ||
|
|
d96ab483a4 | ||
|
|
43dbb4ac70 | ||
|
|
dc6c379aa2 | ||
|
|
8cacd4d984 | ||
|
|
1342a06f43 | ||
|
|
55da8c1ce2 | ||
|
|
6adfef2a48 | ||
|
|
beb6d9535c | ||
|
|
8827434cce | ||
|
|
9845175d29 | ||
|
|
ddf01648b4 | ||
|
|
16f4849323 | ||
|
|
4ae45cd727 | ||
|
|
b0a376aa2b | ||
|
|
d45c97cef0 | ||
|
|
ec40320a2d | ||
|
|
7eb97fcba0 | ||
|
|
73d1805ce6 | ||
|
|
c7f9a6ebc4 | ||
|
|
a2f632beb4 | ||
|
|
567698356e | ||
|
|
887f6a0d0e | ||
|
|
0191135b10 | ||
|
|
8b596ec951 | ||
|
|
4863edc042 | ||
|
|
dc6d85bc34 | ||
|
|
1c48636155 | ||
|
|
bd5f8a9439 | ||
|
|
18850b8d41 | ||
|
|
47bab2a9ed | ||
|
|
9e8b11c34f | ||
|
|
74bfb57d3a | ||
|
|
9fb56a2856 | ||
|
|
9a098c59df | ||
|
|
c781bc3166 | ||
|
|
a027a3d3d5 | ||
|
|
ee37dc499b | ||
|
|
450df679cd | ||
|
|
c9ccef90f3 | ||
|
|
3b2feca0dd | ||
|
|
edfc5d5949 | ||
|
|
e00c7722f1 | ||
|
|
fd2fc3db34 | ||
|
|
5111bb541a | ||
|
|
1d25415c21 | ||
|
|
a423b41e68 | ||
|
|
3e2314a269 | ||
|
|
c143d10130 | ||
|
|
d5407466d5 | ||
|
|
052c042dac | ||
|
|
72b64127c7 | ||
|
|
a938b3523f | ||
|
|
915d5d993b | ||
|
|
e2044338c8 |
295
.github/workflows/00-pr-scanner.yaml
vendored
295
.github/workflows/00-pr-scanner.yaml
vendored
@@ -1,68 +1,243 @@
|
||||
name: 00-pr_scanner
|
||||
permissions: read-all
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths-ignore:
|
||||
- '**.yaml'
|
||||
- '**.yml'
|
||||
- '**.md'
|
||||
- '**.sh'
|
||||
- 'website/*'
|
||||
- 'examples/*'
|
||||
- 'docs/*'
|
||||
- 'build/*'
|
||||
- '.github/*'
|
||||
workflow_dispatch: {}
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths-ignore:
|
||||
- "**.yaml"
|
||||
- "**.yml"
|
||||
- "**.md"
|
||||
- "**.sh"
|
||||
- "website/*"
|
||||
- "examples/*"
|
||||
- "docs/*"
|
||||
- "build/*"
|
||||
- ".github/*"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
pr-scanner:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
contents: read
|
||||
deployments: read
|
||||
id-token: write
|
||||
issues: read
|
||||
discussions: read
|
||||
packages: read
|
||||
pages: read
|
||||
pull-requests: write
|
||||
repository-projects: read
|
||||
security-events: read
|
||||
statuses: read
|
||||
uses: ./.github/workflows/a-pr-scanner.yaml
|
||||
with:
|
||||
RELEASE: ""
|
||||
CLIENT: test
|
||||
secrets: inherit
|
||||
pr-scanner:
|
||||
permissions:
|
||||
actions: read
|
||||
artifact-metadata: read
|
||||
attestations: read
|
||||
checks: read
|
||||
contents: write
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
models: read
|
||||
packages: read
|
||||
pages: read
|
||||
pull-requests: write
|
||||
repository-projects: read
|
||||
security-events: read
|
||||
statuses: read
|
||||
uses: ./.github/workflows/a-pr-scanner.yaml
|
||||
with:
|
||||
RELEASE: ""
|
||||
CLIENT: test
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: ""
|
||||
secrets: inherit
|
||||
|
||||
binary-build:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
contents: read
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
security-events: read
|
||||
statuses: read
|
||||
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
|
||||
with:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 1
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.20"
|
||||
RELEASE: "latest"
|
||||
CLIENT: test
|
||||
ARCH_MATRIX: '[ "" ]'
|
||||
OS_MATRIX: '[ "ubuntu-20.04" ]'
|
||||
secrets: inherit
|
||||
wf-preparation:
|
||||
name: secret-validator
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
|
||||
|
||||
steps:
|
||||
- name: check if the necessary secrets are set in github secrets
|
||||
id: check-secret-set
|
||||
env:
|
||||
CUSTOMER: ${{ secrets.CUSTOMER }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.PASSWORD }}
|
||||
CLIENT_ID: ${{ secrets.CLIENT_ID_PROD }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY_PROD }}
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: "echo \"is-secret-set=${{ env.CUSTOMER != '' && env.USERNAME != '' && env.PASSWORD != '' && env.CLIENT_ID != '' && env.SECRET_KEY != '' && env.REGISTRY_USERNAME != '' && env.REGISTRY_PASSWORD != '' }}\" >> $GITHUB_OUTPUT\n"
|
||||
|
||||
|
||||
run-system-tests:
|
||||
needs: [wf-preparation, pr-scanner]
|
||||
if: ${{ (needs.wf-preparation.outputs.is-secret-set == 'true') && (always() && (contains(needs.*.result, 'success') || contains(needs.*.result, 'skipped')) && !(contains(needs.*.result, 'failure')) && !(contains(needs.*.result, 'cancelled'))) }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Set dispatch info
|
||||
id: dispatch-info
|
||||
run: |
|
||||
# Correlation ID WITHOUT attempt - so re-runs can find the original run
|
||||
CORRELATION_ID="${GITHUB_REPOSITORY##*/}-${{ github.run_id }}"
|
||||
echo "correlation_id=${CORRELATION_ID}" >> "$GITHUB_OUTPUT"
|
||||
echo "Correlation ID: ${CORRELATION_ID}, Attempt: ${{ github.run_attempt }}"
|
||||
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.E2E_DISPATCH_APP_ID }}
|
||||
private-key: ${{ secrets.E2E_DISPATCH_APP_PRIVATE_KEY }}
|
||||
owner: armosec
|
||||
repositories: shared-workflows
|
||||
|
||||
- name: Dispatch system tests to private repo
|
||||
if: ${{ github.run_attempt == 1 }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
CORRELATION_ID: ${{ steps.dispatch-info.outputs.correlation_id }}
|
||||
KS_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||
run: |
|
||||
echo "Dispatching E2E tests with correlation_id: ${CORRELATION_ID}"
|
||||
echo "Using test group: KUBESCAPE_CLI_E2E"
|
||||
|
||||
gh api "repos/armosec/shared-workflows/dispatches" \
|
||||
-f event_type="e2e-test-trigger" \
|
||||
-f "client_payload[correlation_id]=${CORRELATION_ID}" \
|
||||
-f "client_payload[github_repository]=${GITHUB_REPOSITORY}" \
|
||||
-f "client_payload[environment]=production" \
|
||||
-f "client_payload[tests_groups]=KUBESCAPE_CLI_E2E" \
|
||||
-f "client_payload[systests_branch]=master" \
|
||||
-f "client_payload[ks_branch]=${KS_BRANCH}"
|
||||
|
||||
echo "Dispatch completed"
|
||||
|
||||
- name: Find E2E workflow run
|
||||
id: find-run
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
CORRELATION_ID: ${{ steps.dispatch-info.outputs.correlation_id }}
|
||||
run: |
|
||||
for i in {1..15}; do
|
||||
run_id=$(gh api "repos/armosec/shared-workflows/actions/runs?event=repository_dispatch&per_page=30" \
|
||||
--jq '.workflow_runs | map(select(.name | contains("'"$CORRELATION_ID"'"))) | first | .id // empty')
|
||||
|
||||
if [ -n "$run_id" ]; then
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
gh api "repos/armosec/shared-workflows/actions/runs/${run_id}" --jq '"url=" + .html_url' >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "Attempt $i: waiting for run..."
|
||||
sleep $((i < 5 ? 10 : 30))
|
||||
done
|
||||
echo "::error::Could not find workflow run"
|
||||
exit 1
|
||||
|
||||
- name: Re-run failed jobs in private repo
|
||||
id: rerun
|
||||
if: ${{ github.run_attempt > 1 }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
RUN_ID: ${{ steps.find-run.outputs.run_id }}
|
||||
run: |
|
||||
conclusion=$(gh api "repos/armosec/shared-workflows/actions/runs/${RUN_ID}" --jq '.conclusion')
|
||||
echo "Previous conclusion: $conclusion"
|
||||
|
||||
if [ "$conclusion" = "success" ]; then
|
||||
echo "Previous run passed. Nothing to re-run."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Full rerun if cancelled, partial if failed
|
||||
if [ "$conclusion" = "cancelled" ]; then
|
||||
echo "Run was cancelled - triggering full re-run"
|
||||
gh api --method POST "repos/armosec/shared-workflows/actions/runs/${RUN_ID}/rerun"
|
||||
else
|
||||
echo "Re-running failed jobs only"
|
||||
gh api --method POST "repos/armosec/shared-workflows/actions/runs/${RUN_ID}/rerun-failed-jobs"
|
||||
fi
|
||||
|
||||
# Wait for status to flip from 'completed'
|
||||
for i in {1..30}; do
|
||||
[ "$(gh api "repos/armosec/shared-workflows/actions/runs/${RUN_ID}" --jq '.status')" != "completed" ] && break
|
||||
sleep 2
|
||||
done
|
||||
|
||||
- name: Wait for E2E tests to complete
|
||||
if: ${{ steps.rerun.outputs.skip != 'true' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
RUN_ID: ${{ steps.find-run.outputs.run_id }}
|
||||
URL: ${{ steps.find-run.outputs.url }}
|
||||
run: |
|
||||
echo "Monitoring: ${URL}"
|
||||
|
||||
for i in {1..60}; do # 60 iterations × 60s = 1 hour max
|
||||
read status conclusion < <(gh api "repos/armosec/shared-workflows/actions/runs/${RUN_ID}" \
|
||||
--jq '[.status, .conclusion // "null"] | @tsv')
|
||||
|
||||
echo "Status: ${status} | Conclusion: ${conclusion}"
|
||||
|
||||
if [ "$status" = "completed" ]; then
|
||||
if [ "$conclusion" = "success" ]; then
|
||||
echo "E2E tests passed!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::error::E2E tests failed: ${conclusion}"
|
||||
echo ""
|
||||
|
||||
# Get failed job IDs to a file first
|
||||
gh api "repos/armosec/shared-workflows/actions/runs/${RUN_ID}/jobs" \
|
||||
--jq '.jobs[] | select(.conclusion == "failure") | [.id, .name, (.steps[] | select(.conclusion == "failure") | .name)] | @tsv' > /tmp/failed_jobs.txt
|
||||
|
||||
# Process each failed job
|
||||
while IFS=$'\t' read -r job_id job_name step_name; do
|
||||
# Extract test name: "run-helm-e2e / ST (relevancy_python)" → "relevancy_python"
|
||||
test_name=$(echo "$job_name" | sed 's/.*(\(.*\))/\1/')
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "${job_name}"
|
||||
echo " Step: ${step_name}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Fetch logs to temp file
|
||||
gh api "repos/armosec/shared-workflows/actions/jobs/${job_id}/logs" 2>/dev/null > /tmp/job_logs.txt
|
||||
|
||||
# Show summary in console
|
||||
grep -E "(ERROR|FAILURE)" /tmp/job_logs.txt | tail -10
|
||||
echo ""
|
||||
|
||||
# Save to separate file per test
|
||||
log_file="failed_${test_name}.txt"
|
||||
echo "════════════════════════════════════════" > "$log_file"
|
||||
echo "${job_name}" >> "$log_file"
|
||||
echo " Step: ${step_name}" >> "$log_file"
|
||||
echo "════════════════════════════════════════" >> "$log_file"
|
||||
last_endgroup=$(grep -n "##\\[endgroup\\]" /tmp/job_logs.txt | tail -1 | cut -d: -f1)
|
||||
if [ -n "$last_endgroup" ]; then
|
||||
tail -n +$((last_endgroup + 1)) /tmp/job_logs.txt >> "$log_file"
|
||||
else
|
||||
tail -500 /tmp/job_logs.txt >> "$log_file"
|
||||
fi
|
||||
done < /tmp/failed_jobs.txt
|
||||
|
||||
echo "View full logs: ${URL}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 60
|
||||
done
|
||||
|
||||
echo "::error::Timeout waiting for tests"
|
||||
exit 1
|
||||
|
||||
- name: Upload failed step logs
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: failed-e2e-logs-attempt-${{ github.run_attempt }}
|
||||
path: failed_*.txt
|
||||
retention-days: 7
|
||||
|
||||
175
.github/workflows/02-release.yaml
vendored
175
.github/workflows/02-release.yaml
vendored
@@ -3,44 +3,16 @@ permissions: read-all
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*-rc.*'
|
||||
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
skip_publish:
|
||||
description: "Skip publishing artifacts"
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
jobs:
|
||||
retag:
|
||||
outputs:
|
||||
NEW_TAG: ${{ steps.tag-calculator.outputs.NEW_TAG }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
|
||||
- id: tag-calculator
|
||||
uses: ./.github/actions/tag-action
|
||||
with:
|
||||
SUB_STRING: "-rc"
|
||||
binary-build:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
contents: read
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
security-events: read
|
||||
statuses: read
|
||||
needs: [retag]
|
||||
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
|
||||
with:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 1
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.20"
|
||||
RELEASE: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
CLIENT: release
|
||||
secrets: inherit
|
||||
create-release:
|
||||
release:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
@@ -49,40 +21,105 @@ jobs:
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: read
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
statuses: read
|
||||
security-events: read
|
||||
needs: [retag, binary-build]
|
||||
uses: ./.github/workflows/c-create-release.yaml
|
||||
with:
|
||||
RELEASE_NAME: "Release ${{ needs.retag.outputs.NEW_TAG }}"
|
||||
TAG: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
DRAFT: false
|
||||
secrets: inherit
|
||||
publish-image:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
contents: read
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
models: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
security-events: read
|
||||
statuses: read
|
||||
uses: ./.github/workflows/d-publish-image.yaml
|
||||
needs: [create-release, retag]
|
||||
with:
|
||||
client: "image-release"
|
||||
image_name: "quay.io/${{ github.repository_owner }}/kubescape-cli"
|
||||
image_tag: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
support_platforms: true
|
||||
cosign: true
|
||||
secrets: inherit
|
||||
security-events: read
|
||||
attestations: read
|
||||
artifact-metadata: read
|
||||
runs-on: ubuntu-large
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.25"
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Install system dependencies for system-tests
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
libpq5 \
|
||||
libpq-dev \
|
||||
gcc \
|
||||
python3-dev
|
||||
sudo rm -rf /var/lib/apt/lists/*
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.5.0
|
||||
|
||||
- name: Create Cosign Key
|
||||
run: echo "${{ secrets.COSIGN_PRIVATE_KEY_V1 }}" > cosign.key
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Quay.io
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
|
||||
- uses: anchore/sbom-action/download-syft@v0
|
||||
name: Setup Syft
|
||||
|
||||
- name: Create k8s Kind Cluster
|
||||
uses: helm/kind-action@v1.10.0
|
||||
with:
|
||||
cluster_name: kubescape-e2e
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean ${{ inputs.skip_publish == true && '--skip=publish' || '' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
COSIGN_PWD: ${{ secrets.COSIGN_PRIVATE_KEY_V1_PASSWORD }}
|
||||
RELEASE: ${{ github.ref_name }}
|
||||
CLIENT: release
|
||||
RUN_E2E: "true"
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
CUSTOMER: ${{ secrets.CUSTOMER }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.PASSWORD }}
|
||||
CLIENT_ID: ${{ secrets.CLIENT_ID_PROD }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY_PROD }}
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Update new version in krew-index
|
||||
if: github.event_name != 'workflow_dispatch' || inputs.skip_publish != true
|
||||
uses: rajatjindal/krew-release-bot@v0.0.47
|
||||
with:
|
||||
krew_template_file: .krew.yaml
|
||||
|
||||
- name: List collected system-test results (debug)
|
||||
if: always()
|
||||
run: |
|
||||
echo "Listing test-results/system-tests (if any):"
|
||||
ls -laR test-results/system-tests || true
|
||||
|
||||
- name: System Tests Report
|
||||
uses: mikepenz/action-junit-report@v5
|
||||
if: always()
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
report_paths: "test-results/system-tests/**/results_xml_format/**.xml"
|
||||
annotate_only: true
|
||||
job_summary: true
|
||||
|
||||
42
.github/workflows/03-post-release.yaml
vendored
42
.github/workflows/03-post-release.yaml
vendored
@@ -1,42 +0,0 @@
|
||||
name: 03-post_release
|
||||
permissions: read-all
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
branches:
|
||||
- 'master'
|
||||
- 'main'
|
||||
jobs:
|
||||
post_release:
|
||||
name: Post release jobs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Digest
|
||||
uses: MCJack123/ghaction-generate-release-hashes@c03f3111b39432dde3edebe401c5a8d1ffbbf917 # ratchet:MCJack123/ghaction-generate-release-hashes@v1
|
||||
with:
|
||||
hash-type: sha1
|
||||
file-name: kubescape-release-digests
|
||||
- name: Invoke workflow to update packaging
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
if: github.repository_owner == 'kubescape'
|
||||
with:
|
||||
workflow: release.yml
|
||||
repo: kubescape/packaging
|
||||
ref: refs/heads/main
|
||||
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
- name: Invoke workflow to update homebrew tap
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
if: github.repository_owner == 'kubescape'
|
||||
with:
|
||||
workflow: release.yml
|
||||
repo: kubescape/homebrew-tap
|
||||
ref: refs/heads/main
|
||||
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
- name: Invoke workflow to update github action
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
if: github.repository_owner == 'kubescape'
|
||||
with:
|
||||
workflow: release.yaml
|
||||
repo: kubescape/github-action
|
||||
ref: refs/heads/main
|
||||
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
17
.github/workflows/04-publish-krew-plugin.yaml
vendored
17
.github/workflows/04-publish-krew-plugin.yaml
vendored
@@ -1,17 +0,0 @@
|
||||
name: 04-publish_krew_plugin
|
||||
permissions: read-all
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
jobs:
|
||||
publish_krew_plugin:
|
||||
name: Publish Krew plugin
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'kubescape'
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update new version in krew-index
|
||||
uses: rajatjindal/krew-release-bot@92da038bbf995803124a8e50ebd438b2f37bbbb0 # ratchet:rajatjindal/krew-release-bot@v0.0.43
|
||||
106
.github/workflows/a-pr-scanner.yaml
vendored
106
.github/workflows/a-pr-scanner.yaml
vendored
@@ -15,76 +15,62 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: "./..."
|
||||
GO111MODULE:
|
||||
required: true
|
||||
type: string
|
||||
CGO_ENABLED:
|
||||
type: number
|
||||
default: 1
|
||||
jobs:
|
||||
scanners:
|
||||
unit-tests:
|
||||
if: ${{ github.actor != 'kubescape' }}
|
||||
name: Create cross-platform build
|
||||
env:
|
||||
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
name: PR Scanner
|
||||
runs-on: ubuntu-latest
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
runs-on: ubuntu-large
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # Install go because go-licenses use it ratchet:actions/setup-go@v3
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
name: Installing go
|
||||
with:
|
||||
go-version: '1.20'
|
||||
cache: true
|
||||
- name: Scanning - Forbidden Licenses (go-licenses)
|
||||
id: licenses-scan
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo "## Installing go-licenses tool"
|
||||
go install github.com/google/go-licenses@latest
|
||||
echo "## Scanning for forbiden licenses ##"
|
||||
go-licenses check .
|
||||
- name: Scanning - Credentials (GitGuardian)
|
||||
if: ${{ env.GITGUARDIAN_API_KEY }}
|
||||
continue-on-error: true
|
||||
id: credentials-scan
|
||||
uses: GitGuardian/ggshield-action@4ab2994172fadab959240525e6b833d9ae3aca61 # ratchet:GitGuardian/ggshield-action@master
|
||||
go-version: ${{ inputs.GO_VERSION }}
|
||||
|
||||
- name: Test core pkg
|
||||
run: ${{ env.DOCKER_CMD }} go test -v ./...
|
||||
if: startsWith(github.ref, 'refs/tags')
|
||||
|
||||
- name: Test httphandler pkg
|
||||
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test -v ./...'
|
||||
if: startsWith(github.ref, 'refs/tags')
|
||||
|
||||
- uses: anchore/sbom-action/download-syft@v0
|
||||
name: Setup Syft
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v6
|
||||
name: Build
|
||||
with:
|
||||
args: -v --all-policies
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: build --clean --snapshot --single-target
|
||||
env:
|
||||
GITHUB_PUSH_BEFORE_SHA: ${{ github.event.before }}
|
||||
GITHUB_PUSH_BASE_SHA: ${{ github.event.base }}
|
||||
GITHUB_PULL_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
|
||||
- name: Scanning - Vulnerabilities (Snyk)
|
||||
if: ${{ env.SNYK_TOKEN }}
|
||||
id: vulnerabilities-scan
|
||||
continue-on-error: true
|
||||
uses: snyk/actions/golang@806182742461562b67788a64410098c9d9b96adb # ratchet:snyk/actions/golang@master
|
||||
with:
|
||||
command: test --all-projects
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
CLIENT: ${{ inputs.CLIENT }}
|
||||
CGO_ENABLED: ${{ inputs.CGO_ENABLED }}
|
||||
|
||||
- name: Smoke Testing
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ${PWD}/dist/cli_linux_amd64_v1/kubescape
|
||||
|
||||
- name: Test coverage
|
||||
id: unit-test
|
||||
run: go test -v ${{ inputs.UNIT_TESTS_PATH }} -covermode=count -coverprofile=coverage.out
|
||||
|
||||
- name: Convert coverage count to lcov format
|
||||
uses: jandelgado/gcov2lcov-action@v1
|
||||
|
||||
- name: Submit coverage tests to Coveralls
|
||||
continue-on-error: true
|
||||
uses: coverallsapp/github-action@v1
|
||||
- name: golangci-lint
|
||||
continue-on-error: false
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: coverage.lcov
|
||||
|
||||
- name: Comment results to PR
|
||||
continue-on-error: true # Warning: This might break opening PRs from forks
|
||||
uses: peter-evans/create-or-update-comment@5adcb0bb0f9fb3f95ef05400558bdb3f329ee808 # ratchet:peter-evans/create-or-update-comment@v2.1.0
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
Scan results:
|
||||
- License scan: ${{ steps.licenses-scan.outcome }}
|
||||
- Credentials scan: ${{ steps.credentials-scan.outcome }}
|
||||
- Vulnerabilities scan: ${{ steps.vulnerabilities-scan.outcome }}
|
||||
reactions: 'eyes'
|
||||
args: --timeout 10m
|
||||
only-new-issues: true
|
||||
|
||||
387
.github/workflows/b-binary-build-and-e2e-tests.yaml
vendored
387
.github/workflows/b-binary-build-and-e2e-tests.yaml
vendored
@@ -1,387 +0,0 @@
|
||||
name: b-binary-build-and-e2e-tests
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
COMPONENT_NAME:
|
||||
required: false
|
||||
type: string
|
||||
default: "kubescape"
|
||||
RELEASE:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
CLIENT:
|
||||
required: false
|
||||
type: string
|
||||
default: "test"
|
||||
GO_VERSION:
|
||||
required: false
|
||||
type: string
|
||||
default: "1.20"
|
||||
GO111MODULE:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
CGO_ENABLED:
|
||||
type: number
|
||||
default: 1
|
||||
required: false
|
||||
OS_MATRIX:
|
||||
type: string
|
||||
required: false
|
||||
default: '[ "ubuntu-20.04", "macos-latest", "windows-latest"]'
|
||||
ARCH_MATRIX:
|
||||
type: string
|
||||
required: false
|
||||
default: '[ "", "arm64"]'
|
||||
BINARY_TESTS:
|
||||
type: string
|
||||
required: false
|
||||
default: '[ "scan_nsa", "scan_mitre", "scan_with_exceptions", "scan_repository", "scan_local_file", "scan_local_glob_files", "scan_local_list_of_files", "scan_nsa_and_submit_to_backend", "scan_mitre_and_submit_to_backend", "scan_local_repository_and_submit_to_backend", "scan_repository_from_url_and_submit_to_backend", "scan_with_exception_to_backend", "scan_with_custom_framework", "scan_customer_configuration", "host_scanner", "scan_compliance_score", "control_cluster_from_CLI_config_scan_exclude_namespaces", "control_cluster_from_CLI_config_scan_include_namespaces", "control_cluster_from_CLI_config_scan_host_scanner_enabled", "control_cluster_from_CLI_config_scan_MITRE_framework", "control_cluster_from_CLI_vulnerabilities_scan_default", "control_cluster_from_CLI_vulnerabilities_scan_include_namespaces" ]'
|
||||
|
||||
workflow_call:
|
||||
inputs:
|
||||
COMPONENT_NAME:
|
||||
required: true
|
||||
type: string
|
||||
RELEASE:
|
||||
required: true
|
||||
type: string
|
||||
CLIENT:
|
||||
required: true
|
||||
type: string
|
||||
GO_VERSION:
|
||||
type: string
|
||||
default: "1.20"
|
||||
GO111MODULE:
|
||||
required: true
|
||||
type: string
|
||||
CGO_ENABLED:
|
||||
type: number
|
||||
default: 1
|
||||
BINARY_TESTS:
|
||||
type: string
|
||||
default: '[ "scan_nsa", "scan_mitre", "scan_with_exceptions", "scan_repository", "scan_local_file", "scan_local_glob_files", "scan_local_list_of_files", "scan_nsa_and_submit_to_backend", "scan_mitre_and_submit_to_backend", "scan_local_repository_and_submit_to_backend", "scan_repository_from_url_and_submit_to_backend", "scan_with_exception_to_backend", "scan_with_custom_framework", "scan_customer_configuration", "host_scanner", "scan_compliance_score", "scan_custom_framework_scanning_file_scope_testing", "scan_custom_framework_scanning_cluster_scope_testing", "scan_custom_framework_scanning_cluster_and_file_scope_testing", "unified_configuration_config_view", "unified_configuration_config_set", "unified_configuration_config_delete" ]'
|
||||
OS_MATRIX:
|
||||
type: string
|
||||
required: false
|
||||
default: '[ "ubuntu-20.04", "macos-latest", "windows-latest"]'
|
||||
ARCH_MATRIX:
|
||||
type: string
|
||||
required: false
|
||||
default: '[ "", "arm64"]'
|
||||
|
||||
jobs:
|
||||
wf-preparation:
|
||||
name: secret-validator
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
TEST_NAMES: ${{ steps.export_tests_to_env.outputs.TEST_NAMES }}
|
||||
OS_MATRIX: ${{ steps.export_os_to_env.outputs.OS_MATRIX }}
|
||||
ARCH_MATRIX: ${{ steps.export_arch_to_env.outputs.ARCH_MATRIX }}
|
||||
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
|
||||
|
||||
steps:
|
||||
- name: check if the necessary secrets are set in github secrets
|
||||
id: check-secret-set
|
||||
env:
|
||||
CUSTOMER: ${{ secrets.CUSTOMER }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.PASSWORD }}
|
||||
CLIENT_ID: ${{ secrets.CLIENT_ID_PROD }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY_PROD }}
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: "echo \"is-secret-set=${{ env.CUSTOMER != '' && \n env.USERNAME != '' &&\n env.PASSWORD != '' &&\n env.CLIENT_ID != '' &&\n env.SECRET_KEY != '' &&\n env.REGISTRY_USERNAME != '' &&\n env.REGISTRY_PASSWORD != ''\n }}\" >> $GITHUB_OUTPUT\n"
|
||||
|
||||
- id: export_os_to_env
|
||||
name: set test name
|
||||
run: |
|
||||
echo "OS_MATRIX=$input" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
input: ${{ inputs.OS_MATRIX }}
|
||||
|
||||
- id: export_tests_to_env
|
||||
name: set test name
|
||||
run: |
|
||||
echo "TEST_NAMES=$input" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
input: ${{ inputs.BINARY_TESTS }}
|
||||
|
||||
- id: export_arch_to_env
|
||||
name: set test name
|
||||
run: |
|
||||
echo "ARCH_MATRIX=$input" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
input: ${{ inputs.ARCH_MATRIX }}
|
||||
|
||||
check-secret:
|
||||
name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
|
||||
steps:
|
||||
- name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
|
||||
id: check-secret-set
|
||||
env:
|
||||
QUAYIO_REGISTRY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
|
||||
|
||||
binary-build:
|
||||
name: Create cross-platform build
|
||||
needs: wf-preparation
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: ${{ fromJson(needs.wf-preparation.outputs.OS_MATRIX) }}
|
||||
arch: ${{ fromJson(needs.wf-preparation.outputs.ARCH_MATRIX) }}
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
arch: arm64
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Cache Go modules (Linux)
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # ratchet:actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Cache Go modules (macOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # ratchet:actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/Library/Caches/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Cache Go modules (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # ratchet:actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~\AppData\Local\go-build
|
||||
~\go\pkg\mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # ratchet:actions/setup-go@v3
|
||||
name: Installing go
|
||||
with:
|
||||
go-version: ${{ inputs.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: start ${{ matrix.arch }} environment in container
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y binfmt-support qemu-user-static
|
||||
sudo docker run --platform linux/${{ matrix.arch }} -e RELEASE=${{ inputs.RELEASE }} \
|
||||
-e CLIENT=${{ inputs.CLIENT }} -e CGO_ENABLED=${{ inputs.CGO_ENABLED }} \
|
||||
-e KUBESCAPE_SKIP_UPDATE_CHECK=true -e GOARCH=${{ matrix.arch }} -v ${PWD}:/work \
|
||||
-w /work -v ~/go/pkg/mod:/root/go/pkg/mod -v ~/.cache/go-build:/root/.cache/go-build \
|
||||
-d --name build golang:${{ inputs.GO_VERSION }}-bullseye sleep 21600
|
||||
sudo docker ps
|
||||
DOCKER_CMD="sudo docker exec build"
|
||||
${DOCKER_CMD} apt update
|
||||
${DOCKER_CMD} apt install -y cmake python3
|
||||
${DOCKER_CMD} git config --global --add safe.directory '*'
|
||||
echo "DOCKER_CMD=${DOCKER_CMD}" >> $GITHUB_ENV;
|
||||
if: matrix.os == 'ubuntu-20.04' && matrix.arch != ''
|
||||
|
||||
- name: Install pkg-config (macOS)
|
||||
run: brew install pkg-config
|
||||
if: matrix.os == 'macos-latest'
|
||||
|
||||
- name: Install libgit2 (Linux/macOS)
|
||||
run: ${{ env.DOCKER_CMD }} make libgit2${{ matrix.arch }}
|
||||
if: matrix.os != 'windows-latest'
|
||||
|
||||
- name: Test core pkg
|
||||
run: ${{ env.DOCKER_CMD }} go test -v ./...
|
||||
if: "!startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-20.04' && matrix.arch == '' || startsWith(github.ref, 'refs/tags') && (matrix.os != 'macos-latest' || matrix.arch != 'arm64')"
|
||||
|
||||
- name: Test httphandler pkg
|
||||
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test -v ./...'
|
||||
if: "!startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-20.04' && matrix.arch == '' || startsWith(github.ref, 'refs/tags') && (matrix.os != 'macos-latest' || matrix.arch != 'arm64')"
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
CLIENT: ${{ inputs.CLIENT }}
|
||||
CGO_ENABLED: ${{ inputs.CGO_ENABLED }}
|
||||
run: ${{ env.DOCKER_CMD }} python3 --version && ${{ env.DOCKER_CMD }} python3 build.py
|
||||
|
||||
- name: Smoke Testing (Windows / MacOS)
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/kubescape-${{ matrix.os }}
|
||||
if: startsWith(github.ref, 'refs/tags') && matrix.os != 'ubuntu-20.04' && matrix.arch == ''
|
||||
|
||||
- name: Smoke Testing (Linux amd64)
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ${PWD}/build/kubescape-ubuntu-latest
|
||||
if: matrix.os == 'ubuntu-20.04' && matrix.arch == ''
|
||||
|
||||
- name: Smoke Testing (Linux ${{ matrix.arch }})
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ./build/kubescape-${{ matrix.arch }}-ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-20.04' && matrix.arch != ''
|
||||
|
||||
- name: golangci-lint
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
continue-on-error: true
|
||||
uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5 # ratchet:golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout 10m --build-tags=static
|
||||
only-new-issues: true
|
||||
|
||||
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # ratchet:actions/upload-artifact@v3.1.1
|
||||
name: Upload artifact (Linux)
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
with:
|
||||
name: kubescape${{ matrix.arch }}-ubuntu-latest
|
||||
path: build/
|
||||
if-no-files-found: error
|
||||
|
||||
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # ratchet:actions/upload-artifact@v3.1.1
|
||||
name: Upload artifact (MacOS, Win)
|
||||
if: matrix.os != 'ubuntu-20.04'
|
||||
with:
|
||||
name: kubescape${{ matrix.arch }}-${{ matrix.os }}
|
||||
path: build/
|
||||
if-no-files-found: error
|
||||
|
||||
build-http-image:
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
packages: write
|
||||
pull-requests: read
|
||||
needs: [check-secret]
|
||||
uses: kubescape/workflows/.github/workflows/incluster-comp-pr-merged.yaml@main
|
||||
with:
|
||||
IMAGE_NAME: quay.io/${{ github.repository_owner }}/kubescape
|
||||
IMAGE_TAG: ${{ inputs.RELEASE }}
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: "on"
|
||||
BUILD_PLATFORM: linux/amd64,linux/arm64
|
||||
GO_VERSION: "1.20"
|
||||
REQUIRED_TESTS: '[
|
||||
"ks_microservice_create_2_cronjob_mitre_and_nsa_proxy",
|
||||
"ks_microservice_triggering_with_cron_job",
|
||||
"ks_microservice_update_cronjob_schedule",
|
||||
"ks_microservice_delete_cronjob",
|
||||
"ks_microservice_create_2_cronjob_mitre_and_nsa",
|
||||
"ks_microservice_ns_creation",
|
||||
"ks_microservice_on_demand",
|
||||
"ks_microservice_mitre_framework_on_demand",
|
||||
"ks_microservice_nsa_and_mitre_framework_demand",
|
||||
"scan_compliance_score"
|
||||
]'
|
||||
COSIGN: true
|
||||
HELM_E2E_TEST: true
|
||||
FORCE: true
|
||||
secrets: inherit
|
||||
|
||||
run-tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
TEST: ${{ fromJson(needs.wf-preparation.outputs.TEST_NAMES) }}
|
||||
needs: [wf-preparation, binary-build]
|
||||
if: ${{ (needs.wf-preparation.outputs.is-secret-set == 'true') && (always() && (contains(needs.*.result, 'success') || contains(needs.*.result, 'skipped')) && !(contains(needs.*.result, 'failure')) && !(contains(needs.*.result, 'cancelled'))) }}
|
||||
runs-on: ubuntu-latest # This cannot change
|
||||
steps:
|
||||
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3.0.2
|
||||
id: download-artifact
|
||||
with:
|
||||
name: kubescape-ubuntu-latest
|
||||
path: "~"
|
||||
|
||||
- run: ls -laR
|
||||
|
||||
- name: chmod +x
|
||||
run: chmod +x -R ${{steps.download-artifact.outputs.download-path}}/kubescape-ubuntu-latest
|
||||
|
||||
- name: Checkout systests repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
|
||||
with:
|
||||
repository: armosec/system-tests
|
||||
path: .
|
||||
|
||||
- uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # ratchet:actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.8.13'
|
||||
cache: 'pip'
|
||||
|
||||
- name: create env
|
||||
run: ./create_env.sh
|
||||
|
||||
- name: Generate uuid
|
||||
id: uuid
|
||||
run: |
|
||||
echo "RANDOM_UUID=$(uuidgen)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create k8s Kind Cluster
|
||||
id: kind-cluster-install
|
||||
uses: helm/kind-action@d08cf6ff1575077dee99962540d77ce91c62387d # ratchet:helm/kind-action@v1.3.0
|
||||
with:
|
||||
cluster_name: ${{ steps.uuid.outputs.RANDOM_UUID }}
|
||||
|
||||
- name: run-tests-on-local-built-kubescape
|
||||
env:
|
||||
CUSTOMER: ${{ secrets.CUSTOMER }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.PASSWORD }}
|
||||
CLIENT_ID: ${{ secrets.CLIENT_ID_PROD }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY_PROD }}
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
echo "Test history:"
|
||||
echo " ${{ matrix.TEST }} " >/tmp/testhistory
|
||||
cat /tmp/testhistory
|
||||
source systests_python_env/bin/activate
|
||||
|
||||
python3 systest-cli.py \
|
||||
-t ${{ matrix.TEST }} \
|
||||
-b production \
|
||||
-c CyberArmorTests \
|
||||
--duration 3 \
|
||||
--logger DEBUG \
|
||||
--kwargs kubescape=${{steps.download-artifact.outputs.download-path}}/kubescape-ubuntu-latest
|
||||
|
||||
deactivate
|
||||
|
||||
- name: Test Report
|
||||
uses: mikepenz/action-junit-report@6e9933f4a97f4d2b99acef4d7b97924466037882 # ratchet:mikepenz/action-junit-report@v3.6.1
|
||||
if: always() # always run even if the previous step fails
|
||||
with:
|
||||
report_paths: '**/results_xml_format/**.xml'
|
||||
commit: ${{github.event.workflow_run.head_sha}}
|
||||
41
.github/workflows/build-image.yaml
vendored
41
.github/workflows/build-image.yaml
vendored
@@ -1,41 +0,0 @@
|
||||
name: build-image
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
CLIENT:
|
||||
required: false
|
||||
type: string
|
||||
default: "test"
|
||||
IMAGE_TAG:
|
||||
required: true
|
||||
type: string
|
||||
CO_SIGN:
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
PLATFORMS:
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
jobs:
|
||||
build-http-image:
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
contents: read
|
||||
pull-requests: read
|
||||
uses: kubescape/workflows/.github/workflows/incluster-comp-pr-merged.yaml@main
|
||||
with:
|
||||
IMAGE_NAME: quay.io/${{ github.repository_owner }}/kubescape
|
||||
IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: "on"
|
||||
BUILD_PLATFORM: ${{ inputs.PLATFORMS && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
|
||||
GO_VERSION: "1.20"
|
||||
REQUIRED_TESTS: '[]'
|
||||
COSIGN: ${{ inputs.CO_SIGN }}
|
||||
HELM_E2E_TEST: false
|
||||
FORCE: true
|
||||
secrets: inherit
|
||||
73
.github/workflows/c-create-release.yaml
vendored
73
.github/workflows/c-create-release.yaml
vendored
@@ -1,73 +0,0 @@
|
||||
name: c-create_release
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
RELEASE_NAME:
|
||||
description: 'Release name'
|
||||
required: true
|
||||
type: string
|
||||
TAG:
|
||||
description: 'Tag name'
|
||||
required: true
|
||||
type: string
|
||||
DRAFT:
|
||||
description: 'Create draft release'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
jobs:
|
||||
create-release:
|
||||
name: create-release
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
MAC_OS: macos-latest
|
||||
UBUNTU_OS: ubuntu-latest
|
||||
WINDOWS_OS: windows-latest
|
||||
# permissions:
|
||||
# contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3.0.2
|
||||
id: download-artifact
|
||||
with:
|
||||
path: .
|
||||
|
||||
# TODO: kubescape-windows-latest is deprecated and should be removed
|
||||
- name: Get kubescape.exe from kubescape-windows-latest
|
||||
run: cp ./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }} ./kubescape-${{ env.WINDOWS_OS }}/kubescape.exe
|
||||
|
||||
- name: Set release token
|
||||
run: |
|
||||
if [ "${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" != "" ]; then
|
||||
echo "TOKEN=${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" >> $GITHUB_ENV;
|
||||
else
|
||||
echo "TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV;
|
||||
fi
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # ratchet:softprops/action-gh-release@v1
|
||||
with:
|
||||
token: ${{ env.TOKEN }}
|
||||
name: ${{ inputs.RELEASE_NAME }}
|
||||
tag_name: ${{ inputs.TAG }}
|
||||
body: ${{ github.event.pull_request.body }}
|
||||
draft: ${{ inputs.DRAFT }}
|
||||
fail_on_unmatched_files: true
|
||||
prerelease: false
|
||||
# TODO: kubescape-windows-latest is deprecated and should be removed
|
||||
files: |
|
||||
./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }}
|
||||
./kubescape-${{ env.MAC_OS }}/kubescape-${{ env.MAC_OS }}
|
||||
./kubescape-${{ env.MAC_OS }}/kubescape-${{ env.MAC_OS }}.sha256
|
||||
./kubescape-${{ env.MAC_OS }}/kubescape-${{ env.MAC_OS }}.tar.gz
|
||||
./kubescape-${{ env.UBUNTU_OS }}/kubescape-${{ env.UBUNTU_OS }}
|
||||
./kubescape-${{ env.UBUNTU_OS }}/kubescape-${{ env.UBUNTU_OS }}.sha256
|
||||
./kubescape-${{ env.UBUNTU_OS }}/kubescape-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape-${{ env.WINDOWS_OS }}/kubescape.exe
|
||||
./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }}.sha256
|
||||
./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }}.tar.gz
|
||||
./kubescapearm64-${{ env.MAC_OS }}/kubescape-arm64-${{ env.MAC_OS }}
|
||||
./kubescapearm64-${{ env.MAC_OS }}/kubescape-arm64-${{ env.MAC_OS }}.sha256
|
||||
./kubescapearm64-${{ env.MAC_OS }}/kubescape-arm64-${{ env.MAC_OS }}.tar.gz
|
||||
./kubescapearm64-${{ env.UBUNTU_OS }}/kubescape-arm64-${{ env.UBUNTU_OS }}
|
||||
./kubescapearm64-${{ env.UBUNTU_OS }}/kubescape-arm64-${{ env.UBUNTU_OS }}.sha256
|
||||
./kubescapearm64-${{ env.UBUNTU_OS }}/kubescape-arm64-${{ env.UBUNTU_OS }}.tar.gz
|
||||
80
.github/workflows/d-publish-image.yaml
vendored
80
.github/workflows/d-publish-image.yaml
vendored
@@ -1,80 +0,0 @@
|
||||
name: d-publish-image
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
client:
|
||||
description: 'client name'
|
||||
required: true
|
||||
type: string
|
||||
image_tag:
|
||||
description: 'image tag'
|
||||
required: true
|
||||
type: string
|
||||
image_name:
|
||||
description: 'image registry and name'
|
||||
required: true
|
||||
type: string
|
||||
cosign:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
description: 'run cosign on released image'
|
||||
support_platforms:
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
description: 'support amd64/arm64'
|
||||
jobs:
|
||||
check-secret:
|
||||
name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
|
||||
steps:
|
||||
- name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
|
||||
id: check-secret-set
|
||||
env:
|
||||
QUAYIO_REGISTRY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
|
||||
|
||||
build-cli-image:
|
||||
needs: [check-secret]
|
||||
if: needs.check-secret.outputs.is-secret-set == 'true'
|
||||
name: Build image and upload to registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # ratchet:docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # ratchet:docker/setup-buildx-action@v2
|
||||
- name: Login to Quay.io
|
||||
env:
|
||||
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
||||
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3.0.2
|
||||
id: download-artifact
|
||||
with:
|
||||
path: .
|
||||
- name: chmod +x
|
||||
run: chmod +x -v kubescape-*/kubescape-*
|
||||
- name: Build and push image for linux/amd64
|
||||
run: docker buildx build . --file build/kubescape-cli.Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --build-arg ks_binary=kubescape-ubuntu-latest/kubescape-ubuntu-latest --push --platform linux/amd64
|
||||
- name: Build and push image for linux/arm64
|
||||
run: docker buildx build . --file build/kubescape-cli.Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --build-arg ks_binary=kubescape-arm64-ubuntu-latest/kubescape-arm64-ubuntu-latest --push --platform linux/arm64
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@4079ad3567a89f68395480299c77e40170430341 # ratchet:sigstore/cosign-installer@main
|
||||
with:
|
||||
cosign-release: 'v1.12.0'
|
||||
- name: sign kubescape container image
|
||||
if: ${{ inputs.cosign }}
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: "true"
|
||||
run: |
|
||||
cosign sign --force ${{ inputs.image_name }}
|
||||
8
.github/workflows/scorecard.yml
vendored
8
.github/workflows/scorecard.yml
vendored
@@ -32,12 +32,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
|
||||
uses: ossf/scorecard-action@v2.4.3
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -67,6 +67,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
20
.github/workflows/z-close-typos-issues.yaml
vendored
20
.github/workflows/z-close-typos-issues.yaml
vendored
@@ -1,20 +0,0 @@
|
||||
permissions: read-all
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
jobs:
|
||||
open_PR_message:
|
||||
if: github.event.label.name == 'typo'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: ben-z/actions-comment-on-issue@10be23f9c43ac792663043420fda29dde07e2f0f # ratchet:ben-z/actions-comment-on-issue@1.0.2
|
||||
with:
|
||||
message: "Hello! :wave:\n\nThis issue is being automatically closed, Please open a PR with a relevant fix."
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto_close_issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: lee-dohm/close-matching-issues@e9e43aad2fa6f06a058cedfd8fb975fd93b56d8f # ratchet:lee-dohm/close-matching-issues@v2
|
||||
with:
|
||||
query: 'label:typo'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -9,3 +9,10 @@
|
||||
ca.srl
|
||||
*.out
|
||||
ks
|
||||
cosign.key
|
||||
|
||||
dist/
|
||||
|
||||
# Test output files
|
||||
customFilename.pdf
|
||||
customFilename.xml
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "git2go"]
|
||||
path = git2go
|
||||
url = https://github.com/libgit2/git2go.git
|
||||
@@ -1,57 +1,57 @@
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
dupl:
|
||||
threshold: 200
|
||||
goconst:
|
||||
min-len: 3
|
||||
min-occurrences: 2
|
||||
gocognit:
|
||||
min-complexity: 65
|
||||
|
||||
version: "2"
|
||||
linters:
|
||||
enable:
|
||||
- gosec
|
||||
- staticcheck
|
||||
- nolintlint
|
||||
- gofmt
|
||||
- unused
|
||||
- govet
|
||||
- bodyclose
|
||||
- typecheck
|
||||
- goimports
|
||||
- ineffassign
|
||||
- gosimple
|
||||
- gosec
|
||||
- nolintlint
|
||||
disable:
|
||||
# temporarily disabled
|
||||
- varcheck
|
||||
- errcheck
|
||||
- dupl
|
||||
- gocritic
|
||||
- errcheck
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gocognit
|
||||
- gocritic
|
||||
- lll
|
||||
- nakedret
|
||||
- revive
|
||||
- stylecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
#- forbidigo # <- see later
|
||||
# should remain disabled
|
||||
- deadcode # deprecated linter
|
||||
- maligned
|
||||
- lll
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
issues:
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- revive
|
||||
text: "var-naming"
|
||||
- linters:
|
||||
- revive
|
||||
text: "type name will be used as (.+?) by other packages, and that stutters"
|
||||
- linters:
|
||||
- stylecheck
|
||||
text: "ST1003"
|
||||
run:
|
||||
skip-dirs:
|
||||
- git2go
|
||||
settings:
|
||||
dupl:
|
||||
threshold: 200
|
||||
gocognit:
|
||||
min-complexity: 65
|
||||
goconst:
|
||||
min-len: 3
|
||||
min-occurrences: 2
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- revive
|
||||
text: var-naming
|
||||
- linters:
|
||||
- revive
|
||||
text: type name will be used as (.+?) by other packages, and that stutters
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: ST1003
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
||||
148
.goreleaser.yaml
Normal file
148
.goreleaser.yaml
Normal file
@@ -0,0 +1,148 @@
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
|
||||
# The lines below are called `modelines`. See `:help modeline`
|
||||
# Feel free to remove those if you don't want/need to use them.
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- go mod tidy
|
||||
- go test -v ./...
|
||||
- go -C httphandler test -v ./...
|
||||
|
||||
archives:
|
||||
- id: cli
|
||||
ids:
|
||||
- cli
|
||||
|
||||
formats:
|
||||
- binary
|
||||
- tar.gz
|
||||
|
||||
builds:
|
||||
- id: cli
|
||||
binary: kubescape
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
ldflags:
|
||||
- -X main.version={{.Version}}
|
||||
- -X main.commit={{.Commit}}
|
||||
- -X main.date={{.Date}}
|
||||
- -X github.com/kubescape/backend/pkg/versioncheck.Client={{.Env.CLIENT}}
|
||||
hooks:
|
||||
post:
|
||||
- cmd: >
|
||||
{{ if eq .Arch "amd64" }}
|
||||
/bin/sh -lc 'sh build/goreleaser-post-e2e.sh'
|
||||
{{ end }}
|
||||
- id: downloader
|
||||
dir: downloader
|
||||
binary: downloader
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- id: http
|
||||
dir: httphandler
|
||||
binary: ksserver
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
|
||||
nfpms:
|
||||
- id: cli
|
||||
package_name: kubescape
|
||||
ids:
|
||||
- cli
|
||||
vendor: Kubescape
|
||||
homepage: https://kubescape.io/
|
||||
maintainer: matthiasb@kubescape.io
|
||||
formats:
|
||||
- apk
|
||||
- deb
|
||||
- rpm
|
||||
bindir: /usr/bin
|
||||
|
||||
docker_signs:
|
||||
- stdin: "{{ .Env.COSIGN_PWD }}"
|
||||
|
||||
dockers_v2:
|
||||
- id: cli
|
||||
images:
|
||||
- "quay.io/kubescape/kubescape-cli"
|
||||
tags:
|
||||
- "{{ .Tag }}"
|
||||
labels:
|
||||
"org.opencontainers.image.description": "Kubescape CLI"
|
||||
"org.opencontainers.image.created": "{{.Date}}"
|
||||
"org.opencontainers.image.name": "{{.ProjectName}}"
|
||||
"org.opencontainers.image.revision": "{{.FullCommit}}"
|
||||
"org.opencontainers.image.version": "{{.Version}}"
|
||||
"org.opencontainers.image.source": "{{.GitURL}}"
|
||||
ids:
|
||||
- cli
|
||||
dockerfile: build/kubescape-cli.Dockerfile
|
||||
- id: http
|
||||
images:
|
||||
- "quay.io/kubescape/kubescape"
|
||||
tags:
|
||||
- "{{ .Tag }}"
|
||||
labels:
|
||||
"org.opencontainers.image.description": "Kubescape microservice"
|
||||
"org.opencontainers.image.created": "{{.Date}}"
|
||||
"org.opencontainers.image.name": "{{.ProjectName}}"
|
||||
"org.opencontainers.image.revision": "{{.FullCommit}}"
|
||||
"org.opencontainers.image.version": "{{.Version}}"
|
||||
"org.opencontainers.image.source": "{{.GitURL}}"
|
||||
ids:
|
||||
- downloader
|
||||
- http
|
||||
dockerfile: build/Dockerfile
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.sha256"
|
||||
|
||||
sboms:
|
||||
- artifacts: binary
|
||||
|
||||
krews:
|
||||
- name: kubescape
|
||||
ids:
|
||||
- cli
|
||||
skip_upload: true
|
||||
homepage: https://kubescape.io/
|
||||
description: It includes risk analysis, security compliance, and misconfiguration scanning with an easy-to-use CLI interface, flexible output formats, and automated scanning capabilities.
|
||||
short_description: Scan resources and cluster configs against security frameworks.
|
||||
|
||||
release:
|
||||
draft: false
|
||||
footer: >-
|
||||
|
||||
---
|
||||
|
||||
Released by [GoReleaser](https://github.com/goreleaser/goreleaser).
|
||||
58
.krew.yaml
58
.krew.yaml
@@ -3,40 +3,58 @@ kind: Plugin
|
||||
metadata:
|
||||
name: kubescape
|
||||
spec:
|
||||
homepage: https://github.com/kubescape/kubescape/
|
||||
shortDescription: Scan resources and cluster configs against security frameworks.
|
||||
version: {{ .TagName }}
|
||||
description: |
|
||||
It includes risk analysis, security compliance, and misconfiguration scanning
|
||||
with an easy-to-use CLI interface, flexible output formats, and automated scanning capabilities.
|
||||
platforms:
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-macos-latest.tar.gz" .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: arm64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-arm64-macos-latest.tar.gz" .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: amd64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-ubuntu-latest.tar.gz" .TagName }}
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/" .TagName (printf "kubescape_%s_linux_amd64.tar.gz" .TagName) .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: arm64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-arm64-ubuntu-latest.tar.gz" .TagName }}
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/" .TagName (printf "kubescape_%s_linux_arm64.tar.gz" .TagName) .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/" .TagName (printf "kubescape_%s_darwin_amd64.tar.gz" .TagName) .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: arm64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/" .TagName (printf "kubescape_%s_darwin_arm64.tar.gz" .TagName) .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: amd64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-windows-latest.tar.gz" .TagName }}
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/" .TagName (printf "kubescape_%s_windows_amd64.tar.gz" .TagName) .TagName }}
|
||||
bin: kubescape.exe
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: arm64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/" .TagName (printf "kubescape_%s_windows_arm64.tar.gz" .TagName) .TagName }}
|
||||
bin: kubescape.exe
|
||||
shortDescription: Scan resources and cluster configs against security frameworks.
|
||||
description: |
|
||||
Kubescape is the first tool for testing if Kubernetes is deployed securely
|
||||
according to mitigations and best practices. It includes risk analysis,
|
||||
security compliance, and misconfiguration scanning with an easy-to-use
|
||||
CLI interface, flexible output formats, and automated scanning capabilities.
|
||||
|
||||
Features:
|
||||
- Risk analysis: Identify vulnerabilities and security risks in your cluster
|
||||
- Security compliance: Check your cluster against multiple security frameworks
|
||||
- Misconfiguration scanning: Detect security misconfigurations in your workloads
|
||||
- Flexible output: Results in JSON, SARIF, HTML, JUnit, and Prometheus formats
|
||||
- CI/CD integration: Easily integrate into your CI/CD pipeline
|
||||
homepage: https://kubescape.io/
|
||||
caveats: |
|
||||
Requires kubectl and basic knowledge of Kubernetes.
|
||||
Run 'kubescape scan' to scan your Kubernetes cluster or manifests.
|
||||
|
||||
22
ADOPTERS.md
22
ADOPTERS.md
@@ -1,23 +1,5 @@
|
||||
# Adopters
|
||||
|
||||
# Well-known companies
|
||||
Well-known companies who are using and/or contributing to Kubescape are (in alphabetical order):
|
||||
* Accenture
|
||||
* Amazon.com
|
||||
* IBM
|
||||
* Intel
|
||||
* Meetup
|
||||
* RedHat
|
||||
* Scaleway
|
||||
|
||||
# Users
|
||||
|
||||
If you want to be listed here and share with others your experience, open a PR and add the bellow table:
|
||||
|
||||
|
||||
| Name | Company | Use case | Contact for questions (optional) |
|
||||
| ---- | ------- | -------- | -------------------------------- |
|
||||
| Yonathan Amzallag | ARMO | Vulnerability monitoring | yonatana@armosec.io |
|
||||
|
||||
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized ADOPTERS.md](https://github.com/kubescape/project-governance/blob/main/ADOPTERS.md)
|
||||
@@ -1,3 +1,5 @@
|
||||
## Code of Conduct
|
||||
# Code of Conduct
|
||||
|
||||
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized CODE_OF_CONDUCT.md](https://github.com/kubescape/project-governance/blob/main/CODE_OF_CONDUCT.md)
|
||||
|
||||
5
COMMUNITY.md
Normal file
5
COMMUNITY.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Community
|
||||
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized COMMUNITY.md](https://github.com/kubescape/project-governance/blob/main/COMMUNITY.md)
|
||||
@@ -1,98 +1,5 @@
|
||||
# Contributing
|
||||
|
||||
First, it is awesome that you are considering contributing to Kubescape! Contributing is important and fun and we welcome your efforts.
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
When contributing, we categorize contributions into two:
|
||||
* Small code changes or fixes, whose scope is limited to a single or two files
|
||||
* Complex features and improvements, with potentially unlimited scope
|
||||
|
||||
If you have a small change, feel free to fire up a Pull Request.
|
||||
|
||||
When planning a bigger change, please first discuss the change you wish to make via an issue,
|
||||
so the maintainers are able to help guide you and let you know if you are going in the right direction.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Please follow our [code of conduct](CODE_OF_CONDUCT.md) in all of your interactions within the project.
|
||||
|
||||
## Build and test locally
|
||||
|
||||
Please follow the [instructions here](https://github.com/kubescape/kubescape/wiki/Building).
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
|
||||
build.
|
||||
2. Update the README.md with details of changes to the interface, this includes new environment
|
||||
variables, exposed ports, useful file locations and container parameters.
|
||||
3. Open Pull Request to the `master` branch.
|
||||
4. We will merge the Pull Request once you have the sign-off.
|
||||
|
||||
## Developer Certificate of Origin
|
||||
|
||||
All commits to the project must be "signed off", which states that you agree to the terms of the [Developer Certificate of Origin](https://developercertificate.org/). This is done by adding a "Signed-off-by:" line in the commit message, with your name and email address.
|
||||
|
||||
Commits made through the GitHub web application are automatically signed off.
|
||||
|
||||
### Configuring Git to sign off commits
|
||||
|
||||
First, configure your name and email address in Git global settings:
|
||||
|
||||
```
|
||||
$ git config --global user.name "John Doe"
|
||||
$ git config --global user.email johndoe@example.com
|
||||
```
|
||||
|
||||
You can now sign off per-commit, or configure Git to always sign off commits per repository.
|
||||
|
||||
### Sign off per-commit
|
||||
|
||||
Add [`-s`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s) to your Git command line. For example:
|
||||
|
||||
```git commit -s -m "Fix issue 64738"```
|
||||
|
||||
This is tedious, and if you forget, you'll have to [amend your commit](#fixing-a-commit-where-the-dco-failed).
|
||||
|
||||
### Configure a repository to always include sign off
|
||||
|
||||
There are many ways to achieve this with Git hooks, but the simplest is to do the following:
|
||||
|
||||
```
|
||||
cd your-repo
|
||||
curl -Ls https://gist.githubusercontent.com/dixudx/7d7edea35b4d91e1a2a8fbf41d0954fa/raw/prepare-commit-msg -o .git/hooks/prepare-commit-msg
|
||||
chmod +x .git/hooks/prepare-commit-msg
|
||||
```
|
||||
|
||||
### Use semantic commit messages (optional)
|
||||
|
||||
When contributing, you could consider using [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/), in order to improve logs readability and help us to automatically generate `CHANGELOG`s.
|
||||
|
||||
Format: `<type>(<scope>): <subject>`
|
||||
|
||||
`<scope>` is optional
|
||||
|
||||
#### Example
|
||||
|
||||
```
|
||||
feat(cmd): add kubectl plugin
|
||||
^--^ ^-^ ^----------------^
|
||||
| | |
|
||||
| | +-> subject: summary in present tense.
|
||||
| |
|
||||
| +-------> scope: point of interest
|
||||
|
|
||||
+-------> type: chore, docs, feat, fix, refactor, style, or test.
|
||||
```
|
||||
|
||||
More Examples:
|
||||
* `feat`: new feature for the user, not a new feature for build script
|
||||
* `fix`: bug fix for the user, not a fix to a build script
|
||||
* `docs`: changes to the documentation
|
||||
* `style`: formatting, missing semi colons, etc; no production code change
|
||||
* `refactor`: refactoring production code, eg. renaming a variable
|
||||
* `test`: adding missing tests, refactoring tests; no production code change
|
||||
* `chore`: updating grunt tasks etc; no production code change
|
||||
|
||||
## Fixing a commit where the DCO failed
|
||||
|
||||
Check out [this guide](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md).
|
||||
Go to the [centralized CONTRIBUTING.md](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md)
|
||||
|
||||
@@ -1,65 +1,5 @@
|
||||
# Governance of Kubescape
|
||||
# Governance
|
||||
|
||||
## Overview
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
The Kubescape project is an open-source initiative dedicated to improve security and best practices in Kubernetes environments. This document outlines the governance structure of the Kubescape project and provides guidance for its community contributors.
|
||||
|
||||
## Decision Making
|
||||
|
||||
### Maintainers
|
||||
|
||||
- Maintainers are responsible for the smooth operation of the project.
|
||||
- They review and merge pull requests, manage releases, and ensure the quality and stability of the codebase.
|
||||
- Maintainers are chosen based on their ongoing contributions and their demonstrated commitment to the project.
|
||||
- Everyone who had at least 5 code contribution in the last 12 month can submit her/himself for joining the maintainer team
|
||||
- Maintainers who are not taken part in the project work (code, reviews, discussions) for 12 month are automaticaly removed from the maintainer team
|
||||
|
||||
|
||||
### Committers
|
||||
|
||||
- Committers are contributors who have made significant and consistent contributions to the project.
|
||||
- They have the ability to merge minor pull requests if assigned by maintainers.
|
||||
- A contributor can be proposed as a committer by any existing maintainer. The proposal will be reviewed and voted on by the existing maintainers.
|
||||
|
||||
### Community Members
|
||||
|
||||
- Anyone can become a community member by contributing to the project. This can be in the form of code contributions, documentation, or any other form of project support.
|
||||
|
||||
## Processes
|
||||
|
||||
### Proposing Changes
|
||||
|
||||
1. Open an issue on the project repository to discuss the proposed change.
|
||||
2. Once there is consensus around the proposed change, create a pull request.
|
||||
3. Pull requests will be reviewed by committers and/or maintainers.
|
||||
4. Once the pull request has received approval, it can be merged into the main codebase.
|
||||
|
||||
### Conflict Resolution
|
||||
|
||||
1. In case of any conflicts, it is primarily the responsibility of the parties involved to resolve it.
|
||||
2. If the conflict cannot be resolved, it will be escalated to the maintainers for resolution.
|
||||
3. Maintainers' decision will be final in case of unresolved conflicts.
|
||||
|
||||
## Roles and Responsibilities
|
||||
|
||||
### Maintainers
|
||||
|
||||
- Ensure the quality and stability of the project.
|
||||
- Resolve conflicts.
|
||||
- Provide direction and set priorities for the project.
|
||||
|
||||
### Committers
|
||||
|
||||
- Review and merge minor pull requests.
|
||||
- Assist maintainers in project tasks.
|
||||
- Promote best practices within the community.
|
||||
|
||||
### Community Members
|
||||
|
||||
- Contribute to the project in any form.
|
||||
- Participate in discussions and provide feedback.
|
||||
- Respect the code of conduct and governance of the project.
|
||||
|
||||
## Changes to the Governance Document
|
||||
|
||||
Proposed changes to this governance document should follow the same process as any other code change to the Kubescape project (see "Proposing Changes").
|
||||
Go to the [centralized GOVERNANCE.md](https://github.com/kubescape/project-governance/blob/main/GOVERNANCE.md)
|
||||
|
||||
273
KREW_RELEASE.md
Normal file
273
KREW_RELEASE.md
Normal file
@@ -0,0 +1,273 @@
|
||||
# Krew Release Automation Guide
|
||||
|
||||
This document explains how kubescape automates publishing to the Kubernetes plugin package manager, krew.
|
||||
|
||||
## What is Krew?
|
||||
|
||||
Krew is a plugin manager for `kubectl`. It allows users to discover and install `kubectl` plugins easily. You can learn more about krew at [https://krew.sigs.k8s.io/](https://krew.sigs.k8s.io/).
|
||||
|
||||
## How kubescape publishes to krew
|
||||
|
||||
We use the [krew-release-bot](https://github.com/rajatjindal/krew-release-bot) to automatically create pull requests to the [kubernetes-sigs/krew-index](https://github.com/kubernetes-sigs/krew-index) repository whenever a new release of kubescape is published.
|
||||
|
||||
### Setup Overview
|
||||
|
||||
The automation consists of three components:
|
||||
|
||||
1. **`.krew.yaml`** - A template file that the bot uses to generate the krew plugin manifest
|
||||
2. **`.github/workflows/02-release.yaml`** - GitHub Actions workflow that runs the krew-release-bot after a successful release
|
||||
3. **`.goreleaser.yaml`** - GoReleaser configuration that defines the krew manifest (though upload is skipped)
|
||||
|
||||
### Why Use krew-release-bot Instead of GoReleaser's Built-in Krew Support?
|
||||
|
||||
You might have noticed that **GoReleaser has built-in krew support** in its `krews` section. However, almost all projects (including stern) use `skip_upload: true` and rely on **krew-release-bot** instead. Here's why:
|
||||
|
||||
#### Problems with GoReleaser's Built-in Krew Publishing
|
||||
|
||||
To use GoReleaser's direct krew publishing, you would need to:
|
||||
|
||||
```yaml
|
||||
krews:
|
||||
- name: kubescape
|
||||
skip_upload: false # Instead of true
|
||||
repository:
|
||||
owner: kubernetes-sigs
|
||||
name: krew-index
|
||||
token: "{{ .Env.KREW_INDEX_TOKEN }}" # Required!
|
||||
pull_request:
|
||||
enabled: true # Requires GoReleaser Pro for cross-repo PRs
|
||||
```
|
||||
|
||||
This approach has several critical issues:
|
||||
|
||||
1. **Permission Barrier**: Almost no one has write access to `kubernetes-sigs/krew-index`. You would need special permissions from the Krew maintainers, which is rarely granted.
|
||||
|
||||
2. **Security Risk**: You'd need to store a GitHub personal access token with write access to the krew-index in your repository secrets. This token could be compromised and used to make unauthorized changes to the krew-index.
|
||||
|
||||
3. **GoReleaser Pro Required**: To create pull requests to a different repository (cross-repository), you need GoReleaser Pro, which is a paid product.
|
||||
|
||||
4. **Manual Work**: Even if you had access, you'd need to manually configure and maintain the repository settings, tokens, and potentially deal with rate limits and authentication issues.
|
||||
|
||||
#### Why krew-release-bot is the Right Solution
|
||||
|
||||
The **krew-release-bot** was created by the Kubernetes community (in collaboration with the Krew team) specifically to solve these problems:
|
||||
|
||||
- **No Repository Access Required**: The bot acts as an intermediary with pre-configured access to krew-index. You don't need write permissions.
|
||||
|
||||
- **No Tokens Needed**: It uses GitHub's `GITHUB_TOKEN` (automatically available in GitHub Actions) via webhooks and events. No personal access tokens required.
|
||||
|
||||
- **Designed for Krew**: It's specifically built for the krew-index workflow and integrates with Krew's automation.
|
||||
|
||||
- **Automatic Merging**: The Krew team has configured their CI to automatically test and merge PRs from krew-release-bot (usually within 5-10 minutes).
|
||||
|
||||
- **Officially Recommended**: The Krew team explicitly recommends this approach in their documentation as the standard way to automate plugin updates.
|
||||
|
||||
- **Free and Open Source**: No paid subscriptions required.
|
||||
|
||||
#### The Real-World Evidence
|
||||
|
||||
Looking at recent pull requests to `kubernetes-sigs/krew-index`, **almost all automated plugin updates are created by krew-release-bot**. You'll see patterns like:
|
||||
|
||||
```
|
||||
Author: krew-release-bot
|
||||
Title: "release new version v0.6.11 of radar"
|
||||
```
|
||||
|
||||
This demonstrates that the entire Kubernetes ecosystem has standardized on krew-release-bot, not GoReleaser's built-in publishing.
|
||||
|
||||
#### Summary
|
||||
|
||||
While GoReleaser's built-in krew support exists in the code, it's **practically unusable for the krew-index repository** due to permission and security constraints. The krew-release-bot is the de facto standard because:
|
||||
- It works without special permissions
|
||||
- It's more secure
|
||||
- It integrates with Krew's automation
|
||||
- It's free and recommended by the Krew team
|
||||
|
||||
This is why we use `skip_upload: true` in GoReleaser and let krew-release-bot handle the actual publishing.
|
||||
|
||||
### The Template File
|
||||
|
||||
The `.krew.yaml` file in the repository root is a Go template that contains placeholders for dynamic values:
|
||||
|
||||
```yaml
|
||||
apiVersion: krew.googlecontainertools.github.com/v1alpha2
|
||||
kind: Plugin
|
||||
metadata:
|
||||
name: kubescape
|
||||
spec:
|
||||
version: {{ .TagName }}
|
||||
platforms:
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: amd64
|
||||
{{ $version := trimPrefix "v" .TagName }}{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/" .TagName (printf "kubescape_%s_linux_amd64.tar.gz" $version) .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: arm64
|
||||
{{ $version := trimPrefix "v" .TagName }}{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/" .TagName (printf "kubescape_%s_linux_arm64.tar.gz" $version) .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
{{ $version := trimPrefix "v" .TagName }}{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/" .TagName (printf "kubescape_%s_darwin_amd64.tar.gz" $version) .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: arm64
|
||||
{{ $version := trimPrefix "v" .TagName }}{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/" .TagName (printf "kubescape_%s_darwin_arm64.tar.gz" $version) .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: amd64
|
||||
{{ $version := trimPrefix "v" .TagName }}{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/" .TagName (printf "kubescape_%s_windows_amd64.tar.gz" $version) .TagName }}
|
||||
bin: kubescape.exe
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: arm64
|
||||
{{ $version := trimPrefix "v" .TagName }}{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/" .TagName (printf "kubescape_%s_windows_arm64.tar.gz" $version) .TagName }}
|
||||
bin: kubescape.exe
|
||||
shortDescription: Scan resources and cluster configs against security frameworks.
|
||||
description: |
|
||||
Kubescape is the first tool for testing if Kubernetes is deployed securely
|
||||
according to mitigations and best practices. It includes risk analysis,
|
||||
security compliance, and misconfiguration scanning with an easy-to-use
|
||||
CLI interface, flexible output formats, and automated scanning capabilities.
|
||||
|
||||
Features:
|
||||
- Risk analysis: Identify vulnerabilities and security risks in your cluster
|
||||
- Security compliance: Check your cluster against multiple security frameworks
|
||||
- Misconfiguration scanning: Detect security misconfigurations in your workloads
|
||||
- Flexible output: Results in JSON, SARIF, HTML, JUnit, and Prometheus formats
|
||||
- CI/CD integration: Easily integrate into your CI/CD pipeline
|
||||
homepage: https://kubescape.io/
|
||||
caveats: |
|
||||
Requires kubectl and basic knowledge of Kubernetes.
|
||||
Run 'kubescape scan' to scan your Kubernetes cluster or manifests.
|
||||
```
|
||||
|
||||
The `{{ .TagName }}` is replaced with the release tag (e.g., `v3.0.0`), `{{ trimPrefix "v" .TagName }}` removes the version prefix, and `{{ addURIAndSha ... }}` calculates the SHA256 checksum for the binary archive.
|
||||
|
||||
### Release Workflow
|
||||
|
||||
The release workflow (`.github/workflows/02-release.yaml`) can be triggered in two ways:
|
||||
|
||||
1. **Automatic**: When a new tag matching the pattern `v[0-9]+.[0-9]+.[0-9]+` is pushed to the repository
|
||||
2. **Manual**: Via `workflow_dispatch` with an optional `skip_publish` input
|
||||
|
||||
When the workflow is triggered:
|
||||
|
||||
1. GoReleaser builds and publishes the release artifacts (unless `skip_publish=true` is set)
|
||||
2. The krew-release-bot step runs conditionally:
|
||||
- It **runs** when triggered by a tag push OR by `workflow_dispatch` with `skip_publish=false`
|
||||
- It **skips** when triggered by `workflow_dispatch` with `skip_publish=true` (default)
|
||||
3. When it runs, the bot:
|
||||
- Reads the `.krew.yaml` template
|
||||
- Fills in the template with release information
|
||||
- Creates a pull request to the `kubernetes-sigs/krew-index` repository
|
||||
- The PR is automatically tested and merged by krew's infrastructure
|
||||
|
||||
### Workflow Permissions
|
||||
|
||||
The release job has the following permissions:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
contents: write
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
models: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
statuses: read
|
||||
security-events: read
|
||||
attestations: read
|
||||
artifact-metadata: read
|
||||
```
|
||||
|
||||
These permissions are necessary for GoReleaser to create releases and upload artifacts.
|
||||
|
||||
### Testing the Template
|
||||
|
||||
Before committing changes to `.krew.yaml`, you can test how the template will be rendered using Docker:
|
||||
|
||||
```bash
|
||||
docker run -v $(pwd)/.krew.yaml:/tmp/.krew.yaml ghcr.io/rajatjindal/krew-release-bot:v0.0.47 \
|
||||
krew-release-bot template --tag v3.0.0 --template-file /tmp/.krew.yaml
|
||||
```
|
||||
|
||||
This will output the generated krew manifest file, allowing you to verify:
|
||||
- The version field is correct
|
||||
- All download URLs are properly formatted
|
||||
- The SHA256 checksum will be calculated correctly
|
||||
|
||||
### Why skip_upload in GoReleaser?
|
||||
|
||||
In `.goreleaser.yaml`, the `krews` section has `skip_upload: true`:
|
||||
|
||||
```yaml
|
||||
krews:
|
||||
- name: kubescape
|
||||
ids:
|
||||
- cli
|
||||
skip_upload: true # We use krew-release-bot instead
|
||||
homepage: https://kubescape.io/
|
||||
description: It includes risk analysis, security compliance, and misconfiguration scanning with an easy-to-use CLI interface, flexible output formats, and automated scanning capabilities.
|
||||
short_description: Scan resources and cluster configs against security frameworks.
|
||||
```
|
||||
|
||||
This is intentional because:
|
||||
- GoReleaser generates the manifest but doesn't have built-in support for submitting PRs to krew-index
|
||||
- krew-release-bot is the recommended tool for krew automation by the Krew team
|
||||
- Using krew-release-bot provides automatic testing and merging of version bump PRs
|
||||
|
||||
### Manual Release Testing
|
||||
|
||||
You can test the release workflow manually without publishing to krew by using `workflow_dispatch`:
|
||||
|
||||
1. Go to Actions tab in GitHub
|
||||
2. Select "02-create_release" workflow
|
||||
3. Click "Run workflow"
|
||||
4. The `skip_publish` input defaults to `true` (publishing will be skipped)
|
||||
5. Set `skip_publish` to `false` if you want to test the full release process including krew indexing
|
||||
|
||||
### Making Changes to the Template
|
||||
|
||||
If you need to update the krew manifest (e.g., change the description, add platforms, or update the binary location):
|
||||
|
||||
1. Edit the `.krew.yaml` file
|
||||
2. Test your changes with the Docker command shown above
|
||||
3. Commit and push the changes
|
||||
4. The next release will use the updated template
|
||||
|
||||
### Installing kubescape via krew
|
||||
|
||||
Once the plugin is indexed in krew, users can install it with:
|
||||
|
||||
```bash
|
||||
kubectl krew install kubernetes-sigs/kubescape
|
||||
```
|
||||
|
||||
Or after index update:
|
||||
|
||||
```bash
|
||||
kubectl krew install kubescape
|
||||
```
|
||||
|
||||
### Further Reading
|
||||
|
||||
- [Krew official documentation](https://krew.sigs.k8s.io/docs/developer-guide/)
|
||||
- [krew-release-bot repository](https://github.com/rajatjindal/krew-release-bot)
|
||||
- [Krew plugin submission guide](https://krew.sigs.k8s.io/docs/developer-guide/develop/plugins/)
|
||||
@@ -1,12 +1,5 @@
|
||||
# Maintainers
|
||||
|
||||
The following table lists the Kubescape project core maintainers:
|
||||
|
||||
| Name | GitHub | Organization | Added/Renewed On |
|
||||
| --- | --- | --- | --- |
|
||||
| [Matthias Bertschy](https://www.linkedin.com/in/matthias-bertschy-b427b815/) | [@matthyx](https://github.com/matthyx) | [ARMO](https://www.armosec.io/) | 2023-01-01 |
|
||||
| [Craig Box](https://www.linkedin.com/in/crbnz/) | [@craigbox](https://github.com/craigbox) | [ARMO](https://www.armosec.io/) | 2022-10-31 |
|
||||
| [Ben Hirschberg](https://www.linkedin.com/in/benyamin-ben-hirschberg-66141890) | [@slashben](https://github.com/slashben) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
|
||||
| [Rotem Refael](https://www.linkedin.com/in/rotem-refael) | [@rotemamsa](https://github.com/rotemamsa) | [ARMO](https://www.armosec.io/) | 2021-10-11 |
|
||||
| [David Wertenteil](https://www.linkedin.com/in/david-wertenteil-0ba277b9) | [@dwertent](https://github.com/dwertent) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized MAINTAINERS.md](https://github.com/kubescape/project-governance/blob/main/MAINTAINERS.md)
|
||||
|
||||
26
Makefile
26
Makefile
@@ -1,28 +1,12 @@
|
||||
.PHONY: test all build libgit2
|
||||
.PHONY: test all build
|
||||
|
||||
# default task invoked while running make
|
||||
all: libgit2 build
|
||||
all: build
|
||||
|
||||
export CGO_ENABLED=1
|
||||
|
||||
# build and install libgit2
|
||||
libgit2:
|
||||
-git submodule update --init --recursive
|
||||
cd git2go; make install-static
|
||||
|
||||
# build and install libgit2 for macOS m1
|
||||
libgit2arm64:
|
||||
git submodule update --init --recursive
|
||||
if [ "$(shell uname -s)" = "Darwin" ]; then \
|
||||
sed -i '' 's/cmake -D/cmake -DCMAKE_OSX_ARCHITECTURES="arm64" -D/' git2go/script/build-libgit2.sh; \
|
||||
fi
|
||||
cd git2go; make install-static
|
||||
|
||||
# go build tags
|
||||
TAGS = "gitenabled,static"
|
||||
export CGO_ENABLED=0
|
||||
|
||||
build:
|
||||
go build -v -tags=$(TAGS) .
|
||||
go build -v .
|
||||
|
||||
test:
|
||||
go test -v -tags=$(TAGS) ./...
|
||||
go test -v ./...
|
||||
|
||||
482
README.md
482
README.md
@@ -3,12 +3,15 @@
|
||||
[](https://goreportcard.com/report/github.com/kubescape/kubescape)
|
||||
[](https://gitpod.io/#https://github.com/kubescape/kubescape)
|
||||
[](https://github.com/kubescape/kubescape/blob/master/LICENSE)
|
||||
[](https://landscape.cncf.io/card-mode?project=sandbox&selected=kubescape)
|
||||
[](https://landscape.cncf.io/?item=provisioning--security-compliance--kubescape)
|
||||
[](https://artifacthub.io/packages/search?repo=kubescape)
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fkubescape%2Fkubescape?ref=badge_shield&issueType=license)
|
||||
[](https://www.bestpractices.dev/projects/6944)
|
||||
[](https://securityscorecards.dev/viewer/?uri=github.com/kubescape/kubescape)
|
||||
[](https://kubescape.io/docs/)
|
||||
[](https://github.com/kubescape/kubescape/stargazers)
|
||||
[](https://twitter.com/kubescape)
|
||||
[](https://cloud-native.slack.com/archives/C04EY3ZF9GE)
|
||||
|
||||
# Kubescape
|
||||
|
||||
@@ -18,89 +21,480 @@
|
||||
<img alt="Kubescape logo" align="right" src="https://raw.githubusercontent.com/cncf/artwork/master/projects/kubescape/stacked/color/kubescape-stacked-color.svg" width="150">
|
||||
</picture>
|
||||
|
||||
_An open-source Kubernetes security platform for your IDE, CI/CD pipelines, and clusters_
|
||||
_Comprehensive Kubernetes Security from Development to Runtime_
|
||||
|
||||
Kubescape is an open-source Kubernetes security platform. It includes risk analysis, security compliance, and misconfiguration scanning. Targeted at the DevSecOps practitioner or platform engineer, it offers an easy-to-use CLI interface, flexible output formats, and automated scanning capabilities. It saves Kubernetes users and admins precious time, effort, and resources.
|
||||
Kubescape is an open-source Kubernetes security platform that provides comprehensive security coverage, from left to right across the entire development and deployment lifecycle. It offers hardening, posture management, and runtime security capabilities to ensure robust protection for Kubernetes environments.
|
||||
|
||||
Kubescape scans clusters, YAML files, and Helm charts. It detects misconfigurations according to multiple frameworks (including [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo/?utm_source=github&utm_medium=repository), [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) and the [CIS Benchmark](https://www.armosec.io/blog/cis-kubernetes-benchmark-framework-scanning-tools-comparison/?utm_source=github&utm_medium=repository)).
|
||||
Kubescape was created by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository) and is a [Cloud Native Computing Foundation (CNCF) incubating project](https://www.cncf.io/projects/).
|
||||
|
||||
Kubescape was created by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository) and is a [Cloud Native Computing Foundation (CNCF) sandbox project](https://www.cncf.io/sandbox-projects/).
|
||||
_Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape!_
|
||||
|
||||
## Demo
|
||||
<img src="docs/img/demo.gif">
|
||||
---
|
||||
|
||||
_Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape! 😀_
|
||||
## 📑 Table of Contents
|
||||
|
||||
## Getting started
|
||||
- [Features](#-features)
|
||||
- [Demo](#-demo)
|
||||
- [Quick Start](#-quick-start)
|
||||
- [Installation](#-installation)
|
||||
- [CLI Commands](#%EF%B8%8F-cli-commands)
|
||||
- [Usage Examples](#-usage-examples)
|
||||
- [Architecture](#%EF%B8%8F-architecture)
|
||||
- [In-Cluster Operator](#%EF%B8%8F-in-cluster-operator)
|
||||
- [Integrations](#-integrations)
|
||||
- [Community](#-community)
|
||||
- [Changelog](#changelog)
|
||||
- [License](#license)
|
||||
|
||||
Experimenting with Kubescape is as easy as:
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| 🔍 **Misconfiguration Scanning** | Scan clusters, YAML files, and Helm charts against NSA-CISA, MITRE ATT&CK®, and CIS Benchmarks |
|
||||
| 🐳 **Image Vulnerability Scanning** | Detect CVEs in container images using [Grype](https://github.com/anchore/grype) |
|
||||
| 🩹 **Image Patching** | Automatically patch vulnerable images using [Copacetic](https://github.com/project-copacetic/copacetic) |
|
||||
| 🔧 **Auto-Remediation** | Automatically fix misconfigurations in Kubernetes manifests |
|
||||
| 🛡️ **Admission Control** | Enforce security policies with Validating Admission Policies (VAP) |
|
||||
| 📊 **Runtime Security** | eBPF-based runtime monitoring via [Inspektor Gadget](https://github.com/inspektor-gadget) |
|
||||
| 🤖 **AI Integration** | MCP server for AI assistant integration |
|
||||
|
||||
---
|
||||
|
||||
## 🎬 Demo
|
||||
|
||||
<img src="docs/img/demo-v3.gif" alt="Kubescape CLI demo">
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Install Kubescape
|
||||
|
||||
```sh
|
||||
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
|
||||
```
|
||||
|
||||
Learn more about:
|
||||
> 💡 See [Installation](#-installation) for more options (Homebrew, Krew, Windows, etc.)
|
||||
|
||||
* [Installing Kubescape](docs/installation.md)
|
||||
* [Running your first scan](docs/getting-started.md#run-your-first-scan)
|
||||
* [Usage](docs/getting-started.md#examples)
|
||||
* [Architecture](docs/architecture.md)
|
||||
* [Building Kubescape from source](https://github.com/kubescape/kubescape/wiki/Building)
|
||||
### 2. Run Your First Scan
|
||||
|
||||
_Did you know you can use Kubescape in all these places?_
|
||||
```sh
|
||||
# Scan your current cluster
|
||||
kubescape scan
|
||||
|
||||
# Scan a specific YAML file or directory
|
||||
kubescape scan /path/to/manifests/
|
||||
|
||||
# Scan a container image for vulnerabilities
|
||||
kubescape scan image nginx:latest
|
||||
```
|
||||
|
||||
### 3. Explore the Results
|
||||
|
||||
Kubescape provides a detailed security posture overview including:
|
||||
- Control plane security status
|
||||
- Access control risks
|
||||
- Workload misconfigurations
|
||||
- Network policy gaps
|
||||
- Compliance scores (MITRE, NSA)
|
||||
|
||||
---
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
### One-Line Install (Linux/macOS)
|
||||
|
||||
```bash
|
||||
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
|
||||
```
|
||||
|
||||
### Package Managers
|
||||
|
||||
| Platform | Command |
|
||||
|----------|---------|
|
||||
| **Homebrew** | `brew install kubescape` |
|
||||
| **Krew** | `kubectl krew install kubescape` |
|
||||
| **Arch Linux** | `yay -S kubescape` |
|
||||
| **Ubuntu** | `sudo add-apt-repository ppa:kubescape/kubescape && sudo apt install kubescape` |
|
||||
| **NixOS** | `nix-shell -p kubescape` |
|
||||
| **Chocolatey** | `choco install kubescape` |
|
||||
| **Scoop** | `scoop install kubescape` |
|
||||
|
||||
### Windows (PowerShell)
|
||||
|
||||
```powershell
|
||||
iwr -useb https://raw.githubusercontent.com/kubescape/kubescape/master/install.ps1 | iex
|
||||
```
|
||||
|
||||
📖 **[Full Installation Guide →](docs/installation.md)**
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ CLI Commands
|
||||
|
||||
Kubescape provides a comprehensive CLI with the following commands:
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`kubescape scan`](#scanning) | Scan cluster, files, or images for security issues |
|
||||
| [`kubescape scan image`](#image-scanning) | Scan container images for vulnerabilities |
|
||||
| [`kubescape fix`](#auto-fix) | Auto-fix misconfigurations in manifest files |
|
||||
| [`kubescape patch`](#image-patching) | Patch container images to fix vulnerabilities |
|
||||
| [`kubescape list`](#list-frameworks-and-controls) | List available frameworks and controls |
|
||||
| [`kubescape download`](#offline-support) | Download artifacts for offline/air-gapped use |
|
||||
| [`kubescape config`](#configuration) | Manage cached configurations |
|
||||
| [`kubescape operator`](#operator-commands) | Interact with in-cluster Kubescape operator |
|
||||
| [`kubescape vap`](#validating-admission-policies) | Manage Validating Admission Policies |
|
||||
| [`kubescape mcpserver`](#mcp-server) | Start MCP server for AI assistant integration |
|
||||
| `kubescape completion` | Generate shell completion scripts |
|
||||
| `kubescape version` | Display version information |
|
||||
|
||||
---
|
||||
|
||||
## 📖 Usage Examples
|
||||
|
||||
### Scanning
|
||||
|
||||
#### Scan a Running Cluster
|
||||
|
||||
```bash
|
||||
# Default scan (all frameworks)
|
||||
kubescape scan
|
||||
|
||||
# Scan with a specific framework
|
||||
kubescape scan framework nsa
|
||||
kubescape scan framework mitre
|
||||
kubescape scan framework cis-v1.23-t1.0.1
|
||||
|
||||
# Scan a specific control
|
||||
kubescape scan control C-0005 -v
|
||||
```
|
||||
|
||||
#### Scan Files and Repositories
|
||||
|
||||
```bash
|
||||
# Scan local YAML files
|
||||
kubescape scan /path/to/manifests/
|
||||
|
||||
# Scan a Helm chart
|
||||
kubescape scan /path/to/helm/chart/
|
||||
|
||||
# Scan a Git repository
|
||||
kubescape scan https://github.com/kubescape/kubescape
|
||||
|
||||
# Scan with Kustomize
|
||||
kubescape scan /path/to/kustomize/directory/
|
||||
```
|
||||
|
||||
#### Scan Options
|
||||
|
||||
```bash
|
||||
# Include/exclude namespaces
|
||||
kubescape scan --include-namespaces production,staging
|
||||
kubescape scan --exclude-namespaces kube-system,kube-public
|
||||
|
||||
# Use alternative kubeconfig
|
||||
kubescape scan --kubeconfig /path/to/kubeconfig
|
||||
|
||||
# Set compliance threshold (exit code 1 if below threshold)
|
||||
kubescape scan --compliance-threshold 80
|
||||
|
||||
# Set severity threshold
|
||||
kubescape scan --severity-threshold high
|
||||
```
|
||||
|
||||
#### Output Formats
|
||||
|
||||
```bash
|
||||
# JSON output
|
||||
kubescape scan --format json --output results.json
|
||||
|
||||
# JUnit XML (for CI/CD)
|
||||
kubescape scan --format junit --output results.xml
|
||||
|
||||
# SARIF (for GitHub Code Scanning)
|
||||
kubescape scan --format sarif --output results.sarif
|
||||
|
||||
# HTML report
|
||||
kubescape scan --format html --output report.html
|
||||
|
||||
# PDF report
|
||||
kubescape scan --format pdf --output report.pdf
|
||||
```
|
||||
|
||||
### Image Scanning
|
||||
|
||||
```bash
|
||||
# Scan a public image
|
||||
kubescape scan image nginx:1.21
|
||||
|
||||
# Scan with verbose output
|
||||
kubescape scan image nginx:1.21 -v
|
||||
|
||||
# Scan a private registry image
|
||||
kubescape scan image myregistry/myimage:tag --username user --password pass
|
||||
```
|
||||
|
||||
### Auto-Fix
|
||||
|
||||
Automatically fix misconfigurations in your manifest files:
|
||||
|
||||
```bash
|
||||
# First, scan and save results to JSON
|
||||
kubescape scan /path/to/manifests --format json --output results.json
|
||||
|
||||
# Then apply fixes
|
||||
kubescape fix results.json
|
||||
|
||||
# Dry run (preview changes without applying)
|
||||
kubescape fix results.json --dry-run
|
||||
|
||||
# Apply fixes without confirmation prompts
|
||||
kubescape fix results.json --no-confirm
|
||||
```
|
||||
|
||||
### Image Patching
|
||||
|
||||
Patch container images to fix OS-level vulnerabilities:
|
||||
|
||||
```bash
|
||||
# Start buildkitd (required)
|
||||
sudo buildkitd &
|
||||
|
||||
# Patch an image
|
||||
sudo kubescape patch --image docker.io/library/nginx:1.22
|
||||
|
||||
# Specify custom output tag
|
||||
sudo kubescape patch --image nginx:1.22 --tag nginx:1.22-patched
|
||||
|
||||
# See detailed vulnerability report
|
||||
sudo kubescape patch --image nginx:1.22 -v
|
||||
```
|
||||
|
||||
📖 **[Full Patch Command Documentation →](cmd/patch/README.md)**
|
||||
|
||||
### List Frameworks and Controls
|
||||
|
||||
```bash
|
||||
# List available frameworks
|
||||
kubescape list frameworks
|
||||
|
||||
# List all controls
|
||||
kubescape list controls
|
||||
|
||||
# Output as JSON
|
||||
kubescape list controls --format json
|
||||
```
|
||||
|
||||
### Offline Support
|
||||
|
||||
Download artifacts for air-gapped environments:
|
||||
|
||||
```bash
|
||||
# Download all artifacts
|
||||
kubescape download artifacts --output /path/to/offline/dir
|
||||
|
||||
# Download a specific framework
|
||||
kubescape download framework nsa --output /path/to/nsa.json
|
||||
|
||||
# Scan using downloaded artifacts
|
||||
kubescape scan --use-artifacts-from /path/to/offline/dir
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```bash
|
||||
# View current configuration
|
||||
kubescape config view
|
||||
|
||||
# Set account ID
|
||||
kubescape config set accountID <your-account-id>
|
||||
|
||||
# Delete cached configuration
|
||||
kubescape config delete
|
||||
```
|
||||
|
||||
### Operator Commands
|
||||
|
||||
Interact with the in-cluster Kubescape operator:
|
||||
|
||||
```bash
|
||||
# Trigger a configuration scan
|
||||
kubescape operator scan configurations
|
||||
|
||||
# Trigger a vulnerability scan
|
||||
kubescape operator scan vulnerabilities
|
||||
```
|
||||
|
||||
### Validating Admission Policies
|
||||
|
||||
Manage Kubernetes Validating Admission Policies:
|
||||
|
||||
```bash
|
||||
# Deploy the Kubescape CEL admission policy library
|
||||
kubescape vap deploy-library | kubectl apply -f -
|
||||
|
||||
# Create a policy binding
|
||||
kubescape vap create-policy-binding \
|
||||
--name my-policy-binding \
|
||||
--policy c-0016 \
|
||||
--namespace my-namespace | kubectl apply -f -
|
||||
```
|
||||
|
||||
### MCP Server
|
||||
|
||||
Start an MCP (Model Context Protocol) server for AI assistant integration:
|
||||
|
||||
```bash
|
||||
kubescape mcpserver
|
||||
```
|
||||
|
||||
The MCP server exposes Kubescape's vulnerability and configuration scan data to AI assistants, enabling natural language queries about your cluster's security posture.
|
||||
|
||||
**Available MCP Tools:**
|
||||
- `list_vulnerability_manifests` - Discover vulnerability manifests
|
||||
- `list_vulnerabilities_in_manifest` - List CVEs in a manifest
|
||||
- `list_vulnerability_matches_for_cve` - Get details for a specific CVE
|
||||
- `list_configuration_security_scan_manifests` - List configuration scan results
|
||||
- `get_configuration_security_scan_manifest` - Get configuration scan details
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
Kubescape can run in two modes:
|
||||
|
||||
### CLI Mode
|
||||
|
||||
The CLI is a standalone tool that scans clusters, files, and images on-demand.
|
||||
|
||||
<div align="center">
|
||||
<img src="docs/img/ksfromcodetodeploy.png" alt="Places you can use Kubescape: in your IDE, CI, CD, or against a running cluster.">
|
||||
<img src="docs/img/ks-cli-arch.png" width="600" alt="CLI Architecture">
|
||||
</div>
|
||||
|
||||
## Under the hood
|
||||
**Key Components:**
|
||||
- **[Open Policy Agent (OPA)](https://github.com/open-policy-agent/opa)** - Policy evaluation engine
|
||||
- **[Regolibrary](https://github.com/kubescape/regolibrary)** - Library of security controls
|
||||
- **[Grype](https://github.com/anchore/grype)** - Image vulnerability scanning
|
||||
- **[Copacetic](https://github.com/project-copacetic/copacetic)** - Image patching
|
||||
|
||||
Kubescape uses [Open Policy Agent](https://github.com/open-policy-agent/opa) to verify Kubernetes objects against [a library of posture controls](https://github.com/kubescape/regolibrary).
|
||||
### Operator Mode (In-Cluster)
|
||||
|
||||
By default, the results are printed in a console-friendly manner, but they can be:
|
||||
For continuous monitoring, deploy the Kubescape operator via Helm.
|
||||
|
||||
* exported to JSON or junit XML
|
||||
* rendered to HTML or PDF
|
||||
* submitted to a [cloud service](docs/providers.md)
|
||||
<div align="center">
|
||||
<img src="docs/img/ks-operator-arch.png" width="600" alt="Operator Architecture">
|
||||
</div>
|
||||
|
||||
It retrieves Kubernetes objects from the API server and runs a set of [Rego snippets](https://www.openpolicyagent.org/docs/latest/policy-language/) developed by [ARMO](https://www.armosec.io?utm_source=github&utm_medium=repository).
|
||||
**Additional Capabilities:**
|
||||
- Continuous configuration scanning
|
||||
- Image vulnerability scanning
|
||||
- Runtime analysis with eBPF
|
||||
- Network policy generation
|
||||
|
||||
## Community
|
||||
📖 **[Full Architecture Documentation →](docs/architecture.md)**
|
||||
|
||||
Kubescape is an open source project, we welcome your feedback and ideas for improvement. We are part of the Kubernetes community and are building more tests and controls as the ecosystem develops.
|
||||
---
|
||||
|
||||
We hold [community meetings](https://zoom.us/j/95174063585) on Zoom, on the first Tuesday of every month, at 14:00 GMT. ([See that in your local time zone](https://time.is/compare/1400_in_GMT)).
|
||||
## ☸️ In-Cluster Operator
|
||||
|
||||
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
The Kubescape operator provides continuous security monitoring in your cluster:
|
||||
|
||||
### Adopters
|
||||
```bash
|
||||
# Add the Kubescape Helm repository
|
||||
helm repo add kubescape https://kubescape.github.io/helm-charts/
|
||||
|
||||
See [here](ADOPTERS.md) a list of adopters.
|
||||
# Install the operator
|
||||
helm upgrade --install kubescape kubescape/kubescape-operator \
|
||||
--namespace kubescape \
|
||||
--create-namespace
|
||||
```
|
||||
|
||||
## Contributions
|
||||
**Operator Features:**
|
||||
- 🔄 Continuous misconfiguration scanning
|
||||
- 🐳 Image vulnerability scanning for all workloads
|
||||
- 🔍 Runtime threat detection (eBPF-based)
|
||||
- 🌐 Network policy generation
|
||||
- 📈 Prometheus metrics integration
|
||||
|
||||
Thanks to all our contributors! Check out our [CONTRIBUTING](CONTRIBUTING.md) file to learn how to join them.
|
||||
📖 **[Operator Installation Guide →](https://kubescape.io/docs/operator/)**
|
||||
|
||||
* Feel free to pick a task from the [issues](https://github.com/kubescape/kubescape/issues?q=is%3Aissue+is%3Aopen+label%3A%22open+for+contribution%22), [roadmap](docs/roadmap.md) or suggest a feature of your own.
|
||||
* [Open an issue](https://github.com/kubescape/kubescape/issues/new/choose): we aim to respond to all issues within 48 hours.
|
||||
* [Join the CNCF Slack](https://slack.cncf.io/) and then our [users](https://cloud-native.slack.com/archives/C04EY3ZF9GE) or [developers](https://cloud-native.slack.com/archives/C04GY6H082K) channel.
|
||||
---
|
||||
|
||||
<br>
|
||||
## 🔌 Integrations
|
||||
|
||||
<a href = "https://github.com/kubescape/kubescape/graphs/contributors">
|
||||
<img src = "https://contrib.rocks/image?repo=kubescape/kubescape"/>
|
||||
### CI/CD
|
||||
|
||||
| Platform | Integration |
|
||||
|----------|-------------|
|
||||
| **GitHub Actions** | [kubescape/github-action](https://github.com/marketplace/actions/kubescape) |
|
||||
| **GitLab CI** | [Documentation](https://kubescape.io/docs/integrations/gitlab/) |
|
||||
| **Jenkins** | [Documentation](https://kubescape.io/docs/integrations/jenkins/) |
|
||||
|
||||
### IDE Extensions
|
||||
|
||||
| IDE | Extension |
|
||||
|-----|-----------|
|
||||
| **VS Code** | [Kubescape Extension](https://marketplace.visualstudio.com/items?itemName=kubescape.kubescape) |
|
||||
| **Lens** | [Kubescape Lens Extension](https://github.com/armosec/lens-kubescape) |
|
||||
|
||||
### Where You Can Use Kubescape
|
||||
|
||||
<div align="center">
|
||||
<img src="docs/img/ksfromcodetodeploy.png" alt="Kubescape integration points: IDE, CI, CD, Runtime">
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 👥 Community
|
||||
|
||||
Kubescape is a CNCF incubating project with an active community.
|
||||
|
||||
### Get Involved
|
||||
|
||||
- 💬 **[Slack - Users Channel](https://cloud-native.slack.com/archives/C04EY3ZF9GE)** - Ask questions, get help
|
||||
- 💬 **[Slack - Developers Channel](https://cloud-native.slack.com/archives/C04GY6H082K)** - Contribute to development
|
||||
- 🐛 **[GitHub Issues](https://github.com/kubescape/kubescape/issues)** - Report bugs and request features
|
||||
- 📋 **[Project Board](https://github.com/orgs/kubescape/projects/4)** - See what we're working on
|
||||
- 🗺️ **[Roadmap](https://github.com/kubescape/project-governance/blob/main/ROADMAP.md)** - Future plans
|
||||
|
||||
### Contributing
|
||||
|
||||
We welcome contributions! Please see our:
|
||||
- **[Contributing Guide](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md)**
|
||||
- **[Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md)**
|
||||
|
||||
### Community Resources
|
||||
|
||||
- **[Community Info](https://github.com/kubescape/project-governance/blob/main/COMMUNITY.md)**
|
||||
- **[Governance](https://github.com/kubescape/project-governance/blob/main/GOVERNANCE.md)**
|
||||
- **[Security Policy](https://github.com/kubescape/project-governance/blob/main/SECURITY.md)**
|
||||
- **[Maintainers](https://github.com/kubescape/project-governance/blob/main/MAINTAINERS.md)**
|
||||
|
||||
### Contributors
|
||||
|
||||
<a href="https://github.com/kubescape/kubescape/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=kubescape/kubescape"/>
|
||||
</a>
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
Kubescape changes are tracked on the [release](https://github.com/kubescape/kubescape/releases) page
|
||||
Kubescape changes are tracked on the [releases page](https://github.com/kubescape/kubescape/releases).
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2021-2023, the Kubescape Authors. All rights reserved. Kubescape is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details.
|
||||
Copyright 2021-2025, the Kubescape Authors. All rights reserved.
|
||||
|
||||
Kubescape is a [Cloud Native Computing Foundation (CNCF) sandbox project](https://www.cncf.io/sandbox-projects/) and was contributed by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository).
|
||||
Kubescape is released under the [Apache 2.0 license](LICENSE).
|
||||
|
||||
Kubescape is a [Cloud Native Computing Foundation (CNCF) incubating project](https://www.cncf.io/projects/kubescape/) and was contributed by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository).
|
||||
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/cncf/artwork/master/other/cncf-sandbox/horizontal/color/cncf-sandbox-horizontal-color.svg" width="300" alt="CNCF Sandbox Project">
|
||||
</div>
|
||||
<img src="https://raw.githubusercontent.com/cncf/artwork/refs/heads/main/other/cncf-member/incubating/color/cncf-incubating-color.svg" width="300" alt="CNCF Incubating Project">
|
||||
</div>
|
||||
@@ -4,19 +4,30 @@ header:
|
||||
last-reviewed: '2023-10-12'
|
||||
expiration-date: '2024-10-12T01:00:00.000Z'
|
||||
project-url: https://github.com/kubescape/kubescape/
|
||||
project-release: '1.0.0'
|
||||
project-release: 1.0.0
|
||||
project-lifecycle:
|
||||
status: active
|
||||
bug-fixes-only: false
|
||||
core-maintainers:
|
||||
- github:slashben
|
||||
- github:amirmalka
|
||||
- github:amitschendel
|
||||
- github:bezbran
|
||||
- github:craigbox
|
||||
- github:matthyx
|
||||
- github:dwertent
|
||||
- github:matthyx
|
||||
- github:rotemamsa
|
||||
- github:slashben
|
||||
contribution-policy:
|
||||
accepts-pull-requests: true
|
||||
accepts-automated-pull-requests: false
|
||||
code-of-conduct: https://github.com/kubescape/kubescape/blob/master/CODE_OF_CONDUCT.md
|
||||
dependencies:
|
||||
third-party-packages: true
|
||||
dependencies-lists:
|
||||
- https://github.com/kubescape/kubescape/blob/master/go.mod
|
||||
- https://github.com/kubescape/kubescape/blob/master/httphandler/go.mod
|
||||
env-dependencies-policy:
|
||||
policy-url: https://github.com/kubescape/kubescape/blob/master/docs/environment-dependencies-policy.md
|
||||
documentation:
|
||||
- https://github.com/kubescape/kubescape/tree/master/docs
|
||||
distribution-points:
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Reporting Security Issues
|
||||
# Security
|
||||
|
||||
To report a security issue or vulnerability, submit a [private vulnerability report via GitHub](https://github.com/kubescape/kubescape/security/advisories/new) to the repository maintainers with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
The maintainers will respond within 7 working days of your report. If the issue is confirmed as a vulnerability, we will open a Security Advisory and acknowledge your contributions as part of it. This project follows a 90 day disclosure timeline.
|
||||
|
||||
Other contacts: cncf-kubescape-maintainers@lists.cncf.io
|
||||
Go to the [centralized SECURITY.md](https://github.com/kubescape/project-governance/blob/main/SECURITY.md)
|
||||
|
||||
78
build.ps1
78
build.ps1
@@ -1,78 +0,0 @@
|
||||
# Defining input params
|
||||
param (
|
||||
[string]$mode = "error"
|
||||
)
|
||||
|
||||
# Function to install MSYS
|
||||
function Install {
|
||||
Write-Host "Starting install..." -ForegroundColor Cyan
|
||||
|
||||
# Check to see if already installed
|
||||
if (Test-Path "C:\MSYS64\") {
|
||||
Write-Host "MSYS2 already installed" -ForegroundColor Green
|
||||
} else {
|
||||
# Create a temp directory
|
||||
New-Item -Path "$PSScriptRoot\temp_install" -ItemType Directory > $null
|
||||
|
||||
# Download MSYS
|
||||
Write-Host "Downloading MSYS2..." -ForegroundColor Cyan
|
||||
$bitsJobObj = Start-BitsTransfer "https://github.com/msys2/msys2-installer/releases/download/2022-06-03/msys2-x86_64-20220603.exe" -Destination "$PSScriptRoot\temp_install\msys2-x86_64-20220603.exe"
|
||||
switch ($bitsJobObj.JobState) {
|
||||
"Transferred" {
|
||||
Complete-BitsTransfer -BitsJob $bitsJobObj
|
||||
break
|
||||
}
|
||||
"Error" {
|
||||
throw "Error downloading"
|
||||
}
|
||||
}
|
||||
Write-Host "MSYS2 download complete" -ForegroundColor Green
|
||||
|
||||
# Install MSYS
|
||||
Write-Host "Installing MSYS2..." -ForegroundColor Cyan
|
||||
Start-Process -Filepath "$PSScriptRoot\temp_install\msys2-x86_64-20220603.exe" -ArgumentList @("install", "--root", "C:\MSYS64", "--confirm-command") -Wait
|
||||
Write-Host "MSYS2 install complete" -ForegroundColor Green
|
||||
|
||||
# Remove temp directory
|
||||
Remove-Item "$PSScriptRoot\temp_install" -Recurse
|
||||
}
|
||||
|
||||
# Set PATH
|
||||
$env:Path = "C:\MSYS64\mingw64\bin;C:\MSYS64\usr\bin;" + $env:Path
|
||||
|
||||
# Install MSYS packages
|
||||
Write-Host "Installing MSYS2 packages..." -ForegroundColor Cyan
|
||||
Start-Process -Filepath "pacman" -ArgumentList @("-S", "--needed", "--noconfirm", "make") -Wait
|
||||
Start-Process -Filepath "pacman" -ArgumentList @("-S", "--needed", "--noconfirm", "mingw-w64-x86_64-cmake") -Wait
|
||||
Start-Process -Filepath "pacman" -ArgumentList @("-S", "--needed", "--noconfirm", "mingw-w64-x86_64-gcc") -Wait
|
||||
Start-Process -Filepath "pacman" -ArgumentList @("-S", "--needed", "--noconfirm", "mingw-w64-x86_64-pkg-config") -Wait
|
||||
Start-Process -Filepath "pacman" -ArgumentList @("-S", "--needed", "--noconfirm", "msys2-w32api-runtime") -Wait
|
||||
Write-Host "MSYS2 packages install complete" -ForegroundColor Green
|
||||
|
||||
Write-Host "Install complete" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Function to build libgit2
|
||||
function Build {
|
||||
Write-Host "Starting build..." -ForegroundColor Cyan
|
||||
|
||||
# Set PATH
|
||||
$env:Path = "C:\MSYS64\mingw64\bin;C:\MSYS64\usr\bin;" + $env:Path
|
||||
|
||||
# Build
|
||||
Start-Process -Filepath "make" -ArgumentList @("libgit2") -Wait -NoNewWindow
|
||||
|
||||
Write-Host "Build complete" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Check user call mode
|
||||
if ($mode -eq "all") {
|
||||
Install
|
||||
Build
|
||||
} elseif ($mode -eq "install") {
|
||||
Install
|
||||
} elseif ($mode -eq "build") {
|
||||
Build
|
||||
} else {
|
||||
Write-Host "Error: -mode should be one of (all|install|build)" -ForegroundColor Red
|
||||
}
|
||||
97
build.py
97
build.py
@@ -1,97 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
import platform
|
||||
import subprocess
|
||||
import tarfile
|
||||
|
||||
BASE_GETTER_CONST = "github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
CURRENT_PLATFORM = platform.system()
|
||||
|
||||
platformSuffixes = {
|
||||
"Windows": "windows-latest",
|
||||
"Linux": "ubuntu-latest",
|
||||
"Darwin": "macos-latest",
|
||||
}
|
||||
|
||||
def check_status(status, msg):
|
||||
if status != 0:
|
||||
sys.stderr.write(msg)
|
||||
exit(status)
|
||||
|
||||
|
||||
def get_build_dir():
|
||||
return "build"
|
||||
|
||||
|
||||
def get_package_name():
|
||||
if CURRENT_PLATFORM not in platformSuffixes: raise OSError("Platform %s is not supported!" % (CURRENT_PLATFORM))
|
||||
|
||||
# # TODO: kubescape-windows-latest is deprecated and should be removed
|
||||
# if CURRENT_PLATFORM == "Windows": return "kubescape.exe"
|
||||
|
||||
package_name = "kubescape-"
|
||||
if os.getenv("GOARCH"):
|
||||
package_name += os.getenv("GOARCH") + "-"
|
||||
return package_name + platformSuffixes[CURRENT_PLATFORM]
|
||||
|
||||
|
||||
def main():
|
||||
print("Building Kubescape")
|
||||
|
||||
# Set some variables
|
||||
package_name = get_package_name()
|
||||
build_url = "github.com/kubescape/kubescape/v2/core/cautils.BuildNumber"
|
||||
release_version = os.getenv("RELEASE")
|
||||
|
||||
client_var = "github.com/kubescape/kubescape/v2/core/cautils.Client"
|
||||
client_name = os.getenv("CLIENT")
|
||||
|
||||
# Create build directory
|
||||
build_dir = get_build_dir()
|
||||
|
||||
ks_file = os.path.join(build_dir, package_name)
|
||||
hash_file = ks_file + ".sha256"
|
||||
tar_file = ks_file + ".tar.gz"
|
||||
|
||||
if not os.path.isdir(build_dir):
|
||||
os.makedirs(build_dir)
|
||||
|
||||
# Build kubescape
|
||||
ldflags = "-w -s"
|
||||
if release_version:
|
||||
ldflags += " -X {}={}".format(build_url, release_version)
|
||||
if client_name:
|
||||
ldflags += " -X {}={}".format(client_var, client_name)
|
||||
|
||||
build_command = ["go", "build", "-buildmode=pie", "-tags=static,gitenabled", "-o", ks_file, "-ldflags" ,ldflags]
|
||||
if CURRENT_PLATFORM == "Windows":
|
||||
os.putenv("CGO_ENABLED", "0")
|
||||
build_command = ["go", "build", "-o", ks_file, "-ldflags", ldflags]
|
||||
|
||||
print("Building kubescape and saving here: {}".format(ks_file))
|
||||
print("Build command: {}".format(" ".join(build_command)))
|
||||
|
||||
status = subprocess.call(build_command)
|
||||
check_status(status, "Failed to build kubescape")
|
||||
|
||||
sha256 = hashlib.sha256()
|
||||
with open(ks_file, "rb") as kube:
|
||||
sha256.update(kube.read())
|
||||
with open(hash_file, "w") as kube_sha:
|
||||
hash = sha256.hexdigest()
|
||||
print("kubescape hash: {}, file: {}".format(hash, hash_file))
|
||||
kube_sha.write(sha256.hexdigest())
|
||||
|
||||
with tarfile.open(tar_file, 'w:gz') as archive:
|
||||
name = "kubescape"
|
||||
if CURRENT_PLATFORM == "Windows":
|
||||
name += ".exe"
|
||||
archive.add(ks_file, name)
|
||||
archive.add("LICENSE", "LICENSE")
|
||||
|
||||
print("Build Done")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,20 +1,12 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:1.20-bullseye as builder
|
||||
|
||||
ENV GO111MODULE=on CGO_ENABLED=0
|
||||
WORKDIR /work
|
||||
ARG TARGETOS TARGETARCH
|
||||
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg \
|
||||
cd httphandler && GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/ksserver .
|
||||
|
||||
FROM gcr.io/distroless/static-debian11:nonroot
|
||||
FROM gcr.io/distroless/static-debian13:nonroot
|
||||
|
||||
USER nonroot
|
||||
WORKDIR /home/nonroot/
|
||||
|
||||
COPY --from=builder /out/ksserver /usr/bin/ksserver
|
||||
ARG TARGETPLATFORM
|
||||
COPY $TARGETPLATFORM/downloader /usr/bin/downloader
|
||||
RUN ["downloader"]
|
||||
COPY $TARGETPLATFORM/ksserver /usr/bin/ksserver
|
||||
|
||||
ARG image_version client
|
||||
ENV RELEASE=$image_version CLIENT=$client
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
.git
|
||||
git2go
|
||||
kubescape*
|
||||
|
||||
244
build/README.md
244
build/README.md
@@ -1,19 +1,241 @@
|
||||
## Docker Build
|
||||
# Building Kubescape
|
||||
|
||||
### Build your own Docker image
|
||||
This guide covers how to build Kubescape from source.
|
||||
|
||||
1. Clone Project
|
||||
```
|
||||
git clone https://github.com/kubescape/kubescape.git kubescape && cd "$_"
|
||||
## Table of Contents
|
||||
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Building the CLI](#building-the-cli)
|
||||
- [Building Docker Images](#building-docker-images)
|
||||
- [Build Options](#build-options)
|
||||
- [Development Setup](#development-setup)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Required
|
||||
|
||||
- **Go 1.23+** - [Installation Guide](https://golang.org/doc/install)
|
||||
- **Git** - For cloning the repository
|
||||
- **Make** - For running build commands
|
||||
|
||||
### Optional (for Docker builds)
|
||||
|
||||
- **Docker** - [Installation Guide](https://docs.docker.com/get-docker/)
|
||||
- **Docker Buildx** - For multi-platform builds (included with Docker Desktop)
|
||||
- **GoReleaser** - [Installation Guide](https://goreleaser.com/install/)
|
||||
|
||||
### Verify Prerequisites
|
||||
|
||||
```bash
|
||||
go version # Should be 1.23 or higher
|
||||
git --version
|
||||
make --version
|
||||
docker --version # Optional
|
||||
goreleaser --version # Optional
|
||||
```
|
||||
|
||||
2. Build kubescape CLI Docker image
|
||||
```
|
||||
make all
|
||||
docker buildx build -t kubescape-cli -f build/kubescape-cli.Dockerfile --build-arg="ks_binary=kubescape" --load .
|
||||
---
|
||||
|
||||
## Building the CLI
|
||||
|
||||
### Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/kubescape/kubescape.git
|
||||
cd kubescape
|
||||
```
|
||||
|
||||
3. Build kubescape Docker image
|
||||
### Build with Make
|
||||
|
||||
```bash
|
||||
# Build for your current platform
|
||||
make build
|
||||
|
||||
# The binary will be at ./kubescape
|
||||
./kubescape version
|
||||
```
|
||||
docker buildx build -t kubescape -f build/Dockerfile --load .
|
||||
|
||||
### Build Directly with Go
|
||||
|
||||
```bash
|
||||
go build -o kubescape .
|
||||
```
|
||||
|
||||
### Build with GoReleaser
|
||||
|
||||
```bash
|
||||
# Build for your current platform
|
||||
RELEASE=v0.0.1 CLIENT=local goreleaser build --snapshot --clean --single-target
|
||||
```
|
||||
|
||||
### Cross-Compilation
|
||||
|
||||
Build for different platforms:
|
||||
|
||||
```bash
|
||||
# Linux (amd64)
|
||||
GOOS=linux GOARCH=amd64 go build -o kubescape-linux-amd64 .
|
||||
|
||||
# Linux (arm64)
|
||||
GOOS=linux GOARCH=arm64 go build -o kubescape-linux-arm64 .
|
||||
|
||||
# macOS (amd64)
|
||||
GOOS=darwin GOARCH=amd64 go build -o kubescape-darwin-amd64 .
|
||||
|
||||
# macOS (arm64 / Apple Silicon)
|
||||
GOOS=darwin GOARCH=arm64 go build -o kubescape-darwin-arm64 .
|
||||
|
||||
# Windows (amd64)
|
||||
GOOS=windows GOARCH=amd64 go build -o kubescape-windows-amd64.exe .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Building Docker Images
|
||||
|
||||
Kubescape uses [GoReleaser](https://goreleaser.com/) to build its Docker images. The Dockerfiles are specifically designed to work with GoReleaser's build pipeline, which handles cross-compilation and places binaries in the expected directory structure.
|
||||
|
||||
### Build with GoReleaser
|
||||
|
||||
The recommended way to build Docker images locally is using GoReleaser. Note that `RELEASE`, `CLIENT`, and `RUN_E2E` environment variables are required:
|
||||
|
||||
```bash
|
||||
# Build all artifacts and Docker images locally without publishing
|
||||
# --skip=before,krew,nfpm,sbom skips unnecessary steps for faster local builds
|
||||
RELEASE=v0.0.1 CLIENT=local RUN_E2E=false goreleaser release --snapshot --clean --skip=before,nfpm,sbom
|
||||
```
|
||||
|
||||
Please read the [GoReleaser documentation](https://goreleaser.com/customization/dockers_v2/#testing-locally) for more details on using it for local testing.
|
||||
|
||||
---
|
||||
|
||||
## Build Options
|
||||
|
||||
### Make Targets
|
||||
|
||||
| Target | Description |
|
||||
|--------|-------------|
|
||||
| `make build` | Build the Kubescape binary |
|
||||
| `make test` | Run unit tests |
|
||||
| `make all` | Build everything |
|
||||
| `make clean` | Remove build artifacts |
|
||||
|
||||
### Build Tags
|
||||
|
||||
You can use Go build tags to customize the build:
|
||||
|
||||
```bash
|
||||
# Example with build tags
|
||||
go build -tags "netgo" -o kubescape .
|
||||
```
|
||||
|
||||
### Version Information
|
||||
|
||||
To embed version information in the build:
|
||||
|
||||
```bash
|
||||
VERSION=$(git describe --tags --always)
|
||||
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
COMMIT=$(git rev-parse HEAD)
|
||||
|
||||
go build -ldflags "-X main.version=$VERSION -X main.buildDate=$BUILD_DATE -X main.commit=$COMMIT" -o kubescape .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Install Development Dependencies
|
||||
|
||||
```bash
|
||||
# Install golangci-lint for linting
|
||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
|
||||
# Install other tools as needed
|
||||
go mod download
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
make test
|
||||
|
||||
# Run tests with coverage
|
||||
go test -cover ./...
|
||||
|
||||
# Run specific package tests
|
||||
go test ./core/...
|
||||
```
|
||||
|
||||
### Run Linter
|
||||
|
||||
```bash
|
||||
golangci-lint run
|
||||
```
|
||||
|
||||
### Code Formatting
|
||||
|
||||
```bash
|
||||
go fmt ./...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Fails with "module not found"
|
||||
|
||||
```bash
|
||||
# Update dependencies
|
||||
go mod tidy
|
||||
go mod download
|
||||
```
|
||||
|
||||
### CGO-related Errors
|
||||
|
||||
If you encounter CGO errors, try building with CGO disabled:
|
||||
|
||||
```bash
|
||||
CGO_ENABLED=0 go build -o kubescape .
|
||||
```
|
||||
|
||||
### Docker Build Fails
|
||||
|
||||
Ensure Docker daemon is running and you have sufficient permissions.
|
||||
|
||||
If you encounter an error like `failed to calculate checksum ... "/linux/amd64/kubescape": not found`, it usually means you are trying to run `docker build` manually. Because the Dockerfiles are optimized for GoReleaser, you should use the `goreleaser release --snapshot` command described in the [Building Docker Images](#building-docker-images) section instead.
|
||||
|
||||
```bash
|
||||
# Check Docker status
|
||||
docker info
|
||||
```
|
||||
|
||||
### Out of Memory During Build
|
||||
|
||||
For systems with limited memory:
|
||||
|
||||
```bash
|
||||
# Limit Go's memory usage
|
||||
GOGC=50 go build -o kubescape .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dockerfiles
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `build/Dockerfile` | Full Kubescape image with HTTP handler |
|
||||
| `build/kubescape-cli.Dockerfile` | Minimal CLI-only image |
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Contributing Guide](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md)
|
||||
- [Architecture](../docs/architecture.md)
|
||||
- [Getting Started](../docs/getting-started.md)
|
||||
|
||||
151
build/goreleaser-post-e2e.sh
Normal file
151
build/goreleaser-post-e2e.sh
Normal file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env sh
|
||||
#
|
||||
# goreleaser-post-e2e.sh
|
||||
#
|
||||
# A small, robust POSIX shell script intended to be called from the goreleaser
|
||||
# `builds[].hooks.post` entry. It is responsible for optionally running the
|
||||
# repository smoke tests against the artifact produced in `dist/`.
|
||||
#
|
||||
# Usage:
|
||||
# RUN_E2E=true -> enable running smoke tests
|
||||
# E2E_FAIL_ON_ERROR=1 -> (default) treat test failures as fatal (exit non-zero)
|
||||
# E2E_FAIL_ON_ERROR=0 -> treat test failures as non-fatal (log, but exit 0)
|
||||
#
|
||||
# The script is written to be defensive and to work under /bin/sh on CI runners.
|
||||
# Use POSIX-safe flags only.
|
||||
set -eu
|
||||
|
||||
# Helper for logging
|
||||
_now() {
|
||||
date --iso-8601=seconds 2>/dev/null || date
|
||||
}
|
||||
log() {
|
||||
printf '%s [goreleaser-post-e2e] %s\n' "$(_now)" "$*"
|
||||
}
|
||||
|
||||
# GitHub Actions log grouping helpers (no-op outside Actions)
|
||||
gha_group_start() {
|
||||
if [ "${GITHUB_ACTIONS:-}" = "true" ]; then
|
||||
# Titles must be on a single line
|
||||
printf '::group::%s\n' "$*"
|
||||
fi
|
||||
}
|
||||
gha_group_end() {
|
||||
if [ "${GITHUB_ACTIONS:-}" = "true" ]; then
|
||||
printf '::endgroup::\n'
|
||||
fi
|
||||
}
|
||||
|
||||
# Small helper to interpret various truthy forms (1/true/yes/y)
|
||||
is_true() {
|
||||
case "${1:-}" in
|
||||
1|true|TRUE|yes|YES|y|Y) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Determine repo root relative to this script (script is expected to live in kubescape/build/)
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
: "${RUN_E2E:=false}"
|
||||
# Default to fatal E2E failures.
|
||||
: "${E2E_FAIL_ON_ERROR:=1}"
|
||||
|
||||
log "Starting goreleaser post-build e2e script"
|
||||
log "RUN_E2E=${RUN_E2E}"
|
||||
log "E2E_FAIL_ON_ERROR=${E2E_FAIL_ON_ERROR}"
|
||||
|
||||
# Only run on linux/amd64 to avoid running multiple times (once per build)
|
||||
# and to ensure we can run the binary on the current host (assuming host is amd64).
|
||||
if [ -n "${GOARCH:-}" ] && [ "${GOARCH}" != "amd64" ]; then
|
||||
log "Skipping smoke tests for non-amd64 build (GOARCH=${GOARCH})."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! is_true "${RUN_E2E}"; then
|
||||
log "RUN_E2E is not enabled. Skipping smoke tests. (RUN_E2E=${RUN_E2E})"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Locate the amd64 artifact in dist/.
|
||||
# Goreleaser v2 puts binaries in dist/<id>_<os>_<arch>_<version>/<binary>
|
||||
# Example: dist/cli_linux_amd64_v1/kubescape
|
||||
ART_PATH=""
|
||||
if [ -d "$REPO_ROOT/dist" ]; then
|
||||
# Find any file named 'kubescape' inside a directory containing 'linux_amd64' inside 'dist'
|
||||
# We use 'find' for robustness against varying directory names
|
||||
ART_PATH=$(find "$REPO_ROOT/dist" -type f -name "kubescape" -path "*linux_amd64*" | head -n 1)
|
||||
fi
|
||||
|
||||
if [ -z "$ART_PATH" ] || [ ! -f "$ART_PATH" ]; then
|
||||
log "No kubescape artifact found in dist/ matching *linux_amd64*/kubescape. Skipping smoke tests."
|
||||
# If we are supposed to run E2E, not finding the artifact is probably an error.
|
||||
if is_true "${E2E_FAIL_ON_ERROR}"; then
|
||||
log "E2E_FAIL_ON_ERROR enabled -> failing because artifact was not found."
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "Using artifact: $ART_PATH"
|
||||
# Make binary executable if it is a binary
|
||||
chmod +x "$ART_PATH" >/dev/null 2>&1 || true
|
||||
|
||||
# Locate python runner
|
||||
PYTHON=""
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
PYTHON=python3
|
||||
elif command -v python >/dev/null 2>&1; then
|
||||
PYTHON=python
|
||||
fi
|
||||
|
||||
if [ -z "$PYTHON" ]; then
|
||||
log "python3 (or python) not found in PATH."
|
||||
if is_true "${E2E_FAIL_ON_ERROR}"; then
|
||||
log "E2E_FAIL_ON_ERROR enabled -> failing the release because python is missing."
|
||||
exit 2
|
||||
else
|
||||
log "E2E_FAIL_ON_ERROR disabled -> continuing without running tests."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for smoke test runner
|
||||
SMOKE_RUNNER="$REPO_ROOT/smoke_testing/init.py"
|
||||
if [ ! -f "$SMOKE_RUNNER" ]; then
|
||||
log "Smoke test runner not found at $SMOKE_RUNNER"
|
||||
if is_true "${E2E_FAIL_ON_ERROR}"; then
|
||||
log "E2E_FAIL_ON_ERROR enabled -> failing the release because smoke runner is missing."
|
||||
exit 3
|
||||
else
|
||||
log "E2E_FAIL_ON_ERROR disabled -> continuing without running tests."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
gha_group_start "Smoke tests"
|
||||
log "Running smoke tests with $PYTHON $SMOKE_RUNNER \"$ART_PATH\""
|
||||
# Run the test runner, propagate exit code
|
||||
set +e
|
||||
RELEASE="${RELEASE:-}" "$PYTHON" "$SMOKE_RUNNER" "$ART_PATH"
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
if [ $rc -eq 0 ]; then
|
||||
log "Smoke tests passed (exit code 0)."
|
||||
fi
|
||||
|
||||
log "Smoke tests exited with code: $rc"
|
||||
gha_group_end
|
||||
|
||||
if [ $rc -ne 0 ]; then
|
||||
if is_true "${E2E_FAIL_ON_ERROR}"; then
|
||||
log "E2E_FAIL_ON_ERROR enabled -> failing the release (exit code $rc)."
|
||||
exit $rc
|
||||
else
|
||||
log "E2E_FAIL_ON_ERROR disabled -> continuing despite test failures."
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,12 +1,13 @@
|
||||
FROM gcr.io/distroless/base-debian11:debug-nonroot
|
||||
FROM gcr.io/distroless/static-debian13:debug-nonroot
|
||||
|
||||
USER nonroot
|
||||
WORKDIR /home/nonroot/
|
||||
|
||||
ARG image_version client ks_binary
|
||||
ARG image_version client TARGETARCH
|
||||
ENV RELEASE=$image_version CLIENT=$client
|
||||
|
||||
COPY $ks_binary /usr/bin/kubescape
|
||||
ARG TARGETPLATFORM
|
||||
COPY $TARGETPLATFORM/kubescape /usr/bin/kubescape
|
||||
RUN ["kubescape", "download", "artifacts"]
|
||||
|
||||
ENTRYPOINT ["kubescape"]
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
.git
|
||||
git2go
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -29,6 +29,12 @@ func GetCompletionCmd() *cobra.Command {
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Check if args array is not empty
|
||||
if len(args) == 0 {
|
||||
fmt.Println("No arguements provided.")
|
||||
return
|
||||
}
|
||||
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "bash":
|
||||
cmd.Root().GenBashCompletion(os.Stdout)
|
||||
@@ -38,6 +44,8 @@ func GetCompletionCmd() *cobra.Command {
|
||||
cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
case "powershell":
|
||||
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||
default:
|
||||
fmt.Printf("Invalid arguement %s", args[0])
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
187
cmd/completion/completion_test.go
Normal file
187
cmd/completion/completion_test.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package completion
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Generates autocompletion script for valid shell types
|
||||
func TestGetCompletionCmd(t *testing.T) {
|
||||
// Arrange
|
||||
completionCmd := GetCompletionCmd()
|
||||
assert.Equal(t, "completion [bash|zsh|fish|powershell]", completionCmd.Use)
|
||||
assert.Equal(t, "Generate autocompletion script", completionCmd.Short)
|
||||
assert.Equal(t, "To load completions", completionCmd.Long)
|
||||
assert.Equal(t, completionCmdExamples, completionCmd.Example)
|
||||
assert.Equal(t, true, completionCmd.DisableFlagsInUseLine)
|
||||
assert.Equal(t, []string{"bash", "zsh", "fish", "powershell"}, completionCmd.ValidArgs)
|
||||
}
|
||||
|
||||
func TestGetCompletionCmd_RunExpectedOutputs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Unknown completion",
|
||||
args: []string{"unknown"},
|
||||
want: "Invalid arguement unknown",
|
||||
},
|
||||
{
|
||||
name: "Empty arguements",
|
||||
args: []string{},
|
||||
want: "No arguements provided.\n",
|
||||
},
|
||||
}
|
||||
|
||||
completionCmd := GetCompletionCmd()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
completionCmd.Run(&cobra.Command{}, tt.args)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCompletionCmd_RunNotExpectedOutputs(t *testing.T) {
|
||||
notExpectedOutput1 := "No arguments provided."
|
||||
notExpectedOutput2 := "No arguments provided."
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
}{
|
||||
{
|
||||
name: "Bash completion",
|
||||
args: []string{"bash"},
|
||||
},
|
||||
{
|
||||
name: "Zsh completion",
|
||||
args: []string{"zsh"},
|
||||
},
|
||||
{
|
||||
name: "Fish completion",
|
||||
args: []string{"fish"},
|
||||
},
|
||||
{
|
||||
name: "PowerShell completion",
|
||||
args: []string{"powershell"},
|
||||
},
|
||||
}
|
||||
|
||||
completionCmd := GetCompletionCmd()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
completionCmd.Run(&cobra.Command{}, tt.args)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.NotEqual(t, notExpectedOutput1, string(got))
|
||||
assert.NotEqual(t, notExpectedOutput2, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCompletionCmd_RunBashCompletionNotExpectedOutputs(t *testing.T) {
|
||||
notExpectedOutput1 := "Unexpected output for bash completion test 1."
|
||||
notExpectedOutput2 := "Unexpected output for bash completion test 2."
|
||||
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
completionCmd := GetCompletionCmd()
|
||||
completionCmd.Run(&cobra.Command{}, []string{"bash"})
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.NotEqual(t, notExpectedOutput1, string(got))
|
||||
assert.NotEqual(t, notExpectedOutput2, string(got))
|
||||
}
|
||||
|
||||
func TestGetCompletionCmd_RunZshCompletionNotExpectedOutputs(t *testing.T) {
|
||||
notExpectedOutput1 := "Unexpected output for zsh completion test 1."
|
||||
notExpectedOutput2 := "Unexpected output for zsh completion test 2."
|
||||
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
completionCmd := GetCompletionCmd()
|
||||
completionCmd.Run(&cobra.Command{}, []string{"zsh"})
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.NotEqual(t, notExpectedOutput1, string(got))
|
||||
assert.NotEqual(t, notExpectedOutput2, string(got))
|
||||
}
|
||||
|
||||
func TestGetCompletionCmd_RunFishCompletionNotExpectedOutputs(t *testing.T) {
|
||||
notExpectedOutput1 := "Unexpected output for fish completion test 1."
|
||||
notExpectedOutput2 := "Unexpected output for fish completion test 2."
|
||||
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
completionCmd := GetCompletionCmd()
|
||||
completionCmd.Run(&cobra.Command{}, []string{"fish"})
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.NotEqual(t, notExpectedOutput1, string(got))
|
||||
assert.NotEqual(t, notExpectedOutput2, string(got))
|
||||
}
|
||||
|
||||
func TestGetCompletionCmd_RunPowerShellCompletionNotExpectedOutputs(t *testing.T) {
|
||||
notExpectedOutput1 := "Unexpected output for powershell completion test 1."
|
||||
notExpectedOutput2 := "Unexpected output for powershell completion test 2."
|
||||
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
completionCmd := GetCompletionCmd()
|
||||
completionCmd.Run(&cobra.Command{}, []string{"powershell"})
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.NotEqual(t, notExpectedOutput1, string(got))
|
||||
assert.NotEqual(t, notExpectedOutput2, string(got))
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package config
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
44
cmd/config/config_test.go
Normal file
44
cmd/config/config_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetConfigCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetConfigCmd function
|
||||
configCmd := GetConfigCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "config", configCmd.Use)
|
||||
assert.Equal(t, "Handle cached configurations", configCmd.Short)
|
||||
assert.Equal(t, configExample, configCmd.Example)
|
||||
|
||||
// Verify that the subcommands are added correctly
|
||||
assert.Equal(t, 3, len(configCmd.Commands()))
|
||||
|
||||
for _, subcmd := range configCmd.Commands() {
|
||||
switch subcmd.Name() {
|
||||
case "delete":
|
||||
// Verify that the delete subcommand is added correctly
|
||||
assert.Equal(t, "delete", subcmd.Use)
|
||||
assert.Equal(t, "Delete cached configurations", subcmd.Short)
|
||||
case "set":
|
||||
// Verify that the set subcommand is added correctly
|
||||
assert.Equal(t, "set", subcmd.Use)
|
||||
assert.Equal(t, "Set configurations, supported: "+strings.Join(stringKeysToSlice(supportConfigSet), "/"), subcmd.Short)
|
||||
case "view":
|
||||
// Verify that the view subcommand is added correctly
|
||||
assert.Equal(t, "view", subcmd.Use)
|
||||
assert.Equal(t, "View cached configurations", subcmd.Short)
|
||||
default:
|
||||
t.Errorf("Unexpected subcommand name: %s", subcmd.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -15,7 +13,7 @@ func getDeleteCmd(ks meta.IKubescape) *cobra.Command {
|
||||
Short: "Delete cached configurations",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := ks.DeleteCachedConfig(context.TODO(), &v1.DeleteConfig{}); err != nil {
|
||||
if err := ks.DeleteCachedConfig(&v1.DeleteConfig{}); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
|
||||
21
cmd/config/delete_test.go
Normal file
21
cmd/config/delete_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetDeleteCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetConfigCmd function
|
||||
configCmd := getDeleteCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "delete", configCmd.Use)
|
||||
assert.Equal(t, "Delete cached configurations", configCmd.Short)
|
||||
assert.Equal(t, "", configCmd.Long)
|
||||
}
|
||||
@@ -2,11 +2,12 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -33,16 +34,23 @@ func getSetCmd(ks meta.IKubescape) *cobra.Command {
|
||||
}
|
||||
|
||||
var supportConfigSet = map[string]func(*metav1.SetConfig, string){
|
||||
"accessKey": func(s *metav1.SetConfig, accessKey string) { s.AccessKey = accessKey },
|
||||
"accountID": func(s *metav1.SetConfig, account string) { s.Account = account },
|
||||
"cloudAPIURL": func(s *metav1.SetConfig, cloudAPIURL string) { s.CloudAPIURL = cloudAPIURL },
|
||||
"cloudReportURL": func(s *metav1.SetConfig, cloudReportURL string) { s.CloudReportURL = cloudReportURL },
|
||||
}
|
||||
|
||||
func stringKeysToSlice(m map[string]func(*metav1.SetConfig, string)) []string {
|
||||
l := []string{}
|
||||
for i := range m {
|
||||
l = append(l, i)
|
||||
keys := []string{}
|
||||
for key := range m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
// Sort the keys of the map
|
||||
sort.Strings(keys)
|
||||
|
||||
l := []string{}
|
||||
l = append(l, keys...)
|
||||
return l
|
||||
}
|
||||
|
||||
|
||||
81
cmd/config/set_test.go
Normal file
81
cmd/config/set_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetSetCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetConfigCmd function
|
||||
configSetCmd := getSetCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "set", configSetCmd.Use)
|
||||
assert.Equal(t, "Set configurations, supported: "+strings.Join(stringKeysToSlice(supportConfigSet), "/"), configSetCmd.Short)
|
||||
assert.Equal(t, setConfigExample, configSetCmd.Example)
|
||||
assert.Equal(t, stringKeysToSlice(supportConfigSet), configSetCmd.ValidArgs)
|
||||
|
||||
err := configSetCmd.RunE(&cobra.Command{}, []string{"accountID=value1"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = configSetCmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "key '' unknown . supported: accessKey/accountID/cloudAPIURL/cloudReportURL"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
// Should return a slice of keys when given a non-empty map
|
||||
func TestStringKeysToSlice(t *testing.T) {
|
||||
m := map[string]func(*metav1.SetConfig, string){
|
||||
"key1": nil,
|
||||
"key2": nil,
|
||||
"key3": nil,
|
||||
}
|
||||
result := stringKeysToSlice(m)
|
||||
expected := []string{"key1", "key2", "key3"}
|
||||
assert.ElementsMatch(t, expected, result)
|
||||
}
|
||||
|
||||
func TestParseSetArgs_InvalidFormat(t *testing.T) {
|
||||
args := []string{"key"}
|
||||
setConfig, err := parseSetArgs(args)
|
||||
assert.Equal(t, "", setConfig.Account)
|
||||
assert.Equal(t, "", setConfig.AccessKey)
|
||||
assert.Equal(t, "", setConfig.CloudReportURL)
|
||||
assert.Equal(t, "", setConfig.CloudAPIURL)
|
||||
|
||||
expectedErrorMessage := fmt.Sprintf("key '' unknown . supported: %s", strings.Join(stringKeysToSlice(supportConfigSet), "/"))
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
func TestParseSetArgs_AccessKey(t *testing.T) {
|
||||
args := []string{"accessKey", "value1"}
|
||||
setConfig, _ := parseSetArgs(args)
|
||||
assert.Equal(t, "", setConfig.Account)
|
||||
assert.Equal(t, "value1", setConfig.AccessKey)
|
||||
assert.Equal(t, "", setConfig.CloudReportURL)
|
||||
assert.Equal(t, "", setConfig.CloudAPIURL)
|
||||
}
|
||||
|
||||
func TestParseSetArgs_Single(t *testing.T) {
|
||||
args := []string{"accountID=value1"}
|
||||
setConfig, _ := parseSetArgs(args)
|
||||
assert.Equal(t, "value1", setConfig.Account)
|
||||
assert.Equal(t, "", setConfig.AccessKey)
|
||||
assert.Equal(t, "", setConfig.CloudReportURL)
|
||||
assert.Equal(t, "", setConfig.CloudAPIURL)
|
||||
}
|
||||
|
||||
func TestParseSetArgs_InvalidKey(t *testing.T) {
|
||||
args := []string{"invalidKey=value1"}
|
||||
_, err := parseSetArgs(args)
|
||||
assert.Equal(t, "key 'invalidKey' unknown . supported: accessKey/accountID/cloudAPIURL/cloudReportURL", err.Error())
|
||||
}
|
||||
@@ -3,9 +3,9 @@ package config
|
||||
import (
|
||||
"os"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
21
cmd/config/view_test.go
Normal file
21
cmd/config/view_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetViewCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetConfigCmd function
|
||||
configCmd := getViewCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "view", configCmd.Use)
|
||||
assert.Equal(t, "View cached configurations", configCmd.Short)
|
||||
assert.Equal(t, "", configCmd.Long)
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
package download
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/core"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -37,9 +36,6 @@ var (
|
||||
|
||||
# Download the configured controls-inputs
|
||||
%[1]s download controls-inputs
|
||||
|
||||
# Download the attack tracks
|
||||
%[1]s download attack-tracks
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
@@ -70,13 +66,16 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
if filepath.Ext(downloadInfo.Path) == ".json" {
|
||||
downloadInfo.Path, downloadInfo.FileName = filepath.Split(downloadInfo.Path)
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("no arguements provided")
|
||||
}
|
||||
|
||||
downloadInfo.Target = args[0]
|
||||
if len(args) >= 2 {
|
||||
|
||||
downloadInfo.Identifier = args[1]
|
||||
|
||||
}
|
||||
if err := ks.Download(context.TODO(), &downloadInfo); err != nil {
|
||||
if err := ks.Download(&downloadInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
@@ -84,8 +83,7 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
}
|
||||
|
||||
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
downloadCmd.PersistentFlags().MarkDeprecated("client-id", "Client ID is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
|
||||
downloadCmd.PersistentFlags().MarkDeprecated("secret-key", "Secret Key is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
|
||||
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.AccessKey, "access-key", "", "", "Kubescape SaaS access key. Default will load access key from cache")
|
||||
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
|
||||
|
||||
return downloadCmd
|
||||
|
||||
102
cmd/download/download_test.go
Normal file
102
cmd/download/download_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package download
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetViewCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetConfigCmd function
|
||||
configCmd := GetDownloadCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "download <policy> <policy name>", configCmd.Use)
|
||||
assert.Equal(t, fmt.Sprintf("Download %s", strings.Join(core.DownloadSupportCommands(), ",")), configCmd.Short)
|
||||
assert.Equal(t, "", configCmd.Long)
|
||||
assert.Equal(t, downloadExample, configCmd.Example)
|
||||
}
|
||||
|
||||
func TestGetViewCmd_Args(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetConfigCmd function
|
||||
downloadCmd := GetDownloadCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "download <policy> <policy name>", downloadCmd.Use)
|
||||
assert.Equal(t, fmt.Sprintf("Download %s", strings.Join(core.DownloadSupportCommands(), ",")), downloadCmd.Short)
|
||||
assert.Equal(t, "", downloadCmd.Long)
|
||||
assert.Equal(t, downloadExample, downloadCmd.Example)
|
||||
|
||||
err := downloadCmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "no arguements provided"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = downloadCmd.RunE(&cobra.Command{}, []string{"config"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = downloadCmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage = "policy type required, supported: artifacts,attack-tracks,control,controls-inputs,exceptions,framework"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = downloadCmd.Args(&cobra.Command{}, []string{"invalid"})
|
||||
expectedErrorMessage = "invalid parameter 'invalid'. Supported parameters: artifacts,attack-tracks,control,controls-inputs,exceptions,framework"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = downloadCmd.Args(&cobra.Command{}, []string{"attack-tracks"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = downloadCmd.Args(&cobra.Command{}, []string{"control", "random.json"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = downloadCmd.Args(&cobra.Command{}, []string{"control", "C-0001"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = downloadCmd.Args(&cobra.Command{}, []string{"control", "C-0001", "C-0002"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = downloadCmd.RunE(&cobra.Command{}, []string{"control", "C-0001", "C-0002"})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestFlagValidationDownload_NoError(t *testing.T) {
|
||||
downloadInfo := v1.DownloadInfo{
|
||||
AccessKey: "",
|
||||
AccountID: "",
|
||||
}
|
||||
assert.Equal(t, nil, flagValidationDownload(&downloadInfo))
|
||||
}
|
||||
|
||||
func TestFlagValidationDownload_Error(t *testing.T) {
|
||||
tests := []struct {
|
||||
downloadInfo v1.DownloadInfo
|
||||
}{
|
||||
{
|
||||
downloadInfo: v1.DownloadInfo{
|
||||
AccountID: "12345678",
|
||||
},
|
||||
},
|
||||
{
|
||||
downloadInfo: v1.DownloadInfo{
|
||||
AccountID: "New",
|
||||
},
|
||||
},
|
||||
}
|
||||
want := "bad argument: accound ID must be a valid UUID"
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.downloadInfo.AccountID, func(t *testing.T) {
|
||||
assert.Equal(t, want, flagValidationDownload(&tt.downloadInfo).Error())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -36,7 +34,7 @@ func GetFixCmd(ks meta.IKubescape) *cobra.Command {
|
||||
}
|
||||
fixInfo.ReportFile = args[0]
|
||||
|
||||
return ks.Fix(context.TODO(), &fixInfo)
|
||||
return ks.Fix(&fixInfo)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
30
cmd/fix/fix_test.go
Normal file
30
cmd/fix/fix_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetFixCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetFixCmd function
|
||||
fixCmd := GetFixCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "fix <report output file>", fixCmd.Use)
|
||||
assert.Equal(t, "Propose a fix for the misconfiguration found when scanning Kubernetes manifest files", fixCmd.Short)
|
||||
assert.Equal(t, "", fixCmd.Long)
|
||||
assert.Equal(t, fixCmdExamples, fixCmd.Example)
|
||||
|
||||
err := fixCmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "report output file is required"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = fixCmd.RunE(&cobra.Command{}, []string{"random-file.json"})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/core"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -26,7 +26,7 @@ var (
|
||||
%[1]s list controls
|
||||
|
||||
Control documentation:
|
||||
https://hub.armosec.io/docs/controls
|
||||
https://kubescape.io/docs/controls/
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
@@ -55,15 +55,20 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("no arguements provided")
|
||||
}
|
||||
|
||||
listPolicies.Target = args[0]
|
||||
|
||||
if err := ks.List(context.TODO(), &listPolicies); err != nil {
|
||||
if err := ks.List(&listPolicies); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.AccessKey, "access-key", "", "", "Kubescape SaaS access key. Default will load access key from cache")
|
||||
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-print'/'json'")
|
||||
listCmd.PersistentFlags().MarkDeprecated("id", "Control ID's are included in list outputs")
|
||||
|
||||
|
||||
44
cmd/list/list_test.go
Normal file
44
cmd/list/list_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetListCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetListCmd function
|
||||
listCmd := GetListCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "list <policy> [flags]", listCmd.Use)
|
||||
assert.Equal(t, "List frameworks/controls will list the supported frameworks and controls", listCmd.Short)
|
||||
assert.Equal(t, "", listCmd.Long)
|
||||
assert.Equal(t, listExample, listCmd.Example)
|
||||
supported := strings.Join(core.ListSupportActions(), ",")
|
||||
|
||||
err := listCmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "policy type requeued, supported: " + supported
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = listCmd.Args(&cobra.Command{}, []string{"not-frameworks"})
|
||||
expectedErrorMessage = "invalid parameter 'not-frameworks'. Supported parameters: " + supported
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = listCmd.Args(&cobra.Command{}, []string{"frameworks"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = listCmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage = "no arguements provided"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = listCmd.RunE(&cobra.Command{}, []string{"some-value"})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
499
cmd/mcpserver/mcpserver.go
Normal file
499
cmd/mcpserver/mcpserver.go
Normal file
@@ -0,0 +1,499 @@
|
||||
package mcpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
helpersv1 "github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers"
|
||||
"github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1"
|
||||
spdxv1beta1 "github.com/kubescape/storage/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type KubescapeMcpserver struct {
|
||||
s *server.MCPServer
|
||||
ksClient spdxv1beta1.SpdxV1beta1Interface
|
||||
}
|
||||
|
||||
func createVulnerabilityToolsAndResources(ksServer *KubescapeMcpserver) {
|
||||
// Tool to list vulnerability manifests
|
||||
listManifestsTool := mcp.NewTool(
|
||||
"list_vulnerability_manifests",
|
||||
mcp.WithDescription("Discover available vulnerability manifests at image and workload levels"),
|
||||
mcp.WithString("namespace",
|
||||
mcp.Description("Filter by namespace (optional)"),
|
||||
),
|
||||
mcp.WithString("level",
|
||||
mcp.Description("Type of vulnerability manifests to list"),
|
||||
mcp.Enum("image", "workload", "both"),
|
||||
),
|
||||
)
|
||||
|
||||
ksServer.s.AddTool(listManifestsTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return ksServer.CallTool("list_vulnerability_manifests", request.Params.Arguments.(map[string]interface{}))
|
||||
})
|
||||
|
||||
listVulnerabilitiesTool := mcp.NewTool(
|
||||
"list_vulnerabilities_in_manifest",
|
||||
mcp.WithDescription("List all vulnerabilities in a given manifest"),
|
||||
mcp.WithString("namespace",
|
||||
mcp.Description("Filter by namespace (optional)"),
|
||||
),
|
||||
mcp.WithString("manifest_name",
|
||||
mcp.Required(),
|
||||
mcp.Description("Name of the manifest to list vulnerabilities from"),
|
||||
),
|
||||
)
|
||||
|
||||
ksServer.s.AddTool(listVulnerabilitiesTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return ksServer.CallTool("list_vulnerabilities_in_manifest", request.Params.Arguments.(map[string]interface{}))
|
||||
})
|
||||
|
||||
listVulnerabilityMatchesForCVE := mcp.NewTool(
|
||||
"list_vulnerability_matches_for_cve",
|
||||
mcp.WithDescription("List all vulnerability matches for a given CVE in a given manifest"),
|
||||
mcp.WithString("namespace",
|
||||
mcp.Description("Filter by namespace (optional)"),
|
||||
),
|
||||
mcp.WithString("manifest_name",
|
||||
mcp.Required(),
|
||||
mcp.Description("Name of the manifest to list vulnerabilities from"),
|
||||
),
|
||||
mcp.WithString("cve_id",
|
||||
mcp.Required(),
|
||||
mcp.Description("ID of the CVE to list matches for"),
|
||||
),
|
||||
)
|
||||
|
||||
ksServer.s.AddTool(listVulnerabilityMatchesForCVE, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return ksServer.CallTool("list_vulnerability_matches_for_cve", request.Params.Arguments.(map[string]interface{}))
|
||||
})
|
||||
|
||||
vulnerabilityManifestTemplate := mcp.NewResourceTemplate(
|
||||
"kubescape://vulnerability-manifests/{namespace}/{manifest_name}",
|
||||
"Vulnerability Manifest",
|
||||
mcp.WithTemplateDescription("Complete vulnerability manifest either for a specific workload or image. Use 'list_vulnerability_manifests' tool to discover available manifests."),
|
||||
mcp.WithTemplateMIMEType("application/json"),
|
||||
)
|
||||
|
||||
ksServer.s.AddResourceTemplate(vulnerabilityManifestTemplate, ksServer.ReadResource)
|
||||
|
||||
}
|
||||
|
||||
func createConfigurationsToolsAndResources(ksServer *KubescapeMcpserver) {
|
||||
// Tool to list configuration manifests
|
||||
listConfigsTool := mcp.NewTool(
|
||||
"list_configuration_security_scan_manifests",
|
||||
mcp.WithDescription("Discover available security configuration scan results at workload level (this returns a list of manifests, not the scan results themselves, to get the scan results, use the get_configuration_security_scan_manifest tool)"),
|
||||
mcp.WithString("namespace",
|
||||
mcp.Description("Filter by namespace (optional)"),
|
||||
),
|
||||
)
|
||||
|
||||
ksServer.s.AddTool(listConfigsTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return ksServer.CallTool("list_configuration_security_scan_manifests", request.Params.Arguments.(map[string]interface{}))
|
||||
})
|
||||
|
||||
getConfigDetailsTool := mcp.NewTool(
|
||||
"get_configuration_security_scan_manifest",
|
||||
mcp.WithDescription("Get details of a specific security configuration scan result"),
|
||||
mcp.WithString("namespace",
|
||||
mcp.Description("Namespace of the manifest (optional, defaults to 'kubescape')"),
|
||||
),
|
||||
mcp.WithString("manifest_name",
|
||||
mcp.Required(),
|
||||
mcp.Description("Name of the configuration manifest to get details for (get this from the list_configuration_security_scan_manifests tool)"),
|
||||
),
|
||||
)
|
||||
|
||||
ksServer.s.AddTool(getConfigDetailsTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return ksServer.CallTool("get_configuration_security_scan_manifest", request.Params.Arguments.(map[string]interface{}))
|
||||
})
|
||||
|
||||
configManifestTemplate := mcp.NewResourceTemplate(
|
||||
"kubescape://configuration-manifests/{namespace}/{manifest_name}",
|
||||
"Configuration Security Scan Manifest",
|
||||
mcp.WithTemplateDescription("Complete configuration scan manifest for a specific workload. Use 'list_configuration_security_scan_manifests' tool to discover available manifests."),
|
||||
mcp.WithTemplateMIMEType("application/json"),
|
||||
)
|
||||
|
||||
ksServer.s.AddResourceTemplate(configManifestTemplate, ksServer.ReadConfigurationResource)
|
||||
}
|
||||
|
||||
func (ksServer *KubescapeMcpserver) ReadResource(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
|
||||
uri := request.Params.URI
|
||||
// Validate the URI and check if it starts with kubescape://vulnerability-manifests/
|
||||
if !strings.HasPrefix(uri, "kubescape://vulnerability-manifests/") {
|
||||
return nil, fmt.Errorf("invalid URI: %s", uri)
|
||||
}
|
||||
|
||||
// Verify that the URI is either the CVE list or CVE details
|
||||
if !strings.HasSuffix(uri, "/cve_list") && !strings.Contains(uri, "/cve_details/") {
|
||||
return nil, fmt.Errorf("invalid URI: %s", uri)
|
||||
}
|
||||
|
||||
// Split the URI into namespace and manifest name
|
||||
parts := strings.Split(uri, "/")
|
||||
if len(parts) != 4 && len(parts) != 5 {
|
||||
return nil, fmt.Errorf("invalid URI: %s", uri)
|
||||
}
|
||||
|
||||
namespace := parts[1]
|
||||
manifestName := parts[2]
|
||||
cveID := ""
|
||||
if len(parts) == 5 {
|
||||
cveID = parts[3]
|
||||
}
|
||||
|
||||
// Get the vulnerability manifest
|
||||
manifest, err := ksServer.ksClient.VulnerabilityManifests(namespace).Get(ctx, manifestName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get vulnerability manifest: %s", err)
|
||||
}
|
||||
|
||||
var responseJson []byte
|
||||
if cveID == "" {
|
||||
// CVE list
|
||||
var cveList []v1beta1.Vulnerability
|
||||
for _, match := range manifest.Spec.Payload.Matches {
|
||||
cveList = append(cveList, match.Vulnerability)
|
||||
}
|
||||
responseJson, err = json.Marshal(cveList)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal cve list: %s", err)
|
||||
}
|
||||
} else {
|
||||
// CVE details
|
||||
var match []v1beta1.Match
|
||||
for _, m := range manifest.Spec.Payload.Matches {
|
||||
if m.Vulnerability.ID == cveID {
|
||||
match = append(match, m)
|
||||
}
|
||||
}
|
||||
responseJson, err = json.Marshal(match)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal cve details: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return []mcp.ResourceContents{mcp.TextResourceContents{
|
||||
URI: uri,
|
||||
Text: string(responseJson),
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (ksServer *KubescapeMcpserver) ReadConfigurationResource(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
|
||||
uri := request.Params.URI
|
||||
if !strings.HasPrefix(uri, "kubescape://configuration-manifests/") {
|
||||
return nil, fmt.Errorf("invalid URI: %s", uri)
|
||||
}
|
||||
parts := strings.Split(uri[len("kubescape://configuration-manifests/"):], "/")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid URI: %s", uri)
|
||||
}
|
||||
namespace := parts[0]
|
||||
manifestName := parts[1]
|
||||
manifest, err := ksServer.ksClient.WorkloadConfigurationScans(namespace).Get(ctx, manifestName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get configuration manifest: %s", err)
|
||||
}
|
||||
responseJson, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal configuration manifest: %s", err)
|
||||
}
|
||||
return []mcp.ResourceContents{mcp.TextResourceContents{
|
||||
URI: uri,
|
||||
Text: string(responseJson),
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (ksServer *KubescapeMcpserver) CallTool(name string, arguments map[string]interface{}) (*mcp.CallToolResult, error) {
|
||||
switch name {
|
||||
case "list_vulnerability_manifests":
|
||||
//namespace, ok := arguments["namespace"]
|
||||
//if !ok {
|
||||
// namespace = ""
|
||||
//}
|
||||
level, ok := arguments["level"]
|
||||
if !ok {
|
||||
level = "both"
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"vulnerability_manifests": map[string]interface{}{},
|
||||
}
|
||||
|
||||
// Get workload-level manifests
|
||||
labelSelector := ""
|
||||
switch level {
|
||||
case "workload":
|
||||
labelSelector = "kubescape.io/context=filtered"
|
||||
case "image":
|
||||
labelSelector = "kubescape.io/context=non-filtered"
|
||||
}
|
||||
|
||||
var manifests *v1beta1.VulnerabilityManifestList
|
||||
var err error
|
||||
if labelSelector == "" {
|
||||
manifests, err = ksServer.ksClient.VulnerabilityManifests(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{})
|
||||
} else {
|
||||
manifests, err = ksServer.ksClient.VulnerabilityManifests(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{
|
||||
LabelSelector: labelSelector,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("Found %d manifests", len(manifests.Items))
|
||||
|
||||
vulnerabilityManifests := []map[string]interface{}{}
|
||||
for _, manifest := range manifests.Items {
|
||||
isImageLevel := manifest.Annotations[helpersv1.WlidMetadataKey] == ""
|
||||
manifestMap := map[string]interface{}{
|
||||
"type": "workload",
|
||||
"namespace": manifest.Namespace,
|
||||
"manifest_name": manifest.Name,
|
||||
"image-level": isImageLevel,
|
||||
"workload-level": !isImageLevel,
|
||||
"image-id": manifest.Annotations[helpersv1.ImageIDMetadataKey],
|
||||
"image-tag": manifest.Annotations[helpersv1.ImageTagMetadataKey],
|
||||
"workload-id": manifest.Annotations[helpersv1.WlidMetadataKey],
|
||||
"workload-container-name": manifest.Annotations[helpersv1.ContainerNameMetadataKey],
|
||||
"resource_uri": fmt.Sprintf("kubescape://vulnerability-manifests/%s/%s",
|
||||
manifest.Namespace, manifest.Name),
|
||||
}
|
||||
vulnerabilityManifests = append(vulnerabilityManifests, manifestMap)
|
||||
}
|
||||
result["vulnerability_manifests"].(map[string]interface{})["manifests"] = vulnerabilityManifests
|
||||
|
||||
// Add template information
|
||||
result["available_templates"] = map[string]string{
|
||||
"vulnerability_manifest_cve_list": "kubescape://vulnerability-manifests/{namespace}/{manifest_name}/cve_list",
|
||||
"vulnerability_manifest_cve_details": "kubescape://vulnerability-manifests/{namespace}/{manifest_name}/cve_details/{cve_id}",
|
||||
}
|
||||
|
||||
content, _ := json.Marshal(result)
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(content),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case "list_vulnerabilities_in_manifest":
|
||||
namespace, ok := arguments["namespace"]
|
||||
if !ok {
|
||||
namespace = "kubescape"
|
||||
}
|
||||
namespaceStr, ok := namespace.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("namespace must be a string")
|
||||
}
|
||||
manifestName, ok := arguments["manifest_name"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("manifest_name is required")
|
||||
}
|
||||
manifestNameStr, ok := manifestName.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("manifest_name must be a string")
|
||||
}
|
||||
manifest, err := ksServer.ksClient.VulnerabilityManifests(namespaceStr).Get(context.Background(), manifestNameStr, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get vulnerability manifest: %s", err)
|
||||
}
|
||||
var cveList []v1beta1.Vulnerability
|
||||
for _, match := range manifest.Spec.Payload.Matches {
|
||||
cveList = append(cveList, match.Vulnerability)
|
||||
}
|
||||
responseJson, err := json.Marshal(cveList)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal cve list: %s", err)
|
||||
}
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(responseJson),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case "list_vulnerability_matches_for_cve":
|
||||
namespace, ok := arguments["namespace"]
|
||||
if !ok {
|
||||
namespace = "kubescape"
|
||||
}
|
||||
namespaceStr, ok := namespace.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("namespace must be a string")
|
||||
}
|
||||
manifestName, ok := arguments["manifest_name"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("manifest_name is required")
|
||||
}
|
||||
manifestNameStr, ok := manifestName.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("manifest_name must be a string")
|
||||
}
|
||||
cveID, ok := arguments["cve_id"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cve_id is required")
|
||||
}
|
||||
cveIDStr, ok := cveID.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cve_id must be a string")
|
||||
}
|
||||
manifest, err := ksServer.ksClient.VulnerabilityManifests(namespaceStr).Get(context.Background(), manifestNameStr, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get vulnerability manifest: %s", err)
|
||||
}
|
||||
var match []v1beta1.Match
|
||||
for _, m := range manifest.Spec.Payload.Matches {
|
||||
if m.Vulnerability.ID == cveIDStr {
|
||||
match = append(match, m)
|
||||
}
|
||||
}
|
||||
responseJson, err := json.Marshal(match)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal cve details: %s", err)
|
||||
}
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(responseJson),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case "list_configuration_security_scan_manifests":
|
||||
namespace, ok := arguments["namespace"]
|
||||
if !ok {
|
||||
namespace = "kubescape"
|
||||
}
|
||||
namespaceStr, ok := namespace.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("namespace must be a string")
|
||||
}
|
||||
manifests, err := ksServer.ksClient.WorkloadConfigurationScans(namespaceStr).List(context.Background(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("Found %d configuration manifests", len(manifests.Items))
|
||||
configManifests := []map[string]interface{}{}
|
||||
for _, manifest := range manifests.Items {
|
||||
item := map[string]interface{}{
|
||||
"namespace": manifest.Namespace,
|
||||
"manifest_name": manifest.Name,
|
||||
"resource_uri": fmt.Sprintf("kubescape://configuration-manifests/%s/%s", manifest.Namespace, manifest.Name),
|
||||
}
|
||||
configManifests = append(configManifests, item)
|
||||
}
|
||||
result := map[string]interface{}{
|
||||
"configuration_manifests": map[string]interface{}{
|
||||
"manifests": configManifests,
|
||||
},
|
||||
"available_templates": map[string]string{
|
||||
"configuration_manifest_details": "kubescape://configuration-manifests/{namespace}/{manifest_name}",
|
||||
},
|
||||
}
|
||||
content, _ := json.Marshal(result)
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(content),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case "get_configuration_security_scan_manifest":
|
||||
namespace, ok := arguments["namespace"]
|
||||
if !ok {
|
||||
namespace = "kubescape"
|
||||
}
|
||||
namespaceStr, ok := namespace.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("namespace must be a string")
|
||||
}
|
||||
manifestName, ok := arguments["manifest_name"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("manifest_name is required")
|
||||
}
|
||||
manifestNameStr, ok := manifestName.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("manifest_name must be a string")
|
||||
}
|
||||
manifest, err := ksServer.ksClient.WorkloadConfigurationScans(namespaceStr).Get(context.Background(), manifestNameStr, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get configuration manifest: %s", err)
|
||||
}
|
||||
responseJson, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal configuration manifest: %s", err)
|
||||
}
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(responseJson),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown tool: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func mcpServerEntrypoint() error {
|
||||
logger.L().Info("Starting MCP server...")
|
||||
|
||||
// Create a kubernetes client and verify it's working
|
||||
client, err := CreateKsObjectConnection("default", 10*time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create kubernetes client: %v", err)
|
||||
}
|
||||
|
||||
// Create a new MCP server
|
||||
s := server.NewMCPServer(
|
||||
"Kubescape MCP Server",
|
||||
"0.0.1",
|
||||
server.WithToolCapabilities(false),
|
||||
server.WithRecovery(),
|
||||
)
|
||||
|
||||
ksServer := &KubescapeMcpserver{
|
||||
s: s,
|
||||
ksClient: client,
|
||||
}
|
||||
|
||||
// Creating Kubescape tools and resources
|
||||
|
||||
createVulnerabilityToolsAndResources(ksServer)
|
||||
createConfigurationsToolsAndResources(ksServer)
|
||||
|
||||
// Start the server
|
||||
if err := server.ServeStdio(s); err != nil {
|
||||
return fmt.Errorf("server error: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetMCPServerCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "mcpserver",
|
||||
Short: "Start the Kubescape MCP server",
|
||||
Long: `Start the Kubescape MCP server`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return mcpServerEntrypoint()
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
14
cmd/mcpserver/storage.go
Normal file
14
cmd/mcpserver/storage.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package mcpserver
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/pkg/ksinit"
|
||||
|
||||
spdxv1beta1 "github.com/kubescape/storage/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1"
|
||||
)
|
||||
|
||||
// CreateKsObjectConnection delegates to the shared ksinit package
|
||||
func CreateKsObjectConnection(namespace string, maxElapsedTime time.Duration) (spdxv1beta1.SpdxV1beta1Interface, error) {
|
||||
return ksinit.CreateKsObjectConnection(namespace, maxElapsedTime)
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/core"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ var operatorScanConfigExamples = fmt.Sprintf(`
|
||||
func getOperatorScanConfigCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command {
|
||||
configCmd := &cobra.Command{
|
||||
Use: "configurations",
|
||||
Short: "Trigger configuration scanning from the Kubescape-Operator microservice",
|
||||
Short: "Trigger configuration scanning from the Kubescape Operator microservice",
|
||||
Long: ``,
|
||||
Example: operatorScanConfigExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -29,17 +29,17 @@ func getOperatorScanConfigCmd(ks meta.IKubescape, operatorInfo cautils.OperatorI
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo)
|
||||
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo, operatorInfo.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Start("Kubescape-Operator Triggering for configuration scanning")
|
||||
logger.L().Start("Kubescape Operator Triggering for configuration scanning")
|
||||
_, err = operatorAdapter.OperatorScan()
|
||||
if err != nil {
|
||||
logger.L().StopError("Failed to triggering Kubescape-Operator for configuration scanning", helpers.Error(err))
|
||||
logger.L().StopError("Failed to triggering Kubescape Operator for configuration scanning", helpers.Error(err))
|
||||
return err
|
||||
}
|
||||
logger.L().StopSuccess("Triggered Kubescape-Operator for configuration scanning")
|
||||
logger.L().StopSuccess("Triggered Kubescape Operator for configuration scanning")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
32
cmd/operator/configscan_test.go
Normal file
32
cmd/operator/configscan_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetOperatorScanConfigCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
operatorInfo := cautils.OperatorInfo{
|
||||
Namespace: "namespace",
|
||||
}
|
||||
|
||||
cmd := getOperatorScanConfigCmd(mockKubescape, operatorInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "configurations", cmd.Use)
|
||||
assert.Equal(t, "Trigger configuration scanning from the Kubescape Operator microservice", cmd.Short)
|
||||
assert.Equal(t, "", cmd.Long)
|
||||
assert.Equal(t, operatorScanConfigExamples, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"configurations"})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
@@ -4,9 +4,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -15,7 +14,7 @@ const (
|
||||
)
|
||||
|
||||
var operatorExamples = fmt.Sprintf(`
|
||||
|
||||
|
||||
# Trigger a configuration scan
|
||||
%[1]s operator scan configurations
|
||||
|
||||
@@ -29,22 +28,22 @@ func GetOperatorCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
operatorCmd := &cobra.Command{
|
||||
Use: "operator",
|
||||
Short: "The operator is used to communicate with the Kubescape-Operator within the cluster components.",
|
||||
Short: "The operator is used to communicate with the Kubescape Operator within the cluster components.",
|
||||
Long: ``,
|
||||
Example: operatorExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
operatorInfo.Subcommands = append(operatorInfo.Subcommands, "operator")
|
||||
if len(args) < 2 {
|
||||
return errors.New("For the operator sub-command, you need to provide at least one additional sub-command. Refer to the examples above.")
|
||||
return errors.New("for the operator sub-command, you need to provide at least one additional sub-command. Refer to the examples above")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 2 {
|
||||
return errors.New("For the operator sub-command, you need to provide at least one additional sub-command. Refer to the examples above.")
|
||||
return errors.New("for the operator sub-command, you need to provide at least one additional sub-command. Refer to the examples above")
|
||||
}
|
||||
if args[0] != scanSubCommand {
|
||||
return errors.New(fmt.Sprintf("For the operator sub-command, only %s is supported. Refer to the examples above.", scanSubCommand))
|
||||
return fmt.Errorf("for the operator sub-command, only %s is supported. Refer to the examples above", scanSubCommand)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
42
cmd/operator/operator_test.go
Normal file
42
cmd/operator/operator_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetOperatorCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
cmd := GetOperatorCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "operator", cmd.Use)
|
||||
assert.Equal(t, "The operator is used to communicate with the Kubescape Operator within the cluster components.", cmd.Short)
|
||||
assert.Equal(t, "", cmd.Long)
|
||||
assert.Equal(t, operatorExamples, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "for the operator sub-command, you need to provide at least one additional sub-command. Refer to the examples above"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"scan", "configurations"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{})
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"scan", "configurations"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"scan"})
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"random-subcommand", "random-config"})
|
||||
expectedErrorMessage = "for the operator sub-command, only " + scanSubCommand + " is supported. Refer to the examples above"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -32,12 +32,13 @@ func getOperatorScanCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *
|
||||
return errors.New("for operator scan sub command, you must pass at least 1 more sub commands, see above examples")
|
||||
}
|
||||
if (args[0] != vulnerabilitiesSubCommand) && (args[0] != configurationsSubCommand) {
|
||||
return errors.New(fmt.Sprintf("For the operator sub-command, only %s and %s are supported. Refer to the examples above.", vulnerabilitiesSubCommand, configurationsSubCommand))
|
||||
return fmt.Errorf("for the operator sub-command, only %s and %s are supported. Refer to the examples above", vulnerabilitiesSubCommand, configurationsSubCommand)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
operatorCmd.PersistentFlags().StringVar(&operatorInfo.Namespace, "namespace", "kubescape", "namespace of the Kubescape Operator")
|
||||
operatorCmd.AddCommand(getOperatorScanConfigCmd(ks, operatorInfo))
|
||||
operatorCmd.AddCommand(getOperatorScanVulnerabilitiesCmd(ks, operatorInfo))
|
||||
|
||||
|
||||
46
cmd/operator/scan_test.go
Normal file
46
cmd/operator/scan_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetOperatorScanCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
operatorInfo := cautils.OperatorInfo{
|
||||
Namespace: "namespace",
|
||||
}
|
||||
|
||||
cmd := getOperatorScanCmd(mockKubescape, operatorInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "scan", cmd.Use)
|
||||
assert.Equal(t, "Scan your cluster using the Kubescape-operator within the cluster components", cmd.Short)
|
||||
assert.Equal(t, "", cmd.Long)
|
||||
assert.Equal(t, operatorExamples, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "for operator scan sub command, you must pass at least 1 more sub commands, see above examples"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"operator"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{})
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"configurations"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"vulnerabilities"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"random"})
|
||||
expectedErrorMessage = "for the operator sub-command, only " + vulnerabilitiesSubCommand + " and " + configurationsSubCommand + " are supported. Refer to the examples above"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/core"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -30,17 +30,17 @@ func getOperatorScanVulnerabilitiesCmd(ks meta.IKubescape, operatorInfo cautils.
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo)
|
||||
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo, operatorInfo.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Start("Triggering the Kubescape-Operator for vulnerability scanning")
|
||||
logger.L().Start("Triggering the Kubescape Operator for vulnerability scanning")
|
||||
_, err = operatorAdapter.OperatorScan()
|
||||
if err != nil {
|
||||
logger.L().StopError("Failed to trigger the Kubescape-Operator for vulnerability scanning", helpers.Error(err))
|
||||
logger.L().StopError("Failed to trigger the Kubescape Operator for vulnerability scanning", helpers.Error(err))
|
||||
return err
|
||||
}
|
||||
logger.L().StopSuccess("Triggered Kubescape-Operator for vulnerability scanning. View the scanning results once they are ready using the following command: \"kubectl get vulnerabilitysummaries\"")
|
||||
logger.L().StopSuccess("Triggered Kubescape Operator for vulnerability scanning. View the scanning results once they are ready using the following command: \"kubectl get vulnerabilitysummaries\"")
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
29
cmd/operator/vulnerabilitiesscan_test.go
Normal file
29
cmd/operator/vulnerabilitiesscan_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetOperatorScanVulnerabilitiesCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
operatorInfo := cautils.OperatorInfo{
|
||||
Namespace: "namespace",
|
||||
}
|
||||
|
||||
cmd := getOperatorScanVulnerabilitiesCmd(mockKubescape, operatorInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "vulnerabilities", cmd.Use)
|
||||
assert.Equal(t, "Vulnerabilities use for scan your cluster vulnerabilities using Kubescape operator in the in cluster components", cmd.Short)
|
||||
assert.Equal(t, "", cmd.Long)
|
||||
assert.Equal(t, operatorScanVulnerabilitiesExamples, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{"random-arg"})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
142
cmd/patch/README.md
Normal file
142
cmd/patch/README.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Patch Command
|
||||
|
||||
The patch command is used for patching container images with vulnerabilities.
|
||||
It uses [copa](https://github.com/project-copacetic/copacetic) and [buildkit](https://github.com/moby/buildkit) under the hood for patching the container images, and [grype](https://github.com/anchore/grype) as the engine for scanning the images (at the moment).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
kubescape patch --image <image-name> [flags]
|
||||
```
|
||||
|
||||
The patch command can be run in 2 ways:
|
||||
1. **With sudo privileges**
|
||||
|
||||
You will need to start `buildkitd` if it is not already running
|
||||
|
||||
```bash
|
||||
sudo buildkitd &
|
||||
sudo kubescape patch --image <image-name>
|
||||
```
|
||||
|
||||
2. **Without sudo privileges**
|
||||
```bash
|
||||
export BUILDKIT_VERSION=v0.11.4
|
||||
export BUILDKIT_PORT=8888
|
||||
|
||||
docker run \
|
||||
--detach \
|
||||
--rm \
|
||||
--privileged \
|
||||
-p 127.0.0.1:$BUILDKIT_PORT:$BUILDKIT_PORT/tcp \
|
||||
--name buildkitd \
|
||||
--entrypoint buildkitd \
|
||||
"moby/buildkit:$BUILDKIT_VERSION" \
|
||||
--addr tcp://0.0.0.0:$BUILDKIT_PORT
|
||||
|
||||
kubescape patch \
|
||||
-i <image-name> \
|
||||
-a tcp://0.0.0.0:$BUILDKIT_PORT
|
||||
```
|
||||
|
||||
### Flags
|
||||
|
||||
| Flag | Description | Required | Default |
|
||||
| -------------- | ------------------------------------------------------ | -------- | ----------------------------------- |
|
||||
| -i, --image | Image name to be patched (should be in canonical form) | Yes | |
|
||||
| -a, --addr | Address of the buildkitd service | No | unix:///run/buildkit/buildkitd.sock |
|
||||
| -t, --tag | Tag of the resultant patched image | No | image_name-patched |
|
||||
| --timeout | Timeout for the patching process | No | 5m |
|
||||
| --ignore-errors| Ignore errors during patching | No | false |
|
||||
| -u, --username | Username for the image registry login | No | |
|
||||
| -p, --password | Password for the image registry login | No | |
|
||||
| -f, --format | Output file format. | No | |
|
||||
| -o, --output | Output file. Print output to file and not stdout | No | |
|
||||
| -v, --verbose | Display full report. Default to false | No | |
|
||||
| -h, --help | help for patch | No | |
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
We will demonstrate how to use the patch command with an example of [nginx](https://www.nginx.com/) image.
|
||||
|
||||
### Pre-requisites
|
||||
|
||||
- [docker](https://docs.docker.com/desktop/install/linux-install/#generic-installation-steps) daemon must be installed and running.
|
||||
- [buildkit](https://github.com/moby/buildkit) daemon must be installed
|
||||
|
||||
### Steps
|
||||
|
||||
1. Run `buildkitd` service:
|
||||
|
||||
```bash
|
||||
sudo buildkitd
|
||||
```
|
||||
|
||||
2. In a separate terminal, run the `kubescape patch` command:
|
||||
|
||||
```bash
|
||||
sudo kubescape patch --image docker.io/library/nginx:1.22
|
||||
```
|
||||
|
||||
3. You will get an output like below:
|
||||
|
||||
```bash
|
||||
✅ Successfully scanned image: docker.io/library/nginx:1.22
|
||||
✅ Patched image successfully. Loaded image: nginx:1.22-patched
|
||||
✅ Successfully re-scanned image: nginx:1.22-patched
|
||||
|
||||
| Severity | Vulnerability | Component | Version | Fixed In |
|
||||
| -------- | -------------- | ------------- | ----------------------- | -------- |
|
||||
| Critical | CVE-2023-23914 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
|
||||
| Critical | CVE-2019-8457 | libdb5.3 | 5.3.28+dfsg1-0.8 | wont-fix |
|
||||
| High | CVE-2022-42916 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
|
||||
| High | CVE-2022-1304 | libext2fs2 | 1.46.2-2 | wont-fix |
|
||||
| High | CVE-2022-42916 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
|
||||
| High | CVE-2022-1304 | e2fsprogs | 1.46.2-2 | wont-fix |
|
||||
| High | CVE-2022-1304 | libcom-err2 | 1.46.2-2 | wont-fix |
|
||||
| High | CVE-2023-27533 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
|
||||
| High | CVE-2023-27534 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
|
||||
| High | CVE-2023-27533 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
|
||||
| High | CVE-2022-43551 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
|
||||
| High | CVE-2022-3715 | bash | 5.1-2+deb11u1 | wont-fix |
|
||||
| High | CVE-2023-27534 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
|
||||
| High | CVE-2022-43551 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
|
||||
| High | CVE-2021-33560 | libgcrypt20 | 1.8.7-6 | wont-fix |
|
||||
| High | CVE-2023-2953 | libldap-2.4-2 | 2.4.57+dfsg-3+deb11u1 | wont-fix |
|
||||
| High | CVE-2022-1304 | libss2 | 1.46.2-2 | wont-fix |
|
||||
| High | CVE-2020-22218 | libssh2-1 | 1.9.0-2 | wont-fix |
|
||||
| High | CVE-2023-29491 | libtinfo6 | 6.2+20201114-2+deb11u1 | wont-fix |
|
||||
| High | CVE-2022-2309 | libxml2 | 2.9.10+dfsg-6.7+deb11u4 | wont-fix |
|
||||
| High | CVE-2022-4899 | libzstd1 | 1.4.8+dfsg-2.1 | wont-fix |
|
||||
| High | CVE-2022-1304 | logsave | 1.46.2-2 | wont-fix |
|
||||
| High | CVE-2023-29491 | ncurses-base | 6.2+20201114-2+deb11u1 | wont-fix |
|
||||
| High | CVE-2023-29491 | ncurses-bin | 6.2+20201114-2+deb11u1 | wont-fix |
|
||||
| High | CVE-2023-31484 | perl-base | 5.32.1-4+deb11u2 | wont-fix |
|
||||
| High | CVE-2020-16156 | perl-base | 5.32.1-4+deb11u2 | wont-fix |
|
||||
|
||||
Vulnerability summary - 161 vulnerabilities found:
|
||||
Image: nginx:1.22-patched
|
||||
* 3 Critical
|
||||
* 24 High
|
||||
* 31 Medium
|
||||
* 103 Other
|
||||
|
||||
Most vulnerable components:
|
||||
* curl (7.74.0-1.3+deb11u7) - 1 Critical, 4 High, 5 Medium, 1 Low, 3 Negligible
|
||||
* libcurl4 (7.74.0-1.3+deb11u7) - 1 Critical, 4 High, 5 Medium, 1 Low, 3 Negligible
|
||||
* libtiff5 (4.2.0-1+deb11u4) - 7 Medium, 10 Negligible, 2 Unknown
|
||||
* libxml2 (2.9.10+dfsg-6.7+deb11u4) - 1 High, 2 Medium
|
||||
* perl-base (5.32.1-4+deb11u2) - 2 High, 2 Negligible
|
||||
|
||||
What now?
|
||||
─────────
|
||||
* Run with '--verbose'/'-v' flag for detailed vulnerabilities view
|
||||
* Install Kubescape in your cluster for continuous monitoring and a full vulnerability report: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-cloud-operator
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- The patch command can only fix OS-level vulnerability. It cannot fix application-level vulnerabilities. This is a limitation of copa. The reason behind this is that application level vulnerabilities are best suited to be fixed by the developers of the application.
|
||||
Hence, this is not really a limitation but a design decision.
|
||||
- No support for windows containers given the dependency on buildkit.
|
||||
145
cmd/patch/patch.go
Normal file
145
cmd/patch/patch.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package patch
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/cmd/shared"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var patchCmdExamples = fmt.Sprintf(`
|
||||
# Patch the nginx:1.22 image
|
||||
1) sudo buildkitd # start buildkitd service, run in seperate terminal
|
||||
2) sudo %[1]s patch --image docker.io/library/nginx:1.22 # patch the image
|
||||
|
||||
# The patch command can also be run without sudo privileges
|
||||
# Documentation: https://github.com/kubescape/kubescape/tree/master/cmd/patch
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var patchInfo metav1.PatchInfo
|
||||
var scanInfo cautils.ScanInfo
|
||||
var useDefaultMatchers bool
|
||||
|
||||
patchCmd := &cobra.Command{
|
||||
Use: "patch --image <image>:<tag> [flags]",
|
||||
Short: "Patch container images with vulnerabilities",
|
||||
Long: `Patch command is for automatically patching images with vulnerabilities.`,
|
||||
Example: patchCmdExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return fmt.Errorf("the command takes no arguments")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := shared.ValidateImageScanInfo(&scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateImagePatchInfo(&patchInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the UseDefaultMatchers field in scanInfo
|
||||
scanInfo.UseDefaultMatchers = useDefaultMatchers
|
||||
|
||||
exceedsSeverityThreshold, err := ks.Patch(&patchInfo, &scanInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exceedsSeverityThreshold {
|
||||
shared.TerminateOnExceedingSeverity(&scanInfo, logger.L())
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
patchCmd.PersistentFlags().StringVarP(&patchInfo.Image, "image", "i", "", "Application image name and tag to patch")
|
||||
patchCmd.PersistentFlags().StringVarP(&patchInfo.PatchedImageTag, "tag", "t", "", "Tag for the patched image. Defaults to '<image-tag>-patched' ")
|
||||
patchCmd.PersistentFlags().StringVarP(&patchInfo.BuildkitAddress, "address", "a", "unix:///run/buildkit/buildkitd.sock", "Address of buildkitd service, defaults to local buildkitd.sock")
|
||||
patchCmd.PersistentFlags().DurationVar(&patchInfo.Timeout, "timeout", 5*time.Minute, "Timeout for the operation, defaults to '5m'")
|
||||
patchCmd.PersistentFlags().BoolVar(&patchInfo.IgnoreError, "ignore-errors", false, "Ignore errors and continue patching other images. Default to false")
|
||||
|
||||
patchCmd.PersistentFlags().StringVarP(&patchInfo.Username, "username", "u", "", "Username for registry login")
|
||||
patchCmd.PersistentFlags().StringVarP(&patchInfo.Password, "password", "p", "", "Password for registry login")
|
||||
|
||||
patchCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "", `Output file format. Supported formats: "pretty-printer", "json", "sarif"`)
|
||||
patchCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
|
||||
patchCmd.PersistentFlags().BoolVarP(&scanInfo.VerboseMode, "verbose", "v", false, "Display full report. Default to false")
|
||||
|
||||
patchCmd.PersistentFlags().StringVarP(&scanInfo.FailThresholdSeverity, "severity-threshold", "s", "", "Severity threshold is the severity of a vulnerability at which the command fails and returns exit code 1")
|
||||
patchCmd.PersistentFlags().BoolVarP(&useDefaultMatchers, "use-default-matchers", "", true, "Use default matchers (true) or CPE matchers (false) for image scanning")
|
||||
|
||||
return patchCmd
|
||||
}
|
||||
|
||||
// validateImagePatchInfo validates the image patch info for the `patch` command
|
||||
func validateImagePatchInfo(patchInfo *metav1.PatchInfo) error {
|
||||
|
||||
if patchInfo.Image == "" {
|
||||
return errors.New("image tag is required")
|
||||
}
|
||||
|
||||
// Convert image to canonical format (required by copacetic for patching images)
|
||||
patchInfoImage, err := cautils.NormalizeImageName(patchInfo.Image)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse the image full name to get image name and tag
|
||||
named, err := reference.ParseNamed(patchInfoImage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If no tag or digest is provided, default to 'latest'
|
||||
if reference.IsNameOnly(named) {
|
||||
logger.L().Warning("Image name has no tag or digest, using latest as tag")
|
||||
named = reference.TagNameOnly(named)
|
||||
}
|
||||
patchInfo.Image = named.String()
|
||||
|
||||
// If no patched image tag is provided, default to '<image-tag>-patched'
|
||||
if patchInfo.PatchedImageTag == "" {
|
||||
|
||||
taggedName, ok := named.(reference.Tagged)
|
||||
if !ok {
|
||||
return errors.New("unexpected error while parsing image tag")
|
||||
}
|
||||
|
||||
patchInfo.ImageTag = taggedName.Tag()
|
||||
|
||||
if patchInfo.ImageTag == "" {
|
||||
logger.L().Warning("No tag provided, defaulting to 'patched'")
|
||||
patchInfo.PatchedImageTag = "patched"
|
||||
} else {
|
||||
patchInfo.PatchedImageTag = fmt.Sprintf("%s-%s", patchInfo.ImageTag, "patched")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Extract the "image" name from the canonical Image URL
|
||||
// If it's an official docker image, we store just the "image-name". Else if a docker repo then we store as "repo/image". Else complete URL
|
||||
ref, _ := reference.ParseNormalizedNamed(patchInfo.Image)
|
||||
imageName := named.Name()
|
||||
if strings.Contains(imageName, "docker.io/library/") {
|
||||
imageName = reference.Path(ref)
|
||||
imageName = imageName[strings.LastIndex(imageName, "/")+1:]
|
||||
} else if strings.Contains(imageName, "docker.io/") {
|
||||
imageName = reference.Path(ref)
|
||||
}
|
||||
patchInfo.ImageName = imageName
|
||||
|
||||
return nil
|
||||
}
|
||||
69
cmd/patch/patch_test.go
Normal file
69
cmd/patch/patch_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package patch
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetPatchCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
cmd := GetPatchCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "patch --image <image>:<tag> [flags]", cmd.Use)
|
||||
assert.Equal(t, "Patch container images with vulnerabilities", cmd.Short)
|
||||
assert.Equal(t, "Patch command is for automatically patching images with vulnerabilities.", cmd.Long)
|
||||
assert.Equal(t, patchCmdExamples, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"test"})
|
||||
expectedErrorMessage := "the command takes no arguments"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage = "image tag is required"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"patch", "--image", "docker.io/library/nginx:1.22"})
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
func TestGetPatchCmdWithNonExistentImage(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetPatchCmd function
|
||||
cmd := GetPatchCmd(mockKubescape)
|
||||
|
||||
// Run the command with a non-existent image argument
|
||||
err := cmd.RunE(&cobra.Command{}, []string{"patch", "--image", "non-existent-image"})
|
||||
|
||||
// Check that there is an error and the error message is as expected
|
||||
expectedErrorMessage := "image tag is required"
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
func Test_validateImagePatchInfo_EmptyImage(t *testing.T) {
|
||||
patchInfo := &metav1.PatchInfo{}
|
||||
err := validateImagePatchInfo(patchInfo)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "image tag is required", err.Error())
|
||||
}
|
||||
|
||||
func Test_validateImagePatchInfo_Image(t *testing.T) {
|
||||
patchInfo := &metav1.PatchInfo{
|
||||
Image: "testing",
|
||||
}
|
||||
err := validateImagePatchInfo(patchInfo)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
51
cmd/prerequisites/prerequisites.go
Normal file
51
cmd/prerequisites/prerequisites.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package prerequisites
|
||||
|
||||
import (
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
"github.com/kubescape/sizing-checker/pkg/checks/connectivitycheck"
|
||||
"github.com/kubescape/sizing-checker/pkg/checks/ebpfcheck"
|
||||
"github.com/kubescape/sizing-checker/pkg/checks/pvcheck"
|
||||
"github.com/kubescape/sizing-checker/pkg/checks/sizing"
|
||||
"github.com/kubescape/sizing-checker/pkg/common"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func GetPreReqCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var kubeconfigPath *string
|
||||
|
||||
// preReqCmd represents the prerequisites command
|
||||
preReqCmd := &cobra.Command{
|
||||
Use: "prerequisites",
|
||||
Short: "Check prerequisites for installing Kubescape Operator",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clientSet, inCluster := common.BuildKubeClient(*kubeconfigPath)
|
||||
if clientSet == nil {
|
||||
logger.L().Fatal("Could not create kube client. Exiting.")
|
||||
}
|
||||
|
||||
// 1) Collect cluster data
|
||||
clusterData, err := common.CollectClusterData(ks.Context(), clientSet)
|
||||
if err != nil {
|
||||
logger.L().Error("Failed to collect cluster data", helpers.Error(err))
|
||||
}
|
||||
|
||||
// 2) Run checks
|
||||
sizingResult := sizing.RunSizingChecker(clusterData)
|
||||
pvResult := pvcheck.RunPVProvisioningCheck(ks.Context(), clientSet, clusterData, inCluster)
|
||||
connectivityResult := connectivitycheck.RunConnectivityChecks(ks.Context(), clientSet, clusterData, inCluster)
|
||||
ebpfResult := ebpfcheck.RunEbpfCheck(ks.Context(), clientSet, clusterData, inCluster)
|
||||
|
||||
// 3) Build and export the final ReportData
|
||||
finalReport := common.BuildReportData(clusterData, sizingResult, pvResult, connectivityResult, ebpfResult)
|
||||
finalReport.InCluster = inCluster
|
||||
|
||||
common.GenerateOutput(finalReport, inCluster)
|
||||
},
|
||||
}
|
||||
|
||||
kubeconfigPath = preReqCmd.PersistentFlags().String("kubeconfig", "", "Path to the kubeconfig file. If not set, in-cluster config is used or $HOME/.kube/config if outside a cluster.")
|
||||
|
||||
return preReqCmd
|
||||
}
|
||||
60
cmd/root.go
60
cmd/root.go
@@ -1,26 +1,30 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v2/cmd/completion"
|
||||
"github.com/kubescape/kubescape/v2/cmd/config"
|
||||
"github.com/kubescape/kubescape/v2/cmd/download"
|
||||
"github.com/kubescape/kubescape/v2/cmd/fix"
|
||||
"github.com/kubescape/kubescape/v2/cmd/list"
|
||||
"github.com/kubescape/kubescape/v2/cmd/operator"
|
||||
"github.com/kubescape/kubescape/v2/cmd/scan"
|
||||
"github.com/kubescape/kubescape/v2/cmd/update"
|
||||
"github.com/kubescape/kubescape/v2/cmd/version"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/core"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/cmd/completion"
|
||||
"github.com/kubescape/kubescape/v3/cmd/config"
|
||||
"github.com/kubescape/kubescape/v3/cmd/download"
|
||||
"github.com/kubescape/kubescape/v3/cmd/fix"
|
||||
"github.com/kubescape/kubescape/v3/cmd/list"
|
||||
"github.com/kubescape/kubescape/v3/cmd/mcpserver"
|
||||
"github.com/kubescape/kubescape/v3/cmd/operator"
|
||||
"github.com/kubescape/kubescape/v3/cmd/patch"
|
||||
"github.com/kubescape/kubescape/v3/cmd/prerequisites"
|
||||
"github.com/kubescape/kubescape/v3/cmd/scan"
|
||||
"github.com/kubescape/kubescape/v3/cmd/update"
|
||||
"github.com/kubescape/kubescape/v3/cmd/vap"
|
||||
"github.com/kubescape/kubescape/v3/cmd/version"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -40,16 +44,16 @@ var ksExamples = fmt.Sprintf(`
|
||||
%[1]s config view
|
||||
`, cautils.ExecName())
|
||||
|
||||
func NewDefaultKubescapeCommand() *cobra.Command {
|
||||
ks := core.NewKubescape()
|
||||
return getRootCmd(ks)
|
||||
func NewDefaultKubescapeCommand(ctx context.Context, ksVersion, ksCommit, ksDate string) *cobra.Command {
|
||||
ks := core.NewKubescape(ctx)
|
||||
return getRootCmd(ks, ksVersion, ksCommit, ksDate)
|
||||
}
|
||||
|
||||
func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
func getRootCmd(ks meta.IKubescape, ksVersion, ksCommit, ksDate string) *cobra.Command {
|
||||
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "kubescape",
|
||||
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://hub.armosec.io/docs",
|
||||
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://kubescape.io/docs/",
|
||||
Example: ksExamples,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
k8sinterface.SetClusterContextName(rootInfo.KubeContext)
|
||||
@@ -82,8 +86,6 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&rootInfo.Logger, "logger", "l", helpers.InfoLevel.String(), fmt.Sprintf("Logger level. Supported: %s [$KS_LOGGER]", strings.Join(helpers.SupportedLevels(), "/")))
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.CacheDir, "cache-dir", getter.DefaultLocalStore, "Cache directory [$KS_CACHE_DIR]")
|
||||
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable color output for logging")
|
||||
rootCmd.PersistentFlags().BoolVarP(&rootInfo.EnableColor, "enable-color", "", false, "Force enable color output for logging")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&rootInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
|
||||
// Supported commands
|
||||
@@ -91,11 +93,15 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
rootCmd.AddCommand(download.GetDownloadCmd(ks))
|
||||
rootCmd.AddCommand(list.GetListCmd(ks))
|
||||
rootCmd.AddCommand(completion.GetCompletionCmd())
|
||||
rootCmd.AddCommand(version.GetVersionCmd())
|
||||
rootCmd.AddCommand(version.GetVersionCmd(ks, ksVersion, ksCommit, ksDate))
|
||||
rootCmd.AddCommand(config.GetConfigCmd(ks))
|
||||
rootCmd.AddCommand(update.GetUpdateCmd())
|
||||
rootCmd.AddCommand(update.GetUpdateCmd(ks))
|
||||
rootCmd.AddCommand(fix.GetFixCmd(ks))
|
||||
rootCmd.AddCommand(patch.GetPatchCmd(ks))
|
||||
rootCmd.AddCommand(vap.GetVapHelperCmd())
|
||||
rootCmd.AddCommand(operator.GetOperatorCmd(ks))
|
||||
rootCmd.AddCommand(prerequisites.GetPreReqCmd(ks))
|
||||
rootCmd.AddCommand(mcpserver.GetMCPServerCmd())
|
||||
|
||||
// deprecated commands
|
||||
rootCmd.AddCommand(&cobra.Command{
|
||||
@@ -110,7 +116,7 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
func Execute() error {
|
||||
ks := NewDefaultKubescapeCommand()
|
||||
func Execute(ctx context.Context, ksVersion, ksCommit, ksDate string) error {
|
||||
ks := NewDefaultKubescapeCommand(ctx, ksVersion, ksCommit, ksDate)
|
||||
return ks.Execute()
|
||||
}
|
||||
}
|
||||
24
cmd/root_test.go
Normal file
24
cmd/root_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewDefaultKubescapeCommand(t *testing.T) {
|
||||
t.Run("NewDefaultKubescapeCommand", func(t *testing.T) {
|
||||
cmd := NewDefaultKubescapeCommand(context.Background(), "", "", "")
|
||||
assert.NotNil(t, cmd)
|
||||
})
|
||||
}
|
||||
|
||||
func TestExecute(t *testing.T) {
|
||||
t.Run("Execute", func(t *testing.T) {
|
||||
err := Execute(context.Background(), "", "", "")
|
||||
if err != nil {
|
||||
assert.EqualErrorf(t, err, "unknown command \"^\\\\QTestExecute\\\\E$\" for \"kubescape\"", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -7,21 +7,17 @@ import (
|
||||
|
||||
v1 "github.com/kubescape/backend/pkg/client/v1"
|
||||
"github.com/kubescape/backend/pkg/servicediscovery"
|
||||
sdClientV1 "github.com/kubescape/backend/pkg/servicediscovery/v1"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
sdClientV2 "github.com/kubescape/backend/pkg/servicediscovery/v2"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/go-logger/iconlogger"
|
||||
"github.com/kubescape/go-logger/zaplogger"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
func initLogger() {
|
||||
logger.DisableColor(rootInfo.DisableColor)
|
||||
logger.EnableColor(rootInfo.EnableColor)
|
||||
|
||||
if rootInfo.LoggerName == "" {
|
||||
if l := os.Getenv("KS_LOGGER_NAME"); l != "" {
|
||||
rootInfo.LoggerName = l
|
||||
@@ -35,8 +31,8 @@ func initLogger() {
|
||||
}
|
||||
|
||||
logger.InitLogger(rootInfo.LoggerName)
|
||||
|
||||
}
|
||||
|
||||
func initLoggerLevel() {
|
||||
if rootInfo.Logger == helpers.InfoLevel.String() {
|
||||
} else if l := os.Getenv("KS_LOGGER"); l != "" {
|
||||
@@ -66,7 +62,7 @@ func initEnvironment() {
|
||||
|
||||
logger.L().Debug("fetching URLs from service discovery server", helpers.String("server", rootInfo.DiscoveryServerURL))
|
||||
|
||||
client, err := sdClientV1.NewServiceDiscoveryClientV1(rootInfo.DiscoveryServerURL)
|
||||
client, err := sdClientV2.NewServiceDiscoveryClientV2(rootInfo.DiscoveryServerURL)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to create service discovery client", helpers.Error(err), helpers.String("server", rootInfo.DiscoveryServerURL))
|
||||
return
|
||||
@@ -77,13 +73,13 @@ func initEnvironment() {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to to get services from server", helpers.Error(err), helpers.String("server", rootInfo.DiscoveryServerURL))
|
||||
logger.L().Fatal("failed to get services from server", helpers.Error(err), helpers.String("server", rootInfo.DiscoveryServerURL))
|
||||
return
|
||||
}
|
||||
|
||||
logger.L().Debug("configuring service discovery URLs", helpers.String("cloudAPIURL", services.GetApiServerUrl()), helpers.String("cloudReportURL", services.GetReportReceiverHttpUrl()))
|
||||
|
||||
tenant := cautils.GetTenantConfig("", "", "", nil)
|
||||
tenant := cautils.GetTenantConfig("", "", "", "", nil)
|
||||
if services.GetApiServerUrl() != "" {
|
||||
tenant.GetConfigObj().CloudAPIURL = services.GetApiServerUrl()
|
||||
}
|
||||
@@ -99,6 +95,7 @@ func initEnvironment() {
|
||||
services.GetApiServerUrl(),
|
||||
services.GetReportReceiverHttpUrl(),
|
||||
"",
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to create KS Cloud client", helpers.Error(err))
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/cmd/shared"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -31,7 +29,7 @@ var (
|
||||
Run '%[1]s list controls' for the list of supported controls
|
||||
|
||||
Control documentation:
|
||||
https://hub.armosec.io/docs/controls
|
||||
https://kubescape.io/docs/controls/
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
@@ -97,12 +95,11 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
results, err := ks.Scan(ctx, scanInfo)
|
||||
results, err := ks.Scan(scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
if err := results.HandleResults(ctx); err != nil {
|
||||
if err := results.HandleResults(ks.Context(), scanInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
if !scanInfo.VerboseMode {
|
||||
@@ -129,7 +126,7 @@ func validateControlScanInfo(scanInfo *cautils.ScanInfo) error {
|
||||
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
|
||||
}
|
||||
|
||||
if err := validateSeverity(severity); severity != "" && err != nil {
|
||||
if err := shared.ValidateSeverity(severity); severity != "" && err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
60
cmd/scan/control_test.go
Normal file
60
cmd/scan/control_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetControlCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
scanInfo := cautils.ScanInfo{
|
||||
AccountID: "new",
|
||||
}
|
||||
|
||||
cmd := getControlCmd(mockKubescape, &scanInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "control <control names list>/<control ids list>", cmd.Use)
|
||||
assert.Equal(t, fmt.Sprintf("The controls you wish to use. Run '%[1]s list controls' for the list of supported controls", cautils.ExecName()), cmd.Short)
|
||||
assert.Equal(t, controlExample, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "requires at least one control name"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"C-0001,C-0002"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"C-0001,C-0002,"})
|
||||
expectedErrorMessage = "usage: <control-0>,<control-1>"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage = "bad argument: accound ID must be a valid UUID"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
func TestGetControlCmdWithNonExistentControl(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
scanInfo := cautils.ScanInfo{
|
||||
AccountID: "new",
|
||||
}
|
||||
|
||||
// Call the GetControlCmd function
|
||||
cmd := getControlCmd(mockKubescape, &scanInfo)
|
||||
|
||||
// Run the command with a non-existent control argument
|
||||
err := cmd.RunE(&cobra.Command{}, []string{"control", "C-0001,C-0002"})
|
||||
|
||||
// Check that there is an error and the error message is as expected
|
||||
expectedErrorMessage := "bad argument: accound ID must be a valid UUID"
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
@@ -1,24 +1,22 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v3/cmd/shared"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
reporthandlingapis "github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -42,7 +40,6 @@ var (
|
||||
Run '%[1]s list frameworks' for the list of supported frameworks
|
||||
`, cautils.ExecName())
|
||||
|
||||
ErrUnknownSeverity = errors.New("unknown severity")
|
||||
ErrSecurityViewNotSupported = errors.New("security view is not supported for framework scan")
|
||||
ErrBadThreshold = errors.New("bad argument: out of range threshold")
|
||||
ErrKeepLocalOrSubmit = errors.New("you can use `keep-local` or `submit`, but not both")
|
||||
@@ -94,7 +91,7 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
||||
|
||||
}
|
||||
if len(args) > 1 {
|
||||
if len(args[1:]) == 0 || args[1] != "-" {
|
||||
if args[1] != "-" {
|
||||
scanInfo.InputPatterns = args[1:]
|
||||
logger.L().Debug("List of input files", helpers.Interface("patterns", scanInfo.InputPatterns))
|
||||
} else { // store stdin to file - do NOT move to separate function !!
|
||||
@@ -112,23 +109,18 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
||||
}
|
||||
}
|
||||
scanInfo.SetScanType(cautils.ScanTypeFramework)
|
||||
scanInfo.FrameworkScan = true
|
||||
|
||||
scanInfo.SetPolicyIdentifiers(frameworks, apisv1.KindFramework)
|
||||
|
||||
ctx := context.TODO()
|
||||
results, err := ks.Scan(ctx, scanInfo)
|
||||
results, err := ks.Scan(scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
if err = results.HandleResults(ctx); err != nil {
|
||||
if err = results.HandleResults(ks.Context(), scanInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
if !scanInfo.VerboseMode && scanInfo.ScanType == cautils.ScanTypeFramework {
|
||||
logger.L().Info("Run with '--verbose'/'-v' flag for detailed resources view\n")
|
||||
}
|
||||
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
|
||||
logger.L().Fatal("scan risk-score is above permitted threshold", helpers.String("risk-score", fmt.Sprintf("%.2f", results.GetRiskScore())), helpers.String("fail-threshold", fmt.Sprintf("%.2f", scanInfo.FailThreshold)))
|
||||
}
|
||||
@@ -140,12 +132,13 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// countersExceedSeverityThreshold returns true if severity of failed controls exceed the set severity threshold, else returns false
|
||||
func countersExceedSeverityThreshold(severityCounters reportsummary.ISeverityCounters, scanInfo *cautils.ScanInfo) (bool, error) {
|
||||
targetSeverity := scanInfo.FailThresholdSeverity
|
||||
if err := validateSeverity(targetSeverity); err != nil {
|
||||
if err := shared.ValidateSeverity(targetSeverity); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -180,7 +173,7 @@ func countersExceedSeverityThreshold(severityCounters reportsummary.ISeverityCou
|
||||
|
||||
// terminateOnExceedingSeverity terminates the application on exceeding severity
|
||||
func terminateOnExceedingSeverity(scanInfo *cautils.ScanInfo, l helpers.ILogger) {
|
||||
l.Fatal("result exceeds severity threshold", helpers.String("set severity threshold", scanInfo.FailThresholdSeverity))
|
||||
l.Fatal("compliance result exceeds severity threshold", helpers.String("set severity threshold", scanInfo.FailThresholdSeverity))
|
||||
}
|
||||
|
||||
// enforceSeverityThresholds ensures that the scan results are below the defined severity threshold
|
||||
@@ -199,21 +192,10 @@ func enforceSeverityThresholds(severityCounters reportsummary.ISeverityCounters,
|
||||
}
|
||||
}
|
||||
|
||||
// validateSeverity returns an error if a given severity is not known, nil otherwise
|
||||
func validateSeverity(severity string) error {
|
||||
for _, val := range reporthandlingapis.GetSupportedSeverities() {
|
||||
if strings.EqualFold(severity, val) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrUnknownSeverity
|
||||
|
||||
}
|
||||
|
||||
// validateFrameworkScanInfo validates the scan info struct for the `scan framework` command
|
||||
func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
|
||||
if scanInfo.View == string(cautils.SecurityViewType) {
|
||||
return ErrSecurityViewNotSupported
|
||||
scanInfo.View = string(cautils.ResourceViewType)
|
||||
}
|
||||
|
||||
if scanInfo.Submit && scanInfo.Local {
|
||||
@@ -229,7 +211,7 @@ func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
|
||||
return ErrOmitRawResourcesOrSubmit
|
||||
}
|
||||
severity := scanInfo.FailThresholdSeverity
|
||||
if err := validateSeverity(severity); severity != "" && err != nil {
|
||||
if err := shared.ValidateSeverity(severity); severity != "" && err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
60
cmd/scan/framework_test.go
Normal file
60
cmd/scan/framework_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetFrameworkCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
scanInfo := cautils.ScanInfo{
|
||||
AccountID: "new",
|
||||
}
|
||||
|
||||
cmd := getFrameworkCmd(mockKubescape, &scanInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "framework <framework names list> [`<glob pattern>`/`-`] [flags]", cmd.Use)
|
||||
assert.Equal(t, fmt.Sprintf("The framework you wish to use. Run '%[1]s list frameworks' for the list of supported frameworks", cautils.ExecName()), cmd.Short)
|
||||
assert.Equal(t, frameworkExample, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "requires at least one framework name"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"nsa,mitre"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"nsa,mitre,"})
|
||||
expectedErrorMessage = "usage: <framework-0>,<framework-1>"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage = "bad argument: accound ID must be a valid UUID"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
func TestGetFrameworkCmdWithNonExistentFramework(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
scanInfo := cautils.ScanInfo{
|
||||
AccountID: "new",
|
||||
}
|
||||
|
||||
// Call the GetFrameworkCmd function
|
||||
cmd := getFrameworkCmd(mockKubescape, &scanInfo)
|
||||
|
||||
// Run the command with a non-existent framework argument
|
||||
err := cmd.RunE(&cobra.Command{}, []string{"framework", "nsa,mitre"})
|
||||
|
||||
// Check that there is an error and the error message is as expected
|
||||
expectedErrorMessage := "bad argument: accound ID must be a valid UUID"
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
@@ -1,43 +1,41 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/core"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling"
|
||||
"github.com/kubescape/kubescape/v2/pkg/imagescan"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/cmd/shared"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type imageScanInfo struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// TODO(vladklokun): document image scanning on the Kubescape Docs Hub?
|
||||
var (
|
||||
imageExample = fmt.Sprintf(`
|
||||
This command is still in BETA. Feel free to contact the Kubescape maintainers for more information.
|
||||
|
||||
Scan an image for vulnerabilities.
|
||||
|
||||
# Scan the 'nginx' image
|
||||
%[1]s scan image "nginx"
|
||||
|
||||
# Image scan documentation:
|
||||
# https://hub.armosec.io/docs/images
|
||||
# Scan the 'nginx' image and see the full report
|
||||
%[1]s scan image "nginx" -v
|
||||
|
||||
# Scan the 'nginx' image and use exceptions
|
||||
%[1]s scan image "nginx" --exceptions exceptions.json
|
||||
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
// imageCmd represents the image command
|
||||
func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgScanInfo *imageScanInfo) *cobra.Command {
|
||||
// getImageCmd returns the scan image command
|
||||
func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
|
||||
var imgCredentials shared.ImageCredentials
|
||||
var exceptions string
|
||||
var useDefaultMatchers bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "image <IMAGE_NAME>",
|
||||
Use: "image <image>:<tag> [flags]",
|
||||
Short: "Scan an image for vulnerabilities",
|
||||
Example: imageExample,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -47,68 +45,40 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgScanInfo *im
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := validateImageScanInfo(scanInfo); err != nil {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("the command takes exactly one image name as an argument")
|
||||
}
|
||||
|
||||
if err := shared.ValidateImageScanInfo(scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
failOnSeverity := imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
dbCfg, _ := imagescan.NewDefaultDBConfig()
|
||||
svc := imagescan.NewScanService(dbCfg)
|
||||
|
||||
creds := imagescan.RegistryCredentials{
|
||||
Username: imgScanInfo.Username,
|
||||
Password: imgScanInfo.Password,
|
||||
imgScanInfo := &metav1.ImageScanInfo{
|
||||
Image: args[0],
|
||||
Username: imgCredentials.Username,
|
||||
Password: imgCredentials.Password,
|
||||
Exceptions: exceptions,
|
||||
UseDefaultMatchers: useDefaultMatchers,
|
||||
}
|
||||
|
||||
userInput := args[0]
|
||||
|
||||
logger.L().Start(fmt.Sprintf("Scanning image: %s", userInput))
|
||||
scanResults, err := svc.Scan(ctx, userInput, creds)
|
||||
exceedsSeverityThreshold, err := ks.ScanImage(imgScanInfo, scanInfo)
|
||||
if err != nil {
|
||||
logger.L().StopError(fmt.Sprintf("Failed to scan image: %s", userInput))
|
||||
return err
|
||||
}
|
||||
logger.L().StopSuccess(fmt.Sprintf("Successfully scanned image: %s", userInput))
|
||||
|
||||
scanInfo.SetScanType(cautils.ScanTypeImage)
|
||||
|
||||
outputPrinters := core.GetOutputPrinters(scanInfo, ctx, "")
|
||||
|
||||
uiPrinter := core.GetUIPrinter(ctx, scanInfo, "")
|
||||
|
||||
resultsHandler := resultshandling.NewResultsHandler(nil, outputPrinters, uiPrinter)
|
||||
|
||||
resultsHandler.ImageScanData = []cautils.ImageScanData{
|
||||
{
|
||||
PresenterConfig: scanResults,
|
||||
Image: userInput,
|
||||
},
|
||||
if exceedsSeverityThreshold {
|
||||
shared.TerminateOnExceedingSeverity(scanInfo, logger.L())
|
||||
}
|
||||
|
||||
resultsHandler.HandleResults(ctx)
|
||||
|
||||
if imagescan.ExceedsSeverityThreshold(scanResults, failOnSeverity) {
|
||||
terminateOnExceedingSeverity(scanInfo, logger.L())
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().StringVarP(&imgScanInfo.Username, "username", "u", "", "Username for registry login")
|
||||
cmd.PersistentFlags().StringVarP(&imgScanInfo.Password, "password", "p", "", "Password for registry login")
|
||||
// The exceptions flag
|
||||
cmd.PersistentFlags().StringVarP(&exceptions, "exceptions", "", "", "Path to the exceptions file")
|
||||
cmd.PersistentFlags().StringVarP(&imgCredentials.Username, "username", "u", "", "Username for registry login")
|
||||
cmd.PersistentFlags().StringVarP(&imgCredentials.Password, "password", "p", "", "Password for registry login")
|
||||
cmd.PersistentFlags().BoolVarP(&useDefaultMatchers, "use-default-matchers", "", true, "Use default matchers (true) or CPE matchers (false)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// validateImageScanInfo validates the ScanInfo struct for the `image` command
|
||||
func validateImageScanInfo(scanInfo *cautils.ScanInfo) error {
|
||||
severity := scanInfo.FailThresholdSeverity
|
||||
|
||||
if err := validateSeverity(severity); severity != "" && err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
35
cmd/scan/image_test.go
Normal file
35
cmd/scan/image_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetImageCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
scanInfo := cautils.ScanInfo{
|
||||
AccountID: "new",
|
||||
}
|
||||
|
||||
cmd := getImageCmd(mockKubescape, &scanInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "image <image>:<tag> [flags]", cmd.Use)
|
||||
assert.Equal(t, "Scan an image for vulnerabilities", cmd.Short)
|
||||
assert.Equal(t, imageExample, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "the command takes exactly one image name as an argument"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"nginx"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{})
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var scanCmdExamples = fmt.Sprintf(`
|
||||
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
|
||||
|
||||
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
|
||||
|
||||
# Scan current cluster
|
||||
%[1]s scan
|
||||
|
||||
# Scan kubernetes manifest files
|
||||
# Scan kubernetes manifest files
|
||||
%[1]s scan .
|
||||
|
||||
# Scan and save the results in the JSON format
|
||||
@@ -28,7 +28,7 @@ var scanCmdExamples = fmt.Sprintf(`
|
||||
# Display all resources
|
||||
%[1]s scan --verbose
|
||||
|
||||
# Scan different clusters from the kubectl context
|
||||
# Scan different clusters from the kubectl context
|
||||
%[1]s scan --kube-context <kubernetes context>
|
||||
`, cautils.ExecName())
|
||||
|
||||
@@ -45,12 +45,17 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
if scanInfo.View == string(cautils.SecurityViewType) {
|
||||
setSecurityViewScanInfo(args, &scanInfo)
|
||||
|
||||
return securityScan(scanInfo, ks)
|
||||
if err := securityScan(scanInfo, ks); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
} else if len(args) == 0 || (args[0] != "framework" && args[0] != "control") {
|
||||
if err := getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{strings.Join(getter.NativeFrameworks, ",")}, args...)); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("kubescape did not do anything")
|
||||
}
|
||||
|
||||
if len(args) == 0 || (args[0] != "framework" && args[0] != "control") {
|
||||
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{strings.Join(getter.NativeFrameworks, ",")}, args...))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
PostRun: func(cmd *cobra.Command, args []string) {
|
||||
@@ -58,7 +63,10 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
scanInfo.TriggeredByCLI = true
|
||||
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.AccessKey, "access-key", "", "", "Kubescape SaaS access key. Default will load access key from cache")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
|
||||
@@ -68,12 +76,12 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
scanCmd.PersistentFlags().Float32VarP(&scanInfo.ComplianceThreshold, "compliance-threshold", "", 0, "Compliance threshold is the percent below which the command fails and returns exit code 1")
|
||||
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.FailThresholdSeverity, "severity-threshold", "", "Severity threshold is the severity of failed controls at which the command fails and returns exit code 1")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "", `Output file format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html", "sarif"`)
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output file format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html", "sarif"`)
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to configured backend.")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.VerboseMode, "verbose", "v", false, "Display all of the input resources and not only failed resources")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.View, "view", string(cautils.ResourceViewType), fmt.Sprintf("View results based on the %s/%s/%s. default is --view=%s", cautils.ResourceViewType, cautils.ControlViewType, cautils.SecurityViewType, cautils.ResourceViewType))
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.View, "view", string(cautils.SecurityViewType), fmt.Sprintf("View results based on the %s/%s/%s. default is --view=%s", cautils.ResourceViewType, cautils.ControlViewType, cautils.SecurityViewType, cautils.SecurityViewType))
|
||||
scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest")
|
||||
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.HostSensorYamlPath, "host-scan-yaml", "", "Override default host scanner DaemonSet. Use this flag cautiously")
|
||||
@@ -82,18 +90,18 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Submit the scan results to Kubescape SaaS where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not submitted")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.OmitRawResources, "omit-raw-resources", "", false, "Omit raw resources from the output. By default the raw resources are included in the output")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.PrintAttackTree, "print-attack-tree", "", false, "Print attack tree")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.EnableRegoPrint, "enable-rego-prints", "", false, "Enable sending to rego prints to the logs (use with debug log level: -l debug)")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.ScanImages, "scan-images", "", false, "Scan resources images")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.UseDefaultMatchers, "use-default-matchers", "", true, "Use default matchers (true) or CPE matchers (false) for image scanning")
|
||||
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.LabelsToCopy, "labels-to-copy", nil, "Labels to copy from workloads to scan reports for easy identification. e.g: --labels-to-copy=app,team,environment")
|
||||
|
||||
scanCmd.PersistentFlags().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("fail-threshold", "use '--compliance-threshold' flag instead. Flag will be removed at 1.Dec.2023")
|
||||
|
||||
scanCmd.PersistentFlags().MarkDeprecated("client-id", "Client ID is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("create-account", "Create account is no longer supported. In case of a missing Account ID and a configured backend server, a new account id will be generated automatically by Kubescape. Feel free to contact the Kubescape maintainers for more information.")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("secret-key", "Secret Key is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
|
||||
|
||||
// hidden flags
|
||||
scanCmd.PersistentFlags().MarkHidden("omit-raw-resources")
|
||||
scanCmd.PersistentFlags().MarkHidden("print-attack-tree")
|
||||
scanCmd.PersistentFlags().MarkHidden("format-version")
|
||||
|
||||
// Retrieve --kubeconfig flag from https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/cmd.go
|
||||
scanCmd.PersistentFlags().AddGoFlag(flag.Lookup("kubeconfig"))
|
||||
@@ -111,8 +119,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
scanCmd.AddCommand(getFrameworkCmd(ks, &scanInfo))
|
||||
scanCmd.AddCommand(getWorkloadCmd(ks, &scanInfo))
|
||||
|
||||
isi := &imageScanInfo{}
|
||||
scanCmd.AddCommand(getImageCmd(ks, &scanInfo, isi))
|
||||
scanCmd.AddCommand(getImageCmd(ks, &scanInfo))
|
||||
|
||||
return scanCmd
|
||||
}
|
||||
@@ -121,22 +128,20 @@ func setSecurityViewScanInfo(args []string, scanInfo *cautils.ScanInfo) {
|
||||
if len(args) > 0 {
|
||||
scanInfo.SetScanType(cautils.ScanTypeRepo)
|
||||
scanInfo.InputPatterns = args
|
||||
scanInfo.SetPolicyIdentifiers([]string{"workloadscan", "allcontrols"}, v1.KindFramework)
|
||||
} else {
|
||||
scanInfo.SetScanType(cautils.ScanTypeCluster)
|
||||
scanInfo.SetPolicyIdentifiers([]string{"clusterscan", "mitre", "nsa"}, v1.KindFramework)
|
||||
}
|
||||
scanInfo.SetPolicyIdentifiers([]string{"clusterscan", "mitre", "nsa"}, v1.KindFramework)
|
||||
}
|
||||
|
||||
func securityScan(scanInfo cautils.ScanInfo, ks meta.IKubescape) error {
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
results, err := ks.Scan(ctx, &scanInfo)
|
||||
results, err := ks.Scan(&scanInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = results.HandleResults(ctx); err != nil {
|
||||
if err = results.HandleResults(ks.Context(), &scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -2,17 +2,19 @@ package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v3/cmd/shared"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExceedsSeverity(t *testing.T) {
|
||||
@@ -112,7 +114,7 @@ func TestExceedsSeverity(t *testing.T) {
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "unknown"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
|
||||
Want: false,
|
||||
Error: ErrUnknownSeverity,
|
||||
Error: shared.ErrUnknownSeverity,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -185,20 +187,23 @@ type spyLogger struct {
|
||||
setItems []spyLogMessage
|
||||
}
|
||||
|
||||
func (l *spyLogger) Error(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) Success(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) Warning(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) Info(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) Debug(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) SetLevel(level string) error { return nil }
|
||||
func (l *spyLogger) GetLevel() string { return "" }
|
||||
func (l *spyLogger) SetWriter(w *os.File) {}
|
||||
func (l *spyLogger) GetWriter() *os.File { return &os.File{} }
|
||||
func (l *spyLogger) LoggerName() string { return "" }
|
||||
func (l *spyLogger) Ctx(_ context.Context) helpers.ILogger { return l }
|
||||
func (l *spyLogger) Start(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) StopSuccess(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) StopError(msg string, details ...helpers.IDetails) {}
|
||||
var _ helpers.ILogger = &spyLogger{}
|
||||
|
||||
func (l *spyLogger) Error(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) Success(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) Warning(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) Info(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) Debug(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) SetLevel(level string) error { return nil }
|
||||
func (l *spyLogger) GetLevel() string { return "" }
|
||||
func (l *spyLogger) SetWriter(w *os.File) {}
|
||||
func (l *spyLogger) GetWriter() *os.File { return &os.File{} }
|
||||
func (l *spyLogger) LoggerName() string { return "" }
|
||||
func (l *spyLogger) Ctx(_ context.Context) helpers.ILogger { return l }
|
||||
func (l *spyLogger) Start(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) StopSuccess(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) StopError(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) TimedWrapper(funcName string, timeout time.Duration, task func()) {}
|
||||
|
||||
func (l *spyLogger) Fatal(msg string, details ...helpers.IDetails) {
|
||||
firstDetail := details[0]
|
||||
@@ -213,7 +218,7 @@ func (l *spyLogger) GetSpiedItems() []spyLogMessage {
|
||||
}
|
||||
|
||||
func Test_terminateOnExceedingSeverity(t *testing.T) {
|
||||
expectedMessage := "result exceeds severity threshold"
|
||||
expectedMessage := "compliance result exceeds severity threshold"
|
||||
expectedKey := "set severity threshold"
|
||||
|
||||
testCases := []struct {
|
||||
@@ -302,15 +307,11 @@ func TestSetSecurityViewScanInfo(t *testing.T) {
|
||||
PolicyIdentifier: []cautils.PolicyIdentifier{
|
||||
{
|
||||
Kind: v1.KindFramework,
|
||||
Identifier: "clusterscan",
|
||||
Identifier: "workloadscan",
|
||||
},
|
||||
{
|
||||
Kind: v1.KindFramework,
|
||||
Identifier: "mitre",
|
||||
},
|
||||
{
|
||||
Kind: v1.KindFramework,
|
||||
Identifier: "nsa",
|
||||
Identifier: "allcontrols",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -361,3 +362,16 @@ func TestSetSecurityViewScanInfo(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetScanCommand(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
cmd := GetScanCommand(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "scan", cmd.Use)
|
||||
assert.Equal(t, "Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations", cmd.Short)
|
||||
assert.Equal(t, "The action you want to perform", cmd.Long)
|
||||
assert.Equal(t, scanCmdExamples, cmd.Example)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ package scan
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/cmd/shared"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
)
|
||||
|
||||
// Test_validateControlScanInfo tests how scan info is validated for the `scan control` command
|
||||
@@ -26,7 +27,7 @@ func Test_validateControlScanInfo(t *testing.T) {
|
||||
{
|
||||
"Unknown severity should be invalid for scan info",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
|
||||
ErrUnknownSeverity,
|
||||
shared.ErrUnknownSeverity,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -34,7 +35,7 @@ func Test_validateControlScanInfo(t *testing.T) {
|
||||
t.Run(
|
||||
tc.Description,
|
||||
func(t *testing.T) {
|
||||
var want error = tc.Want
|
||||
var want = tc.Want
|
||||
|
||||
got := validateControlScanInfo(tc.ScanInfo)
|
||||
|
||||
@@ -66,12 +67,12 @@ func Test_validateFrameworkScanInfo(t *testing.T) {
|
||||
{
|
||||
"Unknown severity should be invalid for scan info",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
|
||||
ErrUnknownSeverity,
|
||||
shared.ErrUnknownSeverity,
|
||||
},
|
||||
{
|
||||
"Security view should be invalid for scan info",
|
||||
&cautils.ScanInfo{View: string(cautils.SecurityViewType)},
|
||||
ErrSecurityViewNotSupported,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Empty view should be valid for scan info",
|
||||
@@ -84,7 +85,7 @@ func Test_validateFrameworkScanInfo(t *testing.T) {
|
||||
t.Run(
|
||||
tc.Description,
|
||||
func(t *testing.T) {
|
||||
var want error = tc.Want
|
||||
var want = tc.Want
|
||||
|
||||
got := validateFrameworkScanInfo(tc.ScanInfo)
|
||||
|
||||
@@ -96,35 +97,6 @@ func Test_validateFrameworkScanInfo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validateSeverity(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Description string
|
||||
Input string
|
||||
Want error
|
||||
}{
|
||||
{"low should be a valid severity", "low", nil},
|
||||
{"Low should be a valid severity", "Low", nil},
|
||||
{"medium should be a valid severity", "medium", nil},
|
||||
{"Medium should be a valid severity", "Medium", nil},
|
||||
{"high should be a valid severity", "high", nil},
|
||||
{"Critical should be a valid severity", "Critical", nil},
|
||||
{"critical should be a valid severity", "critical", nil},
|
||||
{"Unknown should be an invalid severity", "Unknown", ErrUnknownSeverity},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
input := testCase.Input
|
||||
want := testCase.Want
|
||||
got := validateSeverity(input)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validateWorkloadIdentifier(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Description string
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
workloadExample = fmt.Sprintf(`
|
||||
This command is still in BETA. Feel free to contact the Kubescape maintainers for more information.
|
||||
|
||||
Scan a workload for misconfigurations and image vulnerabilities.
|
||||
|
||||
# Scan an workload
|
||||
@@ -52,6 +48,7 @@ func getWorkloadCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comma
|
||||
return fmt.Errorf("usage: <kind>/<name> [`<glob pattern>`/`-`] [flags]")
|
||||
}
|
||||
|
||||
// Looks strange, a bug maybe????
|
||||
if scanInfo.ChartPath != "" && scanInfo.FilePath == "" {
|
||||
return fmt.Errorf("usage: --chart-path <chart path> --file-path <file path>")
|
||||
}
|
||||
@@ -68,16 +65,17 @@ func getWorkloadCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comma
|
||||
setWorkloadScanInfo(scanInfo, kind, name)
|
||||
|
||||
// todo: add api version if provided
|
||||
ctx := context.TODO()
|
||||
results, err := ks.Scan(ctx, scanInfo)
|
||||
results, err := ks.Scan(scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
if err = results.HandleResults(ctx); err != nil {
|
||||
if err = results.HandleResults(ks.Context(), scanInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -97,7 +95,7 @@ func setWorkloadScanInfo(scanInfo *cautils.ScanInfo, kind string, name string) {
|
||||
scanInfo.ScanObject.SetKind(kind)
|
||||
scanInfo.ScanObject.SetName(name)
|
||||
|
||||
scanInfo.SetPolicyIdentifiers([]string{"workloadscan"}, v1.KindFramework)
|
||||
scanInfo.SetPolicyIdentifiers([]string{"workloadscan", "allcontrols"}, v1.KindFramework)
|
||||
|
||||
if scanInfo.FilePath != "" {
|
||||
scanInfo.InputPatterns = []string{scanInfo.FilePath}
|
||||
|
||||
@@ -3,9 +3,12 @@ package scan
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSetWorkloadScanInfo(t *testing.T) {
|
||||
@@ -25,6 +28,10 @@ func TestSetWorkloadScanInfo(t *testing.T) {
|
||||
Identifier: "workloadscan",
|
||||
Kind: v1.KindFramework,
|
||||
},
|
||||
{
|
||||
Identifier: "allcontrols",
|
||||
Kind: v1.KindFramework,
|
||||
},
|
||||
},
|
||||
ScanType: cautils.ScanTypeWorkload,
|
||||
ScanObject: &objectsenvelopes.ScanObject{
|
||||
@@ -56,14 +63,59 @@ func TestSetWorkloadScanInfo(t *testing.T) {
|
||||
t.Errorf("got: %v, want: %v", scanInfo.ScanObject.Metadata.Name, tc.want.ScanObject.Metadata.Name)
|
||||
}
|
||||
|
||||
if len(scanInfo.PolicyIdentifier) != 1 {
|
||||
t.Errorf("got: %v, want: %v", len(scanInfo.PolicyIdentifier), 1)
|
||||
if len(scanInfo.PolicyIdentifier) != len(tc.want.PolicyIdentifier) {
|
||||
t.Errorf("got: %v policy identifiers, want: %v", len(scanInfo.PolicyIdentifier), len(tc.want.PolicyIdentifier))
|
||||
}
|
||||
|
||||
if scanInfo.PolicyIdentifier[0].Identifier != tc.want.PolicyIdentifier[0].Identifier {
|
||||
t.Errorf("got: %v, want: %v", scanInfo.PolicyIdentifier[0].Identifier, tc.want.PolicyIdentifier[0].Identifier)
|
||||
for i, wantPolicy := range tc.want.PolicyIdentifier {
|
||||
if i < len(scanInfo.PolicyIdentifier) {
|
||||
if scanInfo.PolicyIdentifier[i].Identifier != wantPolicy.Identifier {
|
||||
t.Errorf("got: %v, want: %v", scanInfo.PolicyIdentifier[i].Identifier, wantPolicy.Identifier)
|
||||
}
|
||||
if scanInfo.PolicyIdentifier[i].Kind != wantPolicy.Kind {
|
||||
t.Errorf("got: %v, want: %v", scanInfo.PolicyIdentifier[i].Kind, wantPolicy.Kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWorkloadCmd_ChartPathAndFilePathEmpty(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
scanInfo := cautils.ScanInfo{
|
||||
ChartPath: "temp",
|
||||
FilePath: "",
|
||||
}
|
||||
|
||||
cmd := getWorkloadCmd(mockKubescape, &scanInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "workload <kind>/<name> [`<glob pattern>`/`-`] [flags]", cmd.Use)
|
||||
assert.Equal(t, "Scan a workload for misconfigurations and image vulnerabilities", cmd.Short)
|
||||
assert.Equal(t, workloadExample, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "usage: <kind>/<name> [`<glob pattern>`/`-`] [flags]"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"nginx"})
|
||||
expectedErrorMessage = "invalid workload identifier"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
func Test_parseWorkloadIdentifierString_Empty(t *testing.T) {
|
||||
t.Run("empty identifier", func(t *testing.T) {
|
||||
_, _, err := parseWorkloadIdentifierString("")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_parseWorkloadIdentifierString_NoError(t *testing.T) {
|
||||
t.Run("valid identifier", func(t *testing.T) {
|
||||
_, _, err := parseWorkloadIdentifierString("default/Deployment")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
18
cmd/shared/image_scan.go
Normal file
18
cmd/shared/image_scan.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package shared
|
||||
|
||||
import "github.com/kubescape/kubescape/v3/core/cautils"
|
||||
|
||||
type ImageCredentials struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// ValidateImageScanInfo validates the ScanInfo struct for image scanning commands
|
||||
func ValidateImageScanInfo(scanInfo *cautils.ScanInfo) error {
|
||||
severity := scanInfo.FailThresholdSeverity
|
||||
|
||||
if err := ValidateSeverity(severity); severity != "" && err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
61
cmd/shared/image_scan_test.go
Normal file
61
cmd/shared/image_scan_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Validate a scanInfo struct with a valid fail threshold severity
|
||||
func TestValidateImageScanInfo(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Description string
|
||||
ScanInfo *cautils.ScanInfo
|
||||
Want error
|
||||
}{
|
||||
{
|
||||
"Empty scanInfo is valid",
|
||||
&cautils.ScanInfo{},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Empty severity is valid",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: ""},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"High severity is valid",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "High"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"HIGH severity is valid",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "HIGH"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"high severity is valid",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "high"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Unknown severity is invalid",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "unknown"},
|
||||
ErrUnknownSeverity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(
|
||||
tc.Description,
|
||||
func(t *testing.T) {
|
||||
var want = tc.Want
|
||||
|
||||
got := ValidateImageScanInfo(tc.ScanInfo)
|
||||
|
||||
assert.Equal(t, want, got)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
28
cmd/shared/scan.go
Normal file
28
cmd/shared/scan.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
reporthandlingapis "github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
)
|
||||
|
||||
var ErrUnknownSeverity = fmt.Errorf("unknown severity. Supported severities are: %s", strings.Join(reporthandlingapis.GetSupportedSeverities(), ", "))
|
||||
|
||||
// ValidateSeverity returns an error if a given severity is not known, nil otherwise
|
||||
func ValidateSeverity(severity string) error {
|
||||
for _, val := range reporthandlingapis.GetSupportedSeverities() {
|
||||
if strings.EqualFold(severity, val) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrUnknownSeverity
|
||||
|
||||
}
|
||||
|
||||
// TerminateOnExceedingSeverity terminates the program if the result exceeds the severity threshold
|
||||
func TerminateOnExceedingSeverity(scanInfo *cautils.ScanInfo, l helpers.ILogger) {
|
||||
l.Fatal("result exceeds severity threshold", helpers.String("Set severity threshold", scanInfo.FailThresholdSeverity))
|
||||
}
|
||||
128
cmd/shared/scan_test.go
Normal file
128
cmd/shared/scan_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
)
|
||||
|
||||
type spyLogMessage struct {
|
||||
Message string
|
||||
Details map[string]string
|
||||
}
|
||||
|
||||
type spyLogger struct {
|
||||
setItems []spyLogMessage
|
||||
}
|
||||
|
||||
var _ helpers.ILogger = &spyLogger{}
|
||||
|
||||
func (l *spyLogger) Error(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) Success(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) Warning(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) Info(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) Debug(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) SetLevel(level string) error { return nil }
|
||||
func (l *spyLogger) GetLevel() string { return "" }
|
||||
func (l *spyLogger) SetWriter(w *os.File) {}
|
||||
func (l *spyLogger) GetWriter() *os.File { return &os.File{} }
|
||||
func (l *spyLogger) LoggerName() string { return "" }
|
||||
func (l *spyLogger) Ctx(_ context.Context) helpers.ILogger { return l }
|
||||
func (l *spyLogger) Start(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) StopSuccess(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) StopError(msg string, details ...helpers.IDetails) {}
|
||||
func (l *spyLogger) TimedWrapper(funcName string, timeout time.Duration, task func()) {}
|
||||
|
||||
func (l *spyLogger) Fatal(msg string, details ...helpers.IDetails) {
|
||||
firstDetail := details[0]
|
||||
detailsMap := map[string]string{firstDetail.Key(): firstDetail.Value().(string)}
|
||||
|
||||
newMsg := spyLogMessage{msg, detailsMap}
|
||||
l.setItems = append(l.setItems, newMsg)
|
||||
}
|
||||
|
||||
func (l *spyLogger) GetSpiedItems() []spyLogMessage {
|
||||
return l.setItems
|
||||
}
|
||||
|
||||
func TestTerminateOnExceedingSeverity(t *testing.T) {
|
||||
expectedMessage := "result exceeds severity threshold"
|
||||
expectedKey := "Set severity threshold"
|
||||
|
||||
testCases := []struct {
|
||||
Description string
|
||||
ExpectedMessage string
|
||||
ExpectedKey string
|
||||
ExpectedValue string
|
||||
Logger *spyLogger
|
||||
}{
|
||||
{
|
||||
"Should log the Critical threshold that was set in scan info",
|
||||
expectedMessage,
|
||||
expectedKey,
|
||||
apis.SeverityCriticalString,
|
||||
&spyLogger{},
|
||||
},
|
||||
{
|
||||
"Should log the High threshold that was set in scan info",
|
||||
expectedMessage,
|
||||
expectedKey,
|
||||
apis.SeverityHighString,
|
||||
&spyLogger{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(
|
||||
tc.Description,
|
||||
func(t *testing.T) {
|
||||
want := []spyLogMessage{
|
||||
{tc.ExpectedMessage, map[string]string{tc.ExpectedKey: tc.ExpectedValue}},
|
||||
}
|
||||
scanInfo := &cautils.ScanInfo{FailThresholdSeverity: tc.ExpectedValue}
|
||||
|
||||
TerminateOnExceedingSeverity(scanInfo, tc.Logger)
|
||||
|
||||
got := tc.Logger.GetSpiedItems()
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSeverity(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Description string
|
||||
Input string
|
||||
Want error
|
||||
}{
|
||||
{"low should be a valid severity", "low", nil},
|
||||
{"Low should be a valid severity", "Low", nil},
|
||||
{"medium should be a valid severity", "medium", nil},
|
||||
{"Medium should be a valid severity", "Medium", nil},
|
||||
{"high should be a valid severity", "high", nil},
|
||||
{"Critical should be a valid severity", "Critical", nil},
|
||||
{"critical should be a valid severity", "critical", nil},
|
||||
{"Unknown should be an invalid severity", "Unknown", ErrUnknownSeverity},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
input := testCase.Input
|
||||
want := testCase.Want
|
||||
got := ValidateSeverity(input)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,19 @@ package update
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
|
||||
"github.com/kubescape/backend/pkg/versioncheck"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
installationLink string = "https://github.com/kubescape/kubescape/blob/master/docs/installation.md"
|
||||
installationLink string = "https://kubescape.io/docs/install-cli/"
|
||||
)
|
||||
|
||||
var updateCmdExamples = fmt.Sprintf(`
|
||||
@@ -22,19 +26,31 @@ var updateCmdExamples = fmt.Sprintf(`
|
||||
%[1]s update
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetUpdateCmd() *cobra.Command {
|
||||
func GetUpdateCmd(ks meta.IKubescape) *cobra.Command {
|
||||
updateCmd := &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update to latest release version",
|
||||
Long: ``,
|
||||
Example: updateCmdExamples,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
v := versioncheck.NewVersionCheckHandler()
|
||||
versionCheckRequest := versioncheck.NewVersionCheckRequest("", versioncheck.BuildNumber, "", "", "update", nil)
|
||||
if err := v.CheckLatestVersion(ks.Context(), versionCheckRequest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Checking the user's version of kubescape to the latest release
|
||||
if cautils.BuildNumber == cautils.LatestReleaseVersion {
|
||||
if versioncheck.BuildNumber == "" || strings.Contains(versioncheck.BuildNumber, "rc") {
|
||||
//your version is unknown
|
||||
fmt.Printf("Nothing to update: you are running the development version\n")
|
||||
} else if versioncheck.LatestReleaseVersion == "" {
|
||||
//Failed to check for updates
|
||||
logger.L().Info("Failed to check for updates")
|
||||
} else if versioncheck.BuildNumber == versioncheck.LatestReleaseVersion {
|
||||
//your version == latest version
|
||||
logger.L().Info(("Nothing to update, you are running the latest version"), helpers.String("Version", cautils.BuildNumber))
|
||||
logger.L().Info("Nothing to update: you are running the latest version", helpers.String("Version", versioncheck.BuildNumber))
|
||||
} else {
|
||||
fmt.Printf("Please refer to our installation docs in the following link: %s\n", installationLink)
|
||||
fmt.Printf("Version %s is available. Please refer to our installation documentation: %s\n", versioncheck.LatestReleaseVersion, installationLink)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
18
cmd/update/update_test.go
Normal file
18
cmd/update/update_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetUpdateCmd(t *testing.T) {
|
||||
ks := core.NewKubescape(context.TODO())
|
||||
cmd := GetUpdateCmd(ks)
|
||||
assert.NotNil(t, cmd)
|
||||
|
||||
err := cmd.RunE(cmd, []string{})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
237
cmd/vap/vap.go
Normal file
237
cmd/vap/vap.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package vap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
admissionv1 "k8s.io/api/admissionregistration/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
var vapHelperCmdExamples = fmt.Sprintf(`
|
||||
vap command can be used for managing Validating Admission Policies in a Kubernetes cluster.
|
||||
This is an experimental feature and it might change.
|
||||
|
||||
Examples:
|
||||
|
||||
# Install Kubescape CEL admission policy library
|
||||
%[1]s vap deploy-library | kubectl apply -f -
|
||||
# Create a policy binding
|
||||
%[1]s vap create-policy-binding --name my-policy-binding --policy c-0016 --namespace=my-namespace | kubectl apply -f -
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetVapHelperCmd() *cobra.Command {
|
||||
|
||||
vapHelperCmd := &cobra.Command{
|
||||
Use: "vap",
|
||||
Short: "Helper commands for managing Validating Admission Policies in a Kubernetes cluster",
|
||||
Long: ``,
|
||||
Example: vapHelperCmdExamples,
|
||||
}
|
||||
|
||||
// Create subcommands
|
||||
vapHelperCmd.AddCommand(getDeployLibraryCmd())
|
||||
vapHelperCmd.AddCommand(getCreatePolicyBindingCmd())
|
||||
|
||||
return vapHelperCmd
|
||||
}
|
||||
|
||||
func getDeployLibraryCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "deploy-library",
|
||||
Short: "Install Kubescape CEL admission policy library",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return deployLibrary()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getCreatePolicyBindingCmd() *cobra.Command {
|
||||
var policyBindingName string
|
||||
var policyName string
|
||||
var namespaceArr []string
|
||||
var labelArr []string
|
||||
var action string
|
||||
var parameterReference string
|
||||
|
||||
createPolicyBindingCmd := &cobra.Command{
|
||||
Use: "create-policy-binding",
|
||||
Short: "Create a policy binding",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Validate the inputs
|
||||
if err := isValidK8sObjectName(policyBindingName); err != nil {
|
||||
return fmt.Errorf("invalid policy binding name %s: %w", policyBindingName, err)
|
||||
}
|
||||
if err := isValidK8sObjectName(policyName); err != nil {
|
||||
return fmt.Errorf("invalid policy name %s: %w", policyName, err)
|
||||
}
|
||||
for _, namespace := range namespaceArr {
|
||||
if err := isValidK8sObjectName(namespace); err != nil {
|
||||
return fmt.Errorf("invalid namespace %s: %w", namespace, err)
|
||||
}
|
||||
}
|
||||
for _, label := range labelArr {
|
||||
// Label selector must be in the format key=value
|
||||
if !regexp.MustCompile(`^[a-zA-Z0-9]+=[a-zA-Z0-9]+$`).MatchString(label) {
|
||||
return fmt.Errorf("invalid label selector: %s", label)
|
||||
}
|
||||
}
|
||||
if action != "Deny" && action != "Audit" && action != "Warn" {
|
||||
return fmt.Errorf("invalid action: %s", action)
|
||||
}
|
||||
if parameterReference != "" {
|
||||
if err := isValidK8sObjectName(parameterReference); err != nil {
|
||||
return fmt.Errorf("invalid parameter reference %s: %w", parameterReference, err)
|
||||
}
|
||||
}
|
||||
|
||||
return createPolicyBinding(policyBindingName, policyName, action, parameterReference, namespaceArr, labelArr)
|
||||
},
|
||||
}
|
||||
// Must specify the name of the policy binding
|
||||
createPolicyBindingCmd.Flags().StringVarP(&policyBindingName, "name", "n", "", "Name of the policy binding")
|
||||
createPolicyBindingCmd.MarkFlagRequired("name")
|
||||
createPolicyBindingCmd.Flags().StringVarP(&policyName, "policy", "p", "", "Name of the policy to bind the resources to")
|
||||
createPolicyBindingCmd.MarkFlagRequired("policy")
|
||||
createPolicyBindingCmd.Flags().StringSliceVar(&namespaceArr, "namespace", []string{}, "Resource namespace selector")
|
||||
createPolicyBindingCmd.Flags().StringSliceVar(&labelArr, "label", []string{}, "Resource label selector")
|
||||
createPolicyBindingCmd.Flags().StringVarP(&action, "action", "a", "Deny", "Action to take when policy fails")
|
||||
createPolicyBindingCmd.Flags().StringVarP(¶meterReference, "parameter-reference", "r", "", "Parameter reference object name")
|
||||
|
||||
return createPolicyBindingCmd
|
||||
}
|
||||
|
||||
// Implementation of the VAP helper commands
|
||||
// deploy-library
|
||||
func deployLibrary() error {
|
||||
logger.L().Info("Downloading the Kubescape CEL admission policy library")
|
||||
// Download the policy-configuration-definition.yaml from the latest release URL
|
||||
policyConfigurationDefinitionURL := "https://github.com/kubescape/cel-admission-library/releases/latest/download/policy-configuration-definition.yaml"
|
||||
policyConfigurationDefinition, err := downloadFileToString(policyConfigurationDefinitionURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Download the basic-control-configuration.yaml from the latest release URL
|
||||
basicControlConfigurationURL := "https://github.com/kubescape/cel-admission-library/releases/latest/download/basic-control-configuration.yaml"
|
||||
basicControlConfiguration, err := downloadFileToString(basicControlConfigurationURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Download the kubescape-validating-admission-policies.yaml from the latest release URL
|
||||
kubescapeValidatingAdmissionPoliciesURL := "https://github.com/kubescape/cel-admission-library/releases/latest/download/kubescape-validating-admission-policies.yaml"
|
||||
kubescapeValidatingAdmissionPolicies, err := downloadFileToString(kubescapeValidatingAdmissionPoliciesURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.L().Info("Successfully downloaded admission policy library")
|
||||
|
||||
// Print the downloaded files to the STDOUT for the user to apply connecting them to a single YAML with ---
|
||||
fmt.Println(policyConfigurationDefinition)
|
||||
fmt.Println("---")
|
||||
fmt.Println(basicControlConfiguration)
|
||||
fmt.Println("---")
|
||||
fmt.Println(kubescapeValidatingAdmissionPolicies)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadFileToString(url string) (string, error) {
|
||||
// Send an HTTP GET request to the URL
|
||||
response, err := http.Get(url) //nolint:gosec
|
||||
if err != nil {
|
||||
return "", err // Return an empty string and the error if the request fails
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
// Check for a successful response (HTTP 200 OK)
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("failed to download file: %s", response.Status)
|
||||
}
|
||||
|
||||
// Read the response body
|
||||
bodyBytes, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", err // Return an empty string and the error if reading fails
|
||||
}
|
||||
|
||||
// Convert the byte slice to a string
|
||||
bodyString := string(bodyBytes)
|
||||
return bodyString, nil
|
||||
}
|
||||
|
||||
func isValidK8sObjectName(name string) error {
|
||||
// Kubernetes object names must consist of lower case alphanumeric characters, '-' or '.',
|
||||
// and must start and end with an alphanumeric character (e.g., 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')
|
||||
// Max length of 63 characters.
|
||||
if len(name) > 63 {
|
||||
return errors.New("name should be less than 63 characters")
|
||||
}
|
||||
|
||||
regex := regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`)
|
||||
if !regex.MatchString(name) {
|
||||
return errors.New("name should consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a policy binding
|
||||
func createPolicyBinding(bindingName string, policyName string, action string, paramRefName string, namespaceArr []string, labelMatch []string) error {
|
||||
// Create a policy binding struct
|
||||
policyBinding := &admissionv1.ValidatingAdmissionPolicyBinding{}
|
||||
// Print the policy binding after marshalling it to YAML to the STDOUT
|
||||
// The user can apply the output to the cluster
|
||||
policyBinding.APIVersion = "admissionregistration.k8s.io/v1"
|
||||
policyBinding.Name = bindingName
|
||||
policyBinding.Kind = "ValidatingAdmissionPolicyBinding"
|
||||
policyBinding.Spec.PolicyName = policyName
|
||||
policyBinding.Spec.MatchResources = &admissionv1.MatchResources{}
|
||||
if len(namespaceArr) > 0 {
|
||||
policyBinding.Spec.MatchResources.NamespaceSelector = &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "kubernetes.io/metadata.name",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: namespaceArr,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if len(labelMatch) > 0 {
|
||||
policyBinding.Spec.MatchResources.ObjectSelector = &metav1.LabelSelector{}
|
||||
policyBinding.Spec.MatchResources.ObjectSelector.MatchLabels = make(map[string]string)
|
||||
for _, label := range labelMatch {
|
||||
labelParts := regexp.MustCompile(`=`).Split(label, 2)
|
||||
policyBinding.Spec.MatchResources.ObjectSelector.MatchLabels[labelParts[0]] = labelParts[1]
|
||||
}
|
||||
}
|
||||
|
||||
policyBinding.Spec.ValidationActions = []admissionv1.ValidationAction{admissionv1.ValidationAction(action)}
|
||||
paramAction := admissionv1.DenyAction
|
||||
if paramRefName != "" {
|
||||
policyBinding.Spec.ParamRef = &admissionv1.ParamRef{
|
||||
Name: paramRefName,
|
||||
ParameterNotFoundAction: ¶mAction,
|
||||
}
|
||||
}
|
||||
// Marshal the policy binding to YAML
|
||||
out, err := yaml.Marshal(policyBinding)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(out))
|
||||
return nil
|
||||
}
|
||||
10
cmd/vap/vap_test.go
Normal file
10
cmd/vap/vap_test.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package vap
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetVapHelperCmd(t *testing.T) {
|
||||
// Call the GetFixCmd function
|
||||
_ = GetVapHelperCmd()
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
//go:build !gitenabled
|
||||
|
||||
package version
|
||||
|
||||
func isGitEnabled() bool {
|
||||
return false
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
//go:build gitenabled
|
||||
|
||||
package version
|
||||
|
||||
func isGitEnabled() bool {
|
||||
return true
|
||||
}
|
||||
@@ -1,31 +1,37 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
|
||||
"github.com/kubescape/backend/pkg/versioncheck"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func GetVersionCmd() *cobra.Command {
|
||||
func GetVersionCmd(ks meta.IKubescape, version, commit, date string) *cobra.Command {
|
||||
versionCmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Get current version",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.TODO()
|
||||
v := cautils.NewIVersionCheckHandler(ctx)
|
||||
v.CheckLatestVersion(ctx, cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
|
||||
fmt.Fprintf(os.Stdout,
|
||||
v := versioncheck.NewIVersionCheckHandler(ks.Context())
|
||||
_ = v.CheckLatestVersion(ks.Context(), versioncheck.NewVersionCheckRequest("", version, "", "", "version", nil))
|
||||
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(),
|
||||
"Your current version is: %s\n",
|
||||
cautils.BuildNumber,
|
||||
version,
|
||||
)
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(),
|
||||
"Build commit: %s\n",
|
||||
commit,
|
||||
)
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(),
|
||||
"Build date: %s\n",
|
||||
date,
|
||||
)
|
||||
logger.L().Debug(fmt.Sprintf("git enabled in build: %t", isGitEnabled()))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return versionCmd
|
||||
}
|
||||
}
|
||||
49
cmd/version/version_test.go
Normal file
49
cmd/version/version_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
|
||||
"github.com/kubescape/backend/pkg/versioncheck"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetVersionCmd(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
buildNumber string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Undefined Build Number",
|
||||
buildNumber: "unknown",
|
||||
want: "Your current version is: unknown\nBuild commit: \nBuild date: \n",
|
||||
},
|
||||
{
|
||||
name: "Defined Build Number: v3.0.1",
|
||||
buildNumber: "v3.0.1",
|
||||
want: "Your current version is: v3.0.1\nBuild commit: \nBuild date: \n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
versioncheck.BuildNumber = tt.buildNumber
|
||||
|
||||
ks := core.NewKubescape(context.TODO())
|
||||
if cmd := GetVersionCmd(ks, tt.buildNumber, "", ""); cmd != nil {
|
||||
buf := bytes.NewBufferString("")
|
||||
cmd.SetOut(buf)
|
||||
cmd.Execute()
|
||||
out, err := io.ReadAll(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, tt.want, string(out))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
250
core/README.md
250
core/README.md
@@ -1,14 +1,248 @@
|
||||
# Kubescape core package
|
||||
# Kubescape Core Package
|
||||
|
||||
The `core` package provides the main Kubescape scanning engine as a Go library, allowing you to integrate Kubescape security scanning directly into your applications.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Quick Start](#quick-start)
|
||||
- [API Reference](#api-reference)
|
||||
- [Examples](#examples)
|
||||
- [Configuration Options](#configuration-options)
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/kubescape/kubescape/v3/core
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
// initialize kubescape
|
||||
ks := core.NewKubescape()
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
// scan cluster
|
||||
results, err := ks.Scan(&cautils.ScanInfo{})
|
||||
"github.com/kubescape/kubescape/v3/core"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
)
|
||||
|
||||
// convert scan results to json
|
||||
jsonRes, err := results.ToJson()
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
```
|
||||
// Initialize Kubescape
|
||||
ks := core.NewKubescape(ctx)
|
||||
|
||||
// Configure scan
|
||||
scanInfo := &cautils.ScanInfo{
|
||||
// Scan the current cluster
|
||||
ScanAll: true,
|
||||
}
|
||||
|
||||
// Run scan
|
||||
results, err := ks.Scan(scanInfo)
|
||||
if err != nil {
|
||||
log.Fatalf("Scan failed: %v", err)
|
||||
}
|
||||
|
||||
// Convert results to JSON
|
||||
jsonRes, err := results.ToJson()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to convert results: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println(string(jsonRes))
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Creating a Kubescape Instance
|
||||
|
||||
```go
|
||||
// Create with context
|
||||
ks := core.NewKubescape(ctx)
|
||||
```
|
||||
|
||||
### Scanning
|
||||
|
||||
```go
|
||||
// Scan with configuration
|
||||
results, err := ks.Scan(scanInfo)
|
||||
```
|
||||
|
||||
### Listing Frameworks and Controls
|
||||
|
||||
```go
|
||||
// List available policies
|
||||
err := ks.List(listPolicies)
|
||||
```
|
||||
|
||||
### Downloading Artifacts
|
||||
|
||||
```go
|
||||
// Download for offline use
|
||||
err := ks.Download(downloadInfo)
|
||||
```
|
||||
|
||||
### Image Scanning
|
||||
|
||||
```go
|
||||
// Scan container image
|
||||
exceedsSeverity, err := ks.ScanImage(imgScanInfo, scanInfo)
|
||||
```
|
||||
|
||||
### Fixing Misconfigurations
|
||||
|
||||
```go
|
||||
// Apply fixes to manifests
|
||||
err := ks.Fix(fixInfo)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Scan a Specific Framework
|
||||
|
||||
```go
|
||||
scanInfo := &cautils.ScanInfo{}
|
||||
scanInfo.SetPolicyIdentifiers([]string{"nsa"}, "framework")
|
||||
|
||||
results, err := ks.Scan(scanInfo)
|
||||
```
|
||||
|
||||
### Scan Specific Namespaces
|
||||
|
||||
```go
|
||||
scanInfo := &cautils.ScanInfo{
|
||||
IncludeNamespaces: "production,staging",
|
||||
}
|
||||
|
||||
results, err := ks.Scan(scanInfo)
|
||||
```
|
||||
|
||||
### Scan Local YAML Files
|
||||
|
||||
```go
|
||||
scanInfo := &cautils.ScanInfo{
|
||||
InputPatterns: []string{"/path/to/manifests"},
|
||||
}
|
||||
scanInfo.SetScanType(cautils.ScanTypeRepo)
|
||||
|
||||
results, err := ks.Scan(scanInfo)
|
||||
```
|
||||
|
||||
### Export Results to Different Formats
|
||||
|
||||
```go
|
||||
results, _ := ks.Scan(scanInfo)
|
||||
|
||||
// JSON
|
||||
jsonData, _ := results.ToJson()
|
||||
|
||||
// Get summary
|
||||
summary := results.GetData().Report.SummaryDetails
|
||||
fmt.Printf("Compliance Score: %.2f%%\n", summary.ComplianceScore)
|
||||
```
|
||||
|
||||
### Scan with Compliance Threshold
|
||||
|
||||
```go
|
||||
scanInfo := &cautils.ScanInfo{
|
||||
ComplianceThreshold: 80.0, // Fail if below 80%
|
||||
}
|
||||
|
||||
results, err := ks.Scan(scanInfo)
|
||||
if err != nil {
|
||||
// Handle scan failure
|
||||
}
|
||||
|
||||
// Check if threshold was exceeded
|
||||
if results.GetData().Report.SummaryDetails.ComplianceScore < scanInfo.ComplianceThreshold {
|
||||
log.Fatal("Compliance score below threshold")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### ScanInfo Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `AccountID` | string | Kubescape SaaS account ID |
|
||||
| `AccessKey` | string | Kubescape SaaS access key |
|
||||
| `InputPatterns` | []string | Paths to scan (files, directories, URLs) |
|
||||
| `ExcludedNamespaces` | string | Comma-separated namespaces to exclude |
|
||||
| `IncludeNamespaces` | string | Comma-separated namespaces to include |
|
||||
| `Format` | string | Output format (json, junit, sarif, etc.) |
|
||||
| `Output` | string | Output file path |
|
||||
| `VerboseMode` | bool | Show all resources in output |
|
||||
| `FailThreshold` | float32 | Fail threshold percentage |
|
||||
| `ComplianceThreshold` | float32 | Compliance threshold percentage |
|
||||
| `UseExceptions` | string | Path to exceptions file |
|
||||
| `UseArtifactsFrom` | string | Path to offline artifacts |
|
||||
| `Submit` | bool | Submit results to SaaS |
|
||||
| `Local` | bool | Keep results local (don't submit) |
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
```go
|
||||
results, err := ks.Scan(scanInfo)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, context.DeadlineExceeded):
|
||||
log.Fatal("Scan timed out")
|
||||
case errors.Is(err, context.Canceled):
|
||||
log.Fatal("Scan was canceled")
|
||||
default:
|
||||
log.Fatalf("Scan error: %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The Kubescape instance is safe for concurrent use. You can run multiple scans in parallel:
|
||||
|
||||
```go
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, ns := range namespaces {
|
||||
wg.Add(1)
|
||||
go func(namespace string) {
|
||||
defer wg.Done()
|
||||
|
||||
scanInfo := &cautils.ScanInfo{
|
||||
IncludeNamespaces: namespace,
|
||||
}
|
||||
results, _ := ks.Scan(scanInfo)
|
||||
// Process results...
|
||||
}(ns)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [CLI Reference](../docs/cli-reference.md)
|
||||
- [Getting Started Guide](../docs/getting-started.md)
|
||||
- [Architecture](../docs/architecture.md)
|
||||
53
core/cautils/controllink_test.go
Normal file
53
core/cautils/controllink_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Returns a valid URL when given a valid control ID.
|
||||
func TestGetControlLink_ValidControlID(t *testing.T) {
|
||||
controlID := "cis-1.1.3"
|
||||
expectedURL := "https://hub.armosec.io/docs/cis-1-1-3"
|
||||
|
||||
result := GetControlLink(controlID)
|
||||
|
||||
if result != expectedURL {
|
||||
t.Errorf("Expected URL: %s, but got: %s", expectedURL, result)
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces dots with hyphens in the control ID to generate the correct documentation link.
|
||||
func TestGetControlLink_DotsInControlID(t *testing.T) {
|
||||
controlID := "cis.1.1.3"
|
||||
expectedURL := "https://hub.armosec.io/docs/cis-1-1-3"
|
||||
|
||||
result := GetControlLink(controlID)
|
||||
|
||||
if result != expectedURL {
|
||||
t.Errorf("Expected URL: %s, but got: %s", expectedURL, result)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a lowercase URL.
|
||||
func TestGetControlLink_LowercaseURL(t *testing.T) {
|
||||
controlID := "CIS-1.1.3"
|
||||
expectedURL := "https://hub.armosec.io/docs/cis-1-1-3"
|
||||
|
||||
result := GetControlLink(controlID)
|
||||
|
||||
if result != expectedURL {
|
||||
t.Errorf("Expected URL: %s, but got: %s", expectedURL, result)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns URL to armosec docs when given an empty control ID.
|
||||
func TestGetControlLink_EmptyControlID(t *testing.T) {
|
||||
controlID := ""
|
||||
expectedURL := "https://hub.armosec.io/docs/"
|
||||
|
||||
result := GetControlLink(controlID)
|
||||
|
||||
if result != expectedURL {
|
||||
t.Errorf("Expected URL: %s, but got: %s", expectedURL, result)
|
||||
}
|
||||
}
|
||||
@@ -7,33 +7,34 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/google/uuid"
|
||||
v1 "github.com/kubescape/backend/pkg/client/v1"
|
||||
"github.com/kubescape/backend/pkg/servicediscovery"
|
||||
servicediscoveryv1 "github.com/kubescape/backend/pkg/servicediscovery/v1"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
servicediscoveryv2 "github.com/kubescape/backend/pkg/servicediscovery/v2"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
configFileName string = "config"
|
||||
kubescapeNamespace string = "kubescape"
|
||||
kubescapeConfigMapName string = "kubescape-config"
|
||||
kubescapeCloudConfigMapName string = "ks-cloud-config"
|
||||
configFileName string = "config"
|
||||
kubescapeNamespace string = "kubescape"
|
||||
|
||||
kubescapeConfigMapName string = "kubescape-config" // deprecated - for backward compatibility
|
||||
|
||||
cloudConfigMapLabelSelector string = "kubescape.io/infra=config"
|
||||
credsLabelSelectors string = "kubescape.io/infra=credentials" //nolint:gosec
|
||||
|
||||
// env vars
|
||||
defaultConfigMapNameEnvVar string = "KS_DEFAULT_CONFIGMAP_NAME"
|
||||
defaultCloudConfigMapNameEnvVar string = "KS_DEFAULT_CLOUD_CONFIGMAP_NAME"
|
||||
defaultConfigMapNamespaceEnvVar string = "KS_DEFAULT_CONFIGMAP_NAMESPACE"
|
||||
accountIdEnvVar string = "KS_ACCOUNT_ID"
|
||||
accessKeyEnvVar string = "KS_ACCESS_KEY"
|
||||
cloudApiUrlEnvVar string = "KS_CLOUD_API_URL"
|
||||
cloudReportUrlEnvVar string = "KS_CLOUD_REPORT_URL"
|
||||
storageEnabledEnvVar string = "KS_STORAGE_ENABLED"
|
||||
)
|
||||
|
||||
func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName + ".json") }
|
||||
@@ -47,7 +48,7 @@ type ConfigObj struct {
|
||||
ClusterName string `json:"clusterName,omitempty"`
|
||||
CloudReportURL string `json:"cloudReportURL,omitempty"`
|
||||
CloudAPIURL string `json:"cloudAPIURL,omitempty"`
|
||||
StorageEnabled bool `json:"storageEnabled,omitempty"`
|
||||
AccessKey string `json:"accessKey,omitempty"`
|
||||
}
|
||||
|
||||
// Config - convert ConfigObj to config file
|
||||
@@ -92,15 +93,15 @@ type ITenantConfig interface {
|
||||
UpdateCachedConfig() error
|
||||
DeleteCachedConfig(ctx context.Context) error
|
||||
GenerateAccountID() (string, error)
|
||||
DeleteAccountID() error
|
||||
DeleteCredentials() error
|
||||
|
||||
// getters
|
||||
GetContextName() string
|
||||
GetAccountID() string
|
||||
GetAccessKey() string
|
||||
GetConfigObj() *ConfigObj
|
||||
GetCloudReportURL() string
|
||||
GetCloudAPIURL() string
|
||||
IsStorageEnabled() bool
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
@@ -114,7 +115,7 @@ type LocalConfig struct {
|
||||
configObj *ConfigObj
|
||||
}
|
||||
|
||||
func NewLocalConfig(accountID, clusterName string, customClusterName string) *LocalConfig {
|
||||
func NewLocalConfig(accountID, accessKey, clusterName, customClusterName string) *LocalConfig {
|
||||
lc := &LocalConfig{
|
||||
configObj: &ConfigObj{},
|
||||
}
|
||||
@@ -123,9 +124,8 @@ func NewLocalConfig(accountID, clusterName string, customClusterName string) *Lo
|
||||
loadConfigFromFile(lc.configObj)
|
||||
}
|
||||
|
||||
updateAccountID(lc.configObj, accountID)
|
||||
updateCredentials(lc.configObj, accountID, accessKey)
|
||||
updateCloudURLs(lc.configObj)
|
||||
updateStorageEnabled(lc.configObj)
|
||||
|
||||
// If a custom cluster name is provided then set that name, else use the cluster's original name
|
||||
if customClusterName != "" {
|
||||
@@ -134,8 +134,7 @@ func NewLocalConfig(accountID, clusterName string, customClusterName string) *Lo
|
||||
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
|
||||
}
|
||||
|
||||
updatedKsCloud := initializeCloudAPI(lc)
|
||||
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", updatedKsCloud.GetCloudAPIURL()), helpers.String("report", updatedKsCloud.GetCloudReportURL()))
|
||||
initializeCloudAPI(lc)
|
||||
|
||||
return lc
|
||||
}
|
||||
@@ -145,7 +144,7 @@ func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountI
|
||||
func (lc *LocalConfig) GetContextName() string { return lc.configObj.ClusterName }
|
||||
func (lc *LocalConfig) GetCloudReportURL() string { return lc.configObj.CloudReportURL }
|
||||
func (lc *LocalConfig) GetCloudAPIURL() string { return lc.configObj.CloudAPIURL }
|
||||
func (lc *LocalConfig) IsStorageEnabled() bool { return lc.configObj.StorageEnabled }
|
||||
func (lc *LocalConfig) GetAccessKey() string { return lc.configObj.AccessKey }
|
||||
|
||||
func (lc *LocalConfig) GenerateAccountID() (string, error) {
|
||||
lc.configObj.AccountID = uuid.NewString()
|
||||
@@ -153,7 +152,8 @@ func (lc *LocalConfig) GenerateAccountID() (string, error) {
|
||||
return lc.configObj.AccountID, err
|
||||
}
|
||||
|
||||
func (lc *LocalConfig) DeleteAccountID() error {
|
||||
func (lc *LocalConfig) DeleteCredentials() error {
|
||||
lc.configObj.AccessKey = ""
|
||||
lc.configObj.AccountID = ""
|
||||
return lc.UpdateCachedConfig()
|
||||
}
|
||||
@@ -189,20 +189,16 @@ KS_CACHE // path to cached files
|
||||
var _ ITenantConfig = &ClusterConfig{}
|
||||
|
||||
type ClusterConfig struct {
|
||||
k8s *k8sinterface.KubernetesApi
|
||||
configObj *ConfigObj
|
||||
configMapNamespace string
|
||||
ksConfigMapName string
|
||||
ksCloudConfigMapName string
|
||||
k8s *k8sinterface.KubernetesApi
|
||||
configObj *ConfigObj
|
||||
configMapNamespace string
|
||||
}
|
||||
|
||||
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, clusterName string, customClusterName string) *ClusterConfig {
|
||||
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, accessKey, clusterName, customClusterName string) *ClusterConfig {
|
||||
c := &ClusterConfig{
|
||||
k8s: k8s,
|
||||
configObj: &ConfigObj{},
|
||||
ksConfigMapName: getKubescapeConfigMapName(),
|
||||
ksCloudConfigMapName: getKubescapeCloudConfigMapName(),
|
||||
configMapNamespace: GetConfigMapNamespace(),
|
||||
k8s: k8s,
|
||||
configObj: &ConfigObj{},
|
||||
configMapNamespace: GetConfigMapNamespace(),
|
||||
}
|
||||
|
||||
// first, load from file
|
||||
@@ -210,19 +206,16 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, clusterName st
|
||||
loadConfigFromFile(c.configObj)
|
||||
}
|
||||
|
||||
// second, load from configMap
|
||||
if c.existsConfigMap(c.ksConfigMapName) {
|
||||
c.updateConfigEmptyFieldsFromKubescapeConfigMap()
|
||||
}
|
||||
loadUrlsFromFile(c.configObj)
|
||||
|
||||
// third, load urls from cloudConfigMap
|
||||
if c.existsConfigMap(c.ksCloudConfigMapName) {
|
||||
c.updateConfigEmptyFieldsFromKubescapeCloudConfigMap()
|
||||
}
|
||||
// second, load urls from config map
|
||||
c.updateConfigEmptyFieldsFromKubescapeConfigMap()
|
||||
|
||||
updateAccountID(c.configObj, accountID)
|
||||
// third, credentials from secret
|
||||
c.updateConfigEmptyFieldsFromCredentialsSecret()
|
||||
|
||||
updateCredentials(c.configObj, accountID, accessKey)
|
||||
updateCloudURLs(c.configObj)
|
||||
updateStorageEnabled(c.configObj)
|
||||
|
||||
// If a custom cluster name is provided then set that name, else use the cluster's original name
|
||||
if customClusterName != "" {
|
||||
@@ -236,8 +229,7 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, clusterName st
|
||||
} else { // override the cluster name if it has unwanted characters
|
||||
c.configObj.ClusterName = AdoptClusterName(c.configObj.ClusterName)
|
||||
}
|
||||
updatedKsCloud := initializeCloudAPI(c)
|
||||
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", updatedKsCloud.GetCloudAPIURL()), helpers.String("report", updatedKsCloud.GetCloudReportURL()))
|
||||
initializeCloudAPI(c)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -246,7 +238,7 @@ func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace
|
||||
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
|
||||
func (c *ClusterConfig) GetCloudReportURL() string { return c.configObj.CloudReportURL }
|
||||
func (c *ClusterConfig) GetCloudAPIURL() string { return c.configObj.CloudAPIURL }
|
||||
func (c *ClusterConfig) IsStorageEnabled() bool { return c.configObj.StorageEnabled }
|
||||
func (c *ClusterConfig) GetAccessKey() string { return c.configObj.AccessKey }
|
||||
|
||||
func (c *ClusterConfig) UpdateCachedConfig() error {
|
||||
logger.L().Debug("updating cached config", helpers.Interface("configObj", c.configObj))
|
||||
@@ -272,42 +264,57 @@ func (c *ClusterConfig) ToMapString() map[string]interface{} {
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) updateConfigEmptyFieldsFromKubescapeConfigMap() error {
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.ksConfigMapName, metav1.GetOptions{})
|
||||
configMaps, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).List(context.Background(), metav1.ListOptions{
|
||||
LabelSelector: cloudConfigMapLabelSelector,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tempCO := ConfigObj{}
|
||||
if jsonConf, ok := configMap.Data["config.json"]; ok {
|
||||
if err = json.Unmarshal([]byte(jsonConf), &tempCO); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.configObj.updateEmptyFields(&tempCO)
|
||||
var ksConfigMap *corev1.ConfigMap
|
||||
if len(configMaps.Items) == 0 {
|
||||
// try to find configmaps by name (for backward compatibility)
|
||||
ksConfigMap, _ = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), kubescapeConfigMapName, metav1.GetOptions{})
|
||||
} else {
|
||||
// use the first configmap with the label
|
||||
ksConfigMap = &configMaps.Items[0]
|
||||
}
|
||||
|
||||
if ksConfigMap != nil {
|
||||
if jsonConf, ok := ksConfigMap.Data["clusterData"]; ok {
|
||||
tempCO := ConfigObj{}
|
||||
if err = json.Unmarshal([]byte(jsonConf), &tempCO); err != nil {
|
||||
return err
|
||||
}
|
||||
c.configObj.updateEmptyFields(&tempCO)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) updateConfigEmptyFieldsFromKubescapeCloudConfigMap() error {
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.ksCloudConfigMapName, metav1.GetOptions{})
|
||||
func (c *ClusterConfig) updateConfigEmptyFieldsFromCredentialsSecret() error {
|
||||
secrets, err := c.k8s.KubernetesClient.CoreV1().Secrets(c.configMapNamespace).List(context.Background(),
|
||||
metav1.ListOptions{LabelSelector: credsLabelSelectors})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jsonConf, ok := configMap.Data["services"]; ok {
|
||||
services, err := servicediscovery.GetServices(
|
||||
servicediscoveryv1.NewServiceDiscoveryStreamV1([]byte(jsonConf)),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(secrets.Items) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if services.GetApiServerUrl() != "" {
|
||||
c.configObj.CloudAPIURL = services.GetApiServerUrl()
|
||||
}
|
||||
if services.GetReportReceiverHttpUrl() != "" {
|
||||
c.configObj.CloudReportURL = services.GetReportReceiverHttpUrl()
|
||||
if jsonConf, ok := secrets.Items[0].Data["account"]; ok {
|
||||
if account := string(jsonConf); account != "" {
|
||||
c.configObj.AccountID = account
|
||||
}
|
||||
}
|
||||
|
||||
if jsonConf, ok := secrets.Items[0].Data["accessKey"]; ok {
|
||||
if accessKey := string(jsonConf); accessKey != "" {
|
||||
c.configObj.AccessKey = accessKey
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -323,11 +330,6 @@ func loadConfigFromData(co *ConfigObj, data map[string]string) error {
|
||||
return e
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) existsConfigMap(name string) bool {
|
||||
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), name, metav1.GetOptions{})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func existsConfigFile() bool {
|
||||
_, err := os.ReadFile(ConfigFileFullPath())
|
||||
return err == nil
|
||||
@@ -349,8 +351,9 @@ func (c *ClusterConfig) GenerateAccountID() (string, error) {
|
||||
return c.configObj.AccountID, err
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) DeleteAccountID() error {
|
||||
func (c *ClusterConfig) DeleteCredentials() error {
|
||||
c.configObj.AccountID = ""
|
||||
c.configObj.AccessKey = ""
|
||||
return c.UpdateCachedConfig()
|
||||
}
|
||||
|
||||
@@ -368,7 +371,7 @@ func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {
|
||||
func loadConfigFromFile(configObj *ConfigObj) error {
|
||||
dat, err := os.ReadFile(ConfigFileFullPath())
|
||||
if err != nil {
|
||||
return err
|
||||
return nil // no config file
|
||||
}
|
||||
return readConfig(dat, configObj)
|
||||
}
|
||||
@@ -384,6 +387,32 @@ func readConfig(dat []byte, configObj *ConfigObj) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadUrlsFromFile(obj *ConfigObj) error {
|
||||
dat, err := os.ReadFile("/etc/config/services.json")
|
||||
if err != nil {
|
||||
return nil // no config file
|
||||
}
|
||||
services, err := servicediscovery.GetServices(
|
||||
servicediscoveryv2.NewServiceDiscoveryStreamV2(dat),
|
||||
)
|
||||
if err != nil {
|
||||
// try to parse as v1
|
||||
services, err = servicediscovery.GetServices(
|
||||
servicediscoveryv1.NewServiceDiscoveryStreamV1(dat),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if services.GetApiServerUrl() != "" {
|
||||
obj.CloudAPIURL = services.GetApiServerUrl()
|
||||
}
|
||||
if services.GetReportReceiverHttpUrl() != "" {
|
||||
obj.CloudReportURL = services.GetReportReceiverHttpUrl()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteConfigFile() error {
|
||||
return os.Remove(ConfigFileFullPath())
|
||||
}
|
||||
@@ -396,21 +425,6 @@ func AdoptClusterName(clusterName string) string {
|
||||
return re.ReplaceAllString(clusterName, "-")
|
||||
}
|
||||
|
||||
func getKubescapeConfigMapName() string {
|
||||
if n := os.Getenv(defaultConfigMapNameEnvVar); n != "" {
|
||||
return n
|
||||
}
|
||||
return kubescapeConfigMapName
|
||||
}
|
||||
|
||||
func getKubescapeCloudConfigMapName() string {
|
||||
if n := os.Getenv(defaultCloudConfigMapNameEnvVar); n != "" {
|
||||
return n
|
||||
}
|
||||
|
||||
return kubescapeCloudConfigMapName
|
||||
}
|
||||
|
||||
// GetConfigMapNamespace returns the namespace of the cluster config, which is the same for all in-cluster components
|
||||
func GetConfigMapNamespace() string {
|
||||
if n := os.Getenv(defaultConfigMapNamespaceEnvVar); n != "" {
|
||||
@@ -419,7 +433,15 @@ func GetConfigMapNamespace() string {
|
||||
return kubescapeNamespace
|
||||
}
|
||||
|
||||
func updateAccountID(configObj *ConfigObj, accountID string) {
|
||||
func updateCredentials(configObj *ConfigObj, accountID, accessKey string) {
|
||||
if accessKey != "" {
|
||||
configObj.AccessKey = accessKey
|
||||
}
|
||||
|
||||
if envAccessKey := os.Getenv(accessKeyEnvVar); envAccessKey != "" {
|
||||
configObj.AccessKey = envAccessKey
|
||||
}
|
||||
|
||||
if accountID != "" {
|
||||
configObj.AccountID = accountID
|
||||
}
|
||||
@@ -429,10 +451,6 @@ func updateAccountID(configObj *ConfigObj, accountID string) {
|
||||
}
|
||||
}
|
||||
|
||||
func updateStorageEnabled(configObj *ConfigObj) {
|
||||
configObj.StorageEnabled, _ = ParseBoolEnvVar(storageEnabledEnvVar, configObj.StorageEnabled)
|
||||
}
|
||||
|
||||
func getCloudURLsFromEnv(cloudURLs *CloudURLs) {
|
||||
// load from env
|
||||
if cloudAPIURL := os.Getenv(cloudApiUrlEnvVar); cloudAPIURL != "" {
|
||||
@@ -460,18 +478,46 @@ func updateCloudURLs(configObj *ConfigObj) {
|
||||
}
|
||||
|
||||
func initializeCloudAPI(c ITenantConfig) *v1.KSCloudAPI {
|
||||
logger.L().Debug("initializing KS Cloud API from config", helpers.String("accountID", c.GetAccountID()), helpers.String("cloudAPIURL", c.GetCloudAPIURL()), helpers.String("cloudReportURL", c.GetCloudReportURL()))
|
||||
cloud, err := v1.NewKSCloudAPI(c.GetCloudAPIURL(), c.GetCloudReportURL(), c.GetAccountID())
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to create KS Cloud client", helpers.Error(err))
|
||||
if ksCloud := getter.GetKSCloudAPIConnector(); ksCloud != nil {
|
||||
|
||||
if val := c.GetCloudAPIURL(); val != "" && val != ksCloud.GetCloudAPIURL() {
|
||||
logger.L().Debug("updating KS Cloud API from config", helpers.String("old", ksCloud.GetCloudAPIURL()), helpers.String("new", val))
|
||||
ksCloud.SetCloudAPIURL(val)
|
||||
}
|
||||
if val := c.GetCloudReportURL(); val != "" && val != ksCloud.GetCloudReportURL() {
|
||||
logger.L().Debug("updating KS Cloud Report from config", helpers.String("old", ksCloud.GetCloudReportURL()), helpers.String("new", val))
|
||||
ksCloud.SetCloudReportURL(val)
|
||||
}
|
||||
if val := c.GetAccountID(); val != "" && val != ksCloud.GetAccountID() {
|
||||
logger.L().Debug("updating Account ID from config", helpers.String("old", ksCloud.GetAccountID()), helpers.String("new", val))
|
||||
ksCloud.SetAccountID(val)
|
||||
}
|
||||
if val := c.GetAccessKey(); val != "" && val != ksCloud.GetAccessKey() {
|
||||
logger.L().Debug("updating Access Key from config", helpers.Int("old (len)", len(ksCloud.GetAccessKey())), helpers.Int("new (len)", len(val)))
|
||||
ksCloud.SetAccessKey(val)
|
||||
}
|
||||
getter.SetKSCloudAPIConnector(ksCloud)
|
||||
} else {
|
||||
logger.L().Debug("initializing KS Cloud API from config", helpers.String("accountID", c.GetAccountID()), helpers.String("cloudAPIURL", c.GetCloudAPIURL()), helpers.String("cloudReportURL", c.GetCloudReportURL()))
|
||||
cloud, err := v1.NewKSCloudAPI(
|
||||
c.GetCloudAPIURL(),
|
||||
c.GetCloudReportURL(),
|
||||
c.GetAccountID(),
|
||||
c.GetAccessKey())
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to create KS Cloud client", helpers.Error(err))
|
||||
}
|
||||
getter.SetKSCloudAPIConnector(cloud)
|
||||
}
|
||||
getter.SetKSCloudAPIConnector(cloud)
|
||||
|
||||
return getter.GetKSCloudAPIConnector()
|
||||
}
|
||||
|
||||
func GetTenantConfig(accountID, clusterName, customClusterName string, k8s *k8sinterface.KubernetesApi) ITenantConfig {
|
||||
func GetTenantConfig(accountID, accessKey, clusterName, customClusterName string, k8s *k8sinterface.KubernetesApi) ITenantConfig {
|
||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||
return NewLocalConfig(accountID, clusterName, customClusterName)
|
||||
return NewLocalConfig(accountID, accessKey, clusterName, customClusterName)
|
||||
}
|
||||
return NewClusterConfig(k8s, accountID, clusterName, customClusterName)
|
||||
return NewClusterConfig(k8s, accountID, accessKey, clusterName, customClusterName)
|
||||
}
|
||||
|
||||
// firstNonEmpty returns the first non-empty string
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
@@ -4,7 +4,10 @@ import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/anchore/grype/grype/presenter/models"
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/grype/vulnerability"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
@@ -20,14 +23,20 @@ type K8SResources map[string][]string
|
||||
type ExternalResources map[string][]string
|
||||
|
||||
type ImageScanData struct {
|
||||
PresenterConfig *models.PresenterConfig
|
||||
Image string
|
||||
Context pkg.Context
|
||||
IgnoredMatches []match.IgnoredMatch
|
||||
Image string
|
||||
Matches match.Matches
|
||||
Packages []pkg.Package
|
||||
RemainingMatches *match.Matches
|
||||
SBOM *sbom.SBOM
|
||||
VulnerabilityProvider vulnerability.Provider
|
||||
}
|
||||
|
||||
type ScanTypes string
|
||||
|
||||
const (
|
||||
TopWorkloadsNumber = 5
|
||||
TopWorkloadsNumber = 3
|
||||
ScanTypeCluster ScanTypes = "cluster"
|
||||
ScanTypeRepo ScanTypes = "repo"
|
||||
ScanTypeImage ScanTypes = "image"
|
||||
@@ -58,25 +67,44 @@ type OPASessionObj struct {
|
||||
OmitRawResources bool // omit raw resources from output
|
||||
SingleResourceScan workloadinterface.IWorkload // single resource scan
|
||||
TopWorkloadsByScore []reporthandling.IResource
|
||||
TemplateMapping map[string]MappingNodes // Map chart obj to template (only for rendering from path)
|
||||
TriggeredByCLI bool
|
||||
LabelsToCopy []string // Labels to copy from workloads to scan reports
|
||||
}
|
||||
|
||||
func NewOPASessionObj(ctx context.Context, frameworks []reporthandling.Framework, k8sResources K8SResources, scanInfo *ScanInfo) *OPASessionObj {
|
||||
clusterSize := estimateClusterSize(k8sResources)
|
||||
if clusterSize < 100 {
|
||||
clusterSize = 100
|
||||
}
|
||||
|
||||
return &OPASessionObj{
|
||||
Report: &reporthandlingv2.PostureReport{},
|
||||
Policies: frameworks,
|
||||
K8SResources: k8sResources,
|
||||
AllResources: make(map[string]workloadinterface.IMetadata),
|
||||
ResourcesResult: make(map[string]resourcesresults.Result),
|
||||
ResourcesPrioritized: make(map[string]prioritization.PrioritizedResource),
|
||||
InfoMap: make(map[string]apis.StatusInfo),
|
||||
ResourceToControlsMap: make(map[string][]string),
|
||||
ResourceSource: make(map[string]reporthandling.Source),
|
||||
AllResources: make(map[string]workloadinterface.IMetadata, clusterSize),
|
||||
ResourcesResult: make(map[string]resourcesresults.Result, clusterSize),
|
||||
ResourcesPrioritized: make(map[string]prioritization.PrioritizedResource, clusterSize/10),
|
||||
InfoMap: make(map[string]apis.StatusInfo, clusterSize/10),
|
||||
ResourceToControlsMap: make(map[string][]string, clusterSize/2),
|
||||
ResourceSource: make(map[string]reporthandling.Source, clusterSize),
|
||||
SessionID: scanInfo.ScanID,
|
||||
Metadata: scanInfoToScanMetadata(ctx, scanInfo),
|
||||
OmitRawResources: scanInfo.OmitRawResources,
|
||||
TriggeredByCLI: scanInfo.TriggeredByCLI,
|
||||
TemplateMapping: make(map[string]MappingNodes, clusterSize/10),
|
||||
LabelsToCopy: scanInfo.LabelsToCopy,
|
||||
}
|
||||
}
|
||||
|
||||
func estimateClusterSize(k8sResources K8SResources) int {
|
||||
total := 0
|
||||
for _, resourceIDs := range k8sResources {
|
||||
total += len(resourceIDs)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// SetTopWorkloads sets the top workloads by score
|
||||
func (sessionObj *OPASessionObj) SetTopWorkloads() {
|
||||
count := 0
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"golang.org/x/mod/semver"
|
||||
|
||||
"github.com/armosec/utils-go/boolutils"
|
||||
cloudsupport "github.com/kubescape/k8s-interface/cloudsupport/v1"
|
||||
"github.com/kubescape/backend/pkg/versioncheck"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
func NewPolicies() *Policies {
|
||||
@@ -16,7 +15,7 @@ func NewPolicies() *Policies {
|
||||
}
|
||||
}
|
||||
|
||||
func (policies *Policies) Set(frameworks []reporthandling.Framework, version string, excludedRules map[string]bool, scanningScope reporthandling.ScanningScopeType) {
|
||||
func (policies *Policies) Set(frameworks []reporthandling.Framework, excludedRules map[string]bool, scanningScope reporthandling.ScanningScopeType) {
|
||||
for i := range frameworks {
|
||||
if !isFrameworkFitToScanScope(frameworks[i], scanningScope) {
|
||||
continue
|
||||
@@ -34,9 +33,12 @@ func (policies *Policies) Set(frameworks []reporthandling.Framework, version str
|
||||
}
|
||||
}
|
||||
|
||||
if !ruleWithKSOpaDependency(frameworks[i].Controls[j].Rules[r].Attributes) && isRuleKubescapeVersionCompatible(frameworks[i].Controls[j].Rules[r].Attributes, version) && isControlFitToScanScope(frameworks[i].Controls[j], scanningScope) {
|
||||
compatibleRules = append(compatibleRules, frameworks[i].Controls[j].Rules[r])
|
||||
if ShouldSkipRule(frameworks[i].Controls[j], frameworks[i].Controls[j].Rules[r], scanningScope) {
|
||||
continue
|
||||
}
|
||||
// if isRuleKubescapeVersionCompatible(frameworks[i].Controls[j].Rules[r].Attributes, version) && isControlFitToScanScope(frameworks[i].Controls[j], scanningScope) {
|
||||
compatibleRules = append(compatibleRules, frameworks[i].Controls[j].Rules[r])
|
||||
// }
|
||||
}
|
||||
if len(compatibleRules) > 0 {
|
||||
frameworks[i].Controls[j].Rules = compatibleRules
|
||||
@@ -56,12 +58,16 @@ func (policies *Policies) Set(frameworks []reporthandling.Framework, version str
|
||||
}
|
||||
}
|
||||
|
||||
func ruleWithKSOpaDependency(attributes map[string]interface{}) bool {
|
||||
if attributes == nil {
|
||||
return false
|
||||
// ShouldSkipRule checks if the rule should be skipped
|
||||
// It checks the following:
|
||||
// 1. Rule is compatible with the current kubescape version
|
||||
// 2. Rule fits the current scanning scope
|
||||
func ShouldSkipRule(control reporthandling.Control, rule reporthandling.PolicyRule, scanningScope reporthandling.ScanningScopeType) bool {
|
||||
if !isRuleKubescapeVersionCompatible(rule.Attributes, versioncheck.BuildNumber) {
|
||||
return true
|
||||
}
|
||||
if s, ok := attributes["armoOpa"]; ok { // TODO - make global
|
||||
return boolutils.StringToBool(s.(string))
|
||||
if !isControlFitToScanScope(control, scanningScope) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -70,50 +76,35 @@ func ruleWithKSOpaDependency(attributes map[string]interface{}) bool {
|
||||
// In local build (BuildNumber = ""):
|
||||
// returns true only if rule doesn't have the "until" attribute
|
||||
func isRuleKubescapeVersionCompatible(attributes map[string]interface{}, version string) bool {
|
||||
normalizedVersion := version
|
||||
if version != "" && !semver.IsValid(version) {
|
||||
normalizedVersion = "v" + version
|
||||
}
|
||||
|
||||
if from, ok := attributes["useFromKubescapeVersion"]; ok && from != nil {
|
||||
if version != "" {
|
||||
if semver.Compare(version, from.(string)) == -1 {
|
||||
switch sfrom := from.(type) {
|
||||
case string:
|
||||
if normalizedVersion != "" && semver.IsValid(normalizedVersion) && semver.Compare(normalizedVersion, sfrom) == -1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if until, ok := attributes["useUntilKubescapeVersion"]; ok && until != nil {
|
||||
if version == "" {
|
||||
default:
|
||||
return false
|
||||
}
|
||||
if semver.Compare(version, until.(string)) >= 0 {
|
||||
}
|
||||
|
||||
if until, ok := attributes["useUntilKubescapeVersion"]; ok && until != nil {
|
||||
switch suntil := until.(type) {
|
||||
case string:
|
||||
if normalizedVersion == "" || (semver.IsValid(normalizedVersion) && semver.Compare(normalizedVersion, suntil) >= 0) {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getCloudProvider(scanInfo *ScanInfo) reporthandling.ScanningScopeType {
|
||||
if cloudsupport.IsAKS() {
|
||||
return reporthandling.ScopeCloudAKS
|
||||
}
|
||||
if cloudsupport.IsEKS() {
|
||||
return reporthandling.ScopeCloudEKS
|
||||
}
|
||||
if cloudsupport.IsGKE() {
|
||||
return reporthandling.ScopeCloudGKE
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetScanningScope(scanInfo *ScanInfo) reporthandling.ScanningScopeType {
|
||||
|
||||
switch scanInfo.GetScanningContext() {
|
||||
case ContextCluster:
|
||||
if cloudProvider := getCloudProvider(scanInfo); cloudProvider != "" {
|
||||
return cloudProvider
|
||||
}
|
||||
return reporthandling.ScopeCluster
|
||||
default:
|
||||
return reporthandling.ScopeFile
|
||||
}
|
||||
}
|
||||
|
||||
func isScanningScopeMatchToControlScope(scanScope reporthandling.ScanningScopeType, controlScope reporthandling.ScanningScopeType) bool {
|
||||
|
||||
switch controlScope {
|
||||
@@ -165,3 +156,13 @@ func isFrameworkFitToScanScope(framework reporthandling.Framework, scanScopeMatc
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetScanningScope(ContextMetadata reporthandlingv2.ContextMetadata) reporthandling.ScanningScopeType {
|
||||
if ContextMetadata.ClusterContextMetadata != nil {
|
||||
if ContextMetadata.ClusterContextMetadata.CloudMetadata != nil && ContextMetadata.ClusterContextMetadata.CloudMetadata.CloudProvider != "" {
|
||||
return reporthandling.ScanningScopeType(ContextMetadata.ClusterContextMetadata.CloudMetadata.CloudProvider)
|
||||
}
|
||||
return reporthandling.ScopeCluster
|
||||
}
|
||||
return reporthandling.ScopeFile
|
||||
}
|
||||
|
||||
@@ -9,99 +9,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsControlFitToScanScope(t *testing.T) {
|
||||
tests := []struct {
|
||||
scanInfo *ScanInfo
|
||||
Control reporthandling.Control
|
||||
expected_res bool
|
||||
}{
|
||||
{
|
||||
scanInfo: &ScanInfo{
|
||||
InputPatterns: []string{
|
||||
"./testdata/any_file_for_test.json",
|
||||
},
|
||||
},
|
||||
Control: reporthandling.Control{
|
||||
ScanningScope: &reporthandling.ScanningScope{
|
||||
Matches: []reporthandling.ScanningScopeType{
|
||||
reporthandling.ScopeFile,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected_res: true,
|
||||
},
|
||||
{
|
||||
scanInfo: &ScanInfo{
|
||||
InputPatterns: []string{
|
||||
"./testdata/any_file_for_test.json",
|
||||
},
|
||||
},
|
||||
Control: reporthandling.Control{
|
||||
ScanningScope: &reporthandling.ScanningScope{
|
||||
|
||||
Matches: []reporthandling.ScanningScopeType{
|
||||
reporthandling.ScopeCluster,
|
||||
reporthandling.ScopeFile,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected_res: true,
|
||||
},
|
||||
{
|
||||
scanInfo: &ScanInfo{},
|
||||
Control: reporthandling.Control{
|
||||
ScanningScope: &reporthandling.ScanningScope{
|
||||
|
||||
Matches: []reporthandling.ScanningScopeType{
|
||||
reporthandling.ScopeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected_res: true,
|
||||
},
|
||||
{
|
||||
scanInfo: &ScanInfo{
|
||||
InputPatterns: []string{
|
||||
"./testdata/any_file_for_test.json",
|
||||
},
|
||||
},
|
||||
Control: reporthandling.Control{
|
||||
ScanningScope: &reporthandling.ScanningScope{
|
||||
|
||||
Matches: []reporthandling.ScanningScopeType{
|
||||
reporthandling.ScopeCloudGKE,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected_res: false,
|
||||
},
|
||||
{
|
||||
scanInfo: &ScanInfo{},
|
||||
Control: reporthandling.Control{
|
||||
ScanningScope: &reporthandling.ScanningScope{
|
||||
Matches: []reporthandling.ScanningScopeType{
|
||||
reporthandling.ScopeCloudEKS,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected_res: false,
|
||||
},
|
||||
{
|
||||
scanInfo: &ScanInfo{},
|
||||
Control: reporthandling.Control{
|
||||
ScanningScope: &reporthandling.ScanningScope{
|
||||
Matches: []reporthandling.ScanningScopeType{
|
||||
reporthandling.ScopeCloud,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected_res: false,
|
||||
}}
|
||||
for i := range tests {
|
||||
assert.Equal(t, tests[i].expected_res, isControlFitToScanScope(tests[i].Control, GetScanningScope(tests[i].scanInfo)), fmt.Sprintf("tests_true index %d", i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsScanningScopeMatchToControlScope(t *testing.T) {
|
||||
tests := []struct {
|
||||
scanScope reporthandling.ScanningScopeType
|
||||
@@ -332,3 +239,59 @@ func TestIsFrameworkFitToScanScope(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var rule_v1_0_131 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
|
||||
Attributes: map[string]interface{}{"useUntilKubescapeVersion": "v1.0.132"}}}
|
||||
var rule_v1_0_132 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
|
||||
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.132", "useUntilKubescapeVersion": "v1.0.133"}}}
|
||||
var rule_v1_0_133 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
|
||||
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.133", "useUntilKubescapeVersion": "v1.0.134"}}}
|
||||
var rule_v1_0_134 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
|
||||
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.134"}}}
|
||||
var rule_invalid_from = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
|
||||
Attributes: map[string]interface{}{"useFromKubescapeVersion": 1.0135, "useUntilKubescapeVersion": "v1.0.135"}}}
|
||||
var rule_invalid_until = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
|
||||
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.135", "useUntilKubescapeVersion": 1.0135}}}
|
||||
|
||||
func TestIsRuleKubescapeVersionCompatible(t *testing.T) {
|
||||
// local build- no build number
|
||||
|
||||
// should not crash when the value of useUntilKubescapeVersion is not a string
|
||||
buildNumberMock := "v1.0.135"
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_invalid_from.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_invalid_until.Attributes, buildNumberMock))
|
||||
// should use only rules that don't have "until"
|
||||
buildNumberMock = ""
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_131.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_132.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_133.Attributes, buildNumberMock))
|
||||
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
|
||||
|
||||
// should only use rules that version is in range of use
|
||||
buildNumberMock = "v1.0.130"
|
||||
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_131.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_132.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_133.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
|
||||
|
||||
// should only use rules that version is in range of use
|
||||
buildNumberMock = "v1.0.132"
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_131.Attributes, buildNumberMock))
|
||||
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_132.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_133.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
|
||||
|
||||
// should only use rules that version is in range of use
|
||||
buildNumberMock = "v1.0.133"
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_131.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_132.Attributes, buildNumberMock))
|
||||
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_133.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
|
||||
|
||||
// should only use rules that version is in range of use
|
||||
buildNumberMock = "v1.0.135"
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_131.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_132.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_133.Attributes, buildNumberMock))
|
||||
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
spinnerpkg "github.com/briandowns/spinner"
|
||||
"github.com/jwalton/gchalk"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
@@ -27,7 +28,7 @@ func FailureTextDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
}
|
||||
|
||||
func InfoDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
fmt.Fprintf(w, gchalk.WithCyan().Bold(format), a...)
|
||||
fmt.Fprintf(w, gchalk.WithBrightWhite().Bold(format), a...)
|
||||
}
|
||||
|
||||
func InfoTextDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
@@ -50,6 +51,20 @@ func BoldDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
fmt.Fprintf(w, gchalk.Bold(format), a...)
|
||||
}
|
||||
|
||||
func LineDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
fmt.Fprintf(w, gchalk.WithAnsi256(238).Bold(format), a...)
|
||||
}
|
||||
|
||||
func SectionHeadingDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
fmt.Fprintf(w, "\n"+
|
||||
gchalk.WithBrightWhite().Bold(format)+
|
||||
gchalk.WithAnsi256(238).Bold(fmt.Sprintf("\n%s\n\n", strings.Repeat("─", len(format)))), a...)
|
||||
}
|
||||
|
||||
func StarDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
fmt.Fprintf(w, gchalk.WithAnsi256(238).Bold("* ")+gchalk.White(format), a...)
|
||||
}
|
||||
|
||||
var spinner *spinnerpkg.Spinner
|
||||
|
||||
func StartSpinner() {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStartSpinner(t *testing.T) {
|
||||
@@ -30,3 +33,422 @@ func TestStartSpinner(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailureDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
FailureDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarningDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
WarningDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailureTextDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
FailureTextDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfoDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
InfoDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfoTextDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
InfoTextDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
SimpleDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSuccessDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
SuccessDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescriptionDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
DescriptionDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoldDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
BoldDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLineDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
LineDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSectionHeadingDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test Section",
|
||||
want: "\nTest Section\n────────────\n\n",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "\n\n\n\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
SectionHeadingDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "* Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "* ",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
StarDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a new instance of ProgressHandler with the given title.
|
||||
func TestNewProgressHandler_(t *testing.T) {
|
||||
tests := []struct {
|
||||
title string
|
||||
}{
|
||||
{
|
||||
title: "Test title",
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.title, func(t *testing.T) {
|
||||
progressHandler := NewProgressHandler(tt.title)
|
||||
assert.NotNil(t, progressHandler)
|
||||
|
||||
assert.Equal(t, tt.title, progressHandler.title)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user