mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
Compare commits
875 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ae2d3646b | ||
|
|
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 | ||
|
|
e4110837c7 | ||
|
|
33452517fe | ||
|
|
df602af7cf | ||
|
|
bc327a0d86 | ||
|
|
77888c12a0 | ||
|
|
df56af843e | ||
|
|
1f6ffdfd24 | ||
|
|
6a0a7b84a2 | ||
|
|
f4bb8485cd | ||
|
|
33ec257aa8 | ||
|
|
f31304db7e | ||
|
|
b98d80a912 | ||
|
|
ae7b25a9ae | ||
|
|
4f2d13b151 | ||
|
|
b6b4d6bb46 | ||
|
|
de76c98231 | ||
|
|
0b7d8cd45e | ||
|
|
48c86037fd | ||
|
|
69b28823d9 | ||
|
|
554286e803 | ||
|
|
971e775476 | ||
|
|
95133af9f4 | ||
|
|
3efa40e808 | ||
|
|
a5d1fa3f66 | ||
|
|
3d71246580 | ||
|
|
31a0bd9266 | ||
|
|
143a4d9818 | ||
|
|
0d889bf454 | ||
|
|
f7a5f76285 | ||
|
|
1e527b4174 | ||
|
|
5bf1d6b3c4 | ||
|
|
f622bd0a0e | ||
|
|
f70cf68e4d | ||
|
|
accc8a3834 | ||
|
|
83b6686496 | ||
|
|
934f72203e | ||
|
|
506da9dc22 | ||
|
|
2cfd4f3b31 | ||
|
|
7aa7a9bbda | ||
|
|
5968f97583 | ||
|
|
6070f0f126 | ||
|
|
a52ca0e47d | ||
|
|
2da0293ee2 | ||
|
|
ed1219baf1 | ||
|
|
f073ce0f42 | ||
|
|
9e6efd04ce | ||
|
|
8908a4e8cf | ||
|
|
425f278300 | ||
|
|
5b1aa1501f | ||
|
|
9bb2136ada | ||
|
|
bbabd5373a | ||
|
|
24c48ab58f | ||
|
|
fd6347fac2 | ||
|
|
269945c08c | ||
|
|
10c7c428e4 | ||
|
|
34f0b64946 | ||
|
|
884af50c0b | ||
|
|
e97103494f | ||
|
|
3e7a6b516b | ||
|
|
8257e31232 | ||
|
|
bfc2304a95 | ||
|
|
96337edc67 | ||
|
|
a3f80d91bf | ||
|
|
69c84cdf56 | ||
|
|
a6cca30eb0 | ||
|
|
c74c5f1970 | ||
|
|
e611cec238 | ||
|
|
4372ca320a | ||
|
|
c490dcc9cb | ||
|
|
c914ab1034 | ||
|
|
b39ce4caae | ||
|
|
076aa7f8fe | ||
|
|
df035ea5fc | ||
|
|
58553688e9 | ||
|
|
776173653d | ||
|
|
26c47d501c | ||
|
|
6a8a338945 | ||
|
|
53f23b663b | ||
|
|
592e0e2b43 | ||
|
|
92449bf564 | ||
|
|
8d1547163b | ||
|
|
d16abf376d | ||
|
|
150967eae8 | ||
|
|
150dc61ec7 | ||
|
|
7b46cdd480 | ||
|
|
b67fd95e31 | ||
|
|
f7b3cdcf35 | ||
|
|
d6a47a82d2 | ||
|
|
936cb26c06 | ||
|
|
9265a5d6d0 | ||
|
|
e6f5c7e0dd | ||
|
|
4e48148d40 | ||
|
|
3648ef286d | ||
|
|
d946662e57 | ||
|
|
51b37d5cbf | ||
|
|
9afae713ba | ||
|
|
1d64522607 | ||
|
|
225a923006 | ||
|
|
6c1a3fb89b | ||
|
|
df5f7db51d | ||
|
|
35c593a624 | ||
|
|
869f0ea109 | ||
|
|
cf08daf7fb | ||
|
|
266029eb23 | ||
|
|
4c9fec8ef4 | ||
|
|
b88e4f6169 | ||
|
|
6f07e63d3f | ||
|
|
addd66bf72 | ||
|
|
e2f96200e0 | ||
|
|
f799b63684 | ||
|
|
a088219954 | ||
|
|
1a2e16b895 | ||
|
|
7444acae11 | ||
|
|
8294694e09 | ||
|
|
12d7f18b79 | ||
|
|
83279484bd | ||
|
|
ba134ebc32 | ||
|
|
b44f0a76c9 | ||
|
|
226b4772a2 | ||
|
|
5379b9b0a6 | ||
|
|
98f68d8097 | ||
|
|
f8057b5c79 | ||
|
|
f36d8c31b0 | ||
|
|
3abf18acb7 | ||
|
|
28200b2744 | ||
|
|
678f21e33c | ||
|
|
467a84ddac | ||
|
|
925145724e | ||
|
|
e3677fc45c | ||
|
|
704de5bfc1 | ||
|
|
2494c1971c | ||
|
|
3b8bd7735e | ||
|
|
602591e7f2 | ||
|
|
e276e54d2b | ||
|
|
0c019819ff | ||
|
|
d9e946cf6d | ||
|
|
e3a8ebfe05 | ||
|
|
fd3703b21b | ||
|
|
6bcdda7d56 | ||
|
|
981430d65f | ||
|
|
e91ec69832 | ||
|
|
bbfa5d356a | ||
|
|
d2af7f47db | ||
|
|
d28afcb00c | ||
|
|
ca6bdb0bef | ||
|
|
e424bfa81b | ||
|
|
9f1ff4c090 | ||
|
|
1a2dda700b | ||
|
|
c4e5611c7f | ||
|
|
d8e913fb9f | ||
|
|
a37b1f7319 | ||
|
|
b730ef5154 | ||
|
|
3280173e95 | ||
|
|
d0ae4f1c1a | ||
|
|
e4faad8284 | ||
|
|
bc131efd91 | ||
|
|
4763f0d69d | ||
|
|
22c412ce7f | ||
|
|
1503e984f8 | ||
|
|
a4478ba899 | ||
|
|
fcbcb53995 | ||
|
|
17c43fd366 | ||
|
|
d44746cb85 | ||
|
|
912035662b | ||
|
|
61dac76369 | ||
|
|
bacf15eeb8 | ||
|
|
0a5af235e3 | ||
|
|
6fec02caff | ||
|
|
067655d003 | ||
|
|
d55a74c6b2 | ||
|
|
e470fce6ed | ||
|
|
ea3172eda6 | ||
|
|
31c4badf1c | ||
|
|
f060d02fbc | ||
|
|
43975ddafe | ||
|
|
abe0477249 | ||
|
|
5f197eb27c | ||
|
|
84b43d2b03 | ||
|
|
b149e00d1a | ||
|
|
f98b394ec2 | ||
|
|
492b08c995 | ||
|
|
8fa15688fb | ||
|
|
1a3e140e56 | ||
|
|
72f6988bb4 | ||
|
|
780be45392 | ||
|
|
676771e8b3 | ||
|
|
06f5c24b7d | ||
|
|
c17415d6e9 | ||
|
|
b5bed7bfbb | ||
|
|
3c38021f7c | ||
|
|
8989cc1679 | ||
|
|
0ab9c32715 | ||
|
|
868db91801 | ||
|
|
aa0fe21a2e | ||
|
|
1b181a47ef | ||
|
|
30487dcd0e | ||
|
|
46ad069fe5 | ||
|
|
05d5de17d5 | ||
|
|
6bc79458b0 | ||
|
|
ab85ca2b28 | ||
|
|
99938ecbee | ||
|
|
e2f8e273ad | ||
|
|
be63e1ef7c | ||
|
|
5e5b9d564c | ||
|
|
8ee72895b9 | ||
|
|
6cefada215 | ||
|
|
211ee487b3 | ||
|
|
bbe46c9fab | ||
|
|
ce7fde582c | ||
|
|
1c31e1f015 | ||
|
|
9e2fe607d8 | ||
|
|
5a5ec9b641 | ||
|
|
24c608e204 | ||
|
|
ac43036b4a | ||
|
|
03b89047f8 | ||
|
|
07a5c6488b | ||
|
|
c486b4fed7 | ||
|
|
00c48d756d | ||
|
|
b49563ae8c | ||
|
|
7840ecb5da | ||
|
|
e151c5bf81 | ||
|
|
225545476c | ||
|
|
987f97102d | ||
|
|
7bffed2afe | ||
|
|
3357713903 | ||
|
|
efd48eab08 | ||
|
|
231d9c231a | ||
|
|
91e705a3eb | ||
|
|
a92d573cb8 | ||
|
|
e8c72b9883 | ||
|
|
d380b2cb00 | ||
|
|
50b3d0f313 | ||
|
|
474b6d07ed | ||
|
|
2cddc4b395 | ||
|
|
b5fb355a22 | ||
|
|
d1bc6d0190 | ||
|
|
0a0ef10d50 | ||
|
|
4523dc8456 | ||
|
|
b26f83d0bd | ||
|
|
9cc3053d74 | ||
|
|
84842a6a91 | ||
|
|
aff8cc480e | ||
|
|
7feea43421 | ||
|
|
04ec32c9f4 | ||
|
|
b805f22038 | ||
|
|
092f37a636 | ||
|
|
9a2eb46f65 | ||
|
|
c637c1a589 | ||
|
|
7609a4aa5d | ||
|
|
75d31c22d9 | ||
|
|
b93a97a8c8 | ||
|
|
88696ca233 | ||
|
|
87d94d16ff | ||
|
|
1843bcdaf8 | ||
|
|
cdaff7ddbe | ||
|
|
ec7bc26f64 | ||
|
|
75b64d58f3 | ||
|
|
dce1d762c6 | ||
|
|
f3225855d0 | ||
|
|
5ae421dbc2 | ||
|
|
d4b75dcb0c | ||
|
|
b7935276e3 | ||
|
|
d6edd818b8 | ||
|
|
a73081c816 | ||
|
|
dd961b9e55 | ||
|
|
76ced13a26 | ||
|
|
95e88f8581 | ||
|
|
5955247f01 | ||
|
|
c0530b4f88 | ||
|
|
c23d6a17cc | ||
|
|
d448de131f | ||
|
|
b48c04da63 | ||
|
|
ecf770c756 | ||
|
|
6e33f37aee | ||
|
|
03f792e968 | ||
|
|
b017d77b86 | ||
|
|
2cde591180 | ||
|
|
f25d573f32 | ||
|
|
ebf3e49f53 | ||
|
|
acaf6e78da | ||
|
|
344e9188f6 | ||
|
|
3f69f06df1 | ||
|
|
e0b296c124 | ||
|
|
108bbd8bc4 | ||
|
|
5c1a41e920 | ||
|
|
0b8d207615 | ||
|
|
539b6c51b9 | ||
|
|
19ca590e2f | ||
|
|
4de50f82c0 | ||
|
|
ab41d5dbf4 | ||
|
|
fa6de6dc3f | ||
|
|
96e959c3b7 | ||
|
|
28fdee0dd2 | ||
|
|
9ce25c45fe | ||
|
|
d44b9f7a31 | ||
|
|
c7af6266fd | ||
|
|
91c13381b2 | ||
|
|
30ad3adbb6 | ||
|
|
64e3b08641 | ||
|
|
6d7a89bb74 | ||
|
|
e8d92ffd43 | ||
|
|
48a15e1a8d | ||
|
|
d02f15ef6f | ||
|
|
9327f70e1a | ||
|
|
c498026208 | ||
|
|
3238555df3 | ||
|
|
0c77d89bfc | ||
|
|
875deb7ec3 | ||
|
|
eae234136b | ||
|
|
93a35fffbd | ||
|
|
9a3767ef72 | ||
|
|
9420fd5e79 |
9
.github/actions/tag-action/action.yaml
vendored
9
.github/actions/tag-action/action.yaml
vendored
@@ -17,7 +17,14 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- run: |
|
||||
SUB='-rc'
|
||||
if [[ -z "${{ inputs.ORIGINAL_TAG }}" ]]; then
|
||||
echo "The value of ORIGINAL_TAG is ${{ inputs.ORIGINAL_TAG }}"
|
||||
echo "Setting the value of ORIGINAL_TAG to ${{ github.ref_name }}"
|
||||
echo ORIGINAL_TAG="${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- run: |
|
||||
if [[ "${{ inputs.ORIGINAL_TAG }}" == *"${{ inputs.SUB_STRING }}"* ]]; then
|
||||
echo "Release candidate tag found."
|
||||
else
|
||||
|
||||
11
.github/dependabot.yaml
vendored
Normal file
11
.github/dependabot.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
50
.github/workflows/00-pr-scanner.yaml
vendored
50
.github/workflows/00-pr-scanner.yaml
vendored
@@ -1,13 +1,11 @@
|
||||
name: 00-pr_scanner
|
||||
permissions: read-all
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
branches:
|
||||
- 'master'
|
||||
- 'main'
|
||||
- 'dev'
|
||||
paths-ignore:
|
||||
- '**.yaml'
|
||||
- '**.yml'
|
||||
- '**.md'
|
||||
- '**.sh'
|
||||
- 'website/*'
|
||||
@@ -15,7 +13,7 @@ on:
|
||||
- 'docs/*'
|
||||
- 'build/*'
|
||||
- '.github/*'
|
||||
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -23,9 +21,51 @@ concurrency:
|
||||
jobs:
|
||||
pr-scanner:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: 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
|
||||
attestations: read
|
||||
contents: write
|
||||
uses: ./.github/workflows/a-pr-scanner.yaml
|
||||
with:
|
||||
RELEASE: ""
|
||||
CLIENT: test
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: ""
|
||||
secrets: inherit
|
||||
|
||||
binary-build:
|
||||
if: ${{ github.actor == 'kubescape' }}
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
contents: write
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
security-events: read
|
||||
statuses: read
|
||||
attestations: read
|
||||
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
|
||||
with:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.23"
|
||||
RELEASE: "latest"
|
||||
CLIENT: test
|
||||
secrets: inherit
|
||||
|
||||
34
.github/workflows/01-pr-merged.yaml
vendored
34
.github/workflows/01-pr-merged.yaml
vendored
@@ -1,34 +0,0 @@
|
||||
name: 01-pr-merged
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
branches:
|
||||
- 'master'
|
||||
- 'main'
|
||||
paths-ignore:
|
||||
- '**.yaml'
|
||||
- '**.md'
|
||||
- '**.sh'
|
||||
- 'website/*'
|
||||
- 'examples/*'
|
||||
- 'docs/*'
|
||||
- 'build/*'
|
||||
- '.github/*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
binary-build:
|
||||
if: ${{ github.event.pull_request.merged == true && contains( github.event.pull_request.labels.*.name, 'trigger-integration-test') && github.event.pull_request.base.ref == 'master' }} ## run only if labeled as "trigger-integration-test" and base branch is master
|
||||
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
|
||||
with:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 1
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.19"
|
||||
RELEASE: ""
|
||||
CLIENT: test
|
||||
secrets: inherit
|
||||
73
.github/workflows/02-release.yaml
vendored
73
.github/workflows/02-release.yaml
vendored
@@ -1,4 +1,5 @@
|
||||
name: 02-create_release
|
||||
permissions: read-all
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
@@ -7,27 +8,55 @@ jobs:
|
||||
retag:
|
||||
outputs:
|
||||
NEW_TAG: ${{ steps.tag-calculator.outputs.NEW_TAG }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu22-core4-mem16-ssd150
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- id: tag-calculator
|
||||
uses: ./.github/actions/tag-action
|
||||
with:
|
||||
SUB_STRING: "-rc"
|
||||
binary-build:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: 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
|
||||
contents: write
|
||||
attestations: write
|
||||
needs: [retag]
|
||||
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
|
||||
with:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 1
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.19"
|
||||
GO_VERSION: "1.23"
|
||||
RELEASE: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
CLIENT: release
|
||||
secrets: inherit
|
||||
create-release:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
contents: write
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: read
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
statuses: read
|
||||
security-events: read
|
||||
attestations: read
|
||||
needs: [retag, binary-build]
|
||||
uses: ./.github/workflows/c-create-release.yaml
|
||||
with:
|
||||
@@ -37,15 +66,47 @@ jobs:
|
||||
secrets: inherit
|
||||
publish-image:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: write
|
||||
contents: read
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
security-events: read
|
||||
statuses: read
|
||||
attestations: read
|
||||
contents: write
|
||||
uses: ./.github/workflows/d-publish-image.yaml
|
||||
needs: [create-release, retag]
|
||||
with:
|
||||
client: "image-release"
|
||||
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
|
||||
image_name: "quay.io/${{ github.repository_owner }}/kubescape-cli"
|
||||
image_tag: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
support_platforms: true
|
||||
cosign: true
|
||||
secrets: inherit
|
||||
post-release:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: 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
|
||||
attestations: read
|
||||
contents: write
|
||||
uses: ./.github/workflows/e-post-release.yaml
|
||||
needs: [publish-image]
|
||||
with:
|
||||
TAG: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
secrets: inherit
|
||||
|
||||
17
.github/workflows/03-post-release.yaml
vendored
17
.github/workflows/03-post-release.yaml
vendored
@@ -1,17 +0,0 @@
|
||||
name: 03-create_release_digests
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
branches:
|
||||
- 'master'
|
||||
- 'main'
|
||||
jobs:
|
||||
create_release_digests:
|
||||
name: Creating digests
|
||||
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
|
||||
16
.github/workflows/04-publish-krew-plugin.yaml
vendored
16
.github/workflows/04-publish-krew-plugin.yaml
vendored
@@ -1,16 +0,0 @@
|
||||
name: 04-publish_krew_plugin
|
||||
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
|
||||
186
.github/workflows/a-pr-scanner.yaml
vendored
186
.github/workflows/a-pr-scanner.yaml
vendored
@@ -1,4 +1,5 @@
|
||||
name: a-pr-scanner
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
@@ -10,23 +11,88 @@ on:
|
||||
description: 'Client name'
|
||||
required: true
|
||||
type: string
|
||||
UNIT_TESTS_PATH:
|
||||
required: false
|
||||
type: string
|
||||
default: "./..."
|
||||
GO111MODULE:
|
||||
required: true
|
||||
type: string
|
||||
CGO_ENABLED:
|
||||
type: number
|
||||
default: 1
|
||||
jobs:
|
||||
unit-tests:
|
||||
if: ${{ github.actor != 'kubescape' }}
|
||||
name: Create cross-platform build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
runs-on: ubuntu22-core4-mem16-ssd150
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
name: Installing go
|
||||
with:
|
||||
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.15.2
|
||||
name: Setup Syft
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v5
|
||||
name: Build
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean --snapshot
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
CLIENT: ${{ inputs.CLIENT }}
|
||||
CGO_ENABLED: ${{ inputs.CGO_ENABLED }}
|
||||
|
||||
- name: Smoke Testing
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ${PWD}/dist/kubescape-ubuntu-latest
|
||||
|
||||
- name: golangci-lint
|
||||
continue-on-error: false
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout 10m
|
||||
only-new-issues: true
|
||||
skip-pkg-cache: true
|
||||
skip-build-cache: true
|
||||
|
||||
scanners:
|
||||
env:
|
||||
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
name: PR Scanner
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu22-core4-mem16-ssd150
|
||||
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.19'
|
||||
cache: true
|
||||
go-version: "1.23"
|
||||
- name: Scanning - Forbidden Licenses (go-licenses)
|
||||
id: licenses-scan
|
||||
continue-on-error: true
|
||||
@@ -39,7 +105,7 @@ jobs:
|
||||
if: ${{ env.GITGUARDIAN_API_KEY }}
|
||||
continue-on-error: true
|
||||
id: credentials-scan
|
||||
uses: GitGuardian/ggshield-action@4ab2994172fadab959240525e6b833d9ae3aca61 # ratchet:GitGuardian/ggshield-action@master
|
||||
uses: GitGuardian/ggshield-action@master
|
||||
with:
|
||||
args: -v --all-policies
|
||||
env:
|
||||
@@ -52,14 +118,29 @@ jobs:
|
||||
if: ${{ env.SNYK_TOKEN }}
|
||||
id: vulnerabilities-scan
|
||||
continue-on-error: true
|
||||
uses: snyk/actions/golang@806182742461562b67788a64410098c9d9b96adb # ratchet:snyk/actions/golang@master
|
||||
uses: snyk/actions/golang@master
|
||||
with:
|
||||
command: test --all-projects
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
|
||||
- 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
|
||||
with:
|
||||
github-token: ${{ secrets.GH_PERSONAL_ACCESS_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
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
@@ -68,92 +149,3 @@ jobs:
|
||||
- Credentials scan: ${{ steps.credentials-scan.outcome }}
|
||||
- Vulnerabilities scan: ${{ steps.vulnerabilities-scan.outcome }}
|
||||
reactions: 'eyes'
|
||||
basic-tests:
|
||||
needs: scanners
|
||||
name: Create cross-platform build
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
CLIENT: ${{ inputs.CLIENT }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Cache Go modules (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
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-
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # ratchet:actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
- name: Install MSYS2 & libgit2 (Windows)
|
||||
shell: cmd
|
||||
run: .\build.bat all
|
||||
if: matrix.os == 'windows-latest'
|
||||
- name: Install pkg-config (macOS)
|
||||
run: brew install pkg-config
|
||||
if: matrix.os == 'macos-latest'
|
||||
- name: Install libgit2 (Linux/macOS)
|
||||
run: make libgit2
|
||||
if: matrix.os != 'windows-latest'
|
||||
- name: Test core pkg
|
||||
run: go test "-tags=static,gitenabled" -v ./...
|
||||
- name: Test httphandler pkg
|
||||
run: cd httphandler && go test "-tags=static,gitenabled" -v ./...
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
CLIENT: ${{ inputs.CLIENT }}
|
||||
CGO_ENABLED: 1
|
||||
run: python3 --version && 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: matrix.os != 'ubuntu-20.04'
|
||||
- name: Smoke Testing (Linux)
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/kubescape-ubuntu-latest
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
- 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
|
||||
|
||||
294
.github/workflows/b-binary-build-and-e2e-tests.yaml
vendored
294
.github/workflows/b-binary-build-and-e2e-tests.yaml
vendored
@@ -1,5 +1,62 @@
|
||||
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.23"
|
||||
GO111MODULE:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
CGO_ENABLED:
|
||||
type: number
|
||||
default: 1
|
||||
required: false
|
||||
BINARY_TESTS:
|
||||
type: string
|
||||
required: false
|
||||
default: '[
|
||||
"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_nsa",
|
||||
"scan_mitre",
|
||||
"scan_with_exceptions",
|
||||
"scan_repository",
|
||||
"scan_local_file",
|
||||
"scan_local_glob_files",
|
||||
"scan_local_list_of_files",
|
||||
"scan_with_exception_to_backend",
|
||||
"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_custom_framework",
|
||||
"scan_customer_configuration",
|
||||
"scan_compliance_score"
|
||||
]'
|
||||
|
||||
workflow_call:
|
||||
inputs:
|
||||
COMPONENT_NAME:
|
||||
@@ -13,7 +70,7 @@ on:
|
||||
type: string
|
||||
GO_VERSION:
|
||||
type: string
|
||||
default: "1.19"
|
||||
default: "1.23"
|
||||
GO111MODULE:
|
||||
required: true
|
||||
type: string
|
||||
@@ -22,12 +79,25 @@ on:
|
||||
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" ]'
|
||||
CHECKOUT_REPO:
|
||||
required: false
|
||||
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_custom_framework",
|
||||
"scan_customer_configuration",
|
||||
"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"
|
||||
]'
|
||||
|
||||
jobs:
|
||||
wf-preparation:
|
||||
@@ -36,6 +106,7 @@ jobs:
|
||||
outputs:
|
||||
TEST_NAMES: ${{ steps.export_tests_to_env.outputs.TEST_NAMES }}
|
||||
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
|
||||
@@ -47,7 +118,7 @@ jobs:
|
||||
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"
|
||||
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"
|
||||
|
||||
- id: export_tests_to_env
|
||||
name: set test name
|
||||
@@ -55,127 +126,159 @@ jobs:
|
||||
echo "TEST_NAMES=$input" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
input: ${{ inputs.BINARY_TESTS }}
|
||||
|
||||
|
||||
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 }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
runs-on: ubuntu-large
|
||||
steps:
|
||||
- name: (debug) Step 1 - Check disk space before checkout
|
||||
run: df -h
|
||||
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{inputs.CHECKOUT_REPO}}
|
||||
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: (debug) Step 2 - Check disk space before installing Go
|
||||
run: df -h
|
||||
|
||||
- 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
|
||||
- uses: actions/setup-go@v4
|
||||
name: Installing go
|
||||
with:
|
||||
go-version: ${{ inputs.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Install MSYS2 & libgit2 (Windows)
|
||||
shell: cmd
|
||||
run: .\build.bat all
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
- name: Install pkg-config (macOS)
|
||||
run: brew install pkg-config
|
||||
if: matrix.os == 'macos-latest'
|
||||
|
||||
- name: Install libgit2 (Linux/macOS)
|
||||
run: make libgit2
|
||||
if: matrix.os != 'windows-latest'
|
||||
- name: (debug) Step 3 - Check disk space before build
|
||||
run: df -h
|
||||
|
||||
- name: Test core pkg
|
||||
run: go test "-tags=static,gitenabled" -v ./...
|
||||
run: ${{ env.DOCKER_CMD }} go test -v ./...
|
||||
if: startsWith(github.ref, 'refs/tags')
|
||||
|
||||
- name: (debug) Step 4 - Check disk space before testing httphandler pkg
|
||||
run: df -h
|
||||
|
||||
- name: Test httphandler pkg
|
||||
run: cd httphandler && go test "-tags=static,gitenabled" -v ./...
|
||||
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test -v ./...'
|
||||
if: startsWith(github.ref, 'refs/tags')
|
||||
|
||||
- name: Build
|
||||
- name: (debug) Step 5 - Check disk space before setting up Syft
|
||||
run: df -h
|
||||
|
||||
- uses: anchore/sbom-action/download-syft@v0
|
||||
name: Setup Syft
|
||||
|
||||
- name: (debug) Step 6 - Check disk space before goreleaser
|
||||
run: df -h
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v5
|
||||
name: Build
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean --snapshot
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
CLIENT: ${{ inputs.CLIENT }}
|
||||
CGO_ENABLED: ${{ inputs.CGO_ENABLED }}
|
||||
run: python3 --version && python3 build.py
|
||||
|
||||
- name: Smoke Testing (Windows / MacOS)
|
||||
- name: (debug) Step 7 - Check disk space before smoke testing
|
||||
run: df -h
|
||||
|
||||
- name: Smoke Testing
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/kubescape-${{ matrix.os }}
|
||||
if: matrix.os != 'ubuntu-20.04'
|
||||
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ${PWD}/dist/kubescape-ubuntu-latest
|
||||
|
||||
- name: Smoke Testing (Linux)
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/kubescape-ubuntu-latest
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
- name: (debug) Step 8 - Check disk space before golangci-lint
|
||||
run: df -h
|
||||
|
||||
- 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
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout 10m --build-tags=static
|
||||
args: --timeout 10m
|
||||
only-new-issues: true
|
||||
skip-pkg-cache: true
|
||||
skip-build-cache: true
|
||||
|
||||
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # ratchet:actions/upload-artifact@v3.1.1
|
||||
name: Upload artifact (Linux)
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
- name: (debug) Step 9 - Check disk space before uploading artifacts
|
||||
run: df -h
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
name: Upload artifacts
|
||||
with:
|
||||
name: kubescape-ubuntu-latest
|
||||
path: build/
|
||||
name: kubescape
|
||||
path: dist/kubescape*
|
||||
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.os }}
|
||||
path: build/
|
||||
if-no-files-found: error
|
||||
- name: (debug) Step 10 - Check disk space after uploading artifacts
|
||||
run: df -h
|
||||
|
||||
build-http-image:
|
||||
permissions:
|
||||
contents: write
|
||||
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.23"
|
||||
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_nsa",
|
||||
"scan_mitre",
|
||||
"scan_with_exceptions",
|
||||
"scan_repository",
|
||||
"scan_local_file",
|
||||
"scan_local_glob_files",
|
||||
"scan_local_list_of_files",
|
||||
"scan_with_exception_to_backend",
|
||||
"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_custom_framework",
|
||||
"scan_customer_configuration",
|
||||
"scan_compliance_score"
|
||||
]'
|
||||
COSIGN: true
|
||||
HELM_E2E_TEST: true
|
||||
FORCE: true
|
||||
secrets: inherit
|
||||
|
||||
run-tests:
|
||||
strategy:
|
||||
@@ -186,10 +289,10 @@ jobs:
|
||||
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
|
||||
- uses: actions/download-artifact@v4
|
||||
id: download-artifact
|
||||
with:
|
||||
name: kubescape-ubuntu-latest
|
||||
name: kubescape
|
||||
path: "~"
|
||||
|
||||
- run: ls -laR
|
||||
@@ -198,12 +301,12 @@ jobs:
|
||||
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
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: armosec/system-tests
|
||||
path: .
|
||||
|
||||
- uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # ratchet:actions/setup-python@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.8.13'
|
||||
cache: 'pip'
|
||||
@@ -218,7 +321,7 @@ jobs:
|
||||
|
||||
- name: Create k8s Kind Cluster
|
||||
id: kind-cluster-install
|
||||
uses: helm/kind-action@d08cf6ff1575077dee99962540d77ce91c62387d # ratchet:helm/kind-action@v1.3.0
|
||||
uses: helm/kind-action@v1.10.0
|
||||
with:
|
||||
cluster_name: ${{ steps.uuid.outputs.RANDOM_UUID }}
|
||||
|
||||
@@ -248,8 +351,9 @@ jobs:
|
||||
deactivate
|
||||
|
||||
- name: Test Report
|
||||
uses: mikepenz/action-junit-report@6e9933f4a97f4d2b99acef4d7b97924466037882 # ratchet:mikepenz/action-junit-report@v3.6.1
|
||||
uses: mikepenz/action-junit-report@v5
|
||||
if: always() # always run even if the previous step fails
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
report_paths: '**/results_xml_format/**.xml'
|
||||
commit: ${{github.event.workflow_run.head_sha}}
|
||||
|
||||
41
.github/workflows/build-image.yaml
vendored
Normal file
41
.github/workflows/build-image.yaml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
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: write
|
||||
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.23"
|
||||
REQUIRED_TESTS: '[]'
|
||||
COSIGN: ${{ inputs.CO_SIGN }}
|
||||
HELM_E2E_TEST: false
|
||||
FORCE: true
|
||||
secrets: inherit
|
||||
75
.github/workflows/c-create-release.yaml
vendored
75
.github/workflows/c-create-release.yaml
vendored
@@ -1,4 +1,5 @@
|
||||
name: c-create_release
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
@@ -19,41 +20,73 @@ jobs:
|
||||
create-release:
|
||||
name: create-release
|
||||
runs-on: ubuntu-latest
|
||||
# permissions:
|
||||
# contents: write
|
||||
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
|
||||
- uses: actions/download-artifact@v4
|
||||
id: download-artifact
|
||||
with:
|
||||
name: kubescape
|
||||
path: .
|
||||
|
||||
# TODO: kubescape-windows-latest is deprecated and should be removed
|
||||
- name: Get kubescape.exe from kubescape-windows-latest.exe
|
||||
run: cp ${{steps.download-artifact.outputs.download-path}}/kubescape-${{ env.WINDOWS_OS }}.exe ${{steps.download-artifact.outputs.download-path}}/kubescape.exe
|
||||
|
||||
- name: Set release token
|
||||
id: set-token
|
||||
run: |
|
||||
if [ "${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" != "" ]; then
|
||||
echo "TOKEN=${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" >> $GITHUB_ENV;
|
||||
echo "token=${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" >> $GITHUB_OUTPUT;
|
||||
else
|
||||
echo "TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV;
|
||||
echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT;
|
||||
fi
|
||||
|
||||
- name: List artifacts
|
||||
run: |
|
||||
find . -type f -print
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # ratchet:softprops/action-gh-release@v1
|
||||
env:
|
||||
MAC_OS: macos-latest
|
||||
UBUNTU_OS: ubuntu-latest
|
||||
WINDOWS_OS: windows-latest
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
token: ${{ env.TOKEN }}
|
||||
token: ${{ steps.set-token.outputs.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
|
||||
fail_on_unmatched_files: true
|
||||
files: |
|
||||
./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-${{ env.WINDOWS_OS }}
|
||||
./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }}.sha256
|
||||
./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }}.tar.gz
|
||||
./kubescape-${{ env.MAC_OS }}
|
||||
./kubescape-${{ env.MAC_OS }}.sbom
|
||||
./kubescape-${{ env.MAC_OS }}.sha256
|
||||
./kubescape-${{ env.MAC_OS }}.tar.gz
|
||||
./kubescape-${{ env.UBUNTU_OS }}
|
||||
./kubescape-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape-${{ env.UBUNTU_OS }}.sha256
|
||||
./kubescape-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape-${{ env.WINDOWS_OS }}.exe
|
||||
./kubescape-${{ env.WINDOWS_OS }}.exe.sbom
|
||||
./kubescape-${{ env.WINDOWS_OS }}.exe.sha256
|
||||
./kubescape-${{ env.WINDOWS_OS }}.tar.gz
|
||||
./kubescape-arm64-${{ env.MAC_OS }}
|
||||
./kubescape-arm64-${{ env.MAC_OS }}.sbom
|
||||
./kubescape-arm64-${{ env.MAC_OS }}.sha256
|
||||
./kubescape-arm64-${{ env.MAC_OS }}.tar.gz
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}.sha256
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape-arm64-${{ env.WINDOWS_OS }}.exe
|
||||
./kubescape-arm64-${{ env.WINDOWS_OS }}.exe.sbom
|
||||
./kubescape-arm64-${{ env.WINDOWS_OS }}.exe.sha256
|
||||
./kubescape-arm64-${{ env.WINDOWS_OS }}.tar.gz
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}.sha256
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape.exe
|
||||
|
||||
20
.github/workflows/comments.yaml
vendored
Normal file
20
.github/workflows/comments.yaml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: pr-agent
|
||||
permissions: read-all
|
||||
on:
|
||||
issue_comment:
|
||||
|
||||
jobs:
|
||||
pr_agent:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
name: Run pr agent on every pull request, respond to user comments
|
||||
steps:
|
||||
- name: PR Agent action step
|
||||
continue-on-error: true
|
||||
id: pragent
|
||||
uses: Codium-ai/pr-agent@main
|
||||
env:
|
||||
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
61
.github/workflows/d-publish-image.yaml
vendored
61
.github/workflows/d-publish-image.yaml
vendored
@@ -1,4 +1,18 @@
|
||||
name: d-publish-image
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
contents: write
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: read
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
statuses: read
|
||||
security-events: read
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
@@ -38,37 +52,56 @@ jobs:
|
||||
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
|
||||
build-image:
|
||||
|
||||
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
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # ratchet:docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # ratchet:docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- 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
|
||||
- name: Build and push image
|
||||
if: ${{ inputs.support_platforms }}
|
||||
run: docker buildx build . --file build/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 }} --push --platform linux/amd64,linux/arm64
|
||||
- name: Build and push image without amd64/arm64 support
|
||||
if: ${{ !inputs.support_platforms }}
|
||||
run: docker buildx build . --file build/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 }} --push
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@4079ad3567a89f68395480299c77e40170430341 # ratchet:sigstore/cosign-installer@main
|
||||
- uses: actions/download-artifact@v4
|
||||
id: download-artifact
|
||||
with:
|
||||
cosign-release: 'v1.12.0'
|
||||
name: kubescape
|
||||
path: .
|
||||
- name: mv kubescape amd64 binary
|
||||
run: mv kubescape-ubuntu-latest kubescape-amd64-ubuntu-latest
|
||||
- name: chmod +x
|
||||
run: chmod +x -v kubescape-a*
|
||||
- name: Build and push images
|
||||
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 }} --push --platform linux/amd64,linux/arm64
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@main
|
||||
with:
|
||||
cosign-release: 'v2.2.2'
|
||||
- name: sign kubescape container image
|
||||
if: ${{ inputs.cosign }}
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: "true"
|
||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY_V1 }}
|
||||
COSIGN_PRIVATE_KEY_PASSWORD: ${{ secrets.COSIGN_PRIVATE_KEY_V1_PASSWORD }}
|
||||
COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY_V1 }}
|
||||
run: |
|
||||
cosign sign --force ${{ inputs.image_name }}
|
||||
# Sign the image with keyless mode
|
||||
cosign sign -y ${{ inputs.image_name }}:${{ inputs.image_tag }}
|
||||
|
||||
# Sign the image with key for verifier clients without keyless support
|
||||
# Put the key from environment variable to a file
|
||||
echo "$COSIGN_PRIVATE_KEY" > cosign.key
|
||||
printf "$COSIGN_PRIVATE_KEY_PASSWORD" | cosign sign -key cosign.key -y ${{ inputs.image_name }}:${{ inputs.image_tag }}
|
||||
rm cosign.key
|
||||
# Verify the image
|
||||
echo "$COSIGN_PUBLIC_KEY" > cosign.pub
|
||||
cosign verify -key cosign.pub ${{ inputs.image_name }}:${{ inputs.image_tag }}
|
||||
|
||||
46
.github/workflows/e-post-release.yaml
vendored
Normal file
46
.github/workflows/e-post-release.yaml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: e-post_release
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
TAG:
|
||||
description: 'Tag name'
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
post_release:
|
||||
name: Post release jobs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update new version in krew-index
|
||||
uses: rajatjindal/krew-release-bot@v0.0.47
|
||||
if: github.repository_owner == 'kubescape'
|
||||
env:
|
||||
GITHUB_REF: ${{ inputs.TAG }}
|
||||
- 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 }}
|
||||
72
.github/workflows/scorecard.yml
vendored
Normal file
72
.github/workflows/scorecard.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
# This workflow uses actions that are not certified by GitHub. They are provided
|
||||
# by a third-party and are governed by separate terms of service, privacy
|
||||
# policy, and support documentation.
|
||||
|
||||
name: Scorecard supply-chain security
|
||||
on:
|
||||
# For Branch-Protection check. Only the default branch is supported. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
||||
branch_protection_rule:
|
||||
# To guarantee Maintained check is occasionally updated. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||
schedule:
|
||||
- cron: '0 00 * * 1'
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
# Needed to publish results and get a badge (see publish_results below).
|
||||
id-token: write
|
||||
# Uncomment the permissions below if installing in a private repository.
|
||||
# contents: read
|
||||
# actions: read
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@v2.4.0
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||
# - you want to enable the Branch-Protection check on a *public* repository, or
|
||||
# - you are installing Scorecard on a *private* repository
|
||||
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
|
||||
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
||||
|
||||
# Public repositories:
|
||||
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||
# - Allows the repository to include the Scorecard badge.
|
||||
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||
# For private repositories:
|
||||
# - `publish_results` will always be set to `false`, regardless
|
||||
# of the value entered here.
|
||||
publish_results: true
|
||||
|
||||
# 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@v4
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
5
.github/workflows/z-close-typos-issues.yaml
vendored
5
.github/workflows/z-close-typos-issues.yaml
vendored
@@ -1,3 +1,4 @@
|
||||
permissions: read-all
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
@@ -6,14 +7,14 @@ jobs:
|
||||
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
|
||||
- uses: 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
|
||||
- uses: lee-dohm/close-matching-issues@v2
|
||||
with:
|
||||
query: 'label:typo'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
*.vs*
|
||||
*kubescape*
|
||||
!*Dockerfile*
|
||||
*debug*
|
||||
*vendor*
|
||||
*.pyc*
|
||||
@@ -7,4 +8,6 @@
|
||||
.history
|
||||
ca.srl
|
||||
*.out
|
||||
ks
|
||||
ks
|
||||
|
||||
dist/
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "git2go"]
|
||||
path = git2go
|
||||
url = https://github.com/libgit2/git2go.git
|
||||
@@ -1,6 +1,6 @@
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
shadow: true
|
||||
dupl:
|
||||
threshold: 200
|
||||
goconst:
|
||||
@@ -24,7 +24,6 @@ linters:
|
||||
- gosimple
|
||||
disable:
|
||||
# temporarily disabled
|
||||
- varcheck
|
||||
- errcheck
|
||||
- dupl
|
||||
- gocritic
|
||||
@@ -36,8 +35,6 @@ linters:
|
||||
- unparam
|
||||
#- forbidigo # <- see later
|
||||
# should remain disabled
|
||||
- deadcode # deprecated linter
|
||||
- maligned
|
||||
- lll
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
@@ -52,6 +49,3 @@ issues:
|
||||
- linters:
|
||||
- stylecheck
|
||||
text: "ST1003"
|
||||
run:
|
||||
skip-dirs:
|
||||
- git2go
|
||||
|
||||
60
.goreleaser.yaml
Normal file
60
.goreleaser.yaml
Normal file
@@ -0,0 +1,60 @@
|
||||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
|
||||
# The lines bellow 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
|
||||
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- go mod tidy
|
||||
|
||||
builds:
|
||||
- goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- riscv64
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -X "github.com/kubescape/kubescape/v3/core/cautils.BuildNumber={{.Env.RELEASE}}"
|
||||
- -X "github.com/kubescape/kubescape/v3/core/cautils.Client={{.Env.CLIENT}}"
|
||||
binary: >-
|
||||
{{ .ProjectName }}-
|
||||
{{- if eq .Arch "amd64" }}
|
||||
{{- else }}{{ .Arch }}-{{ end }}
|
||||
{{- if eq .Os "darwin" }}macos
|
||||
{{- else if eq .Os "linux" }}ubuntu
|
||||
{{- else }}{{ .Os }}{{ end }}-latest
|
||||
no_unique_dist_dir: true
|
||||
|
||||
archives:
|
||||
- format: binary
|
||||
id: binaries
|
||||
name_template: >-
|
||||
{{ .Binary }}
|
||||
- format: tar.gz
|
||||
name_template: >-
|
||||
{{ .Binary }}
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
|
||||
checksum:
|
||||
ids:
|
||||
- binaries
|
||||
split: true
|
||||
|
||||
sboms:
|
||||
- artifacts: binary
|
||||
documents:
|
||||
- "{{ .Binary }}.sbom"
|
||||
12
.krew.yaml
12
.krew.yaml
@@ -16,12 +16,24 @@ spec:
|
||||
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 }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: arm64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-arm64-ubuntu-latest.tar.gz" .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: windows
|
||||
|
||||
5
ADOPTERS.md
Normal file
5
ADOPTERS.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Adopters
|
||||
|
||||
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,94 +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.
|
||||
|
||||
## 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)
|
||||
|
||||
5
GOVERNANCE.md
Normal file
5
GOVERNANCE.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Governance
|
||||
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized GOVERNANCE.md](https://github.com/kubescape/project-governance/blob/main/GOVERNANCE.md)
|
||||
@@ -1,11 +1,5 @@
|
||||
# Maintainers
|
||||
|
||||
The following table lists the Kubescape project maintainers:
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
| Name | GitHub | Organization | Added/Renewed On |
|
||||
| --- | --- | --- | --- |
|
||||
| [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 |
|
||||
| [Bezalel Brandwine](https://www.linkedin.com/in/bezalel-brandwine) | [@Bezbran](https://github.com/Bezbran) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
|
||||
| [Craig Box](https://www.linkedin.com/in/crbnz/) | [@craigbox](https://github.com/craigbox) | [ARMO](https://www.armosec.io/) | 2022-10-31 |
|
||||
Go to the [centralized MAINTAINERS.md](https://github.com/kubescape/project-governance/blob/main/MAINTAINERS.md)
|
||||
|
||||
18
Makefile
18
Makefile
@@ -1,20 +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
|
||||
|
||||
# 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 ./...
|
||||
|
||||
79
README.md
79
README.md
@@ -3,8 +3,14 @@
|
||||
[](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://github.com/kubescape/kubescape/stargazers)
|
||||
[](https://twitter.com/kubescape)
|
||||
[](https://cloud-native.slack.com/archives/C04EY3ZF9GE)
|
||||
|
||||
# Kubescape
|
||||
|
||||
@@ -14,19 +20,22 @@
|
||||
<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. It saves Kubernetes users and admins precious time, effort, and resources.
|
||||
|
||||
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 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.armosec.io/glossary/mitre-attck-framework/?utm_source=github&utm_medium=repository) 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) sandbox project](https://www.cncf.io/sandbox-projects/).
|
||||
|
||||
## Demo
|
||||
<img src="docs/img/demo.gif">
|
||||
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/).
|
||||
|
||||
_Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape! 😀_
|
||||
|
||||
## Demo
|
||||
|
||||
Kubescape has a command line tool that you can use to quickly get a report on the security posture of a Kubernetes cluster:
|
||||
|
||||
<img src="docs/img/demo-v3.gif">
|
||||
|
||||
## Getting started
|
||||
|
||||
Experimenting with Kubescape is as easy as:
|
||||
@@ -35,13 +44,13 @@ Experimenting with Kubescape is as easy as:
|
||||
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
|
||||
```
|
||||
|
||||
This script will automatically download the latest Kubescape CLI release and scan the Kubernetes cluster in your current kubectl context.
|
||||
|
||||
Learn more about:
|
||||
|
||||
* [Installing Kubescape](docs/getting-started.md#install-kubescape)
|
||||
* [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](docs/building.md)
|
||||
* [Installing the Kubescape CLI](https://kubescape.io/docs/install-cli/)
|
||||
* [Running your first scan](https://kubescape.io/docs/scanning/)
|
||||
* [Accepting risk with exceptions](https://kubescape.io/docs/accepting-risk/)
|
||||
|
||||
_Did you know you can use Kubescape in all these places?_
|
||||
|
||||
@@ -49,33 +58,47 @@ _Did you know you can use Kubescape in all these places?_
|
||||
<img src="docs/img/ksfromcodetodeploy.png" alt="Places you can use Kubescape: in your IDE, CI, CD, or against a running cluster.">
|
||||
</div>
|
||||
|
||||
### Continuous security monitoring with the Kubescape Operator
|
||||
|
||||
As well as a CLI, Kubescape provides an in-cluster mode, which is installed via a Helm chart. Kubescape in-cluster provides extensive features such as continuous scanning, image vulnerability scanning, runtime analysis, network policy generation, and more. [Learn more about the Kubescape operator](https://kubescape.io/docs/operator/).
|
||||
|
||||
### Using Kubescape as a GitHub Action
|
||||
|
||||
Kubescape can be used as a GitHub Action. This is a great way to integrate Kubescape into your CI/CD pipeline. You can find the Kubescape GitHub Action in the [GitHub Action marketplace](https://github.com/marketplace/actions/kubescape).
|
||||
|
||||
## Under the hood
|
||||
|
||||
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).
|
||||
For image scanning, it uses [Grype](https://github.com/anchore/grype).
|
||||
For image patching, it uses [Copacetic](https://github.com/project-copacetic/copacetic).
|
||||
For eBPF, it uses [Inspektor Gadget](https://github.com/inspektor-gadget)
|
||||
|
||||
By default, the results are printed in a console-friendly manner, but they can be:
|
||||
By default, CLI scan results are printed in a console-friendly manner, but they can be:
|
||||
|
||||
* exported to JSON or junit XML
|
||||
* exported to JSON, junit XML or SARIF
|
||||
* rendered to HTML or PDF
|
||||
* submitted to a [cloud service](docs/providers.md)
|
||||
|
||||
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).
|
||||
### In-cluster architecture
|
||||
|
||||

|
||||
|
||||
## Community
|
||||
|
||||
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.
|
||||
Kubescape is an open source project. We welcome your feedback and ideas for improvement. We are part of the CNCF community and are evolving Kubescape in sync with the security needs of Kubernetes users. To learn more about where Kubescape is heading, please check out our [ROADMAP](https://github.com/kubescape/project-governance/blob/main/ROADMAP.md).
|
||||
|
||||
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)).
|
||||
If you feel inspired to contribute to Kubescape, check out our [CONTRIBUTING](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md) file to learn how. You can find the issues we are working on (triage to development) on the [Kubescaping board](https://github.com/orgs/kubescape/projects/4/views/1)
|
||||
|
||||
* Feel free to pick a task from the [board](https://github.com/orgs/kubescape/projects/4) or suggest a feature of your own.
|
||||
* Open an issue on the board. 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.
|
||||
|
||||
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
|
||||
## Contributions
|
||||
For more information about the Kubescape community, please visit [COMMUNITY](https://github.com/kubescape/project-governance/blob/main/COMMUNITY.md).
|
||||
|
||||
Thanks to all our contributors! Check out our [CONTRIBUTING](CONTRIBUTING.md) file to learn how to join them.
|
||||
|
||||
* 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.
|
||||
We would like to take this opportunity to thank all our contibutors to date.
|
||||
|
||||
<br>
|
||||
|
||||
@@ -83,12 +106,16 @@ Thanks to all our contributors! Check out our [CONTRIBUTING](CONTRIBUTING.md) f
|
||||
<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.
|
||||
|
||||
## 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-2024, the Kubescape Authors. All rights reserved. Kubescape is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details.
|
||||
|
||||
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 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">
|
||||
<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>
|
||||
|
||||
56
SECURITY-INSIGHTS.yml
Normal file
56
SECURITY-INSIGHTS.yml
Normal file
@@ -0,0 +1,56 @@
|
||||
header:
|
||||
schema-version: 1.0.0
|
||||
last-updated: '2023-10-12'
|
||||
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-lifecycle:
|
||||
status: active
|
||||
bug-fixes-only: false
|
||||
core-maintainers:
|
||||
- github:amirmalka
|
||||
- github:amitschendel
|
||||
- github:bezbran
|
||||
- github:craigbox
|
||||
- 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:
|
||||
- https://github.com/kubescape/kubescape/
|
||||
security-artifacts:
|
||||
threat-model:
|
||||
threat-model-created: false
|
||||
security-testing:
|
||||
- tool-type: sca
|
||||
tool-name: Dependabot
|
||||
tool-version: latest
|
||||
integration:
|
||||
ad-hoc: false
|
||||
ci: true
|
||||
before-release: true
|
||||
comment: |
|
||||
Dependabot is enabled for this repo.
|
||||
security-contacts:
|
||||
- type: email
|
||||
value: cncf-kubescape-maintainers@lists.cncf.io
|
||||
vulnerability-reporting:
|
||||
accepts-vulnerability-reports: true
|
||||
security-policy: https://github.com/kubescape/kubescape/security/policy
|
||||
email-contact: cncf-kubescape-maintainers@lists.cncf.io
|
||||
comment: |
|
||||
The first and best way to report a vulnerability is by using private security issues in GitHub.
|
||||
5
SECURITY.md
Normal file
5
SECURITY.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Security
|
||||
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized SECURITY.md](https://github.com/kubescape/project-governance/blob/main/SECURITY.md)
|
||||
51
build.bat
51
build.bat
@@ -1,51 +0,0 @@
|
||||
@ECHO OFF
|
||||
|
||||
IF "%1"=="install" goto Install
|
||||
IF "%1"=="build" goto Build
|
||||
IF "%1"=="all" goto All
|
||||
IF "%1"=="" goto Error ELSE goto Error
|
||||
|
||||
:Install
|
||||
|
||||
if exist C:\MSYS64\ (
|
||||
echo "MSYS2 already installed"
|
||||
) else (
|
||||
mkdir temp_install & cd temp_install
|
||||
|
||||
echo "Downloading MSYS2..."
|
||||
curl -L https://github.com/msys2/msys2-installer/releases/download/2022-06-03/msys2-x86_64-20220603.exe > msys2-x86_64-20220603.exe
|
||||
|
||||
echo "Installing MSYS2..."
|
||||
msys2-x86_64-20220603.exe install --root C:\MSYS64 --confirm-command
|
||||
|
||||
cd .. && rmdir /s /q temp_install
|
||||
)
|
||||
|
||||
|
||||
echo "Adding MSYS2 to path..."
|
||||
SET "PATH=C:\MSYS64\mingw64\bin;C:\MSYS64\usr\bin;%PATH%"
|
||||
echo %PATH%
|
||||
|
||||
echo "Installing MSYS2 packages..."
|
||||
pacman -S --needed --noconfirm make
|
||||
pacman -S --needed --noconfirm mingw-w64-x86_64-cmake
|
||||
pacman -S --needed --noconfirm mingw-w64-x86_64-gcc
|
||||
pacman -S --needed --noconfirm mingw-w64-x86_64-pkg-config
|
||||
pacman -S --needed --noconfirm msys2-w32api-runtime
|
||||
|
||||
IF "%1"=="all" GOTO Build
|
||||
GOTO End
|
||||
|
||||
:Build
|
||||
SET "PATH=C:\MSYS2\mingw64\bin;C:\MSYS2\usr\bin;%PATH%"
|
||||
make libgit2
|
||||
GOTO End
|
||||
|
||||
:All
|
||||
GOTO Install
|
||||
|
||||
:Error
|
||||
echo "Error: Unknown option"
|
||||
GOTO End
|
||||
|
||||
:End
|
||||
88
build.py
88
build.py
@@ -1,88 +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))
|
||||
|
||||
return "kubescape-" + 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]
|
||||
|
||||
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,51 +1,27 @@
|
||||
FROM golang:1.19-alpine as builder
|
||||
|
||||
ARG image_version
|
||||
ARG client
|
||||
|
||||
ENV RELEASE=$image_version
|
||||
ENV CLIENT=$client
|
||||
|
||||
ENV GO111MODULE=
|
||||
|
||||
ENV CGO_ENABLED=1
|
||||
|
||||
# Install required python/pip
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
RUN apk add --update --no-cache python3 gcc make git libc-dev binutils-gold cmake pkgconfig && ln -sf python3 /usr/bin/python
|
||||
RUN python3 -m ensurepip
|
||||
RUN pip3 install --no-cache --upgrade pip setuptools
|
||||
FROM --platform=$BUILDPLATFORM golang:1.23-bookworm AS builder
|
||||
|
||||
ENV GO111MODULE=on CGO_ENABLED=0
|
||||
WORKDIR /work
|
||||
ADD . .
|
||||
ARG TARGETOS TARGETARCH
|
||||
|
||||
# install libgit2
|
||||
RUN rm -rf git2go && make libgit2
|
||||
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 .
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg \
|
||||
go run downloader/main.go
|
||||
|
||||
# build kubescape server
|
||||
WORKDIR /work/httphandler
|
||||
RUN python build.py
|
||||
RUN ls -ltr build/
|
||||
FROM gcr.io/distroless/static-debian12:nonroot
|
||||
|
||||
# build kubescape cmd
|
||||
WORKDIR /work
|
||||
RUN python build.py
|
||||
USER nonroot
|
||||
WORKDIR /home/nonroot/
|
||||
|
||||
RUN /work/build/kubescape-ubuntu-latest download artifacts -o /work/artifacts
|
||||
COPY --from=builder /out/ksserver /usr/bin/ksserver
|
||||
COPY --from=builder /root/.kubescape /home/nonroot/.kubescape
|
||||
|
||||
FROM alpine:3.16.2
|
||||
|
||||
RUN addgroup -S ks && adduser -S ks -G ks
|
||||
|
||||
COPY --from=builder /work/artifacts/ /home/ks/.kubescape
|
||||
|
||||
RUN chown -R ks:ks /home/ks/.kubescape
|
||||
|
||||
USER ks
|
||||
|
||||
WORKDIR /home/ks
|
||||
|
||||
COPY --from=builder /work/httphandler/build/kubescape-ubuntu-latest /usr/bin/ksserver
|
||||
COPY --from=builder /work/build/kubescape-ubuntu-latest /usr/bin/kubescape
|
||||
ARG image_version client
|
||||
ENV RELEASE=$image_version CLIENT=$client
|
||||
|
||||
ENTRYPOINT ["ksserver"]
|
||||
|
||||
2
build/Dockerfile.dockerignore
Normal file
2
build/Dockerfile.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
.git
|
||||
kubescape*
|
||||
@@ -7,7 +7,13 @@
|
||||
git clone https://github.com/kubescape/kubescape.git kubescape && cd "$_"
|
||||
```
|
||||
|
||||
2. Build
|
||||
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 .
|
||||
```
|
||||
|
||||
3. Build kubescape Docker image
|
||||
```
|
||||
docker buildx build -t kubescape -f build/Dockerfile --load .
|
||||
```
|
||||
docker build -t kubescape -f build/Dockerfile .
|
||||
```
|
||||
12
build/kubescape-cli.Dockerfile
Normal file
12
build/kubescape-cli.Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM gcr.io/distroless/static-debian12:debug-nonroot
|
||||
|
||||
USER nonroot
|
||||
WORKDIR /home/nonroot/
|
||||
|
||||
ARG image_version client TARGETARCH
|
||||
ENV RELEASE=$image_version CLIENT=$client
|
||||
|
||||
COPY kubescape-${TARGETARCH}-ubuntu-latest /usr/bin/kubescape
|
||||
RUN ["kubescape", "download", "artifacts"]
|
||||
|
||||
ENTRYPOINT ["kubescape"]
|
||||
1
build/kubescape-cli.Dockerfile.dockerignore
Normal file
1
build/kubescape-cli.Dockerfile.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
.git
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -23,14 +23,8 @@ var (
|
||||
# Set account id
|
||||
%[1]s config set accountID <account id>
|
||||
|
||||
# Set client id
|
||||
%[1]s config set clientID <client id>
|
||||
|
||||
# Set access key
|
||||
%[1]s config set secretKey <access key>
|
||||
|
||||
# Set cloudAPIURL
|
||||
%[1]s config set cloudAPIURL <cloud API URL>
|
||||
# Set cloud report URL
|
||||
%[1]s config set cloudReportURL <cloud Report URL>
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
|
||||
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,20 +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 },
|
||||
"clientID": func(s *metav1.SetConfig, clientID string) { s.ClientID = clientID },
|
||||
"secretKey": func(s *metav1.SetConfig, secretKey string) { s.SecretKey = secretKey },
|
||||
"cloudAPIURL": func(s *metav1.SetConfig, cloudAPIURL string) { s.CloudAPIURL = cloudAPIURL },
|
||||
"cloudAuthURL": func(s *metav1.SetConfig, cloudAuthURL string) { s.CloudAuthURL = cloudAuthURL },
|
||||
"cloudReportURL": func(s *metav1.SetConfig, cloudReportURL string) { s.CloudReportURL = cloudReportURL },
|
||||
"cloudUIURL": func(s *metav1.SetConfig, cloudUIURL string) { s.CloudUIURL = cloudUIURL },
|
||||
}
|
||||
|
||||
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,37 +0,0 @@
|
||||
package delete
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var deleteExceptionsExamples = fmt.Sprintf(`
|
||||
# Delete single exception
|
||||
%[1]s delete exceptions "exception name"
|
||||
|
||||
# Delete multiple exceptions
|
||||
%[1]s delete exceptions "first exception;second exception;third exception"
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetDeleteCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var deleteInfo v1.Delete
|
||||
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete <command>",
|
||||
Short: "Delete configurations in Kubescape SaaS version",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
|
||||
deleteCmd.AddCommand(getExceptionsCmd(ks, &deleteInfo))
|
||||
|
||||
return deleteCmd
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package delete
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func getExceptionsCmd(ks meta.IKubescape, deleteInfo *v1.Delete) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "exceptions <exception name>",
|
||||
Short: fmt.Sprintf("Delete exceptions from Kubescape SaaS version. Run '%[1]s list exceptions' for all exceptions names", cautils.ExecName()),
|
||||
Example: deleteExceptionsExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("missing exceptions names")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
if err := flagValidationDelete(deleteInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
exceptionsNames := strings.Split(args[0], ";")
|
||||
if len(exceptionsNames) == 0 {
|
||||
logger.L().Fatal("missing exceptions names")
|
||||
}
|
||||
if err := ks.DeleteExceptions(&v1.DeleteExceptions{Credentials: deleteInfo.Credentials, Exceptions: exceptionsNames}); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the flag entered are valid
|
||||
func flagValidationDelete(deleteInfo *v1.Delete) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return deleteInfo.Credentials.Validate()
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -36,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())
|
||||
)
|
||||
|
||||
@@ -55,7 +52,7 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("policy type required, supported: %v", supported)
|
||||
}
|
||||
if cautils.StringInSlice(core.DownloadSupportCommands(), args[0]) == cautils.ValueNotFound {
|
||||
if !slices.Contains(core.DownloadSupportCommands(), args[0]) {
|
||||
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
|
||||
}
|
||||
return nil
|
||||
@@ -69,22 +66,24 @@ 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
|
||||
},
|
||||
}
|
||||
|
||||
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
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
|
||||
@@ -94,5 +93,5 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
func flagValidationDownload(downloadInfo *v1.DownloadInfo) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return downloadInfo.Credentials.Validate()
|
||||
return cautils.ValidateAccountID(downloadInfo.AccountID)
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -17,7 +15,7 @@ var fixCmdExamples = fmt.Sprintf(`
|
||||
Use with caution, this command will change your files in-place.
|
||||
|
||||
# Fix kubernetes YAML manifest files based on a scan command output (output.json)
|
||||
1) %[1]s scan --format json --format-version v2 --output output.json
|
||||
1) %[1]s scan . --format json --output output.json
|
||||
2) %[1]s fix output.json
|
||||
|
||||
`, cautils.ExecName())
|
||||
@@ -27,7 +25,7 @@ func GetFixCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
fixCmd := &cobra.Command{
|
||||
Use: "fix <report output file>",
|
||||
Short: "Fix misconfiguration in files",
|
||||
Short: "Propose a fix for the misconfiguration found when scanning Kubernetes manifest files",
|
||||
Long: ``,
|
||||
Example: fixCmdExamples,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -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,15 +1,16 @@
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -43,7 +44,7 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("policy type requeued, supported: %s", supported)
|
||||
}
|
||||
if cautils.StringInSlice(core.ListSupportActions(), args[0]) == cautils.ValueNotFound {
|
||||
if !slices.Contains(core.ListSupportActions(), args[0]) {
|
||||
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
|
||||
}
|
||||
return nil
|
||||
@@ -54,17 +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.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
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")
|
||||
|
||||
@@ -75,5 +79,5 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
||||
func flagValidationList(listPolicies *v1.ListPolicies) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return listPolicies.Credentials.Validate()
|
||||
return cautils.ValidateAccountID(listPolicies.AccountID)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
56
cmd/operator/configscan.go
Normal file
56
cmd/operator/configscan.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"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"
|
||||
)
|
||||
|
||||
var operatorScanConfigExamples = fmt.Sprintf(`
|
||||
|
||||
# Run a configuration scan
|
||||
%[1]s operator scan configurations
|
||||
|
||||
`, cautils.ExecName())
|
||||
|
||||
func getOperatorScanConfigCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command {
|
||||
configCmd := &cobra.Command{
|
||||
Use: "configurations",
|
||||
Short: "Trigger configuration scanning from the Kubescape Operator microservice",
|
||||
Long: ``,
|
||||
Example: operatorScanConfigExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
operatorInfo.Subcommands = append(operatorInfo.Subcommands, "config")
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo, operatorInfo.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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))
|
||||
return err
|
||||
}
|
||||
logger.L().StopSuccess("Triggered Kubescape Operator for configuration scanning")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
configScanInfo := &cautils.ConfigScanInfo{}
|
||||
operatorInfo.OperatorScanInfo = configScanInfo
|
||||
|
||||
configCmd.PersistentFlags().StringSliceVar(&configScanInfo.IncludedNamespaces, "include-namespaces", nil, "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
|
||||
configCmd.PersistentFlags().StringSliceVar(&configScanInfo.ExcludedNamespaces, "exclude-namespaces", nil, "Namespaces to exclude from scanning. e.g: --exclude-namespaces ns-a,ns-b. Notice, when running with `exclude-namespace` kubescape does not scan cluster-scoped objects.")
|
||||
configCmd.PersistentFlags().StringSliceVar(&configScanInfo.Frameworks, "frameworks", nil, "Load frameworks for configuration scanning")
|
||||
configCmd.PersistentFlags().BoolVarP(&configScanInfo.HostScanner, "enable-host-scan", "", false, "Deploy Kubescape host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valuable data from cluster nodes for certain controls. Yaml file: https://github.com/kubescape/kubescape/blob/master/core/pkg/hostsensorutils/hostsensor.yaml")
|
||||
|
||||
return configCmd
|
||||
}
|
||||
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)
|
||||
}
|
||||
55
cmd/operator/operator.go
Normal file
55
cmd/operator/operator.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
scanSubCommand string = "scan"
|
||||
)
|
||||
|
||||
var operatorExamples = fmt.Sprintf(`
|
||||
|
||||
# Trigger a configuration scan
|
||||
%[1]s operator scan configurations
|
||||
|
||||
# Trigger a vulnerabilities scan
|
||||
%[1]s operator scan vulnerabilities
|
||||
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetOperatorCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var operatorInfo cautils.OperatorInfo
|
||||
|
||||
operatorCmd := &cobra.Command{
|
||||
Use: "operator",
|
||||
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 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.")
|
||||
}
|
||||
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 nil
|
||||
},
|
||||
}
|
||||
|
||||
operatorCmd.AddCommand(getOperatorScanCmd(ks, operatorInfo))
|
||||
|
||||
return operatorCmd
|
||||
}
|
||||
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())
|
||||
}
|
||||
46
cmd/operator/scan.go
Normal file
46
cmd/operator/scan.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
vulnerabilitiesSubCommand string = "vulnerabilities"
|
||||
configurationsSubCommand string = "configurations"
|
||||
)
|
||||
|
||||
func getOperatorScanCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command {
|
||||
operatorCmd := &cobra.Command{
|
||||
Use: "scan",
|
||||
Short: "Scan your cluster using the Kubescape-operator within the cluster components",
|
||||
Long: ``,
|
||||
Example: operatorExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
operatorInfo.Subcommands = append(operatorInfo.Subcommands, "scan")
|
||||
if len(args) < 1 {
|
||||
return errors.New("for operator scan sub command, you must pass at least 1 more sub commands, see above examples")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
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 nil
|
||||
},
|
||||
}
|
||||
|
||||
operatorCmd.PersistentFlags().StringVar(&operatorInfo.Namespace, "namespace", "kubescape", "namespace of the Kubescape Operator")
|
||||
operatorCmd.AddCommand(getOperatorScanConfigCmd(ks, operatorInfo))
|
||||
operatorCmd.AddCommand(getOperatorScanVulnerabilitiesCmd(ks, operatorInfo))
|
||||
|
||||
return operatorCmd
|
||||
}
|
||||
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())
|
||||
}
|
||||
56
cmd/operator/vulnerabilitiesscan.go
Normal file
56
cmd/operator/vulnerabilitiesscan.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"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"
|
||||
)
|
||||
|
||||
var operatorScanVulnerabilitiesExamples = fmt.Sprintf(`
|
||||
|
||||
# Trigger a vulnerabilities scan
|
||||
%[1]s operator scan vulnerabilities
|
||||
|
||||
`, cautils.ExecName())
|
||||
|
||||
func getOperatorScanVulnerabilitiesCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command {
|
||||
configCmd := &cobra.Command{
|
||||
Use: "vulnerabilities",
|
||||
Short: "Vulnerabilities use for scan your cluster vulnerabilities using Kubescape operator in the in cluster components",
|
||||
Long: ``,
|
||||
Example: operatorScanVulnerabilitiesExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
operatorInfo.Subcommands = append(operatorInfo.Subcommands, "vulnerabilities")
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo, operatorInfo.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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))
|
||||
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\"")
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
vulnerabilitiesScanInfo := &cautils.VulnerabilitiesScanInfo{
|
||||
ClusterName: k8sinterface.GetContextName(),
|
||||
}
|
||||
operatorInfo.OperatorScanInfo = vulnerabilitiesScanInfo
|
||||
|
||||
configCmd.PersistentFlags().StringSliceVar(&vulnerabilitiesScanInfo.IncludeNamespaces, "include-namespaces", nil, "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
|
||||
|
||||
return configCmd
|
||||
}
|
||||
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 seperate 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.
|
||||
141
cmd/patch/patch.go
Normal file
141
cmd/patch/patch.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package patch
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/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/kubescape/kubescape/v3/pkg/imagescan"
|
||||
"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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
results, err := ks.Patch(&patchInfo, &scanInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if imagescan.ExceedsSeverityThreshold(results, imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)) {
|
||||
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")
|
||||
|
||||
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
|
||||
}
|
||||
52
cmd/patch/patch_test.go
Normal file
52
cmd/patch/patch_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package patch
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"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())
|
||||
}
|
||||
46
cmd/prerequisites/prerequisites.go
Normal file
46
cmd/prerequisites/prerequisites.go
Normal file
@@ -0,0 +1,46 @@
|
||||
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 {
|
||||
// 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()
|
||||
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)
|
||||
},
|
||||
}
|
||||
return preReqCmd
|
||||
}
|
||||
88
cmd/root.go
88
cmd/root.go
@@ -1,37 +1,40 @@
|
||||
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/kubescape/v2/cmd/completion"
|
||||
"github.com/kubescape/kubescape/v2/cmd/config"
|
||||
"github.com/kubescape/kubescape/v2/cmd/delete"
|
||||
"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/scan"
|
||||
"github.com/kubescape/kubescape/v2/cmd/submit"
|
||||
"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/k8s-interface/k8sinterface"
|
||||
"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/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"
|
||||
)
|
||||
|
||||
var rootInfo cautils.RootInfo
|
||||
|
||||
var ksExamples = fmt.Sprintf(`
|
||||
# Scan command
|
||||
# Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations
|
||||
%[1]s scan
|
||||
|
||||
# List supported frameworks
|
||||
%[1]s list frameworks
|
||||
# List supported controls
|
||||
%[1]s list controls
|
||||
|
||||
# Download artifacts (air-gapped environment support)
|
||||
%[1]s download artifacts
|
||||
@@ -40,8 +43,8 @@ var ksExamples = fmt.Sprintf(`
|
||||
%[1]s config view
|
||||
`, cautils.ExecName())
|
||||
|
||||
func NewDefaultKubescapeCommand() *cobra.Command {
|
||||
ks := core.NewKubescape()
|
||||
func NewDefaultKubescapeCommand(ctx context.Context) *cobra.Command {
|
||||
ks := core.NewKubescape(ctx)
|
||||
return getRootCmd(ks)
|
||||
}
|
||||
|
||||
@@ -51,6 +54,13 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
Use: "kubescape",
|
||||
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://hub.armosec.io/docs",
|
||||
Example: ksExamples,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
k8sinterface.SetClusterContextName(rootInfo.KubeContext)
|
||||
initLogger()
|
||||
initLoggerLevel()
|
||||
initEnvironment()
|
||||
initCacheDir()
|
||||
},
|
||||
}
|
||||
|
||||
if cautils.IsKrewPlugin() {
|
||||
@@ -63,9 +73,10 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
rootCmd.SetUsageTemplate(newUsageTemplate)
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.KSCloudBEURLsDep, "environment", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.KSCloudBEURLs, "env", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().MarkDeprecated("environment", "use 'env' instead")
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.DiscoveryServerURL, "server", "", "Backend discovery server URL")
|
||||
|
||||
rootCmd.PersistentFlags().MarkDeprecated("environment", "'environment' is no longer supported, Use 'server' instead. Feel free to contact the Kubescape maintainers for more information.")
|
||||
rootCmd.PersistentFlags().MarkDeprecated("env", "'env' is no longer supported, Use 'server' instead. Feel free to contact the Kubescape maintainers for more information.")
|
||||
rootCmd.PersistentFlags().MarkHidden("environment")
|
||||
rootCmd.PersistentFlags().MarkHidden("env")
|
||||
|
||||
@@ -74,27 +85,36 @@ 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")
|
||||
|
||||
cobra.OnInitialize(initLogger, initLoggerLevel, initEnvironment, initCacheDir)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&rootInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
|
||||
// Supported commands
|
||||
rootCmd.AddCommand(scan.GetScanCommand(ks))
|
||||
rootCmd.AddCommand(download.GetDownloadCmd(ks))
|
||||
rootCmd.AddCommand(delete.GetDeleteCmd(ks))
|
||||
rootCmd.AddCommand(list.GetListCmd(ks))
|
||||
rootCmd.AddCommand(submit.GetSubmitCmd(ks))
|
||||
rootCmd.AddCommand(completion.GetCompletionCmd())
|
||||
rootCmd.AddCommand(version.GetVersionCmd())
|
||||
rootCmd.AddCommand(version.GetVersionCmd(ks))
|
||||
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))
|
||||
|
||||
// deprecated commands
|
||||
rootCmd.AddCommand(&cobra.Command{
|
||||
Use: "submit",
|
||||
Deprecated: "This command is deprecated. Contact Kubescape maintainers for more information.",
|
||||
})
|
||||
rootCmd.AddCommand(&cobra.Command{
|
||||
Use: "delete",
|
||||
Deprecated: "This command is deprecated. Contact Kubescape maintainers for more information.",
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
func Execute() error {
|
||||
ks := NewDefaultKubescapeCommand()
|
||||
func Execute(ctx context.Context) error {
|
||||
ks := NewDefaultKubescapeCommand(ctx)
|
||||
return ks.Execute()
|
||||
}
|
||||
|
||||
@@ -5,34 +5,34 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
v1 "github.com/kubescape/backend/pkg/client/v1"
|
||||
"github.com/kubescape/backend/pkg/servicediscovery"
|
||||
sdClientV2 "github.com/kubescape/backend/pkg/servicediscovery/v2"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
|
||||
"github.com/kubescape/go-logger/iconlogger"
|
||||
"github.com/kubescape/go-logger/zaplogger"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
const envFlagUsage = "Send report results to specific URL. Format:<ReportReceiver>,<Backend>,<Frontend>.\n\t\tExample:report.armo.cloud,api.armo.cloud,portal.armo.cloud"
|
||||
|
||||
func initLogger() {
|
||||
logger.DisableColor(rootInfo.DisableColor)
|
||||
logger.EnableColor(rootInfo.EnableColor)
|
||||
|
||||
if rootInfo.LoggerName == "" {
|
||||
if l := os.Getenv("KS_LOGGER_NAME"); l != "" {
|
||||
rootInfo.LoggerName = l
|
||||
} else {
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
rootInfo.LoggerName = "pretty"
|
||||
rootInfo.LoggerName = iconlogger.LoggerName
|
||||
} else {
|
||||
rootInfo.LoggerName = "zap"
|
||||
rootInfo.LoggerName = zaplogger.LoggerName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.InitLogger(rootInfo.LoggerName)
|
||||
|
||||
}
|
||||
|
||||
func initLoggerLevel() {
|
||||
if rootInfo.Logger == helpers.InfoLevel.String() {
|
||||
} else if l := os.Getenv("KS_LOGGER"); l != "" {
|
||||
@@ -56,40 +56,51 @@ func initCacheDir() {
|
||||
logger.L().Debug("cache dir updated", helpers.String("path", getter.DefaultLocalStore))
|
||||
}
|
||||
func initEnvironment() {
|
||||
if rootInfo.KSCloudBEURLs == "" {
|
||||
rootInfo.KSCloudBEURLs = rootInfo.KSCloudBEURLsDep
|
||||
if rootInfo.DiscoveryServerURL == "" {
|
||||
return
|
||||
}
|
||||
urlSlices := strings.Split(rootInfo.KSCloudBEURLs, ",")
|
||||
if len(urlSlices) != 1 && len(urlSlices) < 3 {
|
||||
logger.L().Fatal("expected at least 3 URLs (report, api, frontend, auth)")
|
||||
}
|
||||
switch len(urlSlices) {
|
||||
case 1:
|
||||
switch urlSlices[0] {
|
||||
case "dev", "development":
|
||||
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIDev())
|
||||
case "stage", "staging":
|
||||
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIStaging())
|
||||
case "":
|
||||
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIProd())
|
||||
default:
|
||||
logger.L().Fatal("--environment flag usage: " + envFlagUsage)
|
||||
}
|
||||
case 2:
|
||||
logger.L().Fatal("--environment flag usage: " + envFlagUsage)
|
||||
case 3, 4:
|
||||
var ksAuthURL string
|
||||
ksEventReceiverURL := urlSlices[0] // mandatory
|
||||
ksBackendURL := urlSlices[1] // mandatory
|
||||
ksFrontendURL := urlSlices[2] // mandatory
|
||||
if len(urlSlices) >= 4 {
|
||||
ksAuthURL = urlSlices[3]
|
||||
}
|
||||
|
||||
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPICustomized(
|
||||
ksBackendURL, ksAuthURL,
|
||||
getter.WithReportURL(ksEventReceiverURL),
|
||||
getter.WithFrontendURL(ksFrontendURL),
|
||||
))
|
||||
logger.L().Debug("fetching URLs from service discovery server", helpers.String("server", 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
|
||||
}
|
||||
|
||||
services, err := servicediscovery.GetServices(
|
||||
client,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
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)
|
||||
if services.GetApiServerUrl() != "" {
|
||||
tenant.GetConfigObj().CloudAPIURL = services.GetApiServerUrl()
|
||||
}
|
||||
if services.GetReportReceiverHttpUrl() != "" {
|
||||
tenant.GetConfigObj().CloudReportURL = services.GetReportReceiverHttpUrl()
|
||||
}
|
||||
|
||||
if err = tenant.UpdateCachedConfig(); err != nil {
|
||||
logger.L().Error("failed to update cached config", helpers.Error(err))
|
||||
}
|
||||
|
||||
ksCloud, err := v1.NewKSCloudAPI(
|
||||
services.GetApiServerUrl(),
|
||||
services.GetReportReceiverHttpUrl(),
|
||||
"",
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to create KS Cloud client", helpers.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
getter.SetKSCloudAPIConnector(ksCloud)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -91,17 +89,17 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
||||
}
|
||||
|
||||
scanInfo.FrameworkScan = false
|
||||
scanInfo.SetScanType(cautils.ScanTypeControl)
|
||||
|
||||
if err := validateControlScanInfo(scanInfo); err != nil {
|
||||
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()); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
if !scanInfo.VerboseMode {
|
||||
@@ -110,6 +108,9 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
||||
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)))
|
||||
}
|
||||
if results.GetComplianceScore() < float32(scanInfo.ComplianceThreshold) {
|
||||
logger.L().Fatal("scan compliance-score is below permitted threshold", helpers.String("compliance score", fmt.Sprintf("%.2f", results.GetComplianceScore())), helpers.String("compliance-threshold", fmt.Sprintf("%.2f", scanInfo.ComplianceThreshold)))
|
||||
}
|
||||
enforceSeverityThresholds(results.GetResults().SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
|
||||
|
||||
return nil
|
||||
@@ -125,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,23 +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"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -41,7 +40,10 @@ 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")
|
||||
ErrOmitRawResourcesOrSubmit = errors.New("you can use `omit-raw-resources` or `submit`, but not both")
|
||||
)
|
||||
|
||||
func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
|
||||
@@ -78,17 +80,18 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
||||
|
||||
var frameworks []string
|
||||
|
||||
if len(args) == 0 { // scan all frameworks
|
||||
if len(args) == 0 {
|
||||
scanInfo.ScanAll = true
|
||||
} else {
|
||||
// Read frameworks from input args
|
||||
frameworks = strings.Split(args[0], ",")
|
||||
if cautils.StringInSlice(frameworks, "all") != cautils.ValueNotFound {
|
||||
if slices.Contains(frameworks, "all") {
|
||||
scanInfo.ScanAll = true
|
||||
frameworks = getter.NativeFrameworks
|
||||
|
||||
}
|
||||
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 !!
|
||||
@@ -105,36 +108,37 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
||||
}
|
||||
}
|
||||
}
|
||||
scanInfo.FrameworkScan = true
|
||||
scanInfo.SetScanType(cautils.ScanTypeFramework)
|
||||
|
||||
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()); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
if !scanInfo.VerboseMode {
|
||||
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)))
|
||||
}
|
||||
if results.GetComplianceScore() < float32(scanInfo.ComplianceThreshold) {
|
||||
logger.L().Fatal("scan compliance-score is below permitted threshold", helpers.String("compliance-score", fmt.Sprintf("%.2f", results.GetComplianceScore())), helpers.String("compliance-threshold", fmt.Sprintf("%.2f", scanInfo.ComplianceThreshold)))
|
||||
}
|
||||
|
||||
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -169,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
|
||||
@@ -188,33 +192,29 @@ 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) {
|
||||
scanInfo.View = string(cautils.ResourceViewType)
|
||||
}
|
||||
|
||||
if scanInfo.Submit && scanInfo.Local {
|
||||
return fmt.Errorf("you can use `keep-local` or `submit`, but not both")
|
||||
return ErrKeepLocalOrSubmit
|
||||
}
|
||||
if 100 < scanInfo.ComplianceThreshold || 0 > scanInfo.ComplianceThreshold {
|
||||
return ErrBadThreshold
|
||||
}
|
||||
if 100 < scanInfo.FailThreshold || 0 > scanInfo.FailThreshold {
|
||||
return fmt.Errorf("bad argument: out of range threshold")
|
||||
return ErrBadThreshold
|
||||
}
|
||||
if scanInfo.Submit && scanInfo.OmitRawResources {
|
||||
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
|
||||
return ErrOmitRawResourcesOrSubmit
|
||||
}
|
||||
severity := scanInfo.FailThresholdSeverity
|
||||
if err := validateSeverity(severity); severity != "" && err != nil {
|
||||
if err := shared.ValidateSeverity(severity); severity != "" && err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate the user's credentials
|
||||
return scanInfo.Credentials.Validate()
|
||||
return cautils.ValidateAccountID(scanInfo.AccountID)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
82
cmd/scan/image.go
Normal file
82
cmd/scan/image.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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/kubescape/kubescape/v3/pkg/imagescan"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// TODO(vladklokun): document image scanning on the Kubescape Docs Hub?
|
||||
var (
|
||||
imageExample = fmt.Sprintf(`
|
||||
Scan an image for vulnerabilities.
|
||||
|
||||
# Scan the 'nginx' image
|
||||
%[1]s scan image "nginx"
|
||||
|
||||
# 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())
|
||||
)
|
||||
|
||||
// getImageCmd returns the scan image command
|
||||
func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
|
||||
var imgCredentials shared.ImageCredentials
|
||||
var exceptions string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "image <image>:<tag> [flags]",
|
||||
Short: "Scan an image for vulnerabilities",
|
||||
Example: imageExample,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("the command takes exactly one image name as an argument")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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
|
||||
}
|
||||
|
||||
imgScanInfo := &metav1.ImageScanInfo{
|
||||
Image: args[0],
|
||||
Username: imgCredentials.Username,
|
||||
Password: imgCredentials.Password,
|
||||
Exceptions: exceptions,
|
||||
}
|
||||
|
||||
results, err := ks.ScanImage(imgScanInfo, scanInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if imagescan.ExceedsSeverityThreshold(results, imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)) {
|
||||
shared.TerminateOnExceedingSeverity(scanInfo, logger.L())
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// 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")
|
||||
|
||||
return cmd
|
||||
}
|
||||
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())
|
||||
}
|
||||
108
cmd/scan/scan.go
108
cmd/scan/scan.go
@@ -5,29 +5,30 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"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 current cluster with all frameworks
|
||||
%[1]s scan --enable-host-scan --verbose
|
||||
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
|
||||
|
||||
# Scan kubernetes YAML manifest files
|
||||
# Scan current cluster
|
||||
%[1]s scan
|
||||
|
||||
# Scan kubernetes manifest files
|
||||
%[1]s scan .
|
||||
|
||||
# Scan and save the results in the JSON format
|
||||
%[1]s scan --format json --output results.json --format-version=v2
|
||||
%[1]s scan --format json --output results.json
|
||||
|
||||
# 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())
|
||||
|
||||
@@ -37,52 +38,50 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
// scanCmd represents the scan command
|
||||
scanCmd := &cobra.Command{
|
||||
Use: "scan",
|
||||
Short: "Scan the current running cluster or yaml files",
|
||||
Short: "Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations",
|
||||
Long: `The action you want to perform`,
|
||||
Example: scanCmdExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
if args[0] != "framework" && args[0] != "control" {
|
||||
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{strings.Join(getter.NativeFrameworks, ",")}, args...))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if scanInfo.View == string(cautils.SecurityViewType) {
|
||||
setSecurityViewScanInfo(args, &scanInfo)
|
||||
|
||||
if len(args) == 0 {
|
||||
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, []string{strings.Join(getter.NativeFrameworks, ",")})
|
||||
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")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
|
||||
|
||||
return nil
|
||||
},
|
||||
PostRun: func(cmd *cobra.Command, args []string) {
|
||||
// TODO - revert context
|
||||
},
|
||||
}
|
||||
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
scanCmd.PersistentFlags().BoolVar(&scanInfo.CreateAccount, "create-account", false, "Create a Kubescape SaaS account ID account ID is not found in cache. After creating the account, the account ID will be saved in cache. In addition, the scanning results will be uploaded to the Kubescape SaaS")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
|
||||
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")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Notice, when running with `exclude-namespace` kubescape does not scan cluster-scoped objects.")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. e.g: --exclude-namespaces ns-a,ns-b. Notice, when running with `exclude-namespace` kubescape does not scan cluster-scoped objects.")
|
||||
|
||||
scanCmd.PersistentFlags().Float32VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above which the command fails and returns exit code 1")
|
||||
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. default is --view=%s", cautils.ResourceViewType, cautils.ControlViewType, 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")
|
||||
@@ -91,13 +90,16 @@ 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().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("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.")
|
||||
|
||||
// hidden flags
|
||||
scanCmd.PersistentFlags().MarkHidden("host-scan-yaml") // this flag should be used very cautiously. We prefer users will not use it at all unless the DaemonSet can not run pods on the nodes
|
||||
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"))
|
||||
@@ -105,9 +107,43 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensorEnabled, "enable-host-scan", "", "Deploy Kubescape host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valuable data from cluster nodes for certain controls. Yaml file: https://github.com/kubescape/kubescape/blob/master/core/pkg/hostsensorutils/hostsensor.yaml")
|
||||
hostF.NoOptDefVal = "true"
|
||||
hostF.DefValue = "false, for no TTY in stdin"
|
||||
scanCmd.PersistentFlags().MarkHidden("enable-host-scan")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("enable-host-scan", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator. The flag will be removed at 1.Dec.2023")
|
||||
|
||||
scanCmd.PersistentFlags().MarkHidden("host-scan-yaml") // this flag should be used very cautiously. We prefer users will not use it at all unless the DaemonSet can not run pods on the nodes
|
||||
scanCmd.PersistentFlags().MarkDeprecated("host-scan-yaml", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator. The flag will be removed at 1.Dec.2023")
|
||||
|
||||
scanCmd.AddCommand(getControlCmd(ks, &scanInfo))
|
||||
scanCmd.AddCommand(getFrameworkCmd(ks, &scanInfo))
|
||||
scanCmd.AddCommand(getWorkloadCmd(ks, &scanInfo))
|
||||
|
||||
scanCmd.AddCommand(getImageCmd(ks, &scanInfo))
|
||||
|
||||
return scanCmd
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func securityScan(scanInfo cautils.ScanInfo, ks meta.IKubescape) error {
|
||||
results, err := ks.Scan(&scanInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = results.HandleResults(ks.Context()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), &scanInfo, terminateOnExceedingSeverity)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,16 +2,18 @@ package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"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) {
|
||||
@@ -111,7 +113,7 @@ func TestExceedsSeverity(t *testing.T) {
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "unknown"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
|
||||
Want: false,
|
||||
Error: ErrUnknownSeverity,
|
||||
Error: shared.ErrUnknownSeverity,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -184,17 +186,20 @@ 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) 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) Fatal(msg string, details ...helpers.IDetails) {
|
||||
firstDetail := details[0]
|
||||
@@ -209,7 +214,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 {
|
||||
@@ -254,3 +259,115 @@ func Test_terminateOnExceedingSeverity(t *testing.T) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetSecurityViewScanInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want *cautils.ScanInfo
|
||||
}{
|
||||
{
|
||||
name: "no args",
|
||||
args: []string{},
|
||||
want: &cautils.ScanInfo{
|
||||
InputPatterns: []string{},
|
||||
ScanType: cautils.ScanTypeCluster,
|
||||
PolicyIdentifier: []cautils.PolicyIdentifier{
|
||||
{
|
||||
Kind: v1.KindFramework,
|
||||
Identifier: "clusterscan",
|
||||
},
|
||||
{
|
||||
Kind: v1.KindFramework,
|
||||
Identifier: "mitre",
|
||||
},
|
||||
{
|
||||
Kind: v1.KindFramework,
|
||||
Identifier: "nsa",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with args",
|
||||
args: []string{
|
||||
"file.yaml",
|
||||
"file2.yaml",
|
||||
},
|
||||
want: &cautils.ScanInfo{
|
||||
ScanType: cautils.ScanTypeRepo,
|
||||
InputPatterns: []string{
|
||||
"file.yaml",
|
||||
"file2.yaml",
|
||||
},
|
||||
PolicyIdentifier: []cautils.PolicyIdentifier{
|
||||
{
|
||||
Kind: v1.KindFramework,
|
||||
Identifier: "workloadscan",
|
||||
},
|
||||
{
|
||||
Kind: v1.KindFramework,
|
||||
Identifier: "allcontrols",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := &cautils.ScanInfo{
|
||||
View: string(cautils.SecurityViewType),
|
||||
}
|
||||
setSecurityViewScanInfo(tt.args, got)
|
||||
|
||||
if len(tt.want.InputPatterns) != len(got.InputPatterns) {
|
||||
t.Errorf("in test: %s, got: %v, want: %v", tt.name, got.InputPatterns, tt.want.InputPatterns)
|
||||
}
|
||||
|
||||
if tt.want.ScanType != got.ScanType {
|
||||
t.Errorf("in test: %s, got: %v, want: %v", tt.name, got.ScanType, tt.want.ScanType)
|
||||
}
|
||||
|
||||
for i := range tt.want.InputPatterns {
|
||||
found := false
|
||||
for j := range tt.want.InputPatterns[i] {
|
||||
if tt.want.InputPatterns[i][j] == got.InputPatterns[i][j] {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("in test: %s, got: %v, want: %v", tt.name, got.InputPatterns, tt.want.InputPatterns)
|
||||
}
|
||||
}
|
||||
|
||||
for i := range tt.want.PolicyIdentifier {
|
||||
found := false
|
||||
for j := range got.PolicyIdentifier {
|
||||
if tt.want.PolicyIdentifier[i].Kind == got.PolicyIdentifier[j].Kind && tt.want.PolicyIdentifier[i].Identifier == got.PolicyIdentifier[j].Identifier {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("in test: %s, got: %v, want: %v", tt.name, got.PolicyIdentifier, tt.want.PolicyIdentifier)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -66,7 +67,17 @@ 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)},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Empty view should be valid for scan info",
|
||||
&cautils.ScanInfo{},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -86,27 +97,22 @@ func Test_validateFrameworkScanInfo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validateSeverity(t *testing.T) {
|
||||
func Test_validateWorkloadIdentifier(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},
|
||||
{"valid workload identifier should be valid", "deployment/test", nil},
|
||||
{"invalid workload identifier missing kind", "deployment", ErrInvalidWorkloadIdentifier},
|
||||
{"invalid workload identifier with namespace", "ns/deployment/name", ErrInvalidWorkloadIdentifier},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
input := testCase.Input
|
||||
want := testCase.Want
|
||||
got := validateSeverity(input)
|
||||
got := validateWorkloadIdentifier(input)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
|
||||
124
cmd/scan/workload.go
Normal file
124
cmd/scan/workload.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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(`
|
||||
Scan a workload for misconfigurations and image vulnerabilities.
|
||||
|
||||
# Scan an workload
|
||||
%[1]s scan workload <kind>/<name>
|
||||
|
||||
# Scan an workload in a specific namespace
|
||||
%[1]s scan workload <kind>/<name> --namespace <namespace>
|
||||
|
||||
# Scan an workload from a file path
|
||||
%[1]s scan workload <kind>/<name> --file-path <file path>
|
||||
|
||||
# Scan an workload from a helm-chart template
|
||||
%[1]s scan workload <kind>/<name> --chart-path <chart path> --file-path <file path>
|
||||
|
||||
|
||||
`, cautils.ExecName())
|
||||
|
||||
ErrInvalidWorkloadIdentifier = errors.New("invalid workload identifier")
|
||||
)
|
||||
|
||||
var namespace string
|
||||
|
||||
// controlCmd represents the control command
|
||||
func getWorkloadCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
|
||||
workloadCmd := &cobra.Command{
|
||||
Use: "workload <kind>/<name> [`<glob pattern>`/`-`] [flags]",
|
||||
Short: "Scan a workload for misconfigurations and image vulnerabilities",
|
||||
Example: workloadExample,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
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>")
|
||||
}
|
||||
|
||||
return validateWorkloadIdentifier(args[0])
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
kind, name, err := parseWorkloadIdentifierString(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid input: %s", err.Error())
|
||||
}
|
||||
|
||||
setWorkloadScanInfo(scanInfo, kind, name)
|
||||
|
||||
// todo: add api version if provided
|
||||
results, err := ks.Scan(scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
if err = results.HandleResults(ks.Context()); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
workloadCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace of the workload. Default will be empty.")
|
||||
workloadCmd.PersistentFlags().StringVar(&scanInfo.FilePath, "file-path", "", "Path to the workload file.")
|
||||
workloadCmd.PersistentFlags().StringVar(&scanInfo.ChartPath, "chart-path", "", "Path to the helm chart the workload is part of. Must be used with --file-path.")
|
||||
|
||||
return workloadCmd
|
||||
}
|
||||
|
||||
func setWorkloadScanInfo(scanInfo *cautils.ScanInfo, kind string, name string) {
|
||||
scanInfo.SetScanType(cautils.ScanTypeWorkload)
|
||||
scanInfo.ScanImages = true
|
||||
|
||||
scanInfo.ScanObject = &objectsenvelopes.ScanObject{}
|
||||
scanInfo.ScanObject.SetNamespace(namespace)
|
||||
scanInfo.ScanObject.SetKind(kind)
|
||||
scanInfo.ScanObject.SetName(name)
|
||||
|
||||
scanInfo.SetPolicyIdentifiers([]string{"workloadscan"}, v1.KindFramework)
|
||||
|
||||
if scanInfo.FilePath != "" {
|
||||
scanInfo.InputPatterns = []string{scanInfo.FilePath}
|
||||
}
|
||||
}
|
||||
|
||||
func validateWorkloadIdentifier(workloadIdentifier string) error {
|
||||
// workloadIdentifier is in the form of kind/name
|
||||
x := strings.Split(workloadIdentifier, "/")
|
||||
if len(x) != 2 || x[0] == "" || x[1] == "" {
|
||||
return ErrInvalidWorkloadIdentifier
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseWorkloadIdentifierString(workloadIdentifier string) (kind, name string, err error) {
|
||||
// workloadIdentifier is in the form of namespace/kind/name
|
||||
// example: default/Deployment/nginx-deployment
|
||||
x := strings.Split(workloadIdentifier, "/")
|
||||
if len(x) != 2 {
|
||||
return "", "", ErrInvalidWorkloadIdentifier
|
||||
}
|
||||
|
||||
return x[0], x[1], nil
|
||||
}
|
||||
96
cmd/scan/workload_test.go
Normal file
96
cmd/scan/workload_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"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) {
|
||||
test := []struct {
|
||||
Description string
|
||||
kind string
|
||||
name string
|
||||
want *cautils.ScanInfo
|
||||
}{
|
||||
{
|
||||
Description: "Set workload scan info",
|
||||
kind: "Deployment",
|
||||
name: "test",
|
||||
want: &cautils.ScanInfo{
|
||||
PolicyIdentifier: []cautils.PolicyIdentifier{
|
||||
{
|
||||
Identifier: "workloadscan",
|
||||
Kind: v1.KindFramework,
|
||||
},
|
||||
},
|
||||
ScanType: cautils.ScanTypeWorkload,
|
||||
ScanObject: &objectsenvelopes.ScanObject{
|
||||
Kind: "Deployment",
|
||||
Metadata: objectsenvelopes.ScanObjectMetadata{
|
||||
Name: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range test {
|
||||
t.Run(
|
||||
tc.Description,
|
||||
func(t *testing.T) {
|
||||
scanInfo := &cautils.ScanInfo{}
|
||||
setWorkloadScanInfo(scanInfo, tc.kind, tc.name)
|
||||
|
||||
if scanInfo.ScanType != tc.want.ScanType {
|
||||
t.Errorf("got: %v, want: %v", scanInfo.ScanType, tc.want.ScanType)
|
||||
}
|
||||
|
||||
if scanInfo.ScanObject.Kind != tc.want.ScanObject.Kind {
|
||||
t.Errorf("got: %v, want: %v", scanInfo.ScanObject.Kind, tc.want.ScanObject.Kind)
|
||||
}
|
||||
|
||||
if scanInfo.ScanObject.Metadata.Name != tc.want.ScanObject.Metadata.Name {
|
||||
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 scanInfo.PolicyIdentifier[0].Identifier != tc.want.PolicyIdentifier[0].Identifier {
|
||||
t.Errorf("got: %v, want: %v", scanInfo.PolicyIdentifier[0].Identifier, tc.want.PolicyIdentifier[0].Identifier)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
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 error = 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))
|
||||
}
|
||||
124
cmd/shared/scan_test.go
Normal file
124
cmd/shared/scan_test.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
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) 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package submit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
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/spf13/cobra"
|
||||
)
|
||||
|
||||
func getExceptionsCmd(ks meta.IKubescape, submitInfo *metav1.Submit) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "exceptions <full path to exceptions file>",
|
||||
Short: "Submit exceptions to the Kubescape SaaS version",
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("missing full path to exceptions file")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
if err := ks.SubmitExceptions(context.TODO(), &submitInfo.Credentials, args[0]); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package submit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
logger "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/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
reporterv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter/v2"
|
||||
|
||||
"github.com/kubescape/rbac-utils/rbacscanner"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
rbacExamples = fmt.Sprintf(`
|
||||
# Submit cluster's Role-Based Access Control(RBAC)
|
||||
%[1]s submit rbac
|
||||
|
||||
# Submit cluster's Role-Based Access Control(RBAC) with account ID
|
||||
%[1]s submit rbac --account <account-id>
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
// getRBACCmd represents the RBAC command
|
||||
func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "rbac",
|
||||
Deprecated: "This command is deprecated and will not be supported after 1/Jan/2023. Please use the 'scan' command instead.",
|
||||
Example: rbacExamples,
|
||||
Short: "Submit cluster's Role-Based Access Control(RBAC)",
|
||||
Long: ``,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
|
||||
// get config
|
||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
|
||||
if err := clusterConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
if clusterConfig.GetAccountID() == "" {
|
||||
return fmt.Errorf("account ID is not set, run '%[1]s submit rbac --account <account-id>'", cautils.ExecName())
|
||||
}
|
||||
|
||||
// list RBAC
|
||||
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetAccountID(), clusterConfig.GetContextName()))
|
||||
|
||||
// submit resources
|
||||
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextRBAC)
|
||||
|
||||
submitInterfaces := cliinterfaces.SubmitInterfaces{
|
||||
ClusterConfig: clusterConfig,
|
||||
SubmitObjects: rbacObjects,
|
||||
Reporter: r,
|
||||
}
|
||||
|
||||
if err := ks.Submit(context.TODO(), submitInterfaces); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// getKubernetesApi
|
||||
func getKubernetesApi() *k8sinterface.KubernetesApi {
|
||||
if !k8sinterface.IsConnectedToCluster() {
|
||||
return nil
|
||||
}
|
||||
return k8sinterface.NewKubernetesApi()
|
||||
}
|
||||
func getTenantConfig(credentials *cautils.Credentials, clusterName string, customClusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
|
||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||
return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
|
||||
}
|
||||
return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
|
||||
}
|
||||
|
||||
// Check if the flag entered are valid
|
||||
func flagValidationSubmit(submitInfo *v1.Submit) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return submitInfo.Credentials.Validate()
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package submit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
reporterv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter/v2"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var formatVersion string
|
||||
|
||||
type ResultsObject struct {
|
||||
filePath string
|
||||
customerGUID string
|
||||
clusterName string
|
||||
}
|
||||
|
||||
func NewResultsObject(customerGUID, clusterName, filePath string) *ResultsObject {
|
||||
return &ResultsObject{
|
||||
filePath: filePath,
|
||||
customerGUID: customerGUID,
|
||||
clusterName: clusterName,
|
||||
}
|
||||
}
|
||||
|
||||
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandlingv2.PostureReport, error) {
|
||||
// load framework results from json file
|
||||
report, err := loadResultsFromFile(resultsObject.filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
|
||||
return map[string]workloadinterface.IMetadata{}, nil
|
||||
}
|
||||
|
||||
func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
var resultsCmd = &cobra.Command{
|
||||
Use: fmt.Sprintf("results <json file>\nExample:\n$ %[1]s submit results path/to/results.json --format-version v2", cautils.ExecName()),
|
||||
Short: "Submit a pre scanned results file. The file must be in json format",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("missing results file")
|
||||
}
|
||||
|
||||
k8s := getKubernetesApi()
|
||||
|
||||
// get config
|
||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
|
||||
if err := clusterConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
resultsObjects := NewResultsObject(clusterConfig.GetAccountID(), clusterConfig.GetContextName(), args[0])
|
||||
|
||||
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextScan)
|
||||
|
||||
submitInterfaces := cliinterfaces.SubmitInterfaces{
|
||||
ClusterConfig: clusterConfig,
|
||||
SubmitObjects: resultsObjects,
|
||||
Reporter: r,
|
||||
}
|
||||
|
||||
if err := ks.Submit(context.TODO(), submitInterfaces); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
resultsCmd.PersistentFlags().StringVar(&formatVersion, "format-version", "v2", "Output object can be different between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
|
||||
|
||||
return resultsCmd
|
||||
}
|
||||
func loadResultsFromFile(filePath string) (*reporthandlingv2.PostureReport, error) {
|
||||
report := &reporthandlingv2.PostureReport{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(f, report); err != nil {
|
||||
return report, fmt.Errorf("failed to unmarshal results file: %s, make sure you run kubescape with '--format=json --format-version=v2'", err.Error())
|
||||
}
|
||||
return report, nil
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package submit
|
||||
|
||||
import (
|
||||
"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/spf13/cobra"
|
||||
)
|
||||
|
||||
var submitCmdExamples = fmt.Sprintf(`
|
||||
# Submit Kubescape scan results file
|
||||
%[1]s submit results
|
||||
|
||||
# Submit exceptions file to Kubescape SaaS
|
||||
%[1]s submit exceptions
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetSubmitCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var submitInfo metav1.Submit
|
||||
|
||||
submitCmd := &cobra.Command{
|
||||
Use: "submit <command>",
|
||||
Short: "Submit an object to the Kubescape SaaS version",
|
||||
Long: ``,
|
||||
Example: submitCmdExamples,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
submitCmd.PersistentFlags().StringVarP(&submitInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
submitCmd.PersistentFlags().StringVarP(&submitInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
submitCmd.PersistentFlags().StringVarP(&submitInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
|
||||
submitCmd.AddCommand(getExceptionsCmd(ks, &submitInfo))
|
||||
submitCmd.AddCommand(getResultsCmd(ks, &submitInfo))
|
||||
submitCmd.AddCommand(getRBACCmd(ks, &submitInfo))
|
||||
|
||||
return submitCmd
|
||||
}
|
||||
@@ -6,58 +6,51 @@ package update
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
logger "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/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
installationLink string = "https://kubescape.io/docs/install-cli/"
|
||||
)
|
||||
|
||||
var updateCmdExamples = fmt.Sprintf(`
|
||||
# Update to the latest kubescape release
|
||||
%[1]s update
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetUpdateCmd() *cobra.Command {
|
||||
func GetUpdateCmd(ks meta.IKubescape) *cobra.Command {
|
||||
updateCmd := &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update your version",
|
||||
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(("You are in the latest version"))
|
||||
logger.L().Info("Nothing to update: you are running the latest version", helpers.String("Version", versioncheck.BuildNumber))
|
||||
} else {
|
||||
|
||||
const OSTYPE string = runtime.GOOS
|
||||
var ShellToUse string
|
||||
switch OSTYPE {
|
||||
|
||||
case "windows":
|
||||
cautils.StartSpinner()
|
||||
//run the installation command for windows
|
||||
ShellToUse = "powershell"
|
||||
_, err := exec.Command(ShellToUse, "-c", "iwr -useb https://raw.githubusercontent.com/kubescape/kubescape/master/install.ps1 | iex").Output()
|
||||
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
cautils.StopSpinner()
|
||||
|
||||
default:
|
||||
ShellToUse = "bash"
|
||||
cautils.StartSpinner()
|
||||
//run the installation command for linux and macOS
|
||||
_, err := exec.Command(ShellToUse, "-c", "curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash").Output()
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
cautils.StopSpinner()
|
||||
}
|
||||
fmt.Printf("Version %s is available. Please refer to our installation documentation: %s\n", versioncheck.LatestReleaseVersion, installationLink)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
235
cmd/vap/vap.go
Normal file
235
cmd/vap/vap.go
Normal file
@@ -0,0 +1,235 @@
|
||||
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)}
|
||||
if paramRefName != "" {
|
||||
policyBinding.Spec.ParamRef = &admissionv1.ParamRef{
|
||||
Name: paramRefName,
|
||||
}
|
||||
}
|
||||
// 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,27 +1,29 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"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) *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,
|
||||
"Your current version is: %s [git enabled in build: %t]\n",
|
||||
cautils.BuildNumber,
|
||||
isGitEnabled(),
|
||||
v := versioncheck.NewIVersionCheckHandler(ks.Context())
|
||||
versionCheckRequest := versioncheck.NewVersionCheckRequest("", versioncheck.BuildNumber, "", "", "version", nil)
|
||||
if err := v.CheckLatestVersion(ks.Context(), versionCheckRequest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(cmd.OutOrStdout(),
|
||||
"Your current version is: %s\n",
|
||||
versionCheckRequest.ClientVersion,
|
||||
)
|
||||
return nil
|
||||
},
|
||||
|
||||
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: "",
|
||||
want: "Your current version is: unknown\n",
|
||||
},
|
||||
{
|
||||
name: "Defined Build Number: v3.0.1",
|
||||
buildNumber: "v3.0.1",
|
||||
want: "Your current version is: v3.0.1\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); 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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
21
core/cautils/buildinfo.go
Normal file
21
core/cautils/buildinfo.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/kubescape/backend/pkg/versioncheck"
|
||||
)
|
||||
|
||||
var BuildNumber string
|
||||
var Client string
|
||||
|
||||
func init() {
|
||||
if BuildNumber != "" {
|
||||
versioncheck.BuildNumber = BuildNumber
|
||||
} else {
|
||||
versioncheck.BuildNumber = os.Getenv("RELEASE")
|
||||
}
|
||||
if Client != "" {
|
||||
versioncheck.Client = Client
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -3,21 +3,40 @@ package cautils
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"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"
|
||||
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 = "config"
|
||||
const (
|
||||
configFileName string = "config"
|
||||
kubescapeNamespace string = "kubescape"
|
||||
|
||||
kubescapeConfigMapName string = "kubescape-config" // deprecated - for backward compatibility
|
||||
kubescapeCloudConfigMapName string = "ks-cloud-config" // deprecated - for backward compatibility
|
||||
|
||||
cloudConfigMapLabelSelector string = "kubescape.io/infra=config"
|
||||
credsLabelSelectors string = "kubescape.io/infra=credentials" //nolint:gosec
|
||||
|
||||
// env vars
|
||||
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"
|
||||
)
|
||||
|
||||
func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName + ".json") }
|
||||
|
||||
@@ -26,17 +45,11 @@ func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName +
|
||||
// ======================================================================================
|
||||
|
||||
type ConfigObj struct {
|
||||
AccountID string `json:"accountID,omitempty"`
|
||||
ClientID string `json:"clientID,omitempty"`
|
||||
SecretKey string `json:"secretKey,omitempty"`
|
||||
CustomerGUID string `json:"customerGUID,omitempty"` // Deprecated
|
||||
Token string `json:"invitationParam,omitempty"`
|
||||
CustomerAdminEMail string `json:"adminMail,omitempty"`
|
||||
ClusterName string `json:"clusterName,omitempty"`
|
||||
CloudReportURL string `json:"cloudReportURL,omitempty"`
|
||||
CloudAPIURL string `json:"cloudAPIURL,omitempty"`
|
||||
CloudUIURL string `json:"cloudUIURL,omitempty"`
|
||||
CloudAuthURL string `json:"cloudAuthURL,omitempty"`
|
||||
AccountID string `json:"accountID,omitempty"`
|
||||
ClusterName string `json:"clusterName,omitempty"`
|
||||
CloudReportURL string `json:"cloudReportURL,omitempty"`
|
||||
CloudAPIURL string `json:"cloudAPIURL,omitempty"`
|
||||
AccessKey string `json:"accessKey,omitempty"`
|
||||
}
|
||||
|
||||
// Config - convert ConfigObj to config file
|
||||
@@ -44,17 +57,11 @@ func (co *ConfigObj) Config() []byte {
|
||||
|
||||
// remove cluster name before saving to file
|
||||
clusterName := co.ClusterName
|
||||
customerAdminEMail := co.CustomerAdminEMail
|
||||
token := co.Token
|
||||
co.ClusterName = ""
|
||||
co.Token = ""
|
||||
co.CustomerAdminEMail = ""
|
||||
|
||||
b, err := json.MarshalIndent(co, "", " ")
|
||||
|
||||
co.ClusterName = clusterName
|
||||
co.CustomerAdminEMail = customerAdminEMail
|
||||
co.Token = token
|
||||
|
||||
if err == nil {
|
||||
return b
|
||||
@@ -63,31 +70,39 @@ func (co *ConfigObj) Config() []byte {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
func (co *ConfigObj) updateEmptyFields(inCO *ConfigObj) error {
|
||||
if inCO.AccountID != "" {
|
||||
co.AccountID = inCO.AccountID
|
||||
}
|
||||
if inCO.CloudAPIURL != "" {
|
||||
co.CloudAPIURL = inCO.CloudAPIURL
|
||||
}
|
||||
if inCO.CloudReportURL != "" {
|
||||
co.CloudReportURL = inCO.CloudReportURL
|
||||
}
|
||||
if inCO.ClusterName != "" {
|
||||
co.ClusterName = inCO.ClusterName
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// =============================== interface ============================================
|
||||
// ======================================================================================
|
||||
type ITenantConfig interface {
|
||||
// set
|
||||
SetTenant() error
|
||||
UpdateCachedConfig() error
|
||||
DeleteCachedConfig(ctx context.Context) error
|
||||
GenerateAccountID() (string, error)
|
||||
DeleteCredentials() error
|
||||
|
||||
// getters
|
||||
GetContextName() string
|
||||
GetAccountID() string
|
||||
GetTenantEmail() string
|
||||
GetToken() string
|
||||
GetClientID() string
|
||||
GetSecretKey() string
|
||||
GetAccessKey() string
|
||||
GetConfigObj() *ConfigObj
|
||||
GetCloudReportURL() string
|
||||
GetCloudAPIURL() string
|
||||
GetCloudUIURL() string
|
||||
GetCloudAuthURL() string
|
||||
// GetBackendAPI() getter.IBackend
|
||||
// GenerateURL()
|
||||
|
||||
IsConfigFound() bool
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
@@ -98,23 +113,19 @@ type ITenantConfig interface {
|
||||
var _ ITenantConfig = &LocalConfig{}
|
||||
|
||||
type LocalConfig struct {
|
||||
backendAPI getter.IBackend
|
||||
configObj *ConfigObj
|
||||
configObj *ConfigObj
|
||||
}
|
||||
|
||||
func NewLocalConfig(
|
||||
backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *LocalConfig {
|
||||
|
||||
func NewLocalConfig(accountID, accessKey, clusterName, customClusterName string) *LocalConfig {
|
||||
lc := &LocalConfig{
|
||||
backendAPI: backendAPI,
|
||||
configObj: &ConfigObj{},
|
||||
configObj: &ConfigObj{},
|
||||
}
|
||||
// get from configMap
|
||||
if existsConfigFile() { // get from file
|
||||
loadConfigFromFile(lc.configObj)
|
||||
}
|
||||
|
||||
updateCredentials(lc.configObj, credentials)
|
||||
updateCredentials(lc.configObj, accountID, accessKey)
|
||||
updateCloudURLs(lc.configObj)
|
||||
|
||||
// If a custom cluster name is provided then set that name, else use the cluster's original name
|
||||
@@ -124,59 +135,32 @@ func NewLocalConfig(
|
||||
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
|
||||
}
|
||||
|
||||
lc.backendAPI.SetAccountID(lc.configObj.AccountID)
|
||||
lc.backendAPI.SetClientID(lc.configObj.ClientID)
|
||||
lc.backendAPI.SetSecretKey(lc.configObj.SecretKey)
|
||||
if lc.configObj.CloudAPIURL != "" {
|
||||
lc.backendAPI.SetCloudAPIURL(lc.configObj.CloudAPIURL)
|
||||
} else {
|
||||
lc.configObj.CloudAPIURL = lc.backendAPI.GetCloudAPIURL()
|
||||
}
|
||||
if lc.configObj.CloudAuthURL != "" {
|
||||
lc.backendAPI.SetCloudAuthURL(lc.configObj.CloudAuthURL)
|
||||
} else {
|
||||
lc.configObj.CloudAuthURL = lc.backendAPI.GetCloudAuthURL()
|
||||
}
|
||||
if lc.configObj.CloudReportURL != "" {
|
||||
lc.backendAPI.SetCloudReportURL(lc.configObj.CloudReportURL)
|
||||
} else {
|
||||
lc.configObj.CloudReportURL = lc.backendAPI.GetCloudReportURL()
|
||||
}
|
||||
if lc.configObj.CloudUIURL != "" {
|
||||
lc.backendAPI.SetCloudUIURL(lc.configObj.CloudUIURL)
|
||||
} else {
|
||||
lc.configObj.CloudUIURL = lc.backendAPI.GetCloudUIURL()
|
||||
}
|
||||
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", lc.backendAPI.GetCloudAPIURL()), helpers.String("auth", lc.backendAPI.GetCloudAuthURL()), helpers.String("report", lc.backendAPI.GetCloudReportURL()), helpers.String("UI", lc.backendAPI.GetCloudUIURL()))
|
||||
|
||||
initializeCloudAPI(lc)
|
||||
|
||||
return lc
|
||||
}
|
||||
|
||||
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
|
||||
func (lc *LocalConfig) GetTenantEmail() string { return lc.configObj.CustomerAdminEMail }
|
||||
func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountID }
|
||||
func (lc *LocalConfig) GetClientID() string { return lc.configObj.ClientID }
|
||||
func (lc *LocalConfig) GetSecretKey() string { return lc.configObj.SecretKey }
|
||||
func (lc *LocalConfig) GetContextName() string { return lc.configObj.ClusterName }
|
||||
func (lc *LocalConfig) GetToken() string { return lc.configObj.Token }
|
||||
func (lc *LocalConfig) GetCloudReportURL() string { return lc.configObj.CloudReportURL }
|
||||
func (lc *LocalConfig) GetCloudAPIURL() string { return lc.configObj.CloudAPIURL }
|
||||
func (lc *LocalConfig) GetCloudUIURL() string { return lc.configObj.CloudUIURL }
|
||||
func (lc *LocalConfig) GetCloudAuthURL() string { return lc.configObj.CloudAuthURL }
|
||||
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
|
||||
func (lc *LocalConfig) SetTenant() error {
|
||||
|
||||
// Kubescape Cloud tenant GUID
|
||||
if err := getTenantConfigFromBE(lc.backendAPI, lc.configObj); err != nil {
|
||||
return err
|
||||
}
|
||||
lc.UpdateCachedConfig()
|
||||
return nil
|
||||
func (lc *LocalConfig) GetAccessKey() string { return lc.configObj.AccessKey }
|
||||
|
||||
func (lc *LocalConfig) GenerateAccountID() (string, error) {
|
||||
lc.configObj.AccountID = uuid.NewString()
|
||||
err := lc.UpdateCachedConfig()
|
||||
return lc.configObj.AccountID, err
|
||||
}
|
||||
|
||||
func (lc *LocalConfig) DeleteCredentials() error {
|
||||
lc.configObj.AccessKey = ""
|
||||
lc.configObj.AccountID = ""
|
||||
return lc.UpdateCachedConfig()
|
||||
}
|
||||
|
||||
func (lc *LocalConfig) UpdateCachedConfig() error {
|
||||
logger.L().Debug("updating cached config", helpers.Interface("configObj", lc.configObj))
|
||||
return updateConfigFile(lc.configObj)
|
||||
}
|
||||
|
||||
@@ -187,26 +171,6 @@ func (lc *LocalConfig) DeleteCachedConfig(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTenantConfigFromBE(backendAPI getter.IBackend, configObj *ConfigObj) error {
|
||||
|
||||
// get from Kubescape Cloud API
|
||||
tenantResponse, err := backendAPI.GetTenant()
|
||||
if err == nil && tenantResponse != nil {
|
||||
if tenantResponse.AdminMail != "" { // registered tenant
|
||||
configObj.CustomerAdminEMail = tenantResponse.AdminMail
|
||||
} else { // new tenant
|
||||
configObj.Token = tenantResponse.Token
|
||||
configObj.AccountID = tenantResponse.TenantID
|
||||
}
|
||||
} else {
|
||||
if err != nil && !strings.Contains(err.Error(), "already exists") {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// ========================== Cluster Config ============================================
|
||||
// ======================================================================================
|
||||
@@ -219,8 +183,6 @@ KS_DEFAULT_CONFIGMAP_NAME // name of configmap, if not set default is 'kubescap
|
||||
KS_DEFAULT_CONFIGMAP_NAMESPACE // configmap namespace, if not set default is 'default'
|
||||
|
||||
KS_ACCOUNT_ID
|
||||
KS_CLIENT_ID
|
||||
KS_SECRET_KEY
|
||||
|
||||
TODO - support:
|
||||
KS_CACHE // path to cached files
|
||||
@@ -228,33 +190,30 @@ KS_CACHE // path to cached files
|
||||
var _ ITenantConfig = &ClusterConfig{}
|
||||
|
||||
type ClusterConfig struct {
|
||||
backendAPI getter.IBackend
|
||||
k8s *k8sinterface.KubernetesApi
|
||||
configObj *ConfigObj
|
||||
configMapName string
|
||||
configMapNamespace string
|
||||
}
|
||||
|
||||
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *ClusterConfig {
|
||||
// var configObj *ConfigObj
|
||||
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, accessKey, clusterName, customClusterName string) *ClusterConfig {
|
||||
c := &ClusterConfig{
|
||||
k8s: k8s,
|
||||
backendAPI: backendAPI,
|
||||
configObj: &ConfigObj{},
|
||||
configMapName: getConfigMapName(),
|
||||
configMapNamespace: getConfigMapNamespace(),
|
||||
configMapNamespace: GetConfigMapNamespace(),
|
||||
}
|
||||
|
||||
// first, load from configMap
|
||||
if c.existsConfigMap() {
|
||||
c.loadConfigFromConfigMap()
|
||||
}
|
||||
|
||||
// second, load from file
|
||||
// first, load from file
|
||||
if existsConfigFile() { // get from file
|
||||
loadConfigFromFile(c.configObj)
|
||||
}
|
||||
updateCredentials(c.configObj, credentials)
|
||||
|
||||
// second, load urls from config map
|
||||
c.updateConfigEmptyFieldsFromKubescapeConfigMap()
|
||||
|
||||
// third, credentials from secret
|
||||
c.updateConfigEmptyFieldsFromCredentialsSecret()
|
||||
|
||||
updateCredentials(c.configObj, accountID, accessKey)
|
||||
updateCloudURLs(c.configObj)
|
||||
|
||||
// If a custom cluster name is provided then set that name, else use the cluster's original name
|
||||
@@ -269,80 +228,23 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
|
||||
} else { // override the cluster name if it has unwanted characters
|
||||
c.configObj.ClusterName = AdoptClusterName(c.configObj.ClusterName)
|
||||
}
|
||||
|
||||
c.backendAPI.SetAccountID(c.configObj.AccountID)
|
||||
c.backendAPI.SetClientID(c.configObj.ClientID)
|
||||
c.backendAPI.SetSecretKey(c.configObj.SecretKey)
|
||||
if c.configObj.CloudAPIURL != "" {
|
||||
c.backendAPI.SetCloudAPIURL(c.configObj.CloudAPIURL)
|
||||
} else {
|
||||
c.configObj.CloudAPIURL = c.backendAPI.GetCloudAPIURL()
|
||||
}
|
||||
if c.configObj.CloudAuthURL != "" {
|
||||
c.backendAPI.SetCloudAuthURL(c.configObj.CloudAuthURL)
|
||||
} else {
|
||||
c.configObj.CloudAuthURL = c.backendAPI.GetCloudAuthURL()
|
||||
}
|
||||
if c.configObj.CloudReportURL != "" {
|
||||
c.backendAPI.SetCloudReportURL(c.configObj.CloudReportURL)
|
||||
} else {
|
||||
c.configObj.CloudReportURL = c.backendAPI.GetCloudReportURL()
|
||||
}
|
||||
if c.configObj.CloudUIURL != "" {
|
||||
c.backendAPI.SetCloudUIURL(c.configObj.CloudUIURL)
|
||||
} else {
|
||||
c.configObj.CloudUIURL = c.backendAPI.GetCloudUIURL()
|
||||
}
|
||||
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", c.backendAPI.GetCloudAPIURL()), helpers.String("auth", c.backendAPI.GetCloudAuthURL()), helpers.String("report", c.backendAPI.GetCloudReportURL()), helpers.String("UI", c.backendAPI.GetCloudUIURL()))
|
||||
|
||||
initializeCloudAPI(c)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
|
||||
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
|
||||
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
|
||||
func (c *ClusterConfig) GetClientID() string { return c.configObj.ClientID }
|
||||
func (c *ClusterConfig) GetSecretKey() string { return c.configObj.SecretKey }
|
||||
func (c *ClusterConfig) GetTenantEmail() string { return c.configObj.CustomerAdminEMail }
|
||||
func (c *ClusterConfig) GetToken() string { return c.configObj.Token }
|
||||
func (c *ClusterConfig) GetCloudReportURL() string { return c.configObj.CloudReportURL }
|
||||
func (c *ClusterConfig) GetCloudAPIURL() string { return c.configObj.CloudAPIURL }
|
||||
func (c *ClusterConfig) GetCloudUIURL() string { return c.configObj.CloudUIURL }
|
||||
func (c *ClusterConfig) GetCloudAuthURL() string { return c.configObj.CloudAuthURL }
|
||||
|
||||
func (c *ClusterConfig) IsConfigFound() bool { return existsConfigFile() || c.existsConfigMap() }
|
||||
|
||||
func (c *ClusterConfig) SetTenant() error {
|
||||
|
||||
// ARMO tenant GUID
|
||||
if err := getTenantConfigFromBE(c.backendAPI, c.configObj); err != nil {
|
||||
return err
|
||||
}
|
||||
c.UpdateCachedConfig()
|
||||
return nil
|
||||
|
||||
}
|
||||
func (c *ClusterConfig) GetAccessKey() string { return c.configObj.AccessKey }
|
||||
|
||||
func (c *ClusterConfig) UpdateCachedConfig() error {
|
||||
// update/create config
|
||||
if c.existsConfigMap() {
|
||||
if err := c.updateConfigMap(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := c.createConfigMap(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
logger.L().Debug("updating cached config", helpers.Interface("configObj", c.configObj))
|
||||
return updateConfigFile(c.configObj)
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) DeleteCachedConfig(ctx context.Context) error {
|
||||
if err := c.deleteConfigMap(); err != nil {
|
||||
logger.L().Ctx(ctx).Warning(err.Error())
|
||||
}
|
||||
if err := DeleteConfigFile(); err != nil {
|
||||
logger.L().Ctx(ctx).Warning(err.Error())
|
||||
}
|
||||
@@ -359,13 +261,87 @@ func (c *ClusterConfig) ToMapString() map[string]interface{} {
|
||||
}
|
||||
return m
|
||||
}
|
||||
func (c *ClusterConfig) loadConfigFromConfigMap() error {
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
|
||||
func (c *ClusterConfig) updateConfigEmptyFieldsFromKubescapeConfigMap() error {
|
||||
configMaps, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).List(context.Background(), metav1.ListOptions{
|
||||
LabelSelector: cloudConfigMapLabelSelector,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ksConfigMap *corev1.ConfigMap
|
||||
var urlsConfigMap *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{})
|
||||
urlsConfigMap, _ = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), kubescapeCloudConfigMapName, metav1.GetOptions{})
|
||||
} else {
|
||||
// use the first configmap with the label
|
||||
ksConfigMap = &configMaps.Items[0]
|
||||
urlsConfigMap = &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)
|
||||
}
|
||||
}
|
||||
|
||||
if urlsConfigMap != nil {
|
||||
if jsonConf, ok := urlsConfigMap.Data["services"]; ok {
|
||||
services, err := servicediscovery.GetServices(
|
||||
servicediscoveryv2.NewServiceDiscoveryStreamV2([]byte(jsonConf)),
|
||||
)
|
||||
if err != nil {
|
||||
// try to parse as v1
|
||||
services, err = servicediscovery.GetServices(
|
||||
servicediscoveryv1.NewServiceDiscoveryStreamV1([]byte(jsonConf)),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if services.GetApiServerUrl() != "" {
|
||||
c.configObj.CloudAPIURL = services.GetApiServerUrl()
|
||||
}
|
||||
if services.GetReportReceiverHttpUrl() != "" {
|
||||
c.configObj.CloudReportURL = services.GetReportReceiverHttpUrl()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return loadConfigFromData(c.configObj, configMap.Data)
|
||||
if len(secrets.Items) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func loadConfigFromData(co *ConfigObj, data map[string]string) error {
|
||||
@@ -379,107 +355,32 @@ func loadConfigFromData(co *ConfigObj, data map[string]string) error {
|
||||
|
||||
return e
|
||||
}
|
||||
func (c *ClusterConfig) existsConfigMap() bool {
|
||||
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
// TODO - check if has customerGUID
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) GetValueByKeyFromConfigMap(key string) (string, error) {
|
||||
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if val, ok := configMap.Data[key]; ok {
|
||||
return val, nil
|
||||
} else {
|
||||
return "", fmt.Errorf("value does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
func GetValueFromConfigJson(key string) (string, error) {
|
||||
data, err := os.ReadFile(ConfigFileFullPath())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var obj map[string]interface{}
|
||||
if err := json.Unmarshal(data, &obj); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if val, ok := obj[key]; ok {
|
||||
return fmt.Sprint(val), nil
|
||||
} else {
|
||||
return "", fmt.Errorf("value does not exist")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) SetKeyValueInConfigmap(key string, value string) error {
|
||||
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
configMap = &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: c.configMapName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if len(configMap.Data) == 0 {
|
||||
configMap.Data = make(map[string]string)
|
||||
}
|
||||
|
||||
configMap.Data[key] = value
|
||||
|
||||
if err != nil {
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
|
||||
} else {
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func existsConfigFile() bool {
|
||||
_, err := os.ReadFile(ConfigFileFullPath())
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) createConfigMap() error {
|
||||
if c.k8s == nil {
|
||||
return nil
|
||||
}
|
||||
configMap := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: c.configMapName,
|
||||
},
|
||||
}
|
||||
c.updateConfigData(configMap)
|
||||
|
||||
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) updateConfigMap() error {
|
||||
if c.k8s == nil {
|
||||
return nil
|
||||
}
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
func updateConfigFile(configObj *ConfigObj) error {
|
||||
fullPath := ConfigFileFullPath()
|
||||
dir := filepath.Dir(fullPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.updateConfigData(configMap)
|
||||
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
|
||||
return err
|
||||
return os.WriteFile(fullPath, configObj.Config(), 0664) //nolint:gosec
|
||||
}
|
||||
|
||||
func updateConfigFile(configObj *ConfigObj) error {
|
||||
return os.WriteFile(ConfigFileFullPath(), configObj.Config(), 0664) //nolint:gosec
|
||||
func (c *ClusterConfig) GenerateAccountID() (string, error) {
|
||||
c.configObj.AccountID = uuid.NewString()
|
||||
err := c.UpdateCachedConfig()
|
||||
return c.configObj.AccountID, err
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) DeleteCredentials() error {
|
||||
c.configObj.AccountID = ""
|
||||
c.configObj.AccessKey = ""
|
||||
return c.UpdateCachedConfig()
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {
|
||||
@@ -509,35 +410,9 @@ func readConfig(dat []byte, configObj *ConfigObj) error {
|
||||
if err := json.Unmarshal(dat, configObj); err != nil {
|
||||
return err
|
||||
}
|
||||
if configObj.AccountID == "" {
|
||||
configObj.AccountID = configObj.CustomerGUID
|
||||
}
|
||||
configObj.CustomerGUID = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the customer is submitted
|
||||
func (clusterConfig *ClusterConfig) IsSubmitted() bool {
|
||||
return clusterConfig.existsConfigMap() || existsConfigFile()
|
||||
}
|
||||
|
||||
// Check if the customer is registered
|
||||
func (clusterConfig *ClusterConfig) IsRegistered() bool {
|
||||
|
||||
// get from armoBE
|
||||
tenantResponse, err := clusterConfig.backendAPI.GetTenant()
|
||||
if err == nil && tenantResponse != nil {
|
||||
if tenantResponse.AdminMail != "" { // this customer already belongs to some user
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (clusterConfig *ClusterConfig) deleteConfigMap() error {
|
||||
return clusterConfig.k8s.KubernetesClient.CoreV1().ConfigMaps(clusterConfig.configMapNamespace).Delete(context.Background(), clusterConfig.configMapName, metav1.DeleteOptions{})
|
||||
}
|
||||
|
||||
func DeleteConfigFile() error {
|
||||
return os.Remove(ConfigFileFullPath())
|
||||
}
|
||||
@@ -550,66 +425,42 @@ func AdoptClusterName(clusterName string) string {
|
||||
return re.ReplaceAllString(clusterName, "-")
|
||||
}
|
||||
|
||||
func getConfigMapName() string {
|
||||
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAME"); n != "" {
|
||||
// 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 != "" {
|
||||
return n
|
||||
}
|
||||
return "kubescape"
|
||||
return kubescapeNamespace
|
||||
}
|
||||
|
||||
func getConfigMapNamespace() string {
|
||||
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAMESPACE"); n != "" {
|
||||
return n
|
||||
}
|
||||
return "default"
|
||||
}
|
||||
|
||||
func getAccountFromEnv(credentials *Credentials) {
|
||||
// load from env
|
||||
if accountID := os.Getenv("KS_ACCOUNT_ID"); credentials.Account == "" && accountID != "" {
|
||||
credentials.Account = accountID
|
||||
}
|
||||
if clientID := os.Getenv("KS_CLIENT_ID"); credentials.ClientID == "" && clientID != "" {
|
||||
credentials.ClientID = clientID
|
||||
}
|
||||
if secretKey := os.Getenv("KS_SECRET_KEY"); credentials.SecretKey == "" && secretKey != "" {
|
||||
credentials.SecretKey = secretKey
|
||||
}
|
||||
}
|
||||
|
||||
func updateCredentials(configObj *ConfigObj, credentials *Credentials) {
|
||||
|
||||
if credentials == nil {
|
||||
credentials = &Credentials{}
|
||||
}
|
||||
getAccountFromEnv(credentials)
|
||||
|
||||
if credentials.Account != "" {
|
||||
configObj.AccountID = credentials.Account // override config Account
|
||||
}
|
||||
if credentials.ClientID != "" {
|
||||
configObj.ClientID = credentials.ClientID // override config ClientID
|
||||
}
|
||||
if credentials.SecretKey != "" {
|
||||
configObj.SecretKey = credentials.SecretKey // override config SecretKey
|
||||
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
|
||||
}
|
||||
|
||||
if envAccountID := os.Getenv(accountIdEnvVar); envAccountID != "" {
|
||||
configObj.AccountID = envAccountID
|
||||
}
|
||||
}
|
||||
|
||||
func getCloudURLsFromEnv(cloudURLs *CloudURLs) {
|
||||
// load from env
|
||||
if cloudAPIURL := os.Getenv("KS_CLOUD_API_URL"); cloudAPIURL != "" {
|
||||
if cloudAPIURL := os.Getenv(cloudApiUrlEnvVar); cloudAPIURL != "" {
|
||||
logger.L().Debug("cloud API URL updated from env var", helpers.Interface(cloudApiUrlEnvVar, cloudAPIURL))
|
||||
cloudURLs.CloudAPIURL = cloudAPIURL
|
||||
}
|
||||
if cloudAuthURL := os.Getenv("KS_CLOUD_AUTH_URL"); cloudAuthURL != "" {
|
||||
cloudURLs.CloudAuthURL = cloudAuthURL
|
||||
}
|
||||
if cloudReportURL := os.Getenv("KS_CLOUD_REPORT_URL"); cloudReportURL != "" {
|
||||
if cloudReportURL := os.Getenv(cloudReportUrlEnvVar); cloudReportURL != "" {
|
||||
logger.L().Debug("cloud Report URL updated from env var", helpers.Interface(cloudReportUrlEnvVar, cloudReportURL))
|
||||
cloudURLs.CloudReportURL = cloudReportURL
|
||||
}
|
||||
if cloudUIURL := os.Getenv("KS_CLOUD_UI_URL"); cloudUIURL != "" {
|
||||
cloudURLs.CloudUIURL = cloudUIURL
|
||||
}
|
||||
}
|
||||
|
||||
func updateCloudURLs(configObj *ConfigObj) {
|
||||
@@ -620,26 +471,59 @@ func updateCloudURLs(configObj *ConfigObj) {
|
||||
if cloudURLs.CloudAPIURL != "" {
|
||||
configObj.CloudAPIURL = cloudURLs.CloudAPIURL // override config CloudAPIURL
|
||||
}
|
||||
if cloudURLs.CloudAuthURL != "" {
|
||||
configObj.CloudAuthURL = cloudURLs.CloudAuthURL // override config CloudAuthURL
|
||||
}
|
||||
if cloudURLs.CloudReportURL != "" {
|
||||
configObj.CloudReportURL = cloudURLs.CloudReportURL // override config CloudReportURL
|
||||
}
|
||||
if cloudURLs.CloudUIURL != "" {
|
||||
configObj.CloudUIURL = cloudURLs.CloudUIURL // override config CloudUIURL
|
||||
|
||||
}
|
||||
|
||||
func initializeCloudAPI(c ITenantConfig) *v1.KSCloudAPI {
|
||||
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)
|
||||
}
|
||||
|
||||
return getter.GetKSCloudAPIConnector()
|
||||
}
|
||||
|
||||
func initializeCloudAPI(c ITenantConfig) {
|
||||
cloud := getter.GetKSCloudAPIConnector()
|
||||
cloud.SetAccountID(c.GetAccountID())
|
||||
cloud.SetClientID(c.GetClientID())
|
||||
cloud.SetSecretKey(c.GetSecretKey())
|
||||
cloud.SetCloudAuthURL(c.GetCloudAuthURL())
|
||||
cloud.SetCloudReportURL(c.GetCloudReportURL())
|
||||
cloud.SetCloudUIURL(c.GetCloudUIURL())
|
||||
cloud.SetCloudAPIURL(c.GetCloudAPIURL())
|
||||
getter.SetKSCloudAPIConnector(cloud)
|
||||
func GetTenantConfig(accountID, accessKey, clusterName, customClusterName string, k8s *k8sinterface.KubernetesApi) ITenantConfig {
|
||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||
return NewLocalConfig(accountID, accessKey, clusterName, customClusterName)
|
||||
}
|
||||
return NewClusterConfig(k8s, accountID, accessKey, clusterName, customClusterName)
|
||||
}
|
||||
|
||||
// firstNonEmpty returns the first non-empty string
|
||||
func firstNonEmpty(s1, s2 string) string {
|
||||
if s1 != "" {
|
||||
return s1
|
||||
}
|
||||
return s2
|
||||
}
|
||||
|
||||
@@ -5,36 +5,28 @@ 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"
|
||||
)
|
||||
|
||||
func mockConfigObj() *ConfigObj {
|
||||
return &ConfigObj{
|
||||
AccountID: "aaa",
|
||||
ClientID: "bbb",
|
||||
SecretKey: "ccc",
|
||||
ClusterName: "ddd",
|
||||
CustomerAdminEMail: "ab@cd",
|
||||
Token: "eee",
|
||||
CloudReportURL: "report.armo.cloud",
|
||||
CloudAPIURL: "api.armosec.io",
|
||||
CloudUIURL: "cloud.armosec.io",
|
||||
CloudAuthURL: "auth.armosec.io",
|
||||
AccountID: "aaa",
|
||||
ClusterName: "ddd",
|
||||
CloudReportURL: "report.domain.com",
|
||||
CloudAPIURL: "api.domain.com",
|
||||
}
|
||||
}
|
||||
func mockLocalConfig() *LocalConfig {
|
||||
return &LocalConfig{
|
||||
backendAPI: nil,
|
||||
configObj: mockConfigObj(),
|
||||
configObj: mockConfigObj(),
|
||||
}
|
||||
}
|
||||
|
||||
func mockClusterConfig() *ClusterConfig {
|
||||
return &ClusterConfig{
|
||||
backendAPI: nil,
|
||||
configObj: mockConfigObj(),
|
||||
configObj: mockConfigObj(),
|
||||
}
|
||||
}
|
||||
func TestConfig(t *testing.T) {
|
||||
@@ -43,15 +35,9 @@ func TestConfig(t *testing.T) {
|
||||
|
||||
assert.NoError(t, json.Unmarshal(co.Config(), &cop))
|
||||
assert.Equal(t, co.AccountID, cop.AccountID)
|
||||
assert.Equal(t, co.ClientID, cop.ClientID)
|
||||
assert.Equal(t, co.SecretKey, cop.SecretKey)
|
||||
assert.Equal(t, co.CloudReportURL, cop.CloudReportURL)
|
||||
assert.Equal(t, co.CloudAPIURL, cop.CloudAPIURL)
|
||||
assert.Equal(t, co.CloudUIURL, cop.CloudUIURL)
|
||||
assert.Equal(t, co.CloudAuthURL, cop.CloudAuthURL)
|
||||
assert.Equal(t, "", cop.ClusterName) // Not copied to bytes
|
||||
assert.Equal(t, "", cop.CustomerAdminEMail) // Not copied to bytes
|
||||
assert.Equal(t, "", cop.Token) // Not copied to bytes
|
||||
assert.Equal(t, "", cop.ClusterName) // Not copied to bytes
|
||||
|
||||
}
|
||||
|
||||
@@ -65,27 +51,15 @@ func TestITenantConfig(t *testing.T) {
|
||||
|
||||
// test LocalConfig methods
|
||||
assert.Equal(t, co.AccountID, lc.GetAccountID())
|
||||
assert.Equal(t, co.ClientID, lc.GetClientID())
|
||||
assert.Equal(t, co.SecretKey, lc.GetSecretKey())
|
||||
assert.Equal(t, co.ClusterName, lc.GetContextName())
|
||||
assert.Equal(t, co.CustomerAdminEMail, lc.GetTenantEmail())
|
||||
assert.Equal(t, co.Token, lc.GetToken())
|
||||
assert.Equal(t, co.CloudReportURL, lc.GetCloudReportURL())
|
||||
assert.Equal(t, co.CloudAPIURL, lc.GetCloudAPIURL())
|
||||
assert.Equal(t, co.CloudUIURL, lc.GetCloudUIURL())
|
||||
assert.Equal(t, co.CloudAuthURL, lc.GetCloudAuthURL())
|
||||
|
||||
// test ClusterConfig methods
|
||||
assert.Equal(t, co.AccountID, c.GetAccountID())
|
||||
assert.Equal(t, co.ClientID, c.GetClientID())
|
||||
assert.Equal(t, co.SecretKey, c.GetSecretKey())
|
||||
assert.Equal(t, co.ClusterName, c.GetContextName())
|
||||
assert.Equal(t, co.CustomerAdminEMail, c.GetTenantEmail())
|
||||
assert.Equal(t, co.Token, c.GetToken())
|
||||
assert.Equal(t, co.CloudReportURL, c.GetCloudReportURL())
|
||||
assert.Equal(t, co.CloudAPIURL, c.GetCloudAPIURL())
|
||||
assert.Equal(t, co.CloudUIURL, c.GetCloudUIURL())
|
||||
assert.Equal(t, co.CloudAuthURL, c.GetCloudAuthURL())
|
||||
}
|
||||
|
||||
func TestUpdateConfigData(t *testing.T) {
|
||||
@@ -96,12 +70,8 @@ func TestUpdateConfigData(t *testing.T) {
|
||||
c.updateConfigData(configMap)
|
||||
|
||||
assert.Equal(t, c.GetAccountID(), configMap.Data["accountID"])
|
||||
assert.Equal(t, c.GetClientID(), configMap.Data["clientID"])
|
||||
assert.Equal(t, c.GetSecretKey(), configMap.Data["secretKey"])
|
||||
assert.Equal(t, c.GetCloudReportURL(), configMap.Data["cloudReportURL"])
|
||||
assert.Equal(t, c.GetCloudAPIURL(), configMap.Data["cloudAPIURL"])
|
||||
assert.Equal(t, c.GetCloudUIURL(), configMap.Data["cloudUIURL"])
|
||||
assert.Equal(t, c.GetCloudAuthURL(), configMap.Data["cloudAuthURL"])
|
||||
}
|
||||
|
||||
func TestReadConfig(t *testing.T) {
|
||||
@@ -114,15 +84,9 @@ func TestReadConfig(t *testing.T) {
|
||||
readConfig(b, co)
|
||||
|
||||
assert.Equal(t, com.AccountID, co.AccountID)
|
||||
assert.Equal(t, com.ClientID, co.ClientID)
|
||||
assert.Equal(t, com.SecretKey, co.SecretKey)
|
||||
assert.Equal(t, com.ClusterName, co.ClusterName)
|
||||
assert.Equal(t, com.CustomerAdminEMail, co.CustomerAdminEMail)
|
||||
assert.Equal(t, com.Token, co.Token)
|
||||
assert.Equal(t, com.CloudReportURL, co.CloudReportURL)
|
||||
assert.Equal(t, com.CloudAPIURL, co.CloudAPIURL)
|
||||
assert.Equal(t, com.CloudUIURL, co.CloudUIURL)
|
||||
assert.Equal(t, com.CloudAuthURL, co.CloudAuthURL)
|
||||
}
|
||||
|
||||
func TestLoadConfigFromData(t *testing.T) {
|
||||
@@ -141,15 +105,9 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
loadConfigFromData(c.configObj, configMap.Data)
|
||||
|
||||
assert.Equal(t, c.GetAccountID(), co.AccountID)
|
||||
assert.Equal(t, c.GetClientID(), co.ClientID)
|
||||
assert.Equal(t, c.GetSecretKey(), co.SecretKey)
|
||||
assert.Equal(t, c.GetContextName(), co.ClusterName)
|
||||
assert.Equal(t, c.GetTenantEmail(), co.CustomerAdminEMail)
|
||||
assert.Equal(t, c.GetToken(), co.Token)
|
||||
assert.Equal(t, c.GetCloudReportURL(), co.CloudReportURL)
|
||||
assert.Equal(t, c.GetCloudAPIURL(), co.CloudAPIURL)
|
||||
assert.Equal(t, c.GetCloudUIURL(), co.CloudUIURL)
|
||||
assert.Equal(t, c.GetCloudAuthURL(), co.CloudAuthURL)
|
||||
}
|
||||
|
||||
// use case: all data is in config.json
|
||||
@@ -167,12 +125,8 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
loadConfigFromData(c.configObj, configMap.Data)
|
||||
|
||||
assert.Equal(t, c.GetAccountID(), co.AccountID)
|
||||
assert.Equal(t, c.GetClientID(), co.ClientID)
|
||||
assert.Equal(t, c.GetSecretKey(), co.SecretKey)
|
||||
assert.Equal(t, c.GetCloudReportURL(), co.CloudReportURL)
|
||||
assert.Equal(t, c.GetCloudAPIURL(), co.CloudAPIURL)
|
||||
assert.Equal(t, c.GetCloudUIURL(), co.CloudUIURL)
|
||||
assert.Equal(t, c.GetCloudAuthURL(), co.CloudAuthURL)
|
||||
}
|
||||
|
||||
// use case: some data is in config.json
|
||||
@@ -183,21 +137,15 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
}
|
||||
|
||||
// add to map
|
||||
configMap.Data["clientID"] = c.configObj.ClientID
|
||||
configMap.Data["secretKey"] = c.configObj.SecretKey
|
||||
configMap.Data["cloudReportURL"] = c.configObj.CloudReportURL
|
||||
|
||||
// delete the content
|
||||
c.configObj.ClientID = ""
|
||||
c.configObj.SecretKey = ""
|
||||
c.configObj.CloudReportURL = ""
|
||||
|
||||
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
|
||||
loadConfigFromData(c.configObj, configMap.Data)
|
||||
|
||||
assert.NotEmpty(t, c.GetAccountID())
|
||||
assert.NotEmpty(t, c.GetClientID())
|
||||
assert.NotEmpty(t, c.GetSecretKey())
|
||||
assert.NotEmpty(t, c.GetCloudReportURL())
|
||||
}
|
||||
|
||||
@@ -212,19 +160,11 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
|
||||
// add to map
|
||||
configMap.Data["accountID"] = mockConfigObj().AccountID
|
||||
configMap.Data["clientID"] = c.configObj.ClientID
|
||||
configMap.Data["secretKey"] = c.configObj.SecretKey
|
||||
|
||||
// delete the content
|
||||
c.configObj.ClientID = ""
|
||||
c.configObj.SecretKey = ""
|
||||
|
||||
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
|
||||
loadConfigFromData(c.configObj, configMap.Data)
|
||||
|
||||
assert.Equal(t, mockConfigObj().AccountID, c.GetAccountID())
|
||||
assert.NotEmpty(t, c.GetClientID())
|
||||
assert.NotEmpty(t, c.GetSecretKey())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -289,13 +229,129 @@ func Test_initializeCloudAPI(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
initializeCloudAPI(tt.args.c)
|
||||
cloud := getter.GetKSCloudAPIConnector()
|
||||
assert.Equal(t, tt.args.c.GetCloudAPIURL(), cloud.GetCloudAPIURL())
|
||||
assert.Equal(t, tt.args.c.GetCloudAuthURL(), cloud.GetCloudAuthURL())
|
||||
assert.Equal(t, tt.args.c.GetCloudUIURL(), cloud.GetCloudUIURL())
|
||||
assert.Equal(t, tt.args.c.GetCloudReportURL(), cloud.GetCloudReportURL())
|
||||
assert.Equal(t, "https://api.domain.com", cloud.GetCloudAPIURL())
|
||||
assert.Equal(t, "https://report.domain.com", cloud.GetCloudReportURL())
|
||||
assert.Equal(t, tt.args.c.GetAccountID(), cloud.GetAccountID())
|
||||
assert.Equal(t, tt.args.c.GetClientID(), cloud.GetClientID())
|
||||
assert.Equal(t, tt.args.c.GetSecretKey(), cloud.GetSecretKey())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfigMapNamespace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
env string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "no env",
|
||||
want: kubescapeNamespace,
|
||||
},
|
||||
{
|
||||
name: "default ns",
|
||||
env: kubescapeNamespace,
|
||||
want: kubescapeNamespace,
|
||||
},
|
||||
{
|
||||
name: "custom ns",
|
||||
env: "my-ns",
|
||||
want: "my-ns",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.env != "" {
|
||||
_ = os.Setenv("KS_DEFAULT_CONFIGMAP_NAMESPACE", tt.env)
|
||||
}
|
||||
assert.Equalf(t, tt.want, GetConfigMapNamespace(), "GetConfigMapNamespace()")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
anyString string = "anyString"
|
||||
shouldNotUpdate string = "shouldNotUpdate"
|
||||
shouldUpdate string = "shouldUpdate"
|
||||
)
|
||||
|
||||
func checkIsUpdateCorrectly(t *testing.T, beforeField string, afterField string) {
|
||||
switch beforeField {
|
||||
case anyString:
|
||||
assert.Equal(t, anyString, afterField)
|
||||
case "":
|
||||
assert.Equal(t, shouldUpdate, afterField)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateEmptyFields(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
inCo *ConfigObj
|
||||
outCo *ConfigObj
|
||||
}{
|
||||
{
|
||||
outCo: &ConfigObj{
|
||||
AccountID: "",
|
||||
ClusterName: "",
|
||||
CloudReportURL: "",
|
||||
CloudAPIURL: "",
|
||||
},
|
||||
inCo: &ConfigObj{
|
||||
AccountID: shouldUpdate,
|
||||
ClusterName: shouldUpdate,
|
||||
CloudReportURL: shouldUpdate,
|
||||
CloudAPIURL: shouldUpdate,
|
||||
},
|
||||
},
|
||||
{
|
||||
outCo: &ConfigObj{
|
||||
AccountID: anyString,
|
||||
ClusterName: "",
|
||||
CloudReportURL: "",
|
||||
CloudAPIURL: "",
|
||||
},
|
||||
inCo: &ConfigObj{
|
||||
AccountID: shouldNotUpdate,
|
||||
ClusterName: shouldUpdate,
|
||||
CloudReportURL: shouldUpdate,
|
||||
CloudAPIURL: shouldUpdate,
|
||||
},
|
||||
},
|
||||
{
|
||||
outCo: &ConfigObj{
|
||||
AccountID: "",
|
||||
ClusterName: anyString,
|
||||
CloudReportURL: anyString,
|
||||
CloudAPIURL: anyString,
|
||||
},
|
||||
inCo: &ConfigObj{
|
||||
AccountID: shouldUpdate,
|
||||
ClusterName: shouldNotUpdate,
|
||||
CloudReportURL: shouldNotUpdate,
|
||||
CloudAPIURL: shouldNotUpdate,
|
||||
},
|
||||
},
|
||||
{
|
||||
outCo: &ConfigObj{
|
||||
AccountID: anyString,
|
||||
ClusterName: anyString,
|
||||
CloudReportURL: "",
|
||||
CloudAPIURL: anyString,
|
||||
},
|
||||
inCo: &ConfigObj{
|
||||
AccountID: shouldNotUpdate,
|
||||
ClusterName: shouldNotUpdate,
|
||||
CloudReportURL: shouldUpdate,
|
||||
CloudAPIURL: shouldNotUpdate,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
beforeChangesOutCO := tests[i].outCo
|
||||
tests[i].outCo.updateEmptyFields(tests[i].inCo)
|
||||
checkIsUpdateCorrectly(t, beforeChangesOutCO.AccountID, tests[i].outCo.AccountID)
|
||||
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudAPIURL, tests[i].outCo.CloudAPIURL)
|
||||
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudReportURL, tests[i].outCo.CloudReportURL)
|
||||
checkIsUpdateCorrectly(t, beforeChangesOutCO.ClusterName, tests[i].outCo.ClusterName)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user