mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
Compare commits
1111 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8253d4193 | ||
|
|
8b8fe92072 | ||
|
|
bcf9a10131 | ||
|
|
b6d21ffd01 | ||
|
|
086144c3da | ||
|
|
a45ee8ed42 | ||
|
|
129b0f3ee3 | ||
|
|
01a8a34637 | ||
|
|
bcb6c06e73 | ||
|
|
da03022b94 | ||
|
|
17f313177c | ||
|
|
a81353aa15 | ||
|
|
e0b82edd1e | ||
|
|
b675d09fe2 | ||
|
|
29b9448dc0 | ||
|
|
e1020dd1a6 | ||
|
|
9b734b1fa4 | ||
|
|
9f97f91f32 | ||
|
|
c6eff8cbaa | ||
|
|
af9df548d6 | ||
|
|
786f3e6b41 | ||
|
|
904751e117 | ||
|
|
ce43661307 | ||
|
|
cd4b601557 | ||
|
|
f34f1449db | ||
|
|
16c74a228f | ||
|
|
ad01f01a6c | ||
|
|
da0b9883ea | ||
|
|
ac60dbed5e | ||
|
|
3a90682c9e | ||
|
|
160ac0db7c | ||
|
|
7ec4fb75e3 | ||
|
|
7e88357940 | ||
|
|
1ac808a935 | ||
|
|
45fcc59b5f | ||
|
|
7875c14adf | ||
|
|
5cddba77aa | ||
|
|
f3058bf168 | ||
|
|
0d1b92c2ee | ||
|
|
8de308a5b1 | ||
|
|
a7f810f0d1 | ||
|
|
e4e3071f5f | ||
|
|
9a7e61edd1 | ||
|
|
5368330df9 | ||
|
|
5e6a4cfb3f | ||
|
|
052773b0dc | ||
|
|
d462224b7a | ||
|
|
de1d8a9d86 | ||
|
|
d346b05b76 | ||
|
|
a3a61d65e9 | ||
|
|
606b0e77ca | ||
|
|
2a82d6cd21 | ||
|
|
530ffde50d | ||
|
|
7cf23e9730 | ||
|
|
8d5a8f8e22 | ||
|
|
b820ce1311 | ||
|
|
dae2458867 | ||
|
|
d45e636b52 | ||
|
|
8810631d5c | ||
|
|
6cddce7399 | ||
|
|
5d5c4f2c9f | ||
|
|
e37049f68e | ||
|
|
c717a9233b | ||
|
|
e37f47de3a | ||
|
|
0622a474eb | ||
|
|
c357f12c82 | ||
|
|
2cec58384a | ||
|
|
5e4bc5ddb8 | ||
|
|
f30752d9c3 | ||
|
|
a586549c57 | ||
|
|
7c67a54230 | ||
|
|
0006d7d8e7 | ||
|
|
63083ae48a | ||
|
|
2ce37bd66e | ||
|
|
13c760c116 | ||
|
|
c6261e45a8 | ||
|
|
0c06b6c3e6 | ||
|
|
18a9ac3d6e | ||
|
|
2bfe2a590c | ||
|
|
fb54f4e6cf | ||
|
|
9025ba5537 | ||
|
|
0c23579db7 | ||
|
|
a755f365df | ||
|
|
15f7b9f954 | ||
|
|
92a2704fa6 | ||
|
|
a3defe3025 | ||
|
|
2be0ef48d8 | ||
|
|
c97513e4e8 | ||
|
|
1757c891aa | ||
|
|
b02410184e | ||
|
|
b4a6a18a56 | ||
|
|
571a68fb58 | ||
|
|
ef306ca0bf | ||
|
|
1a011f4968 | ||
|
|
13ca0027a2 | ||
|
|
93b626bb1e | ||
|
|
6b4310cd88 | ||
|
|
c883a297b3 | ||
|
|
3af351d91f | ||
|
|
93cde0f1a0 | ||
|
|
0a5715393c | ||
|
|
9a1cc33efa | ||
|
|
02720d32dd | ||
|
|
ebada00cf1 | ||
|
|
3b68fc94d1 | ||
|
|
10d534b5bf | ||
|
|
2d740fbf4f | ||
|
|
d0728676ee | ||
|
|
8856c84a17 | ||
|
|
0c87ff6843 | ||
|
|
a48d9be386 | ||
|
|
3cece6cf35 | ||
|
|
3c93c2c45c | ||
|
|
7fc10e8213 | ||
|
|
bb8f0e3c46 | ||
|
|
cfd85eadab | ||
|
|
77e0a04c99 | ||
|
|
b8762b924c | ||
|
|
025e75213a | ||
|
|
c39683872e | ||
|
|
1a3a58a309 | ||
|
|
19438e6143 | ||
|
|
284c8c737b | ||
|
|
3441a65290 | ||
|
|
773e43b1e1 | ||
|
|
ddc0b2daf2 | ||
|
|
596686602c | ||
|
|
5bb0c97f8f | ||
|
|
256db4abfb | ||
|
|
3546961a5e | ||
|
|
e6dc7c2367 | ||
|
|
07fa3b4589 | ||
|
|
d6ed4b1aca | ||
|
|
69846bb4c0 | ||
|
|
2e5ad85fe0 | ||
|
|
1025431d64 | ||
|
|
1a863473e7 | ||
|
|
28a44ac531 | ||
|
|
cf484c328b | ||
|
|
668514e08d | ||
|
|
dc45efb6ef | ||
|
|
6d3844f187 | ||
|
|
4d6e85d4c7 | ||
|
|
d336f4484c | ||
|
|
bf263d8d51 | ||
|
|
cc3cf1932c | ||
|
|
6a4dc79689 | ||
|
|
8c189f6e3c | ||
|
|
f1514d6e76 | ||
|
|
3a038c9a0e | ||
|
|
b4bdf4d860 | ||
|
|
b309cfca7a | ||
|
|
c4b3ef5b80 | ||
|
|
aba978e94a | ||
|
|
a49781e9a8 | ||
|
|
3ba19f55f1 | ||
|
|
40a9b9406d | ||
|
|
d6b8f5862f | ||
|
|
09f13c05e1 | ||
|
|
b1c8872a29 | ||
|
|
22052f5869 | ||
|
|
afce43add6 | ||
|
|
4752364699 | ||
|
|
08e7108dc0 | ||
|
|
108a2d6dd8 | ||
|
|
2c28286bb1 | ||
|
|
79858b7ed7 | ||
|
|
bb2e83eb3b | ||
|
|
282a29b971 | ||
|
|
60b9edc463 | ||
|
|
0f9a5e3127 | ||
|
|
7c79c14363 | ||
|
|
fe84225252 | ||
|
|
56da8d8d92 | ||
|
|
f135e95d2c | ||
|
|
db34183fc1 | ||
|
|
8f3af71c84 | ||
|
|
116aee0c9c | ||
|
|
e5d44f741d | ||
|
|
f005cb7f80 | ||
|
|
9ae9d35ccb | ||
|
|
cb38a4e8a1 | ||
|
|
eb6d39be42 | ||
|
|
3160d74c42 | ||
|
|
5076c38482 | ||
|
|
73c55fe253 | ||
|
|
f48f81c0b5 | ||
|
|
81c1c29b7c | ||
|
|
874aa38f68 | ||
|
|
b9caaf5025 | ||
|
|
61c120de0e | ||
|
|
de3408bf57 | ||
|
|
8d32032ec1 | ||
|
|
42ed787f7b | ||
|
|
ccdba85b3c | ||
|
|
c59f7691dc | ||
|
|
cf87c2d30b | ||
|
|
b547814dec | ||
|
|
b476a72e04 | ||
|
|
4f6f85710a | ||
|
|
47c23de160 | ||
|
|
bc85844ec0 | ||
|
|
134d854722 | ||
|
|
e3522c19cc | ||
|
|
967fc3fe81 | ||
|
|
896a0699ec | ||
|
|
a53375204e | ||
|
|
b1392361f8 | ||
|
|
7b4fbffae2 | ||
|
|
34e7b9f2ad | ||
|
|
f0080bdeae | ||
|
|
0eb27389da | ||
|
|
2c5eed9ee2 | ||
|
|
87e2986024 | ||
|
|
2c1a5bd032 | ||
|
|
298f8346e9 | ||
|
|
1897c5a4ba | ||
|
|
57e435271e | ||
|
|
7e9b430347 | ||
|
|
ca5b3e626b | ||
|
|
3a404f29fa | ||
|
|
16073d6872 | ||
|
|
dce563d2f5 | ||
|
|
8d556a5b84 | ||
|
|
a61063e5b8 | ||
|
|
94973867db | ||
|
|
214c2dcae8 | ||
|
|
72b36bf012 | ||
|
|
4335e6ceac | ||
|
|
b5f92a7d54 | ||
|
|
41ec75d264 | ||
|
|
6d6ad1f487 | ||
|
|
3ac33d21ac | ||
|
|
04e4b37f6f | ||
|
|
3e5903de6a | ||
|
|
04ea0fe524 | ||
|
|
955d6751a9 | ||
|
|
30c43bff10 | ||
|
|
e009244566 | ||
|
|
3d3cd2c2d8 | ||
|
|
f5498371ec | ||
|
|
c3b95bed8c | ||
|
|
8ce7d6c0f6 | ||
|
|
e875f429a9 | ||
|
|
b6beff0488 | ||
|
|
60c69ac3f0 | ||
|
|
1fb9320421 | ||
|
|
9a176f6667 | ||
|
|
96ea9a9e42 | ||
|
|
e39fca0c11 | ||
|
|
2ec035005d | ||
|
|
b734b3aef0 | ||
|
|
0f5635f42d | ||
|
|
8557075b7c | ||
|
|
bc0f0e7087 | ||
|
|
8ce5f9aea3 | ||
|
|
050f9d3a4e | ||
|
|
a81bf0deb4 | ||
|
|
2059324c27 | ||
|
|
a09a0a1bca | ||
|
|
83712bb9f5 | ||
|
|
728ae47b9a | ||
|
|
2a9b272a14 | ||
|
|
8662deac43 | ||
|
|
e42644bbd8 | ||
|
|
07d30b6272 | ||
|
|
2a4f8543cc | ||
|
|
186b293cce | ||
|
|
2bfe72f39d | ||
|
|
f99f955223 | ||
|
|
ec56e69a3c | ||
|
|
3942583b1d | ||
|
|
a10b15ba4b | ||
|
|
5003cbd7a8 | ||
|
|
481a137c23 | ||
|
|
c3f7f0938d | ||
|
|
b1925fa38d | ||
|
|
d9f8a7a46f | ||
|
|
846a072bf9 | ||
|
|
5dd7bbd8a7 | ||
|
|
e1773acf24 | ||
|
|
03a0f97669 | ||
|
|
917a3f41e8 | ||
|
|
3c8da1b299 | ||
|
|
c61c7edbd0 | ||
|
|
53402d9a1c | ||
|
|
de9278b388 | ||
|
|
4fef6200f8 | ||
|
|
81771b7bd7 | ||
|
|
2fee77c42c | ||
|
|
968ecdb31d | ||
|
|
af7b36a88b | ||
|
|
6ad58d38e2 | ||
|
|
681b4ce155 | ||
|
|
9d21ac1b16 | ||
|
|
2b3fcca7e8 | ||
|
|
af8e786ab5 | ||
|
|
c8df1b8f1f | ||
|
|
4f921ddf6f | ||
|
|
4f5839870b | ||
|
|
c0d7f51d6c | ||
|
|
a81d770360 | ||
|
|
f64d5eab50 | ||
|
|
d773397fe9 | ||
|
|
2e30995bfc | ||
|
|
17a2547f18 | ||
|
|
87a5cd66c8 | ||
|
|
9436ace64f | ||
|
|
fde00f6bd8 | ||
|
|
04a72a069a | ||
|
|
e2dcb5bc15 | ||
|
|
c7040a257c | ||
|
|
602dc00c65 | ||
|
|
0339691571 | ||
|
|
9e1f3ec131 | ||
|
|
b8589819dc | ||
|
|
a3e87f4c01 | ||
|
|
21ab5a602e | ||
|
|
5d97d7b4b2 | ||
|
|
d8d7d0b372 | ||
|
|
b8323d41fc | ||
|
|
d0b5314201 | ||
|
|
547e36e73f | ||
|
|
e593a772cb | ||
|
|
4da09529b6 | ||
|
|
de375992e8 | ||
|
|
0bc4a29881 | ||
|
|
9575c92713 | ||
|
|
cf277874eb | ||
|
|
746e060402 | ||
|
|
dd3a7c816e | ||
|
|
814bc3ab2c | ||
|
|
dbaf6761df | ||
|
|
580e45827d | ||
|
|
f3b8de9d1f | ||
|
|
fb1c728b12 | ||
|
|
6964ca0d18 | ||
|
|
6e9a2f55fd | ||
|
|
691fa61362 | ||
|
|
0c1eda0d08 | ||
|
|
767eac2fa6 | ||
|
|
6f651fa2d0 | ||
|
|
e3362c2e3d | ||
|
|
08b8b68f9a | ||
|
|
daf9ca9e7f | ||
|
|
d1024359c9 | ||
|
|
ed6070aff9 | ||
|
|
e4dbfa3534 | ||
|
|
ddd2b707c0 | ||
|
|
cd4f1077c2 | ||
|
|
b472d1cb9d | ||
|
|
922e2548f4 | ||
|
|
45caa7c120 | ||
|
|
670ae45d62 | ||
|
|
05bcf018d1 | ||
|
|
0af5d2e0bb | ||
|
|
eaf05fe9be | ||
|
|
e97b23f345 | ||
|
|
83a00ded3d | ||
|
|
78f81cc968 | ||
|
|
5d3347b4fe | ||
|
|
64d2ef8170 | ||
|
|
7c1e360b9a | ||
|
|
575d36dcde | ||
|
|
8dba8f7491 | ||
|
|
cc39e5b905 | ||
|
|
0be7e6018f | ||
|
|
7697e3f0c4 | ||
|
|
379800c49f | ||
|
|
79e2515807 | ||
|
|
342f5743e2 | ||
|
|
0e81870b85 | ||
|
|
dd7a8fd0c1 | ||
|
|
4277331ee2 | ||
|
|
53561a728f | ||
|
|
d0fd8c4fe4 | ||
|
|
398989510b | ||
|
|
f8e3ad5685 | ||
|
|
fbea7ef874 | ||
|
|
dc2c6f8a21 | ||
|
|
5ee08583b6 | ||
|
|
bfbd278e7c | ||
|
|
4c6e5903e3 | ||
|
|
a7cd5672c1 | ||
|
|
3373b728b7 | ||
|
|
22521b7159 | ||
|
|
e5fb14138e | ||
|
|
1b2242330c | ||
|
|
356958cc55 | ||
|
|
8f1da32001 | ||
|
|
686352a397 | ||
|
|
ef79c42ebc | ||
|
|
c8fc5378c1 | ||
|
|
c296666d8e | ||
|
|
f193e260b0 | ||
|
|
82981a9a54 | ||
|
|
3be54ca484 | ||
|
|
2f2c177674 | ||
|
|
1f47223918 | ||
|
|
eb646696a3 | ||
|
|
7cfe5160d5 | ||
|
|
95135c4379 | ||
|
|
7e604d6a5b | ||
|
|
64ac2666f9 | ||
|
|
05b3459342 | ||
|
|
92ad5f2407 | ||
|
|
e3c60e3202 | ||
|
|
7b5bcb05b1 | ||
|
|
154f94a0af | ||
|
|
063d3ee313 | ||
|
|
79859d05c0 | ||
|
|
acd3a94c46 | ||
|
|
13f09315e7 | ||
|
|
890528bf14 | ||
|
|
e4aafcf81e | ||
|
|
81c3c34ab8 | ||
|
|
b7b83b26b5 | ||
|
|
639cd3dfae | ||
|
|
7cf1302e8a | ||
|
|
dd5dd53a38 | ||
|
|
7275b8eac7 | ||
|
|
408c6fc998 | ||
|
|
5ce638572f | ||
|
|
4b98490ff9 | ||
|
|
6ea18ec75b | ||
|
|
56e2ffec5c | ||
|
|
fa204a208a | ||
|
|
9ab0fc593f | ||
|
|
3b9c454245 | ||
|
|
a6fc7a0da0 | ||
|
|
53ae57e478 | ||
|
|
1d3401e3b4 | ||
|
|
634198df06 | ||
|
|
cffc3953ea | ||
|
|
ea768602fb | ||
|
|
b4fc6dddd3 | ||
|
|
96d90c217e | ||
|
|
a2f1722455 | ||
|
|
400b51df1c | ||
|
|
0f3ce6917e | ||
|
|
11e57fe7ad | ||
|
|
2ddce8723d | ||
|
|
291668647c | ||
|
|
d3c41f2492 | ||
|
|
10fa3cb27d | ||
|
|
d8f95edff5 | ||
|
|
37ffe86d8b | ||
|
|
87fdbfdcc5 | ||
|
|
424a218860 | ||
|
|
faf0ae6bdc | ||
|
|
e46c42554b | ||
|
|
eb16440ba6 | ||
|
|
12f81353e0 | ||
|
|
fd33a8acd1 | ||
|
|
374e268a4f | ||
|
|
405bfbf9ba | ||
|
|
304227200f | ||
|
|
dc10125380 | ||
|
|
51c417ebc3 | ||
|
|
862230f58a | ||
|
|
6416fc56d7 | ||
|
|
a8ad8e5f5a | ||
|
|
d5edf29554 | ||
|
|
4351099e79 | ||
|
|
196d07edc6 | ||
|
|
f4bb03039a | ||
|
|
d6427f0fc8 | ||
|
|
f33a6d7634 | ||
|
|
2b931fb3f0 | ||
|
|
bb586892ba | ||
|
|
cb18f60f82 | ||
|
|
e5023943e5 | ||
|
|
c565dc5af7 | ||
|
|
5634903aa0 | ||
|
|
ce81a9cb22 | ||
|
|
5a01a1a30a | ||
|
|
cb704cb1e7 | ||
|
|
6e2dda7993 | ||
|
|
15e1d6d1a2 | ||
|
|
ba588b9eef | ||
|
|
f48b848eb6 | ||
|
|
f81fd74aa3 | ||
|
|
ad608b08e0 | ||
|
|
f9e80b709a | ||
|
|
f75b62e62c | ||
|
|
1c24a55d4b | ||
|
|
43dbb55d50 | ||
|
|
6ec974f996 | ||
|
|
03418299b8 | ||
|
|
f5bd86593c | ||
|
|
2af78eaab2 | ||
|
|
67cd003afe | ||
|
|
f7f11abfc2 | ||
|
|
52aa5f02e2 | ||
|
|
ce8175be61 | ||
|
|
0bc542f851 | ||
|
|
8e4f88ce5b | ||
|
|
d1c759f04f | ||
|
|
362ea83549 | ||
|
|
4cb7b999ad | ||
|
|
81482b7421 | ||
|
|
2b7807f300 | ||
|
|
ef23d022ee | ||
|
|
02d7fdc4f9 | ||
|
|
ccb3351607 | ||
|
|
72f9c6d81b | ||
|
|
bba70b4c46 | ||
|
|
46073e0a6c | ||
|
|
93a44f494d | ||
|
|
5c96f877ed | ||
|
|
23ea7e0511 | ||
|
|
137b3d7b5d | ||
|
|
13ffd92210 | ||
|
|
4725f8b3ca | ||
|
|
6d65a90de9 | ||
|
|
faf928527d | ||
|
|
18c6e80c3c | ||
|
|
83045c743a | ||
|
|
4940912784 | ||
|
|
a7fd2bd058 | ||
|
|
aa1f61a4f8 | ||
|
|
b103e817ed | ||
|
|
55045badce | ||
|
|
e951e23bc4 | ||
|
|
d467f159ad | ||
|
|
bb2a7b8d6c | ||
|
|
23bb8ec482 | ||
|
|
6c50fe1011 | ||
|
|
4268cb31c3 | ||
|
|
3b37d56427 | ||
|
|
f239075c26 | ||
|
|
b0c8c42c85 | ||
|
|
ea777b67ec | ||
|
|
ebf1486a7d | ||
|
|
2db2f55d16 | ||
|
|
cf9f34c0be | ||
|
|
4d4bec95f2 | ||
|
|
f3a5ce75d5 | ||
|
|
b38ce5e812 | ||
|
|
e4733fa02c | ||
|
|
39ea443f81 | ||
|
|
d03806aea2 | ||
|
|
576c281150 | ||
|
|
dfabcd691a | ||
|
|
e2698e71a3 | ||
|
|
6901628b5a | ||
|
|
fc3912ca7d | ||
|
|
c83cb4496d | ||
|
|
a76228c1e1 | ||
|
|
9447f2933a | ||
|
|
26d4664cc5 | ||
|
|
05fa9d887d | ||
|
|
acdad028a3 | ||
|
|
890ababe0a | ||
|
|
de78615038 | ||
|
|
db35670432 | ||
|
|
4d954b2ab0 | ||
|
|
1c215c36af | ||
|
|
2e8f64b20a | ||
|
|
9c764c90e3 | ||
|
|
95a4c19dc6 | ||
|
|
4d155a6b4f | ||
|
|
e3352f90e1 | ||
|
|
677a9da80a | ||
|
|
83e53c09eb | ||
|
|
c7e1e251ba | ||
|
|
aff7af5159 | ||
|
|
7bd77d666d | ||
|
|
9a7eb4b9a5 | ||
|
|
903b5f39df | ||
|
|
55f0ca3e9e | ||
|
|
3387e677ba | ||
|
|
5774acfc81 | ||
|
|
0eee2d1d0a | ||
|
|
0c624cc576 | ||
|
|
aade1008c4 | ||
|
|
c58b099230 | ||
|
|
786092bdaf | ||
|
|
80adf03926 | ||
|
|
4b9c35d53b | ||
|
|
f3623dccf6 | ||
|
|
b0db1e3d40 | ||
|
|
b936c3f857 | ||
|
|
600b9a6fb0 | ||
|
|
3bec2ef0b7 | ||
|
|
3d8344f23c | ||
|
|
d87836d0a9 | ||
|
|
42908ceb6f | ||
|
|
70288c94c3 | ||
|
|
2bc63c2ab6 | ||
|
|
609cbff2da | ||
|
|
3cf0931fb8 | ||
|
|
a42d2452fd | ||
|
|
7dd79874cc | ||
|
|
d1a75f076e | ||
|
|
08fa833f82 | ||
|
|
45e869e0d6 | ||
|
|
46cfc882c2 | ||
|
|
10583a4b9b | ||
|
|
da2adf3059 | ||
|
|
da24c9164a | ||
|
|
8ac41533b6 | ||
|
|
76958f285c | ||
|
|
93f6f3aecf | ||
|
|
971f0c06e7 | ||
|
|
bd4e0483d4 | ||
|
|
838eff3037 | ||
|
|
1ee1c11700 | ||
|
|
daa6db164a | ||
|
|
eb33542e4a | ||
|
|
a03b0c94c4 | ||
|
|
402aea1493 | ||
|
|
26c0baefe7 | ||
|
|
057d22adc1 | ||
|
|
77f3806abf | ||
|
|
5f6689adc1 | ||
|
|
051ec71263 | ||
|
|
c3434814c1 | ||
|
|
38325c5af4 | ||
|
|
589d0545cb | ||
|
|
32b74608bf | ||
|
|
98c0be147b | ||
|
|
6442e8c891 | ||
|
|
9454924b9f | ||
|
|
7233f00c32 | ||
|
|
905db42625 | ||
|
|
40e02899bb | ||
|
|
bfdf24afb4 | ||
|
|
588269f1a0 | ||
|
|
040b965be2 | ||
|
|
363951eb94 | ||
|
|
a45283b128 | ||
|
|
05d5ad47f2 | ||
|
|
5ccb858d7f | ||
|
|
c49c808730 | ||
|
|
23d44aef7e | ||
|
|
657beea858 | ||
|
|
4becfc6b88 | ||
|
|
b2763b1f4f | ||
|
|
cbd4fc1a80 | ||
|
|
6a17eb1e86 | ||
|
|
4e847b4293 | ||
|
|
e81d8ffa3c | ||
|
|
2e1de8a48f | ||
|
|
240971172d | ||
|
|
1b54f3b87c | ||
|
|
cf6ae51f76 | ||
|
|
12619f4f3b | ||
|
|
4b96ce4a54 | ||
|
|
86c1f57128 | ||
|
|
7a51b46178 | ||
|
|
eeb1d8bff7 | ||
|
|
04f757913a | ||
|
|
307b7be28d | ||
|
|
c3d188bde4 | ||
|
|
52c2b4e854 | ||
|
|
bd970eff83 | ||
|
|
7bba5fafe0 | ||
|
|
d25c69abbf | ||
|
|
22e1e8a82f | ||
|
|
c8c3ab76d6 | ||
|
|
ad1c39a7e5 | ||
|
|
e6398872cd | ||
|
|
811d27529c | ||
|
|
a1efafc871 | ||
|
|
7f2a4bdfd8 | ||
|
|
8381b2d348 | ||
|
|
a95c2ec42b | ||
|
|
ad91178ef7 | ||
|
|
db179d7b67 | ||
|
|
f6e2651f88 | ||
|
|
6a52945e5a | ||
|
|
f8a66b0f9b | ||
|
|
bfc3b0cc43 | ||
|
|
2432378a57 | ||
|
|
2f1ae9418a | ||
|
|
619eeb3f02 | ||
|
|
ed1862cf72 | ||
|
|
40d5b08f39 | ||
|
|
fcfccd18dc | ||
|
|
0be1acfe72 | ||
|
|
7407cbb4fb | ||
|
|
79158aa3e5 | ||
|
|
e7d212fb8c | ||
|
|
4d6f75a65a | ||
|
|
17445ee8b9 | ||
|
|
2de950cce7 | ||
|
|
326dd096fd | ||
|
|
416b5e691e | ||
|
|
f7d91f1b48 | ||
|
|
46f823eb89 | ||
|
|
e54bfec4c1 | ||
|
|
e95a09dbff | ||
|
|
e1f01f963f | ||
|
|
a397f681b3 | ||
|
|
0b52c498da | ||
|
|
5141d82e49 | ||
|
|
262c014cfe | ||
|
|
dcd4ac9703 | ||
|
|
566457308f | ||
|
|
c967dbeefc | ||
|
|
0bcef1be3b | ||
|
|
11037f1071 | ||
|
|
bec9fc9c80 | ||
|
|
7e1cf051e8 | ||
|
|
7ea8e89766 | ||
|
|
4fc20964b3 | ||
|
|
cbc14a8c83 | ||
|
|
355be63b6f | ||
|
|
29b431009c | ||
|
|
4114730d91 | ||
|
|
b1731531b1 | ||
|
|
d2a092d032 | ||
|
|
4ce8c63044 | ||
|
|
5b450be094 | ||
|
|
9c80f4d9cb | ||
|
|
3c615f180b | ||
|
|
13b37abcb4 | ||
|
|
46696d9eb3 | ||
|
|
9f450ef0cb | ||
|
|
1a15ee757b | ||
|
|
eab0d6650e | ||
|
|
2829c77cc8 | ||
|
|
25d3a9004c | ||
|
|
b0bdab3ef2 | ||
|
|
e69cf89fec | ||
|
|
45007a6aa4 | ||
|
|
40e8dd3575 | ||
|
|
be92c0a3e1 | ||
|
|
46a1a4ce04 | ||
|
|
4fadb413c3 | ||
|
|
99436f1b4d | ||
|
|
3f7a55c48e | ||
|
|
2fffd25e05 | ||
|
|
e5d02419f7 | ||
|
|
30919d7e9e | ||
|
|
96903ea77d | ||
|
|
f4572f6f53 | ||
|
|
55dbafb9b0 | ||
|
|
a6c19bc286 | ||
|
|
c7450adc77 | ||
|
|
f4f3adf576 | ||
|
|
0e9ccc955b | ||
|
|
54b502629f | ||
|
|
8a25d0d293 | ||
|
|
a4af46fcf9 | ||
|
|
e9d3b573b3 | ||
|
|
17f48a9bc1 | ||
|
|
32922c6263 | ||
|
|
57874f399b | ||
|
|
5abca6711e | ||
|
|
875b98415b | ||
|
|
d577b1a135 | ||
|
|
2cd52e43b0 | ||
|
|
69bdc358eb | ||
|
|
2b2034f2da | ||
|
|
fb114a17a3 | ||
|
|
ba2e4fe16e | ||
|
|
948681b82e | ||
|
|
5bd532dd57 | ||
|
|
aef74d6480 | ||
|
|
ef8565b67f | ||
|
|
7b38b5dc96 | ||
|
|
9f5d9fe36b | ||
|
|
643d0620d7 | ||
|
|
8ecc1839a0 | ||
|
|
31aeae8bd1 | ||
|
|
26bbcae0bd | ||
|
|
0feca50ebb | ||
|
|
895f330e14 | ||
|
|
30f454de08 | ||
|
|
8347fa7874 | ||
|
|
6b5d335519 | ||
|
|
44f0473a09 | ||
|
|
a6bae01476 | ||
|
|
c356246f82 | ||
|
|
a6d9badc5f | ||
|
|
5bf179810b | ||
|
|
fbb75d6dd1 | ||
|
|
ac9db6706c | ||
|
|
9b489f1e5c | ||
|
|
ab788eaaa2 | ||
|
|
826106090b | ||
|
|
e6e9a74766 | ||
|
|
5fee3efb35 | ||
|
|
a3d77a76aa | ||
|
|
3a958294f3 | ||
|
|
086a518a53 | ||
|
|
705fabb32b | ||
|
|
3d37a6ac2f | ||
|
|
dd79e348d3 | ||
|
|
a913d3eb32 | ||
|
|
f32049bdb3 | ||
|
|
bbcc7a502d | ||
|
|
2fb2ab02c4 | ||
|
|
1a94004de4 | ||
|
|
424f2cc403 | ||
|
|
bae960fd5b | ||
|
|
eab77a9e61 | ||
|
|
fc78d9143b | ||
|
|
034dbca30c | ||
|
|
a41adc6c9e | ||
|
|
bd170938c5 | ||
|
|
e91a73a32e | ||
|
|
099886e1bb | ||
|
|
c05dc8d7ae | ||
|
|
3cebfb3065 | ||
|
|
4e9f4a8010 | ||
|
|
94d99da821 | ||
|
|
ee1b596358 | ||
|
|
f7445d1777 | ||
|
|
ed5abd5791 | ||
|
|
898b847211 | ||
|
|
889dd15772 | ||
|
|
2ce6c1840b | ||
|
|
81f0cecb79 | ||
|
|
d46d77411b | ||
|
|
ee4f4d8af1 | ||
|
|
ea1426a24b | ||
|
|
120677a91f | ||
|
|
bd78e4c4de | ||
|
|
e3f70b6cd6 | ||
|
|
616712cf79 | ||
|
|
a5007df1bc | ||
|
|
d6720b67ed | ||
|
|
2261fd6adb | ||
|
|
9334ad6991 | ||
|
|
7b5e4143c3 | ||
|
|
e63e5502cd | ||
|
|
154794e774 | ||
|
|
4aa71725dd | ||
|
|
9bd2e7fea4 | ||
|
|
e6d3e7d7da | ||
|
|
35bb15b5df | ||
|
|
e54d61fd87 | ||
|
|
f28b2836c7 | ||
|
|
d196f1f327 | ||
|
|
995f90ca53 | ||
|
|
c1da380c9b | ||
|
|
77f77b8c7d | ||
|
|
b3c1aec461 | ||
|
|
ef242b52bb | ||
|
|
af1d5694dc | ||
|
|
a5ac47ff6d | ||
|
|
b03a4974c4 | ||
|
|
8385cd0bd7 | ||
|
|
ca67aa7f5f | ||
|
|
9b9ed514c8 | ||
|
|
11b7f6ab2f | ||
|
|
021f2074b8 | ||
|
|
c1ba2d4b3c | ||
|
|
141ad17ece | ||
|
|
74c81e2270 | ||
|
|
7bb124b6fe | ||
|
|
8a8ff10b19 | ||
|
|
1eef32dd8e | ||
|
|
d7b5dd416d | ||
|
|
536a94de45 | ||
|
|
d8ef471eb2 | ||
|
|
ff07a80078 | ||
|
|
310d31a3b1 | ||
|
|
8a1ef7da87 | ||
|
|
c142779ee8 | ||
|
|
54020d317e | ||
|
|
91d1ec6c2f | ||
|
|
1d3fd0dc9d | ||
|
|
8a7511cecb | ||
|
|
640f366c7e | ||
|
|
9f36c1d6de | ||
|
|
b3c8c078a8 | ||
|
|
0af0c01ec0 | ||
|
|
3ff2b0d6ff | ||
|
|
35b2b350a0 | ||
|
|
046ea1d79f | ||
|
|
3081508863 | ||
|
|
4a757c1bf1 | ||
|
|
4f1971a63d | ||
|
|
dec4bcca00 | ||
|
|
5443039b8c | ||
|
|
95e68f49f3 | ||
|
|
7e90956b50 | ||
|
|
0c84c8f1f3 | ||
|
|
56b3239e30 | ||
|
|
f8e85941da | ||
|
|
15081aa9c3 | ||
|
|
ac03a2bda3 | ||
|
|
b7ffa22f3a | ||
|
|
ac5e7069da | ||
|
|
5a83f38bca | ||
|
|
3abd59e290 | ||
|
|
d08fdf2e9e | ||
|
|
bad2f54e72 | ||
|
|
fc9b713851 | ||
|
|
245200840d | ||
|
|
3f87610e8c | ||
|
|
c285cb1bcc | ||
|
|
63968b564b | ||
|
|
e237c48186 | ||
|
|
622b121535 | ||
|
|
20774d4a40 | ||
|
|
7bb6bb85ec | ||
|
|
da908a84bc | ||
|
|
b515e259c0 | ||
|
|
facd551518 | ||
|
|
0fc569d9d9 | ||
|
|
da27a27ad5 | ||
|
|
5d4a20f622 | ||
|
|
70b15a373b | ||
|
|
01353f81b3 | ||
|
|
22f10b6581 | ||
|
|
785178ffb1 | ||
|
|
f9b5c58402 | ||
|
|
8ed6d63ce5 | ||
|
|
990a7c2052 | ||
|
|
09b0c09472 | ||
|
|
f83c38b58e | ||
|
|
51e600797a | ||
|
|
afb6ea1d9c | ||
|
|
39d6d1fd26 | ||
|
|
2dff63b101 | ||
|
|
b928892f0a | ||
|
|
c0188ea51d | ||
|
|
fa376ed5a4 | ||
|
|
6382edeb6e | ||
|
|
61d6c2dd1f | ||
|
|
44194f0b4e | ||
|
|
7103c7d32c | ||
|
|
b4e1663cd1 | ||
|
|
b3d16875d6 | ||
|
|
47412c89ca | ||
|
|
20d65f2ed3 | ||
|
|
46a559fb1d | ||
|
|
2769b22721 | ||
|
|
60ec6e8294 | ||
|
|
63520f9aff | ||
|
|
333b55a9f2 | ||
|
|
c6d3fd1a82 | ||
|
|
8106133ed0 | ||
|
|
b36111f63e | ||
|
|
3ad0284394 | ||
|
|
245ebf8c41 | ||
|
|
8309562da1 | ||
|
|
de807a65a6 | ||
|
|
92fe583421 | ||
|
|
b7ec05e88a | ||
|
|
203e925888 | ||
|
|
fde5453bf3 | ||
|
|
4c6a65565b | ||
|
|
e60ecfb8f5 | ||
|
|
b72e2610ca | ||
|
|
8d4bae06bc | ||
|
|
847b597d0f | ||
|
|
db1743f617 | ||
|
|
7ac1b8aacf | ||
|
|
55f8cb1f0e | ||
|
|
93574736cd | ||
|
|
e43f4b1a37 | ||
|
|
4ba33578ce | ||
|
|
ae00866005 | ||
|
|
21cb4dae29 | ||
|
|
cf086e6614 | ||
|
|
7d3ac98998 | ||
|
|
5e9d01aec2 | ||
|
|
a27d2d41f2 | ||
|
|
c09eabf347 | ||
|
|
38c2aed74a | ||
|
|
cf70671dba | ||
|
|
f90ce83a74 | ||
|
|
fab594ee32 | ||
|
|
d25cefe355 | ||
|
|
747eee1d29 | ||
|
|
0c43ee9ab8 | ||
|
|
466f3acd71 | ||
|
|
80add4ef12 | ||
|
|
959319c335 | ||
|
|
0e9ca547cb | ||
|
|
6e17e5ce7e | ||
|
|
858d7ac2ef | ||
|
|
4046321297 | ||
|
|
0cfdabd25a | ||
|
|
8b6cb6c5d8 | ||
|
|
3df3b7766c | ||
|
|
0d83654197 | ||
|
|
bd9e44382e | ||
|
|
2a7c20ea94 | ||
|
|
bde0dc9a17 | ||
|
|
7d7336ae01 | ||
|
|
e5e608324d | ||
|
|
569c1444f7 | ||
|
|
dc5ef28324 | ||
|
|
aea6c0eab8 | ||
|
|
c80a15d0cf | ||
|
|
1ddd57aa1d | ||
|
|
55adb0da6b | ||
|
|
a716289cc8 | ||
|
|
093d71fff4 | ||
|
|
4fe40e348d | ||
|
|
29a67b806d | ||
|
|
0169f42747 | ||
|
|
ed45b09241 | ||
|
|
107903cc99 | ||
|
|
92e100c497 | ||
|
|
536fe970f7 | ||
|
|
85526b06b6 | ||
|
|
d9b6c048d5 | ||
|
|
7e46a6529a | ||
|
|
2dc5fd80da | ||
|
|
e89cc8ca24 | ||
|
|
d39aeb0691 | ||
|
|
da9d98134a | ||
|
|
9992a9a0e4 | ||
|
|
adc8a16e85 | ||
|
|
13fb586ded | ||
|
|
2e63982f5a | ||
|
|
58b833c18a | ||
|
|
cb424eab00 | ||
|
|
9f2e18c3ee | ||
|
|
b44a73aea5 | ||
|
|
9c5759286f | ||
|
|
74dc714736 | ||
|
|
83751e22cc | ||
|
|
db5fdd75c4 | ||
|
|
4be2104d4b | ||
|
|
b6bab7618f | ||
|
|
3e1fda6f3b | ||
|
|
8487a031ee | ||
|
|
efbb123fce | ||
|
|
5a335d4f1c | ||
|
|
5770a823d6 | ||
|
|
52d7be9108 | ||
|
|
9512b9c6c4 | ||
|
|
da9ab642ec | ||
|
|
718ca1c7ab | ||
|
|
ee3742c5a0 | ||
|
|
7eef843a7a | ||
|
|
b4a8b06f07 | ||
|
|
4e13609985 | ||
|
|
2e5e4328f6 | ||
|
|
d98a11a8fa | ||
|
|
bdb25cbb66 | ||
|
|
369804cb6e | ||
|
|
1b08a92095 | ||
|
|
e787454d53 | ||
|
|
31d1ba663a | ||
|
|
c3731d8ff6 | ||
|
|
c5b46beb1a | ||
|
|
c5ca576c98 | ||
|
|
eae6458b42 | ||
|
|
aa1aa913b6 | ||
|
|
44084592cb | ||
|
|
6cacfb7b16 | ||
|
|
6372ce5647 | ||
|
|
306d3a7081 | ||
|
|
442530061f | ||
|
|
961a6f6ebc | ||
|
|
0d0c8e1b97 | ||
|
|
5b843ba2c4 | ||
|
|
8f9b46cdbe | ||
|
|
e16885a044 | ||
|
|
06a2fa05be | ||
|
|
d26f90b98e | ||
|
|
b47c128eb3 | ||
|
|
9d957b3c77 | ||
|
|
8ec5615569 | ||
|
|
fae73b827a | ||
|
|
6477437872 | ||
|
|
6099f46dea | ||
|
|
5009e6ef47 | ||
|
|
c4450d3259 | ||
|
|
0c3339f1c9 | ||
|
|
faee3d5ad6 | ||
|
|
a279963b28 | ||
|
|
f7c0c95d3b | ||
|
|
b8df07b547 | ||
|
|
d0e2730518 | ||
|
|
6dab82f01a | ||
|
|
7a01116db5 | ||
|
|
8f1e4ceff0 | ||
|
|
353a39d66a | ||
|
|
66196c0d56 | ||
|
|
2d59ba0943 | ||
|
|
33f92d1a5f | ||
|
|
4bd468f03e | ||
|
|
c6eaecd596 | ||
|
|
a2a5b06024 | ||
|
|
825732f60f | ||
|
|
596ec17106 | ||
|
|
fbd0f352c4 | ||
|
|
2600052735 | ||
|
|
a985b2ce09 | ||
|
|
829c176644 | ||
|
|
7d7d247bc2 | ||
|
|
43ae8e2a81 | ||
|
|
b0f37e9465 | ||
|
|
396ef55267 | ||
|
|
4b07469bb2 | ||
|
|
260f7b06c1 | ||
|
|
9733178228 | ||
|
|
67ba28a3cb | ||
|
|
a768d22a1d | ||
|
|
ede88550da | ||
|
|
ab55a0d134 | ||
|
|
bfd7060044 | ||
|
|
bf215a0f96 | ||
|
|
b08c0f2ec6 | ||
|
|
2df0c12e10 | ||
|
|
d37025dc6c | ||
|
|
d537c56159 | ||
|
|
37644e1f57 | ||
|
|
8a04934fbd | ||
|
|
31e1b3055f |
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Keep CRLF newlines in appropriate test files to have reproducible tests
|
||||
core/pkg/fixhandler/testdata/inserts/*-crlf-newlines.yaml text eol=crlf
|
||||
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
# Description
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
# Environment
|
||||
OS: ` ` <!-- the OS + version you’re running Kubescape on, e.g Ubuntu 22.04 LTS -->
|
||||
Version: ` ` <!-- the version that Kubescape reports when you run `kubescape version` -->
|
||||
|
||||
# Steps To Reproduce
|
||||
<!--
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
-->
|
||||
|
||||
# Expected behavior
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
# Actual Behavior
|
||||
<!-- A clear and concise description of what happened. If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
# Additional context
|
||||
<!-- Add any other context about the problem here. -->
|
||||
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: 'feature'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
<!-- A brief overview of the related current state -->
|
||||
|
||||
## Problem
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
## Solution
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
## Alternatives
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
## Additional context
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
44
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
44
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
## Overview
|
||||
<!-- Please provide a brief overview of the changes made in this pull request. e.g. current behavior/future behavior -->
|
||||
|
||||
<!--
|
||||
## Additional Information
|
||||
|
||||
> Any additional information that may be useful for reviewers to know
|
||||
-->
|
||||
|
||||
<!--
|
||||
## How to Test
|
||||
|
||||
> Please provide instructions on how to test the changes made in this pull request
|
||||
-->
|
||||
|
||||
<!--
|
||||
## Examples/Screenshots
|
||||
|
||||
> Here you add related screenshots
|
||||
-->
|
||||
|
||||
<!--
|
||||
## Related issues/PRs:
|
||||
|
||||
Here you add related issues and PRs.
|
||||
If this resolved an issue, write "Resolved #<issue number>
|
||||
|
||||
e.g. If this PR resolves issues 1 and 2, it should look as follows:
|
||||
* Resolved #1
|
||||
* Resolved #2
|
||||
-->
|
||||
|
||||
<!--
|
||||
## Checklist before requesting a review
|
||||
|
||||
put an [x] in the box to get it checked
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have commented on my code, particularly in hard-to-understand areas
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] If it is a core feature, I have added thorough tests.
|
||||
- [ ] New and existing unit tests pass locally with my changes
|
||||
|
||||
-->
|
||||
37
.github/actions/tag-action/action.yaml
vendored
Normal file
37
.github/actions/tag-action/action.yaml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: 'Tag validator and retag'
|
||||
description: 'This action will check if the tag is rc and create a new tag for release'
|
||||
inputs:
|
||||
ORIGINAL_TAG: # id of input
|
||||
description: 'Original tag'
|
||||
required: true
|
||||
default: ${{ github.ref_name }}
|
||||
SUB_STRING:
|
||||
description: 'Sub string for rc tag'
|
||||
required: true
|
||||
default: "-rc"
|
||||
outputs:
|
||||
NEW_TAG:
|
||||
description: "The new tag for release"
|
||||
value: ${{ steps.retag.outputs.NEW_TAG }}
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- run: |
|
||||
SUB='-rc'
|
||||
if [[ "${{ inputs.ORIGINAL_TAG }}" == *"${{ inputs.SUB_STRING }}"* ]]; then
|
||||
echo "Release candidate tag found."
|
||||
else
|
||||
echo "Release candidate tag not found."
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
|
||||
- id: retag
|
||||
run: |
|
||||
NEW_TAG=
|
||||
echo "Original tag: ${{ inputs.ORIGINAL_TAG }}"
|
||||
NEW_TAG=$(echo ${{ inputs.ORIGINAL_TAG }} | awk -F '-rc' '{print $1}')
|
||||
echo "New tag: $NEW_TAG"
|
||||
echo "NEW_TAG=$NEW_TAG" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
33
.github/workflows/00-pr-scanner.yaml
vendored
Normal file
33
.github/workflows/00-pr-scanner.yaml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: 00-pr_scanner
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
branches:
|
||||
- 'master'
|
||||
- 'main'
|
||||
- 'dev'
|
||||
paths-ignore:
|
||||
- '**.yaml'
|
||||
- '**.md'
|
||||
- '**.sh'
|
||||
- 'website/*'
|
||||
- 'examples/*'
|
||||
- 'docs/*'
|
||||
- 'build/*'
|
||||
- '.github/*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.head_ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
jobs:
|
||||
pr-scanner:
|
||||
permissions:
|
||||
pull-requests: write
|
||||
uses: ./.github/workflows/a-pr-scanner.yaml
|
||||
with:
|
||||
RELEASE: ""
|
||||
CLIENT: test
|
||||
secrets: inherit
|
||||
57
.github/workflows/01-code-review-approved.yaml
vendored
Normal file
57
.github/workflows/01-code-review-approved.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: 01-code_review_approved
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
branches:
|
||||
- 'master'
|
||||
- 'main'
|
||||
paths-ignore:
|
||||
- '**.yaml'
|
||||
- '**.md'
|
||||
- '**.sh'
|
||||
- 'website/*'
|
||||
- 'examples/*'
|
||||
- 'docs/*'
|
||||
- 'build/*'
|
||||
- '.github/*'
|
||||
|
||||
|
||||
concurrency:
|
||||
group: code-review-approved
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
binary-build:
|
||||
if: ${{ github.event.review.state == 'approved' &&
|
||||
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
|
||||
|
||||
|
||||
merge-to-master:
|
||||
needs: binary-build
|
||||
env:
|
||||
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
if: ${{ (github.event.review.state == 'approved' && github.event.pull_request.base.ref == 'master') &&
|
||||
(always() && (contains(needs.*.result, 'success') || contains(needs.*.result, 'skipped')) && !(contains(needs.*.result, 'failure')) && !(contains(needs.*.result, 'cancelled'))) }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: merge-to-master
|
||||
if: ${{ env.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
uses: pascalgn/automerge-action@v0.15.5
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}"
|
||||
MERGE_COMMIT_MESSAGE: "Merge to master - PR number: {pullRequest.number}"
|
||||
MERGE_ERROR_FAIL: "true"
|
||||
MERGE_METHOD: "merge"
|
||||
MERGE_LABELS: ""
|
||||
UPDATE_LABELS: ""
|
||||
69
.github/workflows/02-release.yaml
vendored
Normal file
69
.github/workflows/02-release.yaml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: 02-create_release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*-rc.*'
|
||||
|
||||
jobs:
|
||||
retag:
|
||||
outputs:
|
||||
NEW_TAG: ${{ steps.tag-calculator.outputs.NEW_TAG }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- id: tag-calculator
|
||||
uses: ./.github/actions/tag-action
|
||||
with:
|
||||
SUB_STRING: "-rc"
|
||||
|
||||
binary-build:
|
||||
needs: [retag]
|
||||
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
|
||||
with:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 1
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.19"
|
||||
RELEASE: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
CLIENT: release
|
||||
secrets: inherit
|
||||
|
||||
create-release:
|
||||
permissions:
|
||||
contents: write
|
||||
needs: [retag, binary-build]
|
||||
uses: ./.github/workflows/c-create-release.yaml
|
||||
with:
|
||||
RELEASE_NAME: "Release ${{ needs.retag.outputs.NEW_TAG }}"
|
||||
TAG: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
DRAFT: false
|
||||
secrets: inherit
|
||||
|
||||
# publish-krew-plugin:
|
||||
# name: Publish Krew plugin
|
||||
# runs-on: ubuntu-latest
|
||||
# if: "${{ github.repository_owner }} == kubescape"
|
||||
# needs: create-release
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
# with:
|
||||
# submodules: recursive
|
||||
# - name: Update new version in krew-index
|
||||
# uses: rajatjindal/krew-release-bot@v0.0.43
|
||||
|
||||
publish-image:
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
contents: read
|
||||
uses: ./.github/workflows/d-publish-image.yaml
|
||||
needs: [ create-release, retag ]
|
||||
with:
|
||||
client: "image-release"
|
||||
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
|
||||
image_tag: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
support_platforms: true
|
||||
cosign: true
|
||||
secrets: inherit
|
||||
@@ -1,12 +1,14 @@
|
||||
name: create release digests
|
||||
name: 03-create_release_digests
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published]
|
||||
branches: [ master ]
|
||||
types: [ published ]
|
||||
branches:
|
||||
- 'master'
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
once:
|
||||
create_release_digests:
|
||||
name: Creating digests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
52
.github/workflows/README.md
vendored
Normal file
52
.github/workflows/README.md
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
# Kubescape workflows
|
||||
|
||||
Tag terminology: `v<major>.<minor>.<patch>`
|
||||
|
||||
## Developing process
|
||||
|
||||
Kubescape's main branch is `main`, any PR will be opened against the main branch.
|
||||
|
||||
### Opening a PR
|
||||
|
||||
When a user opens a PR, this will trigger some basic tests (units, license, etc.)
|
||||
|
||||
### Reviewing a PR
|
||||
|
||||
The reviewer/maintainer of a PR will decide whether the PR introduces changes that require running the E2E system tests. If so, the reviewer will add the `trigger-integration-test` label.
|
||||
|
||||
### Approving a PR
|
||||
|
||||
Once a maintainer approves the PR, if the `trigger-integration-test` label was added to the PR, the GitHub actions will trigger the system test. The PR will be merged only after the system tests passed successfully. If the label was not added, the PR can be merged.
|
||||
|
||||
### Merging a PR
|
||||
|
||||
The code is merged, no other actions are needed
|
||||
|
||||
|
||||
## Release process
|
||||
|
||||
Every two weeks, we will create a new tag by bumping the minor version, this will create the release and publish the artifacts.
|
||||
If we are introducing breaking changes, we will update the `major` version instead.
|
||||
|
||||
When we wish to push a hot-fix/feature within the two weeks, we will bump the `patch`.
|
||||
|
||||
### Creating a new tag
|
||||
Every two weeks or upon the decision of the maintainers, a maintainer can create a tag.
|
||||
|
||||
The tag should look as follows: `v<A>.<B>.<C>-rc.D` (release candidate).
|
||||
|
||||
When creating a tag, GitHub will trigger the following actions:
|
||||
1. Basic tests - unit tests, license, etc.
|
||||
2. System tests (integration tests). If the tests fail, the actions will stop here.
|
||||
3. Create a new tag: `v<A>.<B>.<C>` (same tag just without the `rc` suffix)
|
||||
4. Create a release
|
||||
5. Publish artifacts
|
||||
6. Build and publish the docker image (this is meanwhile until we separate the microservice code from the LCI codebase)
|
||||
|
||||
## Additional Information
|
||||
|
||||
The "callers" have the alphabetic prefix and the "executes" have the numeric prefix
|
||||
|
||||
## Screenshot
|
||||
|
||||
<img width="1469" alt="image" src="https://user-images.githubusercontent.com/64066841/212532727-e82ec9e7-263d-408b-b4b0-a8c943f0109a.png">
|
||||
177
.github/workflows/a-pr-scanner.yaml
vendored
Normal file
177
.github/workflows/a-pr-scanner.yaml
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
name: a-pr-scanner
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
RELEASE:
|
||||
description: 'release'
|
||||
required: true
|
||||
type: string
|
||||
CLIENT:
|
||||
description: 'Client name'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
|
||||
jobs:
|
||||
scanners:
|
||||
env:
|
||||
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
name: PR Scanner
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-go@v3 # Install go because go-licenses use it
|
||||
name: Installing go
|
||||
with:
|
||||
go-version: '1.19'
|
||||
cache: true
|
||||
|
||||
- name: Scanning - Forbidden Licenses (go-licenses)
|
||||
id: licenses-scan
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo "## Installing go-licenses tool"
|
||||
go install github.com/google/go-licenses@latest
|
||||
echo "## Scanning for forbiden licenses ##"
|
||||
go-licenses check .
|
||||
|
||||
- name: Scanning - Credentials (GitGuardian)
|
||||
if: ${{ env.GITGUARDIAN_API_KEY }}
|
||||
continue-on-error: true
|
||||
id: credentials-scan
|
||||
uses: GitGuardian/ggshield-action@master
|
||||
with:
|
||||
args: -v --all-policies
|
||||
env:
|
||||
GITHUB_PUSH_BEFORE_SHA: ${{ github.event.before }}
|
||||
GITHUB_PUSH_BASE_SHA: ${{ github.event.base }}
|
||||
GITHUB_PULL_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
|
||||
|
||||
- name: Scanning - Vulnerabilities (Snyk)
|
||||
if: ${{ env.SNYK_TOKEN }}
|
||||
id: vulnerabilities-scan
|
||||
continue-on-error: true
|
||||
uses: snyk/actions/golang@master
|
||||
with:
|
||||
command: test --all-projects
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
|
||||
- name: Comment results to PR
|
||||
continue-on-error: true # Warning: This might break opening PRs from forks
|
||||
uses: peter-evans/create-or-update-comment@v2.1.0
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
Scan results:
|
||||
- License scan: ${{ steps.licenses-scan.outcome }}
|
||||
- Credentials scan: ${{ steps.credentials-scan.outcome }}
|
||||
- Vulnerabilities scan: ${{ steps.vulnerabilities-scan.outcome }}
|
||||
reactions: 'eyes'
|
||||
|
||||
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@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Cache Go modules (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
uses: 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@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@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@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
|
||||
- name: Install MSYS2 & libgit2 (Windows)
|
||||
shell: cmd
|
||||
run: .\build.bat all
|
||||
if: matrix.os == 'windows-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@v3
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout 10m --build-tags=static
|
||||
only-new-issues: true
|
||||
275
.github/workflows/b-binary-build-and-e2e-tests.yaml
vendored
Normal file
275
.github/workflows/b-binary-build-and-e2e-tests.yaml
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
name: b-binary-build-and-e2e-tests
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
COMPONENT_NAME:
|
||||
required: true
|
||||
type: string
|
||||
RELEASE:
|
||||
required: true
|
||||
type: string
|
||||
CLIENT:
|
||||
required: true
|
||||
type: string
|
||||
GO_VERSION:
|
||||
type: string
|
||||
default: "1.19"
|
||||
GO111MODULE:
|
||||
required: true
|
||||
type: string
|
||||
CGO_ENABLED:
|
||||
type: number
|
||||
default: 1
|
||||
BINARY_TESTS:
|
||||
type: string
|
||||
default: '[
|
||||
"scan_nsa",
|
||||
"scan_mitre",
|
||||
"scan_with_exceptions",
|
||||
"scan_repository",
|
||||
"scan_local_file",
|
||||
"scan_local_glob_files",
|
||||
"scan_local_list_of_files",
|
||||
"scan_nsa_and_submit_to_backend",
|
||||
"scan_mitre_and_submit_to_backend",
|
||||
"scan_local_repository_and_submit_to_backend",
|
||||
"scan_repository_from_url_and_submit_to_backend",
|
||||
"scan_with_exception_to_backend",
|
||||
"scan_with_custom_framework",
|
||||
"scan_customer_configuration",
|
||||
"host_scanner"
|
||||
]'
|
||||
|
||||
jobs:
|
||||
|
||||
check-secret:
|
||||
name: secret-validator
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
|
||||
steps:
|
||||
- name: check if the necessary secrets are set in github secrets
|
||||
id: check-secret-set
|
||||
env:
|
||||
CUSTOMER: ${{ secrets.CUSTOMER }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.PASSWORD }}
|
||||
CLIENT_ID: ${{ secrets.CLIENT_ID_PROD }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY_PROD }}
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
echo "is-secret-set=${{ env.CUSTOMER != '' &&
|
||||
env.USERNAME != '' &&
|
||||
env.PASSWORD != '' &&
|
||||
env.CLIENT_ID != '' &&
|
||||
env.SECRET_KEY != '' &&
|
||||
env.REGISTRY_USERNAME != '' &&
|
||||
env.REGISTRY_PASSWORD != ''
|
||||
}}" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
binary-build:
|
||||
name: Create cross-platform build
|
||||
outputs:
|
||||
TEST_NAMES: ${{ steps.export_tests_to_env.outputs.TEST_NAMES }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Cache Go modules (Linux)
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
uses: 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@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@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@v3
|
||||
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 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: ${{ inputs.CGO_ENABLED }}
|
||||
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@v3
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout 10m --build-tags=static
|
||||
only-new-issues: true
|
||||
|
||||
- id: export_tests_to_env
|
||||
name: set test name
|
||||
run: |
|
||||
echo "TEST_NAMES=$input" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
input: ${{ inputs.BINARY_TESTS }}
|
||||
|
||||
- uses: actions/upload-artifact@v3.1.1
|
||||
name: Upload artifact (Linux)
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
with:
|
||||
name: kubescape-ubuntu-latest
|
||||
path: build/
|
||||
if-no-files-found: error
|
||||
|
||||
- uses: 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
|
||||
|
||||
run-tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
TEST: ${{ fromJson(needs.binary-build.outputs.TEST_NAMES) }}
|
||||
needs: [check-secret, binary-build]
|
||||
if: needs.check-secret.outputs.is-secret-set == 'true'
|
||||
runs-on: ubuntu-latest # This cannot change
|
||||
steps:
|
||||
|
||||
- uses: actions/download-artifact@v3.0.2
|
||||
id: download-artifact
|
||||
with:
|
||||
name: kubescape-ubuntu-latest
|
||||
path: "~"
|
||||
|
||||
- run: ls -laR
|
||||
|
||||
- name: chmod +x
|
||||
run: chmod +x -R ${{steps.download-artifact.outputs.download-path}}/kubescape-ubuntu-latest
|
||||
|
||||
- name: Checkout systests repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: armosec/system-tests
|
||||
path: .
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.8.13'
|
||||
cache: 'pip'
|
||||
|
||||
- name: create env
|
||||
run: ./create_env.sh
|
||||
|
||||
- name: Generate uuid
|
||||
id: uuid
|
||||
run: |
|
||||
echo "RANDOM_UUID=$(uuidgen)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create k8s Kind Cluster
|
||||
id: kind-cluster-install
|
||||
uses: helm/kind-action@v1.3.0
|
||||
with:
|
||||
cluster_name: ${{ steps.uuid.outputs.RANDOM_UUID }}
|
||||
|
||||
- name: run-tests
|
||||
env:
|
||||
CUSTOMER: ${{ secrets.CUSTOMER }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.PASSWORD }}
|
||||
CLIENT_ID: ${{ secrets.CLIENT_ID_PROD }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY_PROD }}
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
run: |
|
||||
echo "Test history:"
|
||||
echo " ${{ matrix.TEST }} " >/tmp/testhistory
|
||||
cat /tmp/testhistory
|
||||
source systests_python_env/bin/activate
|
||||
|
||||
python3 systest-cli.py \
|
||||
-t ${{ matrix.TEST }} \
|
||||
-b production \
|
||||
-c CyberArmorTests \
|
||||
--duration 3 \
|
||||
--logger DEBUG \
|
||||
--kwargs kubescape=${{steps.download-artifact.outputs.download-path}}/kubescape-ubuntu-latest
|
||||
|
||||
deactivate
|
||||
|
||||
- name: Test Report
|
||||
uses: mikepenz/action-junit-report@v3.6.1
|
||||
if: always() # always run even if the previous step fails
|
||||
with:
|
||||
report_paths: '**/results_xml_format/**.xml'
|
||||
commit: ${{github.event.workflow_run.head_sha}}
|
||||
|
||||
|
||||
134
.github/workflows/build.yaml
vendored
134
.github/workflows/build.yaml
vendored
@@ -1,134 +0,0 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
jobs:
|
||||
once:
|
||||
name: Create release
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
steps:
|
||||
- name: Create a release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: v2.0.${{ github.run_number }}
|
||||
release_name: Release v2.0.${{ github.run_number }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
build:
|
||||
name: Create cross-platform release build, tag and upload binaries
|
||||
needs: once
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
ArmoBEServer: api.armo.cloud
|
||||
ArmoAuthServer: auth.armo.cloud
|
||||
ArmoERServer: report.armo.cloud
|
||||
ArmoWebsite: portal.armo.cloud
|
||||
CGO_ENABLED: 0
|
||||
run: python3 --version && python3 build.py
|
||||
|
||||
- name: Smoke Testing
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
|
||||
|
||||
- name: Upload release binaries
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.once.outputs.upload_url }}
|
||||
asset_path: build/${{ matrix.os }}/kubescape
|
||||
asset_name: kubescape-${{ matrix.os }}
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload release hash
|
||||
id: upload-release-hash
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.once.outputs.upload_url }}
|
||||
asset_path: build/${{ matrix.os }}/kubescape.sha256
|
||||
asset_name: kubescape-${{ matrix.os }}-sha256
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
|
||||
|
||||
build-docker:
|
||||
name: Build docker container, tag and upload to registry
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'armosec/kubescape' }} # TODO
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set image version
|
||||
id: image-version
|
||||
run: echo '::set-output name=IMAGE_VERSION::v2.0.${{ github.run_number }}'
|
||||
|
||||
- name: Set image name
|
||||
id: image-name
|
||||
run: echo '::set-output name=IMAGE_NAME::quay.io/${{ github.repository_owner }}/kubescape'
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
|
||||
- name: Re-Tag Image to latest
|
||||
run: docker tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} ${{ steps.image-name.outputs.IMAGE_NAME }}:latest
|
||||
|
||||
- name: Login to Quay.io
|
||||
env: # Or as an environment variable
|
||||
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
||||
# - name: Login to GitHub Container Registry
|
||||
# uses: docker/login-action@v1
|
||||
# with:
|
||||
# registry: ghcr.io
|
||||
# username: ${{ github.actor }}
|
||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Push Docker image
|
||||
run: |
|
||||
docker push ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
docker push ${{ steps.image-name.outputs.IMAGE_NAME }}:latest
|
||||
|
||||
# TODO - Wait for casign to support fixed tags -> https://github.com/sigstore/cosign/issues/1424
|
||||
# - name: Install cosign
|
||||
# uses: sigstore/cosign-installer@main
|
||||
# with:
|
||||
# cosign-release: 'v1.5.1' # optional
|
||||
# - name: sign kubescape container image
|
||||
# env:
|
||||
# COSIGN_EXPERIMENTAL: "true"
|
||||
# run: |
|
||||
# cosign sign --force ${{ steps.image-name.outputs.IMAGE_NAME }}:latest
|
||||
# cosign sign --force ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
|
||||
84
.github/workflows/build_dev.yaml
vendored
84
.github/workflows/build_dev.yaml
vendored
@@ -1,84 +0,0 @@
|
||||
name: build-dev
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
jobs:
|
||||
build:
|
||||
name: Create cross-platform dev build
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
ArmoBEServer: api.armo.cloud
|
||||
ArmoAuthServer: auth.armo.cloud
|
||||
ArmoERServer: report.armo.cloud
|
||||
ArmoWebsite: portal.armo.cloud
|
||||
CGO_ENABLED: 0
|
||||
run: python3 --version && python3 build.py
|
||||
|
||||
- name: Smoke Testing
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: kubescape-${{ matrix.os }}
|
||||
path: build/${{ matrix.os }}/kubescape
|
||||
|
||||
build-docker:
|
||||
name: Build docker container, tag and upload to registry
|
||||
needs: build
|
||||
if: ${{ github.repository == 'armosec/kubescape' }} # TODO
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set image version
|
||||
id: image-version
|
||||
run: echo '::set-output name=IMAGE_VERSION::dev-v2.0.${{ github.run_number }}'
|
||||
|
||||
- name: Set image name
|
||||
id: image-name
|
||||
run: echo '::set-output name=IMAGE_NAME::quay.io/${{ github.repository_owner }}/kubescape'
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
|
||||
- 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: Login to GitHub Container Registry
|
||||
# uses: docker/login-action@v1
|
||||
# with:
|
||||
# registry: ghcr.io
|
||||
# username: ${{ github.actor }}
|
||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Push Docker image
|
||||
run: |
|
||||
docker push ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
|
||||
57
.github/workflows/c-create-release.yaml
vendored
Normal file
57
.github/workflows/c-create-release.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: c-create_release
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
RELEASE_NAME:
|
||||
description: 'Release name'
|
||||
required: true
|
||||
type: string
|
||||
TAG:
|
||||
description: 'Tag name'
|
||||
required: true
|
||||
type: string
|
||||
DRAFT:
|
||||
description: 'Create draft release'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
|
||||
create-release:
|
||||
name: create-release
|
||||
runs-on: ubuntu-latest
|
||||
# permissions:
|
||||
# contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3.0.2
|
||||
id: download-artifact
|
||||
with:
|
||||
path: .
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
MAC_OS: macos-latest
|
||||
UBUNTU_OS: ubuntu-latest
|
||||
WINDOWS_OS: windows-latest
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_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
|
||||
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
|
||||
|
||||
|
||||
85
.github/workflows/d-publish-image.yaml
vendored
Normal file
85
.github/workflows/d-publish-image.yaml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: d-publish-image
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
client:
|
||||
description: 'client name'
|
||||
required: true
|
||||
type: string
|
||||
image_tag:
|
||||
description: 'image tag'
|
||||
required: true
|
||||
type: string
|
||||
image_name:
|
||||
description: 'image registry and name'
|
||||
required: true
|
||||
type: string
|
||||
cosign:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
description: 'run cosign on released image'
|
||||
support_platforms:
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
description: 'support amd64/arm64'
|
||||
|
||||
jobs:
|
||||
check-secret:
|
||||
name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
|
||||
steps:
|
||||
- name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
|
||||
id: check-secret-set
|
||||
env:
|
||||
QUAYIO_REGISTRY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
|
||||
|
||||
build-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@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Quay.io
|
||||
env:
|
||||
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
||||
|
||||
- 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@main
|
||||
with:
|
||||
cosign-release: 'v1.12.0'
|
||||
- name: sign kubescape container image
|
||||
if: ${{ inputs.cosign }}
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: "true"
|
||||
run: |
|
||||
cosign sign --force ${{ inputs.image_name }}
|
||||
|
||||
40
.github/workflows/master_pr_checks.yaml
vendored
40
.github/workflows/master_pr_checks.yaml
vendored
@@ -1,40 +0,0 @@
|
||||
name: master-pr
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
types: [ edited, opened, synchronize, reopened ]
|
||||
jobs:
|
||||
build:
|
||||
name: Create cross-platform build
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
ArmoBEServer: api.armo.cloud
|
||||
ArmoAuthServer: auth.armo.cloud
|
||||
ArmoERServer: report.armo.cloud
|
||||
ArmoWebsite: portal.armo.cloud
|
||||
CGO_ENABLED: 0
|
||||
run: python3 --version && python3 build.py
|
||||
|
||||
- name: Smoke Testing
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
|
||||
|
||||
23
.github/workflows/z-close-typos-issues.yaml
vendored
Normal file
23
.github/workflows/z-close-typos-issues.yaml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
|
||||
jobs:
|
||||
open_PR_message:
|
||||
if: github.event.label.name == 'typo'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: ben-z/actions-comment-on-issue@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@v2
|
||||
with:
|
||||
query: 'label:typo'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,7 +1,9 @@
|
||||
*.vs*
|
||||
*kubescape*
|
||||
*debug*
|
||||
*vender*
|
||||
*vendor*
|
||||
*.pyc*
|
||||
.idea
|
||||
ca.srl
|
||||
.history
|
||||
ca.srl
|
||||
*.out
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "git2go"]
|
||||
path = git2go
|
||||
url = https://github.com/libgit2/git2go.git
|
||||
57
.golangci.yml
Normal file
57
.golangci.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
dupl:
|
||||
threshold: 200
|
||||
goconst:
|
||||
min-len: 3
|
||||
min-occurrences: 2
|
||||
gocognit:
|
||||
min-complexity: 65
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- gosec
|
||||
- staticcheck
|
||||
- nolintlint
|
||||
- gofmt
|
||||
- unused
|
||||
- govet
|
||||
- bodyclose
|
||||
- typecheck
|
||||
- goimports
|
||||
- ineffassign
|
||||
- gosimple
|
||||
disable:
|
||||
# temporarily disabled
|
||||
- varcheck
|
||||
- errcheck
|
||||
- dupl
|
||||
- gocritic
|
||||
- gocognit
|
||||
- nakedret
|
||||
- revive
|
||||
- stylecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
#- forbidigo # <- see later
|
||||
# should remain disabled
|
||||
- deadcode # deprecated linter
|
||||
- maligned
|
||||
- lll
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
issues:
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- revive
|
||||
text: "var-naming"
|
||||
- linters:
|
||||
- revive
|
||||
text: "type name will be used as (.+?) by other packages, and that stutters"
|
||||
- linters:
|
||||
- stylecheck
|
||||
text: "ST1003"
|
||||
run:
|
||||
skip-dirs:
|
||||
- git2go
|
||||
36
.krew.yaml
Normal file
36
.krew.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
apiVersion: krew.googlecontainertools.github.com/v1alpha2
|
||||
kind: Plugin
|
||||
metadata:
|
||||
name: kubescape
|
||||
spec:
|
||||
homepage: https://kubescape.io/
|
||||
shortDescription: An open-source Kubernetes security platform for your IDE, CI/CD pipelines, and clusters
|
||||
version: {{ .TagName }}
|
||||
description: |
|
||||
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 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/).
|
||||
platforms:
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-macos-latest" .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: amd64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-ubuntu-latest" .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: amd64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-windows-latest" .TagName }}
|
||||
bin: kubescape
|
||||
@@ -1,127 +1,3 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
## Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement [here](mailto:ben@armosec.io).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
|
||||
100
CONTRIBUTING.md
100
CONTRIBUTING.md
@@ -3,15 +3,17 @@
|
||||
First, it is awesome that you are considering contributing to Kubescape! Contributing is important and fun and we welcome your efforts.
|
||||
|
||||
When contributing, we categorize contributions into two:
|
||||
* Small code changes or fixes, whose scope are limited to a single or two files
|
||||
* Complex features and improvements, whose are not limited
|
||||
* 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 issue,
|
||||
email, or any other method with the owners of this repository before making a change. Most likely your changes or features are great, but sometimes we might already going to this direction (or the exact opposite ;-) ) and we don't want to waste your time.
|
||||
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.
|
||||
|
||||
Please note we have a code of conduct, please follow it in all your interactions with the project.
|
||||
## Code of Conduct
|
||||
|
||||
Please follow our [code of conduct](CODE_OF_CONDUCT.md) in all of your interactions within the project.
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
@@ -19,82 +21,44 @@ Please note we have a code of conduct, please follow it in all your interactions
|
||||
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 `dev` branch - we test the component before merging into the `master` branch
|
||||
4. We will merge the Pull Request in once you have the sign-off.
|
||||
3. Open Pull Request to the `master` branch.
|
||||
4. We will merge the Pull Request once you have the sign-off.
|
||||
|
||||
## Code of Conduct
|
||||
## Developer Certificate of Origin
|
||||
|
||||
### Our Pledge
|
||||
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.
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
Commits made through the GitHub web application are automatically signed off.
|
||||
|
||||
### Our Standards
|
||||
### Configuring Git to sign off commits
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
First, configure your name and email address in Git global settings:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
```
|
||||
$ git config --global user.name "John Doe"
|
||||
$ git config --global user.email johndoe@example.com
|
||||
```
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
You can now sign off per-commit, or configure Git to always sign off commits per repository.
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
### Sign off per-commit
|
||||
|
||||
We will distance those who are constantly adhere to unacceptable behavior.
|
||||
Add [`-s`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s) to your Git command line. For example:
|
||||
|
||||
### Our Responsibilities
|
||||
```git commit -s -m "Fix issue 64738"```
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
This is tedious, and if you forget, you'll have to [amend your commit](#f)
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
### Configure a repository to always include sign off
|
||||
|
||||
### Scope
|
||||
There are many ways to achieve this with Git hooks, but the simplest is to do the following:
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
### Enforcement
|
||||
## Fixing a commit where the DCO failed
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
Check out [this guide](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md).
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# Maintainers
|
||||
|
||||
The following table lists Kubescape project maintainers
|
||||
The following table lists the Kubescape project maintainers:
|
||||
|
||||
| Name | GitHub | Email | Organization | Role | Added/Renewed On |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| [Ben Hirschberg](https://www.linkedin.com/in/benyamin-ben-hirschberg-66141890) | [@slashben](https://github.com/slashben) | ben@armosec.io | [ARMO](https://www.armosec.io/) | VP R&D | 2021-09-01 |
|
||||
| [Rotem Refael](https://www.linkedin.com/in/rotem-refael) | [@rotemamsa](https://github.com/rotemamsa) | rrefael@armosec.io | [ARMO](https://www.armosec.io/) | Team Leader | 2021-10-11 |
|
||||
| [David Wertenteil](https://www.linkedin.com/in/david-wertenteil-0ba277b9) | [@dwertent](https://github.com/dwertent) | dwertent@armosec.io | [ARMO](https://www.armosec.io/) | Kubescape CLI Developer | 2021-09-01 |
|
||||
| [Bezalel Brandwine](https://www.linkedin.com/in/bezalel-brandwine) | [@Bezbran](https://github.com/Bezbran) | bbrandwine@armosec.io | [ARMO](https://www.armosec.io/) | Kubescape SaaS Developer | 2021-09-01 |
|
||||
| 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 |
|
||||
|
||||
20
Makefile
Normal file
20
Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
.PHONY: test all build libgit2
|
||||
|
||||
# default task invoked while running make
|
||||
all: libgit2 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"
|
||||
|
||||
build:
|
||||
go build -v -tags=$(TAGS) .
|
||||
|
||||
test:
|
||||
go test -v -tags=$(TAGS) ./...
|
||||
311
README.md
311
README.md
@@ -1,273 +1,94 @@
|
||||
<img src="docs/kubescape.png" width="300" alt="logo" align="center">
|
||||
[](releases)
|
||||
[](https://github.com/kubescape/kubescape/actions/workflows/build.yaml)
|
||||
[](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://twitter.com/kubescape)
|
||||
|
||||
[](https://github.com/armosec/kubescape/actions/workflows/build.yaml)
|
||||
[](https://goreportcard.com/report/github.com/armosec/kubescape)
|
||||
# Kubescape
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/cncf/artwork/master/projects/kubescape/stacked/white/kubescape-stacked-white.svg" width="150">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/cncf/artwork/master/projects/kubescape/stacked/color/kubescape-stacked-color.svg" width="150">
|
||||
<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_
|
||||
|
||||
Kubescape is a K8s open-source tool providing a multi-cloud K8s single pane of glass, including risk analysis, security compliance, RBAC visualizer and image vulnerabilities scanning.
|
||||
Kubescape scans K8s clusters, YAML files, and HELM charts, detecting misconfigurations according to multiple frameworks (such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo) , [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/)), software vulnerabilities, and RBAC (role-based-access-control) violations at early stages of the CI/CD pipeline, calculates risk score instantly and shows risk trends over time.
|
||||
It became one of the fastest-growing Kubernetes tools among developers due to its easy-to-use CLI interface, flexible output formats, and automated scanning capabilities, saving Kubernetes users and admins’ precious time, effort, and resources.
|
||||
Kubescape integrates natively with other DevOps tools, including Jenkins, CircleCI, Github workflows, Prometheus, and Slack, and supports multi-cloud K8s deployments like EKS, GKE, and AKS.
|
||||
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.
|
||||
|
||||
</br>
|
||||
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)).
|
||||
|
||||
<img src="docs/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) sandbox project](https://www.cncf.io/sandbox-projects/).
|
||||
|
||||
# TL;DR
|
||||
## Install:
|
||||
```
|
||||
curl -s https://raw.githubusercontent.com/armosec/kubescape/master/install.sh | /bin/bash
|
||||
## Demo
|
||||
<img src="docs/img/demo.gif">
|
||||
|
||||
_Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape! 😀_
|
||||
|
||||
## Getting started
|
||||
|
||||
Experimenting with Kubescape is as easy as:
|
||||
|
||||
```sh
|
||||
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
|
||||
```
|
||||
|
||||
[Install on windows](#install-on-windows)
|
||||
Learn more about:
|
||||
|
||||
[Install on macOS](#install-on-macos)
|
||||
* [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)
|
||||
|
||||
## Run:
|
||||
```
|
||||
kubescape scan --submit --enable-host-scan
|
||||
```
|
||||
_Did you know you can use Kubescape in all these places?_
|
||||
|
||||
<img src="docs/summary.png">
|
||||
<div align="center">
|
||||
<img src="docs/img/ksfromcodetodeploy.png" alt="Places you can use Kubescape: in your IDE, CI, CD, or against a running cluster.">
|
||||
</div>
|
||||
|
||||
</br>
|
||||
## Under the hood
|
||||
|
||||
> Kubescape is an open source project, we welcome your feedback and ideas for improvement. We’re also aiming to collaborate with the Kubernetes community to help make the tests themselves more robust and complete as Kubernetes develops.
|
||||
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).
|
||||
|
||||
</br>
|
||||
By default, the results are printed in a console-friendly manner, but they can be:
|
||||
|
||||
### Click [👍](https://github.com/armosec/kubescape/stargazers) if you want us to continue to develop and improve Kubescape 😀
|
||||
* exported to JSON or junit XML
|
||||
* rendered to HTML or PDF
|
||||
* submitted to a [cloud service](docs/providers.md)
|
||||
|
||||
</br>
|
||||
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).
|
||||
|
||||
## Community
|
||||
|
||||
# Being part of the team
|
||||
Kubescape is an open source project, we welcome your feedback and ideas for improvement. We are part of the Kubernetes community and are building more tests and controls as the ecosystem develops.
|
||||
|
||||
We invite you to our team! We are excited about this project and want to return the love we get.
|
||||
We hold [community meetings](https://us02web.zoom.us/j/84020231442) on Zoom, on the first Tuesday of every month, at 14:00 GMT.
|
||||
|
||||
Want to contribute? Want to discuss something? Have an issue?
|
||||
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
|
||||
* Feel free to pick a task from the [roadmap](docs/roadmap.md) or suggest a feature of your own. [Contact us](MAINTAINERS.md) directly for more information :)
|
||||
* Open a issue, we are trying to respond within 48 hours
|
||||
* [Join us](https://armosec.github.io/kubescape/) in a discussion on our discord server!
|
||||
## Contributions
|
||||
|
||||
[<img src="docs/discord-banner.png" width="100" alt="logo" align="center">](https://armosec.github.io/kubescape/)
|
||||

|
||||
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.
|
||||
|
||||
# Options and examples
|
||||
<br>
|
||||
|
||||
[Kubescape docs](https://hub.armo.cloud/docs)
|
||||
|
||||
## Playground
|
||||
* [Kubescape playground](https://www.katacoda.com/pathaksaiyam/scenarios/kubescape)
|
||||
|
||||
## Tutorials
|
||||
|
||||
* [Overview](https://youtu.be/wdBkt_0Qhbg)
|
||||
* [How To Secure Kubernetes Clusters With Kubescape And Armo](https://youtu.be/ZATGiDIDBQk)
|
||||
* [Scan Kubernetes YAML files](https://youtu.be/Ox6DaR7_4ZI)
|
||||
* [Scan Kubescape on an air-gapped environment (offline support)](https://youtu.be/IGXL9s37smM)
|
||||
* [Managing exceptions in the Kubescape SaaS version](https://youtu.be/OzpvxGmCR80)
|
||||
* [Configure and run customized frameworks](https://youtu.be/12Sanq_rEhs)
|
||||
* Customize controls configurations. [Kubescape CLI](https://youtu.be/955psg6TVu4), [Kubescape SaaS](https://youtu.be/lIMVSVhH33o)
|
||||
|
||||
## Install on Windows
|
||||
|
||||
**Requires powershell v5.0+**
|
||||
|
||||
``` powershell
|
||||
iwr -useb https://raw.githubusercontent.com/armosec/kubescape/master/install.ps1 | iex
|
||||
```
|
||||
|
||||
Note: if you get an error you might need to change the execution policy (i.e. enable Powershell) with
|
||||
|
||||
``` powershell
|
||||
Set-ExecutionPolicy RemoteSigned -scope CurrentUser
|
||||
```
|
||||
|
||||
## Install on macOS
|
||||
|
||||
1. ```
|
||||
brew tap armosec/kubescape
|
||||
```
|
||||
2. ```
|
||||
brew install kubescape
|
||||
```
|
||||
|
||||
## Usage & Examples
|
||||
|
||||
### Examples
|
||||
|
||||
|
||||
#### Scan a running Kubernetes cluster and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
|
||||
```
|
||||
kubescape scan --submit --enable-host-scan
|
||||
```
|
||||
|
||||
> Read [here](https://hub.armo.cloud/docs/host-sensor) more about the `enable-host-scan` flag
|
||||
|
||||
#### Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
|
||||
```
|
||||
kubescape scan framework nsa --submit
|
||||
```
|
||||
|
||||
|
||||
#### Scan a running Kubernetes cluster with [`MITRE ATT&CK®`](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) framework and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
|
||||
```
|
||||
kubescape scan framework mitre --submit
|
||||
```
|
||||
|
||||
|
||||
#### Scan a running Kubernetes cluster with a specific control using the control name or control ID. [List of controls](https://hub.armo.cloud/docs/controls)
|
||||
```
|
||||
kubescape scan control "Privileged container"
|
||||
```
|
||||
|
||||
#### Scan specific namespaces
|
||||
```
|
||||
kubescape scan --include-namespaces development,staging,production
|
||||
```
|
||||
|
||||
#### Scan cluster and exclude some namespaces
|
||||
```
|
||||
kubescape scan --exclude-namespaces kube-system,kube-public
|
||||
```
|
||||
|
||||
#### Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI)
|
||||
```
|
||||
kubescape scan *.yaml
|
||||
```
|
||||
|
||||
#### Scan kubernetes manifest files from a public github repository
|
||||
```
|
||||
kubescape scan https://github.com/armosec/kubescape
|
||||
```
|
||||
|
||||
#### Display all scanned resources (including the resources who passed)
|
||||
```
|
||||
kubescape scan --verbose
|
||||
```
|
||||
|
||||
#### Output in `json` format
|
||||
|
||||
> Add the `--format-version v2` flag
|
||||
|
||||
```
|
||||
kubescape scan --format json --format-version v2 --output results.json
|
||||
```
|
||||
|
||||
#### Output in `junit xml` format
|
||||
```
|
||||
kubescape scan --format junit --output results.xml
|
||||
```
|
||||
|
||||
#### Output in `pdf` format - Contributed by [@alegrey91](https://github.com/alegrey91)
|
||||
|
||||
```
|
||||
kubescape scan --format pdf --output results.pdf
|
||||
```
|
||||
|
||||
#### Output in `prometheus` metrics format - Contributed by [@Joibel](https://github.com/Joibel)
|
||||
|
||||
```
|
||||
kubescape scan --format prometheus
|
||||
```
|
||||
|
||||
#### Scan with exceptions, objects with exceptions will be presented as `exclude` and not `fail`
|
||||
[Full documentation](examples/exceptions/README.md)
|
||||
```
|
||||
kubescape scan --exceptions examples/exceptions/exclude-kube-namespaces.json
|
||||
```
|
||||
|
||||
#### Scan Helm charts - Render the helm chart using [`helm template`](https://helm.sh/docs/helm/helm_template/) and pass to stdout
|
||||
```
|
||||
helm template [NAME] [CHART] [flags] --dry-run | kubescape scan -
|
||||
```
|
||||
|
||||
e.g.
|
||||
```
|
||||
helm template bitnami/mysql --generate-name --dry-run | kubescape scan -
|
||||
```
|
||||
|
||||
|
||||
### Offline/Air-gaped Environment Support
|
||||
|
||||
[Video tutorial](https://youtu.be/IGXL9s37smM)
|
||||
|
||||
It is possible to run Kubescape offline!
|
||||
#### Download all artifacts
|
||||
|
||||
1. Download and save in local directory, if path not specified, will save all in `~/.kubescape`
|
||||
```
|
||||
kubescape download artifacts --output path/to/local/dir
|
||||
```
|
||||
2. Copy the downloaded artifacts to the air-gaped/offline environment
|
||||
|
||||
3. Scan using the downloaded artifacts
|
||||
```
|
||||
kubescape scan --use-artifacts-from path/to/local/dir
|
||||
```
|
||||
|
||||
#### Download a single artifacts
|
||||
|
||||
You can also download a single artifacts and scan with the `--use-from` flag
|
||||
|
||||
1. Download and save in file, if file name not specified, will save in `~/.kubescape/<framework name>.json`
|
||||
```
|
||||
kubescape download framework nsa --output /path/nsa.json
|
||||
```
|
||||
2. Copy the downloaded artifacts to the air-gaped/offline environment
|
||||
|
||||
3. Scan using the downloaded framework
|
||||
```
|
||||
kubescape scan framework nsa --use-from /path/nsa.json
|
||||
```
|
||||
|
||||
|
||||
## Scan Periodically using Helm - Contributed by [@yonahd](https://github.com/yonahd)
|
||||
[Please follow the instructions here](https://hub.armo.cloud/docs/installation-of-armo-in-cluster)
|
||||
[helm chart repo](https://github.com/armosec/armo-helm)
|
||||
|
||||
## Scan using docker image
|
||||
|
||||
Official Docker image `quay.io/armosec/kubescape`
|
||||
|
||||
```
|
||||
docker run -v "$(pwd)/example.yaml:/app/example.yaml quay.io/armosec/kubescape scan /app/example.yaml
|
||||
```
|
||||
|
||||
If you wish, you can [build the docker image on your own](build/README.md)
|
||||
|
||||
# Submit data manually
|
||||
|
||||
Use the `submit` command if you wish to submit data manually
|
||||
|
||||
## Submit scan results manually
|
||||
|
||||
> Support forward compatibility by using the `--format-version v2` flag
|
||||
|
||||
First, scan your cluster using the `json` format flag: `kubescape scan framework <name> --format json --format-version v2 --output path/to/results.json`.
|
||||
|
||||
Now you can submit the results to the Kubescape SaaS version -
|
||||
```
|
||||
kubescape submit results path/to/results.json
|
||||
```
|
||||
|
||||
# Under the hood
|
||||
|
||||
## Technology
|
||||
Kubescape based on OPA engine: https://github.com/open-policy-agent/opa and ARMO's posture controls.
|
||||
|
||||
The tools retrieves Kubernetes objects from the API server and runs a set of [rego's snippets](https://www.openpolicyagent.org/docs/latest/policy-language/) developed by [ARMO](https://www.armosec.io/).
|
||||
|
||||
The results by default printed in a pretty "console friendly" manner, but they can be retrieved in JSON format for further processing.
|
||||
|
||||
Kubescape is an open source project, we welcome your feedback and ideas for improvement. We’re also aiming to collaborate with the Kubernetes community to help make the tests themselves more robust and complete as Kubernetes develops.
|
||||
|
||||
## Thanks to all the contributors ❤️
|
||||
<a href = "https://github.com/armosec/kubescape/graphs/contributors">
|
||||
<img src = "https://contrib.rocks/image?repo=armosec/kubescape"/>
|
||||
<a href = "https://github.com/kubescape/kubescape/graphs/contributors">
|
||||
<img src = "https://contrib.rocks/image?repo=kubescape/kubescape"/>
|
||||
</a>
|
||||
|
||||
## 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.
|
||||
|
||||
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).
|
||||
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/cncf/artwork/master/other/cncf-sandbox/horizontal/color/cncf-sandbox-horizontal-color.svg" width="300" alt="CNCF Sandbox Project">
|
||||
</div>
|
||||
|
||||
51
build.bat
Normal file
51
build.bat
Normal file
@@ -0,0 +1,51 @@
|
||||
@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
|
||||
85
build.py
85
build.py
@@ -3,82 +3,70 @@ import sys
|
||||
import hashlib
|
||||
import platform
|
||||
import subprocess
|
||||
import tarfile
|
||||
|
||||
BASE_GETTER_CONST = "github.com/armosec/kubescape/cautils/getter"
|
||||
BE_SERVER_CONST = BASE_GETTER_CONST + ".ArmoBEURL"
|
||||
ER_SERVER_CONST = BASE_GETTER_CONST + ".ArmoERURL"
|
||||
WEBSITE_CONST = BASE_GETTER_CONST + ".ArmoFEURL"
|
||||
AUTH_SERVER_CONST = BASE_GETTER_CONST + ".armoAUTHURL"
|
||||
BASE_GETTER_CONST = "github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
|
||||
def checkStatus(status, msg):
|
||||
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 getBuildDir():
|
||||
currentPlatform = platform.system()
|
||||
buildDir = "build/"
|
||||
def get_build_dir():
|
||||
return "build"
|
||||
|
||||
if currentPlatform == "Windows": buildDir += "windows-latest"
|
||||
elif currentPlatform == "Linux": buildDir += "ubuntu-latest"
|
||||
elif currentPlatform == "Darwin": buildDir += "macos-latest"
|
||||
else: raise OSError("Platform %s is not supported!" % (currentPlatform))
|
||||
|
||||
return buildDir
|
||||
def get_package_name():
|
||||
current_platform = platform.system()
|
||||
|
||||
def getPackageName():
|
||||
packageName = "kubescape"
|
||||
# if platform.system() == "Windows": packageName += ".exe"
|
||||
if current_platform not in platformSuffixes: raise OSError("Platform %s is not supported!" % (current_platform))
|
||||
|
||||
return packageName
|
||||
return "kubescape-" + platformSuffixes[current_platform]
|
||||
|
||||
|
||||
def main():
|
||||
print("Building Kubescape")
|
||||
|
||||
# print environment variables
|
||||
# print(os.environ)
|
||||
|
||||
# Set some variables
|
||||
packageName = getPackageName()
|
||||
buildUrl = "github.com/armosec/kubescape/cautils.BuildNumber"
|
||||
releaseVersion = os.getenv("RELEASE")
|
||||
ArmoBEServer = os.getenv("ArmoBEServer")
|
||||
ArmoERServer = os.getenv("ArmoERServer")
|
||||
ArmoWebsite = os.getenv("ArmoWebsite")
|
||||
ArmoAuthServer = os.getenv("ArmoAuthServer")
|
||||
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
|
||||
buildDir = getBuildDir()
|
||||
build_dir = get_build_dir()
|
||||
|
||||
ks_file = os.path.join(buildDir, packageName)
|
||||
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(buildDir):
|
||||
os.makedirs(buildDir)
|
||||
if not os.path.isdir(build_dir):
|
||||
os.makedirs(build_dir)
|
||||
|
||||
# Build kubescape
|
||||
ldflags = "-w -s"
|
||||
if releaseVersion:
|
||||
ldflags += " -X {}={}".format(buildUrl, releaseVersion)
|
||||
if ArmoBEServer:
|
||||
ldflags += " -X {}={}".format(BE_SERVER_CONST, ArmoBEServer)
|
||||
if ArmoERServer:
|
||||
ldflags += " -X {}={}".format(ER_SERVER_CONST, ArmoERServer)
|
||||
if ArmoWebsite:
|
||||
ldflags += " -X {}={}".format(WEBSITE_CONST, ArmoWebsite)
|
||||
if ArmoAuthServer:
|
||||
ldflags += " -X {}={}".format(AUTH_SERVER_CONST, ArmoAuthServer)
|
||||
if release_version:
|
||||
ldflags += " -X {}={}".format(build_url, release_version)
|
||||
if client_name:
|
||||
ldflags += " -X {}={}".format(client_var, client_name)
|
||||
|
||||
build_command = ["go", "build", "-o", ks_file, "-ldflags" ,ldflags]
|
||||
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)
|
||||
checkStatus(status, "Failed to build kubescape")
|
||||
|
||||
check_status(status, "Failed to build kubescape")
|
||||
|
||||
sha256 = hashlib.sha256()
|
||||
with open(ks_file, "rb") as kube:
|
||||
sha256.update(kube.read())
|
||||
@@ -87,8 +75,11 @@ def main():
|
||||
print("kubescape hash: {}, file: {}".format(hash, hash_file))
|
||||
kube_sha.write(sha256.hexdigest())
|
||||
|
||||
with tarfile.open(tar_file, 'w:gz') as archive:
|
||||
archive.add(ks_file, "kubescape")
|
||||
|
||||
print("Build Done")
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,31 +1,51 @@
|
||||
FROM golang:1.17-alpine as builder
|
||||
#ENV GOPROXY=https://goproxy.io,direct
|
||||
FROM golang:1.19-alpine as builder
|
||||
|
||||
ARG image_version
|
||||
ARG client
|
||||
|
||||
ENV RELEASE=$image_version
|
||||
ENV CLIENT=$client
|
||||
|
||||
ENV GO111MODULE=
|
||||
|
||||
ENV CGO_ENABLED=0
|
||||
ENV CGO_ENABLED=1
|
||||
|
||||
# Install required python/pip
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
|
||||
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
|
||||
|
||||
WORKDIR /work
|
||||
ADD . .
|
||||
|
||||
# install libgit2
|
||||
RUN rm -rf git2go && make libgit2
|
||||
|
||||
# build kubescape server
|
||||
WORKDIR /work/httphandler
|
||||
RUN python build.py
|
||||
RUN ls -ltr build/
|
||||
|
||||
# build kubescape cmd
|
||||
WORKDIR /work
|
||||
RUN python build.py
|
||||
|
||||
RUN ls -ltr build/ubuntu-latest
|
||||
RUN /work/build/kubescape-ubuntu-latest download artifacts -o /work/artifacts
|
||||
|
||||
FROM alpine
|
||||
COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kubescape
|
||||
FROM alpine:3.16.2
|
||||
|
||||
# # Download the frameworks. Use the "--use-default" flag when running kubescape
|
||||
# RUN kubescape download framework nsa && kubescape download framework mitre
|
||||
RUN addgroup -S ks && adduser -S ks -G ks
|
||||
|
||||
ENTRYPOINT ["kubescape"]
|
||||
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
|
||||
|
||||
ENTRYPOINT ["ksserver"]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
1. Clone Project
|
||||
```
|
||||
git clone https://github.com/armosec/kubescape.git kubescape && cd "$_"
|
||||
git clone https://github.com/kubescape/kubescape.git kubescape && cd "$_"
|
||||
```
|
||||
|
||||
2. Build
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
|
||||
)
|
||||
|
||||
// K8SResources map[<api group>/<api version>/<resource>][]<resourceID>
|
||||
type K8SResources map[string][]string
|
||||
|
||||
type OPASessionObj struct {
|
||||
K8SResources *K8SResources // input k8s objects
|
||||
Frameworks []reporthandling.Framework // list of frameworks to scan
|
||||
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<rtesource ID>]<resource>
|
||||
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<rtesource ID>]<resource result>
|
||||
PostureReport *reporthandling.PostureReport // scan results v1
|
||||
Report *reporthandlingv2.PostureReport // scan results v2
|
||||
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
|
||||
RegoInputData RegoInputData // input passed to rgo for scanning. map[<control name>][<input arguments>]
|
||||
}
|
||||
|
||||
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources) *OPASessionObj {
|
||||
return &OPASessionObj{
|
||||
Report: &reporthandlingv2.PostureReport{},
|
||||
Frameworks: frameworks,
|
||||
K8SResources: k8sResources,
|
||||
AllResources: make(map[string]workloadinterface.IMetadata),
|
||||
ResourcesResult: make(map[string]resourcesresults.Result),
|
||||
PostureReport: &reporthandling.PostureReport{
|
||||
ClusterName: ClusterName,
|
||||
CustomerGUID: CustomerGUID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewOPASessionObjMock() *OPASessionObj {
|
||||
return &OPASessionObj{
|
||||
Frameworks: nil,
|
||||
K8SResources: nil,
|
||||
AllResources: make(map[string]workloadinterface.IMetadata),
|
||||
ResourcesResult: make(map[string]resourcesresults.Result),
|
||||
Report: &reporthandlingv2.PostureReport{},
|
||||
PostureReport: &reporthandling.PostureReport{
|
||||
ClusterName: "",
|
||||
CustomerGUID: "",
|
||||
ReportID: "",
|
||||
JobID: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type ComponentConfig struct {
|
||||
Exceptions Exception `json:"exceptions"`
|
||||
}
|
||||
|
||||
type Exception struct {
|
||||
Ignore *bool `json:"ignore"` // ignore test results
|
||||
MultipleScore *reporthandling.AlertScore `json:"multipleScore"` // MultipleScore number - float32
|
||||
Namespaces []string `json:"namespaces"`
|
||||
Regex string `json:"regex"` // not supported
|
||||
}
|
||||
|
||||
type RegoInputData struct {
|
||||
PostureControlInputs map[string][]string `json:"postureControlInputs"`
|
||||
// ClusterName string `json:"clusterName"`
|
||||
// K8sConfig RegoK8sConfig `json:"k8sconfig"`
|
||||
}
|
||||
|
||||
type Policies struct {
|
||||
Frameworks []string
|
||||
Controls map[string]reporthandling.Control // map[<control ID>]<control>
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/fatih/color"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
var silent = false
|
||||
|
||||
func SetSilentMode(s bool) {
|
||||
silent = s
|
||||
}
|
||||
|
||||
func IsSilent() bool {
|
||||
return silent
|
||||
}
|
||||
|
||||
var FailureDisplay = color.New(color.Bold, color.FgHiRed).FprintfFunc()
|
||||
var WarningDisplay = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
|
||||
var FailureTextDisplay = color.New(color.Faint, color.FgHiRed).FprintfFunc()
|
||||
var InfoDisplay = color.New(color.Bold, color.FgCyan).FprintfFunc()
|
||||
var InfoTextDisplay = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
|
||||
var SimpleDisplay = color.New().FprintfFunc()
|
||||
var SuccessDisplay = color.New(color.Bold, color.FgHiGreen).FprintfFunc()
|
||||
var DescriptionDisplay = color.New(color.Faint, color.FgWhite).FprintfFunc()
|
||||
|
||||
var Spinner *spinner.Spinner
|
||||
|
||||
func StartSpinner() {
|
||||
if !IsSilent() && isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
Spinner = spinner.New(spinner.CharSets[7], 100*time.Millisecond) // Build our new spinner
|
||||
Spinner.Start()
|
||||
}
|
||||
}
|
||||
|
||||
func StopSpinner() {
|
||||
if Spinner == nil {
|
||||
return
|
||||
}
|
||||
Spinner.Stop()
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package cautils
|
||||
|
||||
type DownloadInfo struct {
|
||||
Path string // directory to save artifact. Default is "~/.kubescape/"
|
||||
FileName string // can be empty
|
||||
Target string // type of artifact to download
|
||||
Name string // name of artifact to download
|
||||
Account string // customerGUID
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package cautils
|
||||
|
||||
// CA environment vars
|
||||
var (
|
||||
CustomerGUID = ""
|
||||
ClusterName = ""
|
||||
EventReceiverURL = ""
|
||||
NotificationServerURL = ""
|
||||
DashboardBackendURL = ""
|
||||
RestAPIPort = "4001"
|
||||
)
|
||||
@@ -1,239 +0,0 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
YAML_PREFIX = []string{".yaml", ".yml"}
|
||||
JSON_PREFIX = []string{".json"}
|
||||
)
|
||||
|
||||
type FileFormat string
|
||||
|
||||
const (
|
||||
YAML_FILE_FORMAT FileFormat = "yaml"
|
||||
JSON_FILE_FORMAT FileFormat = "json"
|
||||
)
|
||||
|
||||
func LoadResourcesFromFiles(inputPatterns []string) ([]workloadinterface.IMetadata, error) {
|
||||
files, errs := listFiles(inputPatterns)
|
||||
if len(errs) > 0 {
|
||||
logger.L().Error(fmt.Sprintf("%v", errs))
|
||||
}
|
||||
if len(files) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
workloads, errs := loadFiles(files)
|
||||
if len(errs) > 0 {
|
||||
logger.L().Error(fmt.Sprintf("%v", errs))
|
||||
}
|
||||
return workloads, nil
|
||||
}
|
||||
|
||||
func loadFiles(filePaths []string) ([]workloadinterface.IMetadata, []error) {
|
||||
workloads := []workloadinterface.IMetadata{}
|
||||
errs := []error{}
|
||||
for i := range filePaths {
|
||||
f, err := loadFile(filePaths[i])
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
w, e := ReadFile(f, GetFileFormat(filePaths[i]))
|
||||
errs = append(errs, e...)
|
||||
if w != nil {
|
||||
workloads = append(workloads, w...)
|
||||
}
|
||||
}
|
||||
return workloads, errs
|
||||
}
|
||||
|
||||
func loadFile(filePath string) ([]byte, error) {
|
||||
return os.ReadFile(filePath)
|
||||
}
|
||||
func ReadFile(fileContent []byte, fileFromat FileFormat) ([]workloadinterface.IMetadata, []error) {
|
||||
|
||||
switch fileFromat {
|
||||
case YAML_FILE_FORMAT:
|
||||
return readYamlFile(fileContent)
|
||||
case JSON_FILE_FORMAT:
|
||||
return readJsonFile(fileContent)
|
||||
default:
|
||||
return nil, nil // []error{fmt.Errorf("file extension %s not supported", fileFromat)}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func listFiles(patterns []string) ([]string, []error) {
|
||||
files := []string{}
|
||||
errs := []error{}
|
||||
for i := range patterns {
|
||||
if strings.HasPrefix(patterns[i], "http") {
|
||||
continue
|
||||
}
|
||||
if !filepath.IsAbs(patterns[i]) {
|
||||
o, _ := os.Getwd()
|
||||
patterns[i] = filepath.Join(o, patterns[i])
|
||||
}
|
||||
if IsFile(patterns[i]) {
|
||||
files = append(files, patterns[i])
|
||||
} else {
|
||||
f, err := glob(filepath.Split(patterns[i])) //filepath.Glob(patterns[i])
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
files = append(files, f...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return files, errs
|
||||
}
|
||||
|
||||
func readYamlFile(yamlFile []byte) ([]workloadinterface.IMetadata, []error) {
|
||||
errs := []error{}
|
||||
|
||||
r := bytes.NewReader(yamlFile)
|
||||
dec := yaml.NewDecoder(r)
|
||||
yamlObjs := []workloadinterface.IMetadata{}
|
||||
|
||||
var t interface{}
|
||||
for dec.Decode(&t) == nil {
|
||||
j := convertYamlToJson(t)
|
||||
if j == nil {
|
||||
continue
|
||||
}
|
||||
if obj, ok := j.(map[string]interface{}); ok {
|
||||
if o := objectsenvelopes.NewObject(obj); o != nil {
|
||||
if o.GetKind() == "List" {
|
||||
yamlObjs = append(yamlObjs, handleListObject(o)...)
|
||||
} else {
|
||||
yamlObjs = append(yamlObjs, o)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("failed to convert yaml file to map[string]interface, file content: %v", j))
|
||||
}
|
||||
}
|
||||
|
||||
return yamlObjs, errs
|
||||
}
|
||||
|
||||
func readJsonFile(jsonFile []byte) ([]workloadinterface.IMetadata, []error) {
|
||||
workloads := []workloadinterface.IMetadata{}
|
||||
var jsonObj interface{}
|
||||
if err := json.Unmarshal(jsonFile, &jsonObj); err != nil {
|
||||
return workloads, []error{err}
|
||||
}
|
||||
|
||||
convertJsonToWorkload(jsonObj, &workloads)
|
||||
|
||||
return workloads, nil
|
||||
}
|
||||
func convertJsonToWorkload(jsonObj interface{}, workloads *[]workloadinterface.IMetadata) {
|
||||
|
||||
switch x := jsonObj.(type) {
|
||||
case map[string]interface{}:
|
||||
if o := objectsenvelopes.NewObject(x); o != nil {
|
||||
(*workloads) = append(*workloads, o)
|
||||
}
|
||||
case []interface{}:
|
||||
for i := range x {
|
||||
convertJsonToWorkload(x[i], workloads)
|
||||
}
|
||||
}
|
||||
}
|
||||
func convertYamlToJson(i interface{}) interface{} {
|
||||
switch x := i.(type) {
|
||||
case map[interface{}]interface{}:
|
||||
m2 := map[string]interface{}{}
|
||||
for k, v := range x {
|
||||
if s, ok := k.(string); ok {
|
||||
m2[s] = convertYamlToJson(v)
|
||||
}
|
||||
}
|
||||
return m2
|
||||
case []interface{}:
|
||||
for i, v := range x {
|
||||
x[i] = convertYamlToJson(v)
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func IsYaml(filePath string) bool {
|
||||
return StringInSlice(YAML_PREFIX, filepath.Ext(filePath)) != ValueNotFound
|
||||
}
|
||||
|
||||
func IsJson(filePath string) bool {
|
||||
return StringInSlice(JSON_PREFIX, filepath.Ext(filePath)) != ValueNotFound
|
||||
}
|
||||
|
||||
func glob(root, pattern string) ([]string, error) {
|
||||
var matches []string
|
||||
|
||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if matched, err := filepath.Match(pattern, filepath.Base(path)); err != nil {
|
||||
return err
|
||||
} else if matched {
|
||||
matches = append(matches, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
func IsFile(name string) bool {
|
||||
if fi, err := os.Stat(name); err == nil {
|
||||
if fi.Mode().IsRegular() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetFileFormat(filePath string) FileFormat {
|
||||
if IsYaml(filePath) {
|
||||
return YAML_FILE_FORMAT
|
||||
} else if IsJson(filePath) {
|
||||
return JSON_FILE_FORMAT
|
||||
} else {
|
||||
return FileFormat(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
// handleListObject handle a List manifest
|
||||
func handleListObject(obj workloadinterface.IMetadata) []workloadinterface.IMetadata {
|
||||
yamlObjs := []workloadinterface.IMetadata{}
|
||||
if i, ok := workloadinterface.InspectMap(obj.GetObject(), "items"); ok && i != nil {
|
||||
if items, ok := i.([]interface{}); ok && items != nil {
|
||||
for item := range items {
|
||||
if m, ok := items[item].(map[string]interface{}); ok && m != nil {
|
||||
if o := objectsenvelopes.NewObject(m); o != nil {
|
||||
yamlObjs = append(yamlObjs, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return yamlObjs
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func onlineBoutiquePath() string {
|
||||
o, _ := os.Getwd()
|
||||
return filepath.Join(filepath.Dir(o), "examples/online-boutique/*")
|
||||
}
|
||||
|
||||
func TestListFiles(t *testing.T) {
|
||||
|
||||
filesPath := onlineBoutiquePath()
|
||||
|
||||
files, errs := listFiles([]string{filesPath})
|
||||
assert.Equal(t, 0, len(errs))
|
||||
assert.Equal(t, 12, len(files))
|
||||
}
|
||||
|
||||
func TestLoadFiles(t *testing.T) {
|
||||
files, _ := listFiles([]string{onlineBoutiquePath()})
|
||||
_, err := loadFiles(files)
|
||||
assert.Equal(t, 0, len(err))
|
||||
}
|
||||
|
||||
func TestLoadFile(t *testing.T) {
|
||||
files, _ := listFiles([]string{strings.Replace(onlineBoutiquePath(), "*", "adservice.yaml", 1)})
|
||||
assert.Equal(t, 1, len(files))
|
||||
|
||||
_, err := loadFile(files[0])
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
func TestMapResources(t *testing.T) {
|
||||
// policyHandler := &PolicyHandler{}
|
||||
// k8sResources, err := policyHandler.loadResources(opaSessionObj.Frameworks, scanInfo)
|
||||
// files, _ := listFiles([]string{onlineBoutiquePath()})
|
||||
// bb, err := loadFile(files[0])
|
||||
// if len(err) > 0 {
|
||||
// t.Errorf("%v", err)
|
||||
// }
|
||||
// for i := range bb {
|
||||
// t.Errorf("%s", bb[i].ToString())
|
||||
// }
|
||||
}
|
||||
@@ -1,363 +0,0 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
// =======================================================================================================================
|
||||
// =============================================== ArmoAPI ===============================================================
|
||||
// =======================================================================================================================
|
||||
|
||||
var (
|
||||
// ATTENTION!!!
|
||||
// Changes in this URLs variable names, or in the usage is affecting the build process! BE CAREFULL
|
||||
armoERURL = "report.armo.cloud"
|
||||
armoBEURL = "api.armo.cloud"
|
||||
armoFEURL = "portal.armo.cloud"
|
||||
armoAUTHURL = "auth.armo.cloud"
|
||||
|
||||
armoStageERURL = "report-ks.eustage2.cyberarmorsoft.com"
|
||||
armoStageBEURL = "api-stage.armo.cloud"
|
||||
armoStageFEURL = "armoui.eustage2.cyberarmorsoft.com"
|
||||
armoStageAUTHURL = "eggauth.eustage2.cyberarmorsoft.com"
|
||||
|
||||
armoDevERURL = "report.eudev3.cyberarmorsoft.com"
|
||||
armoDevBEURL = "api-dev.armo.cloud"
|
||||
armoDevFEURL = "armoui-dev.eudev3.cyberarmorsoft.com"
|
||||
armoDevAUTHURL = "eggauth.eudev3.cyberarmorsoft.com"
|
||||
)
|
||||
|
||||
// Armo API for downloading policies
|
||||
type ArmoAPI struct {
|
||||
httpClient *http.Client
|
||||
apiURL string
|
||||
authURL string
|
||||
erURL string
|
||||
feURL string
|
||||
accountID string
|
||||
clientID string
|
||||
secretKey string
|
||||
feToken FeLoginResponse
|
||||
authCookie string
|
||||
loggedIn bool
|
||||
}
|
||||
|
||||
var globalArmoAPIConnector *ArmoAPI
|
||||
|
||||
func SetARMOAPIConnector(armoAPI *ArmoAPI) {
|
||||
logger.L().Debug("Armo URLs", helpers.String("api", armoAPI.apiURL), helpers.String("auth", armoAPI.authURL), helpers.String("report", armoAPI.erURL), helpers.String("UI", armoAPI.feURL))
|
||||
globalArmoAPIConnector = armoAPI
|
||||
}
|
||||
|
||||
func GetArmoAPIConnector() *ArmoAPI {
|
||||
if globalArmoAPIConnector == nil {
|
||||
logger.L().Error("returning nil API connector")
|
||||
}
|
||||
return globalArmoAPIConnector
|
||||
}
|
||||
|
||||
func NewARMOAPIDev() *ArmoAPI {
|
||||
apiObj := newArmoAPI()
|
||||
|
||||
apiObj.apiURL = armoDevBEURL
|
||||
apiObj.authURL = armoDevAUTHURL
|
||||
apiObj.erURL = armoDevERURL
|
||||
apiObj.feURL = armoDevFEURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func NewARMOAPIProd() *ArmoAPI {
|
||||
apiObj := newArmoAPI()
|
||||
|
||||
apiObj.apiURL = armoBEURL
|
||||
apiObj.erURL = armoERURL
|
||||
apiObj.feURL = armoFEURL
|
||||
apiObj.authURL = armoAUTHURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func NewARMOAPIStaging() *ArmoAPI {
|
||||
apiObj := newArmoAPI()
|
||||
|
||||
apiObj.apiURL = armoStageBEURL
|
||||
apiObj.erURL = armoStageERURL
|
||||
apiObj.feURL = armoStageFEURL
|
||||
apiObj.authURL = armoStageAUTHURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func NewARMOAPICustomized(armoERURL, armoBEURL, armoFEURL, armoAUTHURL string) *ArmoAPI {
|
||||
apiObj := newArmoAPI()
|
||||
|
||||
apiObj.erURL = armoERURL
|
||||
apiObj.apiURL = armoBEURL
|
||||
apiObj.feURL = armoFEURL
|
||||
apiObj.authURL = armoAUTHURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func newArmoAPI() *ArmoAPI {
|
||||
return &ArmoAPI{
|
||||
httpClient: &http.Client{Timeout: time.Duration(61) * time.Second},
|
||||
loggedIn: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) Post(fullURL string, headers map[string]string, body []byte) (string, error) {
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
armoAPI.appendAuthHeaders(headers)
|
||||
return HttpPost(armoAPI.httpClient, fullURL, headers, body)
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) Delete(fullURL string, headers map[string]string) (string, error) {
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
armoAPI.appendAuthHeaders(headers)
|
||||
return HttpDelete(armoAPI.httpClient, fullURL, headers)
|
||||
}
|
||||
func (armoAPI *ArmoAPI) Get(fullURL string, headers map[string]string) (string, error) {
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
armoAPI.appendAuthHeaders(headers)
|
||||
return HttpGetter(armoAPI.httpClient, fullURL, headers)
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetAccountID() string { return armoAPI.accountID }
|
||||
func (armoAPI *ArmoAPI) IsLoggedIn() bool { return armoAPI.loggedIn }
|
||||
func (armoAPI *ArmoAPI) GetClientID() string { return armoAPI.clientID }
|
||||
func (armoAPI *ArmoAPI) GetSecretKey() string { return armoAPI.secretKey }
|
||||
func (armoAPI *ArmoAPI) GetFrontendURL() string { return armoAPI.feURL }
|
||||
func (armoAPI *ArmoAPI) GetAPIURL() string { return armoAPI.apiURL }
|
||||
func (armoAPI *ArmoAPI) GetReportReceiverURL() string { return armoAPI.erURL }
|
||||
func (armoAPI *ArmoAPI) SetAccountID(accountID string) { armoAPI.accountID = accountID }
|
||||
func (armoAPI *ArmoAPI) SetClientID(clientID string) { armoAPI.clientID = clientID }
|
||||
func (armoAPI *ArmoAPI) SetSecretKey(secretKey string) { armoAPI.secretKey = secretKey }
|
||||
|
||||
func (armoAPI *ArmoAPI) GetFramework(name string) (*reporthandling.Framework, error) {
|
||||
respStr, err := armoAPI.Get(armoAPI.getFrameworkURL(name), nil)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
framework := &reporthandling.Framework{}
|
||||
if err = JSONDecoder(respStr).Decode(framework); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
SaveInFile(framework, GetDefaultPath(name+".json"))
|
||||
|
||||
return framework, err
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetFrameworks() ([]reporthandling.Framework, error) {
|
||||
respStr, err := armoAPI.Get(armoAPI.getListFrameworkURL(), nil)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
frameworks := []reporthandling.Framework{}
|
||||
if err = JSONDecoder(respStr).Decode(&frameworks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// SaveInFile(framework, GetDefaultPath(name+".json"))
|
||||
|
||||
return frameworks, err
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetControl(policyName string) (*reporthandling.Control, error) {
|
||||
return nil, fmt.Errorf("control api is not public")
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||
exceptions := []armotypes.PostureExceptionPolicy{}
|
||||
|
||||
respStr, err := armoAPI.Get(armoAPI.getExceptionsURL(clusterName), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = JSONDecoder(respStr).Decode(&exceptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exceptions, nil
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetTenant() (*TenantResponse, error) {
|
||||
url := armoAPI.getAccountURL()
|
||||
if armoAPI.accountID != "" {
|
||||
url = fmt.Sprintf("%s?customerGUID=%s", url, armoAPI.accountID)
|
||||
}
|
||||
respStr, err := armoAPI.Get(url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant := &TenantResponse{}
|
||||
if err = JSONDecoder(respStr).Decode(tenant); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tenant.TenantID != "" {
|
||||
armoAPI.accountID = tenant.TenantID
|
||||
}
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
// ControlsInputs // map[<control name>][<input arguments>]
|
||||
func (armoAPI *ArmoAPI) GetAccountConfig(clusterName string) (*armotypes.CustomerConfig, error) {
|
||||
accountConfig := &armotypes.CustomerConfig{}
|
||||
if armoAPI.accountID == "" {
|
||||
return accountConfig, nil
|
||||
}
|
||||
respStr, err := armoAPI.Get(armoAPI.getAccountConfig(clusterName), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = JSONDecoder(respStr).Decode(&accountConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return accountConfig, nil
|
||||
}
|
||||
|
||||
// ControlsInputs // map[<control name>][<input arguments>]
|
||||
func (armoAPI *ArmoAPI) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
||||
accountConfig, err := armoAPI.GetAccountConfig(clusterName)
|
||||
if err == nil {
|
||||
return accountConfig.Settings.PostureControlInputs, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) ListCustomFrameworks() ([]string, error) {
|
||||
respStr, err := armoAPI.Get(armoAPI.getListFrameworkURL(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frs := []reporthandling.Framework{}
|
||||
if err = json.Unmarshal([]byte(respStr), &frs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
frameworkList := []string{}
|
||||
for _, fr := range frs {
|
||||
if !isNativeFramework(fr.Name) {
|
||||
frameworkList = append(frameworkList, fr.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return frameworkList, nil
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) ListFrameworks() ([]string, error) {
|
||||
respStr, err := armoAPI.Get(armoAPI.getListFrameworkURL(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frs := []reporthandling.Framework{}
|
||||
if err = json.Unmarshal([]byte(respStr), &frs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
frameworkList := []string{}
|
||||
for _, fr := range frs {
|
||||
if isNativeFramework(fr.Name) {
|
||||
frameworkList = append(frameworkList, strings.ToLower(fr.Name))
|
||||
} else {
|
||||
frameworkList = append(frameworkList, fr.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return frameworkList, nil
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) ListControls(l ListType) ([]string, error) {
|
||||
return nil, fmt.Errorf("control api is not public")
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) PostExceptions(exceptions []armotypes.PostureExceptionPolicy) error {
|
||||
|
||||
for i := range exceptions {
|
||||
ex, err := json.Marshal(exceptions[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = armoAPI.Post(armoAPI.exceptionsURL(""), map[string]string{"Content-Type": "application/json"}, ex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) DeleteException(exceptionName string) error {
|
||||
|
||||
_, err := armoAPI.Delete(armoAPI.exceptionsURL(exceptionName), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (armoAPI *ArmoAPI) Login() error {
|
||||
if armoAPI.accountID == "" {
|
||||
return fmt.Errorf("failed to login, missing accountID")
|
||||
}
|
||||
if armoAPI.clientID == "" {
|
||||
return fmt.Errorf("failed to login, missing clientID")
|
||||
}
|
||||
if armoAPI.secretKey == "" {
|
||||
return fmt.Errorf("failed to login, missing secretKey")
|
||||
}
|
||||
|
||||
// init URLs
|
||||
feLoginData := FeLoginData{ClientId: armoAPI.clientID, Secret: armoAPI.secretKey}
|
||||
body, _ := json.Marshal(feLoginData)
|
||||
|
||||
resp, err := http.Post(armoAPI.getApiToken(), "application/json", bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("error authenticating: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
responseBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var feLoginResponse FeLoginResponse
|
||||
|
||||
if err = json.Unmarshal(responseBody, &feLoginResponse); err != nil {
|
||||
return err
|
||||
}
|
||||
armoAPI.feToken = feLoginResponse
|
||||
|
||||
/* Now we have JWT */
|
||||
|
||||
armoAPI.authCookie, err = armoAPI.getAuthCookie()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
armoAPI.loggedIn = true
|
||||
return nil
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var NativeFrameworks = []string{"nsa", "mitre", "armobest", "devopsbest"}
|
||||
|
||||
func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoFrameworks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
if isNativeFramework(frameworkName) {
|
||||
q.Add("frameworkName", strings.ToUpper(frameworkName))
|
||||
} else {
|
||||
// For customer framework has to be the way it was added
|
||||
q.Add("frameworkName", frameworkName)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getListFrameworkURL() string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoFrameworks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
func (armoAPI *ArmoAPI) getExceptionsURL(clusterName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoPostureExceptions"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
// if clusterName != "" { // TODO - fix customer name support in Armo BE
|
||||
// q.Add("clusterName", clusterName)
|
||||
// }
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) exceptionsURL(exceptionsPolicyName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/postureExceptionPolicy"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
if exceptionsPolicyName != "" { // for delete
|
||||
q.Add("policyName", exceptionsPolicyName)
|
||||
}
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getAccountConfig(clusterName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoCustomerConfiguration"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
if clusterName != "" { // TODO - fix customer name support in Armo BE
|
||||
q.Add("clusterName", clusterName)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getAccountURL() string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/createTenant"
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getApiToken() string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.authURL
|
||||
u.Path = "frontegg/identity/resources/auth/v1/api-token"
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getOpenidCustomers() string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/openid_customers"
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getAuthCookie() (string, error) {
|
||||
selectCustomer := ArmoSelectCustomer{SelectedCustomerGuid: armoAPI.accountID}
|
||||
requestBody, _ := json.Marshal(selectCustomer)
|
||||
client := &http.Client{}
|
||||
httpRequest, err := http.NewRequest(http.MethodPost, armoAPI.getOpenidCustomers(), bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
httpRequest.Header.Set("Content-Type", "application/json")
|
||||
httpRequest.Header.Set("Authorization", fmt.Sprintf("Bearer %s", armoAPI.feToken.Token))
|
||||
httpResponse, err := client.Do(httpRequest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer httpResponse.Body.Close()
|
||||
if httpResponse.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("failed to get cookie from %s: status %d", armoAPI.getOpenidCustomers(), httpResponse.StatusCode)
|
||||
}
|
||||
|
||||
cookies := httpResponse.Header.Get("set-cookie")
|
||||
if len(cookies) == 0 {
|
||||
return "", fmt.Errorf("no cookie field in response from %s", armoAPI.getOpenidCustomers())
|
||||
}
|
||||
|
||||
authCookie := ""
|
||||
for _, cookie := range strings.Split(cookies, ";") {
|
||||
kv := strings.Split(cookie, "=")
|
||||
if kv[0] == "auth" {
|
||||
authCookie = kv[1]
|
||||
}
|
||||
}
|
||||
|
||||
if len(authCookie) == 0 {
|
||||
return "", fmt.Errorf("no auth cookie field in response from %s", armoAPI.getOpenidCustomers())
|
||||
}
|
||||
|
||||
return authCookie, nil
|
||||
}
|
||||
func (armoAPI *ArmoAPI) appendAuthHeaders(headers map[string]string) {
|
||||
|
||||
if armoAPI.feToken.Token != "" {
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", armoAPI.feToken.Token)
|
||||
}
|
||||
if armoAPI.authCookie != "" {
|
||||
headers["Cookie"] = fmt.Sprintf("auth=%s", armoAPI.authCookie)
|
||||
}
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getCustomerGUIDFallBack() string {
|
||||
if armoAPI.accountID != "" {
|
||||
return armoAPI.accountID
|
||||
}
|
||||
return "11111111-1111-1111-1111-111111111111"
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
// =======================================================================================================================
|
||||
// ============================================== LoadPolicy =============================================================
|
||||
// =======================================================================================================================
|
||||
var DefaultLocalStore = getCacheDir()
|
||||
|
||||
func getCacheDir() string {
|
||||
defaultDirPath := ".kubescape"
|
||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
||||
defaultDirPath = filepath.Join(homeDir, defaultDirPath)
|
||||
}
|
||||
return defaultDirPath
|
||||
}
|
||||
|
||||
// Load policies from a local repository
|
||||
type LoadPolicy struct {
|
||||
filePaths []string
|
||||
}
|
||||
|
||||
func NewLoadPolicy(filePaths []string) *LoadPolicy {
|
||||
return &LoadPolicy{
|
||||
filePaths: filePaths,
|
||||
}
|
||||
}
|
||||
|
||||
// Return control from file
|
||||
func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, error) {
|
||||
|
||||
control := &reporthandling.Control{}
|
||||
filePath := lp.filePath()
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(f, control); err != nil {
|
||||
return control, err
|
||||
}
|
||||
if controlName != "" && !strings.EqualFold(controlName, control.Name) && !strings.EqualFold(controlName, control.ControlID) {
|
||||
framework, err := lp.GetFramework(control.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("control from file not matching")
|
||||
} else {
|
||||
for _, ctrl := range framework.Controls {
|
||||
if strings.EqualFold(ctrl.Name, controlName) || strings.EqualFold(ctrl.ControlID, controlName) {
|
||||
control = &ctrl
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return control, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) {
|
||||
framework := &reporthandling.Framework{}
|
||||
var err error
|
||||
for _, filePath := range lp.filePaths {
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(f, framework); err != nil {
|
||||
return framework, err
|
||||
}
|
||||
if strings.EqualFold(frameworkName, framework.Name) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if frameworkName != "" && !strings.EqualFold(frameworkName, framework.Name) {
|
||||
|
||||
return nil, fmt.Errorf("framework from file not matching")
|
||||
}
|
||||
return framework, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
|
||||
frameworks := []reporthandling.Framework{}
|
||||
var err error
|
||||
return frameworks, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) ListFrameworks() ([]string, error) {
|
||||
fwNames := []string{}
|
||||
framework := &reporthandling.Framework{}
|
||||
for _, f := range lp.filePaths {
|
||||
file, err := os.ReadFile(f)
|
||||
if err == nil {
|
||||
if err := json.Unmarshal(file, framework); err == nil {
|
||||
if !contains(fwNames, framework.Name) {
|
||||
fwNames = append(fwNames, framework.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fwNames, nil
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) ListControls(listType ListType) ([]string, error) {
|
||||
// TODO - Support
|
||||
return []string{}, fmt.Errorf("loading controls list from file is not supported")
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||
filePath := lp.filePath()
|
||||
exception := []armotypes.PostureExceptionPolicy{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(f, &exception)
|
||||
return exception, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
||||
filePath := lp.filePath()
|
||||
accountConfig := &armotypes.CustomerConfig{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(f, &accountConfig.Settings.PostureControlInputs); err == nil {
|
||||
return accountConfig.Settings.PostureControlInputs, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// temporary support for a list of files
|
||||
func (lp *LoadPolicy) filePath() string {
|
||||
if len(lp.filePaths) > 0 {
|
||||
return lp.filePaths[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var mockFrameworkBasePath = filepath.Join("examples", "mocks", "frameworks")
|
||||
|
||||
func MockNewLoadPolicy() *LoadPolicy {
|
||||
return &LoadPolicy{
|
||||
filePaths: []string{""},
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package helpers
|
||||
|
||||
type StringObj struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
|
||||
type ErrorObj struct {
|
||||
key string
|
||||
value error
|
||||
}
|
||||
|
||||
type IntObj struct {
|
||||
key string
|
||||
value int
|
||||
}
|
||||
|
||||
type InterfaceObj struct {
|
||||
key string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func Error(e error) *ErrorObj { return &ErrorObj{key: "error", value: e} }
|
||||
func Int(k string, v int) *IntObj { return &IntObj{key: k, value: v} }
|
||||
func String(k, v string) *StringObj { return &StringObj{key: k, value: v} }
|
||||
func Interface(k string, v interface{}) *InterfaceObj { return &InterfaceObj{key: k, value: v} }
|
||||
@@ -1,69 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Level int8
|
||||
|
||||
const (
|
||||
UnknownLevel Level = iota - -1
|
||||
DebugLevel
|
||||
InfoLevel //default
|
||||
SuccessLevel
|
||||
WarningLevel
|
||||
ErrorLevel
|
||||
FatalLevel
|
||||
|
||||
_defaultLevel = InfoLevel
|
||||
_minLevel = DebugLevel
|
||||
_maxLevel = FatalLevel
|
||||
)
|
||||
|
||||
func ToLevel(level string) Level {
|
||||
switch strings.ToLower(level) {
|
||||
case "debug":
|
||||
return DebugLevel
|
||||
case "info":
|
||||
return InfoLevel
|
||||
case "success":
|
||||
return SuccessLevel
|
||||
case "warning", "warn":
|
||||
return WarningLevel
|
||||
case "error":
|
||||
return ErrorLevel
|
||||
case "fatal":
|
||||
return FatalLevel
|
||||
default:
|
||||
return UnknownLevel
|
||||
}
|
||||
}
|
||||
func (l Level) String() string {
|
||||
switch l {
|
||||
case DebugLevel:
|
||||
return "debug"
|
||||
case InfoLevel:
|
||||
return "info"
|
||||
case SuccessLevel:
|
||||
return "success"
|
||||
case WarningLevel:
|
||||
return "warning"
|
||||
case ErrorLevel:
|
||||
return "error"
|
||||
case FatalLevel:
|
||||
return "fatal"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (l Level) Skip(l2 Level) bool {
|
||||
return l < l2
|
||||
}
|
||||
|
||||
func SupportedLevels() []string {
|
||||
levels := []string{}
|
||||
for i := _minLevel; i <= _maxLevel; i++ {
|
||||
levels = append(levels, i.String())
|
||||
}
|
||||
return levels
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package helpers
|
||||
|
||||
type IDetails interface {
|
||||
Key() string
|
||||
Value() interface{}
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// ============================== String ================================================
|
||||
// ======================================================================================
|
||||
|
||||
// Key
|
||||
func (s *StringObj) Key() string {
|
||||
return s.key
|
||||
}
|
||||
|
||||
// Value
|
||||
func (s *StringObj) Value() interface{} {
|
||||
return s.value
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// =============================== Error ================================================
|
||||
// ======================================================================================
|
||||
|
||||
// Key
|
||||
func (s *ErrorObj) Key() string {
|
||||
return s.key
|
||||
}
|
||||
|
||||
// Value
|
||||
func (s *ErrorObj) Value() interface{} {
|
||||
return s.value
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// ================================= Int ================================================
|
||||
// ======================================================================================
|
||||
|
||||
// Key
|
||||
func (s *IntObj) Key() string {
|
||||
return s.key
|
||||
}
|
||||
|
||||
// Value
|
||||
func (s *IntObj) Value() interface{} {
|
||||
return s.value
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// =========================== Interface ================================================
|
||||
// ======================================================================================
|
||||
|
||||
// Key
|
||||
func (s *InterfaceObj) Key() string {
|
||||
return s.key
|
||||
}
|
||||
|
||||
// Value
|
||||
func (s *InterfaceObj) Value() interface{} {
|
||||
return s.value
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/cautils/logger/prettylogger"
|
||||
"github.com/armosec/kubescape/cautils/logger/zaplogger"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
type ILogger interface {
|
||||
Fatal(msg string, details ...helpers.IDetails) // print log and exit 1
|
||||
Error(msg string, details ...helpers.IDetails)
|
||||
Success(msg string, details ...helpers.IDetails)
|
||||
Warning(msg string, details ...helpers.IDetails)
|
||||
Info(msg string, details ...helpers.IDetails)
|
||||
Debug(msg string, details ...helpers.IDetails)
|
||||
|
||||
SetLevel(level string) error
|
||||
GetLevel() string
|
||||
|
||||
SetWriter(w *os.File)
|
||||
GetWriter() *os.File
|
||||
}
|
||||
|
||||
var l ILogger
|
||||
|
||||
func L() ILogger {
|
||||
if l == nil {
|
||||
InitializeLogger("")
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func InitializeLogger(loggerName string) {
|
||||
|
||||
switch strings.ToLower(loggerName) {
|
||||
case "zap":
|
||||
l = zaplogger.NewZapLogger()
|
||||
case "pretty":
|
||||
l = prettylogger.NewPrettyLogger()
|
||||
default:
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
l = prettylogger.NewPrettyLogger()
|
||||
} else {
|
||||
l = zaplogger.NewZapLogger()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package prettylogger
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
var prefixError = color.New(color.Bold, color.FgHiRed).FprintfFunc()
|
||||
var prefixWarning = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
|
||||
var prefixInfo = color.New(color.Bold, color.FgCyan).FprintfFunc()
|
||||
var prefixSuccess = color.New(color.Bold, color.FgHiGreen).FprintfFunc()
|
||||
var prefixDebug = color.New(color.Bold, color.FgWhite).FprintfFunc()
|
||||
var message = color.New().FprintfFunc()
|
||||
|
||||
func prefix(l helpers.Level) func(w io.Writer, format string, a ...interface{}) {
|
||||
switch l {
|
||||
case helpers.DebugLevel:
|
||||
return prefixDebug
|
||||
case helpers.InfoLevel:
|
||||
return prefixInfo
|
||||
case helpers.SuccessLevel:
|
||||
return prefixSuccess
|
||||
case helpers.WarningLevel:
|
||||
return prefixWarning
|
||||
case helpers.ErrorLevel, helpers.FatalLevel:
|
||||
return prefixError
|
||||
}
|
||||
return message
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package prettylogger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
)
|
||||
|
||||
type PrettyLogger struct {
|
||||
writer *os.File
|
||||
level helpers.Level
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewPrettyLogger() *PrettyLogger {
|
||||
return &PrettyLogger{
|
||||
writer: os.Stderr, // default to stderr
|
||||
level: helpers.InfoLevel,
|
||||
mutex: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (pl *PrettyLogger) GetLevel() string { return pl.level.String() }
|
||||
func (pl *PrettyLogger) SetWriter(w *os.File) { pl.writer = w }
|
||||
func (pl *PrettyLogger) GetWriter() *os.File { return pl.writer }
|
||||
|
||||
func (pl *PrettyLogger) SetLevel(level string) error {
|
||||
pl.level = helpers.ToLevel(level)
|
||||
if pl.level == helpers.UnknownLevel {
|
||||
return fmt.Errorf("level '%s' unknown", level)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (pl *PrettyLogger) Fatal(msg string, details ...helpers.IDetails) {
|
||||
pl.print(helpers.FatalLevel, msg, details...)
|
||||
os.Exit(1)
|
||||
}
|
||||
func (pl *PrettyLogger) Error(msg string, details ...helpers.IDetails) {
|
||||
pl.print(helpers.ErrorLevel, msg, details...)
|
||||
}
|
||||
func (pl *PrettyLogger) Warning(msg string, details ...helpers.IDetails) {
|
||||
pl.print(helpers.WarningLevel, msg, details...)
|
||||
}
|
||||
func (pl *PrettyLogger) Info(msg string, details ...helpers.IDetails) {
|
||||
pl.print(helpers.InfoLevel, msg, details...)
|
||||
}
|
||||
func (pl *PrettyLogger) Debug(msg string, details ...helpers.IDetails) {
|
||||
pl.print(helpers.DebugLevel, msg, details...)
|
||||
}
|
||||
func (pl *PrettyLogger) Success(msg string, details ...helpers.IDetails) {
|
||||
pl.print(helpers.SuccessLevel, msg, details...)
|
||||
}
|
||||
|
||||
func (pl *PrettyLogger) print(level helpers.Level, msg string, details ...helpers.IDetails) {
|
||||
if !level.Skip(pl.level) {
|
||||
pl.mutex.Lock()
|
||||
prefix(level)(pl.writer, "[%s] ", level.String())
|
||||
if d := detailsToString(details); d != "" {
|
||||
msg = fmt.Sprintf("%s. %s", msg, d)
|
||||
}
|
||||
message(pl.writer, fmt.Sprintf("%s\n", msg))
|
||||
pl.mutex.Unlock()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func detailsToString(details []helpers.IDetails) string {
|
||||
s := ""
|
||||
for i := range details {
|
||||
s += fmt.Sprintf("%s: %s", details[i].Key(), details[i].Value())
|
||||
if i < len(details)-1 {
|
||||
s += "; "
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package zaplogger
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type ZapLogger struct {
|
||||
zapL *zap.Logger
|
||||
cfg zap.Config
|
||||
}
|
||||
|
||||
func NewZapLogger() *ZapLogger {
|
||||
ec := zap.NewProductionEncoderConfig()
|
||||
ec.EncodeTime = zapcore.RFC3339TimeEncoder
|
||||
cfg := zap.NewProductionConfig()
|
||||
cfg.DisableCaller = true
|
||||
cfg.DisableStacktrace = true
|
||||
cfg.Encoding = "json"
|
||||
cfg.EncoderConfig = ec
|
||||
|
||||
zapLogger, err := cfg.Build()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &ZapLogger{
|
||||
zapL: zapLogger,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (zl *ZapLogger) GetLevel() string { return zl.cfg.Level.Level().String() }
|
||||
func (zl *ZapLogger) SetWriter(w *os.File) {}
|
||||
func (zl *ZapLogger) GetWriter() *os.File { return nil }
|
||||
func GetWriter() *os.File { return nil }
|
||||
|
||||
func (zl *ZapLogger) SetLevel(level string) error {
|
||||
l := zapcore.Level(1)
|
||||
err := l.Set(level)
|
||||
if err == nil {
|
||||
zl.cfg.Level.SetLevel(l)
|
||||
}
|
||||
return err
|
||||
}
|
||||
func (zl *ZapLogger) Fatal(msg string, details ...helpers.IDetails) {
|
||||
zl.zapL.Fatal(msg, detailsToZapFields(details)...)
|
||||
}
|
||||
|
||||
func (zl *ZapLogger) Error(msg string, details ...helpers.IDetails) {
|
||||
zl.zapL.Error(msg, detailsToZapFields(details)...)
|
||||
}
|
||||
|
||||
func (zl *ZapLogger) Warning(msg string, details ...helpers.IDetails) {
|
||||
zl.zapL.Warn(msg, detailsToZapFields(details)...)
|
||||
}
|
||||
|
||||
func (zl *ZapLogger) Success(msg string, details ...helpers.IDetails) {
|
||||
zl.zapL.Info(msg, detailsToZapFields(details)...)
|
||||
}
|
||||
|
||||
func (zl *ZapLogger) Info(msg string, details ...helpers.IDetails) {
|
||||
zl.zapL.Info(msg, detailsToZapFields(details)...)
|
||||
}
|
||||
|
||||
func (zl *ZapLogger) Debug(msg string, details ...helpers.IDetails) {
|
||||
zl.zapL.Debug(msg, detailsToZapFields(details)...)
|
||||
}
|
||||
|
||||
func detailsToZapFields(details []helpers.IDetails) []zapcore.Field {
|
||||
zapFields := []zapcore.Field{}
|
||||
for i := range details {
|
||||
zapFields = append(zapFields, zap.Any(details[i].Key(), details[i].Value()))
|
||||
}
|
||||
return zapFields
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
const (
|
||||
ScanCluster string = "cluster"
|
||||
ScanLocalFiles string = "yaml"
|
||||
localControlInputsFilename string = "controls-inputs.json"
|
||||
localExceptionsFilename string = "exceptions.json"
|
||||
)
|
||||
|
||||
type BoolPtrFlag struct {
|
||||
valPtr *bool
|
||||
}
|
||||
|
||||
func (bpf *BoolPtrFlag) Type() string {
|
||||
return "bool"
|
||||
}
|
||||
|
||||
func (bpf *BoolPtrFlag) String() string {
|
||||
if bpf.valPtr != nil {
|
||||
return fmt.Sprintf("%v", *bpf.valPtr)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (bpf *BoolPtrFlag) Get() *bool {
|
||||
return bpf.valPtr
|
||||
}
|
||||
|
||||
func (bpf *BoolPtrFlag) SetBool(val bool) {
|
||||
bpf.valPtr = &val
|
||||
}
|
||||
|
||||
func (bpf *BoolPtrFlag) Set(val string) error {
|
||||
switch val {
|
||||
case "true":
|
||||
bpf.SetBool(true)
|
||||
case "false":
|
||||
bpf.SetBool(false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RootInfo struct {
|
||||
Logger string // logger level
|
||||
CacheDir string // cached dir
|
||||
}
|
||||
type ScanInfo struct {
|
||||
Getters
|
||||
PolicyIdentifier []reporthandling.PolicyIdentifier
|
||||
UseExceptions string // Load file with exceptions configuration
|
||||
ControlsInputs string // Load file with inputs for controls
|
||||
UseFrom []string // Load framework from local file (instead of download). Use when running offline
|
||||
UseDefault bool // Load framework from cached file (instead of download). Use when running offline
|
||||
UseArtifactsFrom string // Load artifacts from local path. Use when running offline
|
||||
VerboseMode bool // Display all of the input resources and not only failed resources
|
||||
Format string // Format results (table, json, junit ...)
|
||||
Output string // Store results in an output file, Output file name
|
||||
FormatVersion string // Output object can be differnet between versions, this is for testing and backward compatibility
|
||||
ExcludedNamespaces string // used for host sensor namespace
|
||||
IncludeNamespaces string // DEPRECATED?
|
||||
InputPatterns []string // Yaml files input patterns
|
||||
Silent bool // Silent mode - Do not print progress logs
|
||||
FailThreshold float32 // Failure score threshold
|
||||
Submit bool // Submit results to Armo BE
|
||||
HostSensorEnabled BoolPtrFlag // Deploy ARMO K8s host sensor to collect data from certain controls
|
||||
HostSensorYamlPath string // Path to hostsensor file
|
||||
Local bool // Do not submit results
|
||||
Account string // account ID
|
||||
KubeContext string // context name
|
||||
FrameworkScan bool // false if scanning control
|
||||
ScanAll bool // true if scan all frameworks
|
||||
}
|
||||
|
||||
type Getters struct {
|
||||
ExceptionsGetter getter.IExceptionsGetter
|
||||
ControlsInputsGetter getter.IControlsInputsGetter
|
||||
PolicyGetter getter.IPolicyGetter
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) Init() {
|
||||
scanInfo.setUseFrom()
|
||||
scanInfo.setOutputFile()
|
||||
scanInfo.setUseArtifactsFrom()
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setUseArtifactsFrom() {
|
||||
if scanInfo.UseArtifactsFrom == "" {
|
||||
return
|
||||
}
|
||||
// UseArtifactsFrom must be a path without a filename
|
||||
dir, file := filepath.Split(scanInfo.UseArtifactsFrom)
|
||||
if dir == "" {
|
||||
scanInfo.UseArtifactsFrom = file
|
||||
} else if strings.Contains(file, ".json") {
|
||||
scanInfo.UseArtifactsFrom = dir
|
||||
}
|
||||
// set frameworks files
|
||||
files, err := ioutil.ReadDir(scanInfo.UseArtifactsFrom)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to read files from directory", helpers.String("dir", scanInfo.UseArtifactsFrom), helpers.Error(err))
|
||||
}
|
||||
framework := &reporthandling.Framework{}
|
||||
for _, f := range files {
|
||||
filePath := filepath.Join(scanInfo.UseArtifactsFrom, f.Name())
|
||||
file, err := os.ReadFile(filePath)
|
||||
if err == nil {
|
||||
if err := json.Unmarshal(file, framework); err == nil {
|
||||
scanInfo.UseFrom = append(scanInfo.UseFrom, filepath.Join(scanInfo.UseArtifactsFrom, f.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
// set config-inputs file
|
||||
scanInfo.ControlsInputs = filepath.Join(scanInfo.UseArtifactsFrom, localControlInputsFilename)
|
||||
// set exceptions
|
||||
scanInfo.UseExceptions = filepath.Join(scanInfo.UseArtifactsFrom, localExceptionsFilename)
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setUseExceptions() {
|
||||
if scanInfo.UseExceptions != "" {
|
||||
// load exceptions from file
|
||||
scanInfo.ExceptionsGetter = getter.NewLoadPolicy([]string{scanInfo.UseExceptions})
|
||||
} else {
|
||||
scanInfo.ExceptionsGetter = getter.GetArmoAPIConnector()
|
||||
}
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setUseFrom() {
|
||||
if scanInfo.UseDefault {
|
||||
for _, policy := range scanInfo.PolicyIdentifier {
|
||||
scanInfo.UseFrom = append(scanInfo.UseFrom, getter.GetDefaultPath(policy.Name+".json"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setOutputFile() {
|
||||
if scanInfo.Output == "" {
|
||||
return
|
||||
}
|
||||
if scanInfo.Format == "json" {
|
||||
if filepath.Ext(scanInfo.Output) != ".json" {
|
||||
scanInfo.Output += ".json"
|
||||
}
|
||||
}
|
||||
if scanInfo.Format == "junit" {
|
||||
if filepath.Ext(scanInfo.Output) != ".xml" {
|
||||
scanInfo.Output += ".xml"
|
||||
}
|
||||
}
|
||||
if scanInfo.Format == "pdf" {
|
||||
if filepath.Ext(scanInfo.Output) != ".pdf" {
|
||||
scanInfo.Output += ".pdf"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) GetScanningEnvironment() string {
|
||||
if len(scanInfo.InputPatterns) != 0 {
|
||||
return ScanLocalFiles
|
||||
}
|
||||
return ScanCluster
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) SetPolicyIdentifiers(policies []string, kind reporthandling.NotificationPolicyKind) {
|
||||
for _, policy := range policies {
|
||||
if !scanInfo.contains(policy) {
|
||||
newPolicy := reporthandling.PolicyIdentifier{}
|
||||
newPolicy.Kind = kind // reporthandling.KindFramework
|
||||
newPolicy.Name = policy
|
||||
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) contains(policyName string) bool {
|
||||
for _, policy := range scanInfo.PolicyIdentifier {
|
||||
if policy.Name == policyName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package clihandler
|
||||
|
||||
func CliDelete() error {
|
||||
|
||||
tenant := getTenantConfig("", "", getKubernetesApi()) // change k8sinterface
|
||||
return tenant.DeleteCachedConfig()
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package clihandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
)
|
||||
|
||||
func DeleteExceptions(accountID string, exceptions []string) error {
|
||||
|
||||
// load cached config
|
||||
getTenantConfig(accountID, "", getKubernetesApi())
|
||||
|
||||
// login kubescape SaaS
|
||||
armoAPI := getter.GetArmoAPIConnector()
|
||||
if err := armoAPI.Login(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range exceptions {
|
||||
exceptionName := exceptions[i]
|
||||
if exceptionName == "" {
|
||||
continue
|
||||
}
|
||||
logger.L().Info("Deleting exception", helpers.String("name", exceptionName))
|
||||
if err := armoAPI.DeleteException(exceptionName); err != nil {
|
||||
return fmt.Errorf("failed to delete exception '%s', reason: %s", exceptionName, err.Error())
|
||||
}
|
||||
logger.L().Success("Exception deleted successfully")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
package clihandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
)
|
||||
|
||||
var downloadFunc = map[string]func(*cautils.DownloadInfo) error{
|
||||
"controls-inputs": downloadConfigInputs,
|
||||
"exceptions": downloadExceptions,
|
||||
"control": downloadControl,
|
||||
"framework": downloadFramework,
|
||||
"artifacts": downloadArtifacts,
|
||||
}
|
||||
|
||||
func DownloadSupportCommands() []string {
|
||||
commands := []string{}
|
||||
for k := range downloadFunc {
|
||||
commands = append(commands, k)
|
||||
}
|
||||
return commands
|
||||
}
|
||||
|
||||
func CliDownload(downloadInfo *cautils.DownloadInfo) error {
|
||||
setPathandFilename(downloadInfo)
|
||||
if err := downloadArtifact(downloadInfo, downloadFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadArtifact(downloadInfo *cautils.DownloadInfo, downloadArtifactFunc map[string]func(*cautils.DownloadInfo) error) error {
|
||||
if f, ok := downloadArtifactFunc[downloadInfo.Target]; ok {
|
||||
if err := f(downloadInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown command to download")
|
||||
}
|
||||
|
||||
func setPathandFilename(downloadInfo *cautils.DownloadInfo) {
|
||||
if downloadInfo.Path == "" {
|
||||
downloadInfo.Path = getter.GetDefaultPath("")
|
||||
} else {
|
||||
dir, file := filepath.Split(downloadInfo.Path)
|
||||
if dir == "" {
|
||||
downloadInfo.Path = file
|
||||
} else if strings.Contains(file, ".json") {
|
||||
downloadInfo.Path = dir
|
||||
downloadInfo.FileName = file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func downloadArtifacts(downloadInfo *cautils.DownloadInfo) error {
|
||||
downloadInfo.FileName = ""
|
||||
var artifacts = map[string]func(*cautils.DownloadInfo) error{
|
||||
"controls-inputs": downloadConfigInputs,
|
||||
"exceptions": downloadExceptions,
|
||||
"framework": downloadFramework,
|
||||
}
|
||||
for artifact := range artifacts {
|
||||
if err := downloadArtifact(&cautils.DownloadInfo{Target: artifact, Path: downloadInfo.Path, FileName: fmt.Sprintf("%s.json", artifact)}, artifacts); err != nil {
|
||||
logger.L().Error("error downloading", helpers.String("artifact", artifact), helpers.Error(err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadConfigInputs(downloadInfo *cautils.DownloadInfo) error {
|
||||
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
|
||||
|
||||
controlsInputsGetter := getConfigInputsGetter(downloadInfo.Name, tenant.GetAccountID(), nil)
|
||||
controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetClusterName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if downloadInfo.FileName == "" {
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Target)
|
||||
}
|
||||
// save in file
|
||||
err = getter.SaveInFile(controlInputs, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("path", filepath.Join(downloadInfo.Path, downloadInfo.FileName)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadExceptions(downloadInfo *cautils.DownloadInfo) error {
|
||||
var err error
|
||||
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
|
||||
|
||||
exceptionsGetter := getExceptionsGetter("")
|
||||
exceptions := []armotypes.PostureExceptionPolicy{}
|
||||
if tenant.GetAccountID() != "" {
|
||||
exceptions, err = exceptionsGetter.GetExceptions(tenant.GetClusterName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if downloadInfo.FileName == "" {
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Target)
|
||||
}
|
||||
// save in file
|
||||
err = getter.SaveInFile(exceptions, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("path", filepath.Join(downloadInfo.Path, downloadInfo.FileName)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadFramework(downloadInfo *cautils.DownloadInfo) error {
|
||||
|
||||
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
|
||||
|
||||
g := getPolicyGetter(nil, tenant.GetAccountID(), true, nil)
|
||||
|
||||
if downloadInfo.Name == "" {
|
||||
// if framework name not specified - download all frameworks
|
||||
frameworks, err := g.GetFrameworks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fw := range frameworks {
|
||||
downloadTo := filepath.Join(downloadInfo.Path, (strings.ToLower(fw.Name) + ".json"))
|
||||
err = getter.SaveInFile(fw, downloadTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", fw.Name), helpers.String("path", downloadTo))
|
||||
}
|
||||
// return fmt.Errorf("missing framework name")
|
||||
} else {
|
||||
if downloadInfo.FileName == "" {
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Name)
|
||||
}
|
||||
framework, err := g.GetFramework(downloadInfo.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
downloadTo := filepath.Join(downloadInfo.Path, downloadInfo.FileName)
|
||||
err = getter.SaveInFile(framework, downloadTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", framework.Name), helpers.String("path", downloadTo))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadControl(downloadInfo *cautils.DownloadInfo) error {
|
||||
|
||||
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
|
||||
|
||||
g := getPolicyGetter(nil, tenant.GetAccountID(), false, nil)
|
||||
|
||||
if downloadInfo.Name == "" {
|
||||
// TODO - support
|
||||
return fmt.Errorf("missing control name")
|
||||
}
|
||||
if downloadInfo.FileName == "" {
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Name)
|
||||
}
|
||||
controls, err := g.GetControl(downloadInfo.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
downloadTo := filepath.Join(downloadInfo.Path, downloadInfo.FileName)
|
||||
err = getter.SaveInFile(controls, downloadTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", downloadInfo.Name), helpers.String("path", downloadTo))
|
||||
return nil
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package cliinterfaces
|
||||
|
||||
import (
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/resultshandling/reporter"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
type ISubmitObjects interface {
|
||||
SetResourcesReport() (*reporthandling.PostureReport, error)
|
||||
ListAllResources() (map[string]workloadinterface.IMetadata, error)
|
||||
}
|
||||
|
||||
type SubmitInterfaces struct {
|
||||
SubmitObjects ISubmitObjects
|
||||
Reporter reporter.IReport
|
||||
ClusterConfig cautils.ITenantConfig
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package clihandler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/clihandler/cliobjects"
|
||||
)
|
||||
|
||||
var listFunc = map[string]func(*cliobjects.ListPolicies) ([]string, error){
|
||||
"controls": listControls,
|
||||
"frameworks": listFrameworks,
|
||||
"exceptions": listExceptions,
|
||||
}
|
||||
|
||||
var listFormatFunc = map[string]func(*cliobjects.ListPolicies, []string){
|
||||
"pretty-print": prettyPrintListFormat,
|
||||
"json": jsonListFormat,
|
||||
}
|
||||
|
||||
func ListSupportCommands() []string {
|
||||
commands := []string{}
|
||||
for k := range listFunc {
|
||||
commands = append(commands, k)
|
||||
}
|
||||
return commands
|
||||
}
|
||||
func CliList(listPolicies *cliobjects.ListPolicies) error {
|
||||
if f, ok := listFunc[listPolicies.Target]; ok {
|
||||
policies, err := f(listPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Strings(policies)
|
||||
|
||||
listFormatFunc[listPolicies.Format](listPolicies, policies)
|
||||
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown command to download")
|
||||
}
|
||||
|
||||
func listFrameworks(listPolicies *cliobjects.ListPolicies) ([]string, error) {
|
||||
tenant := getTenantConfig(listPolicies.Account, "", getKubernetesApi()) // change k8sinterface
|
||||
g := getPolicyGetter(nil, tenant.GetAccountID(), true, nil)
|
||||
|
||||
return listFrameworksNames(g), nil
|
||||
}
|
||||
|
||||
func listControls(listPolicies *cliobjects.ListPolicies) ([]string, error) {
|
||||
tenant := getTenantConfig(listPolicies.Account, "", getKubernetesApi()) // change k8sinterface
|
||||
|
||||
g := getPolicyGetter(nil, tenant.GetAccountID(), false, nil)
|
||||
l := getter.ListName
|
||||
if listPolicies.ListIDs {
|
||||
l = getter.ListID
|
||||
}
|
||||
return g.ListControls(l)
|
||||
}
|
||||
|
||||
func listExceptions(listPolicies *cliobjects.ListPolicies) ([]string, error) {
|
||||
// load tenant config
|
||||
getTenantConfig(listPolicies.Account, "", getKubernetesApi())
|
||||
|
||||
var exceptionsNames []string
|
||||
armoAPI := getExceptionsGetter("")
|
||||
exceptions, err := armoAPI.GetExceptions("")
|
||||
if err != nil {
|
||||
return exceptionsNames, err
|
||||
}
|
||||
for i := range exceptions {
|
||||
exceptionsNames = append(exceptionsNames, exceptions[i].Name)
|
||||
}
|
||||
return exceptionsNames, nil
|
||||
}
|
||||
|
||||
func prettyPrintListFormat(listPolicies *cliobjects.ListPolicies, policies []string) {
|
||||
sep := "\n * "
|
||||
fmt.Printf("Supported %s:%s%s\n", listPolicies.Target, sep, strings.Join(policies, sep))
|
||||
}
|
||||
|
||||
func jsonListFormat(listPolicies *cliobjects.ListPolicies, policies []string) {
|
||||
j, _ := json.MarshalIndent(policies, "", " ")
|
||||
fmt.Printf("%s\n", j)
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package cliobjects
|
||||
|
||||
type ListPolicies struct {
|
||||
Target string
|
||||
ListIDs bool
|
||||
Account string
|
||||
Format string
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package cliobjects
|
||||
|
||||
type SetConfig struct {
|
||||
Account string
|
||||
ClientID string
|
||||
SecretKey string
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package cliobjects
|
||||
|
||||
type Submit struct {
|
||||
Account string
|
||||
}
|
||||
|
||||
type Delete struct {
|
||||
Account string
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package clihandler
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/clihandler/cliobjects"
|
||||
)
|
||||
|
||||
func CliSetConfig(setConfig *cliobjects.SetConfig) error {
|
||||
|
||||
tenant := getTenantConfig("", "", getKubernetesApi())
|
||||
|
||||
if setConfig.Account != "" {
|
||||
tenant.GetConfigObj().AccountID = setConfig.Account
|
||||
}
|
||||
if setConfig.SecretKey != "" {
|
||||
tenant.GetConfigObj().SecretKey = setConfig.SecretKey
|
||||
}
|
||||
if setConfig.ClientID != "" {
|
||||
tenant.GetConfigObj().ClientID = setConfig.ClientID
|
||||
}
|
||||
|
||||
return tenant.UpdateCachedConfig()
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package clihandler
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/clihandler/cliinterfaces"
|
||||
)
|
||||
|
||||
func Submit(submitInterfaces cliinterfaces.SubmitInterfaces) error {
|
||||
|
||||
// list resources
|
||||
postureReport, err := submitInterfaces.SubmitObjects.SetResourcesReport()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allresources, err := submitInterfaces.SubmitObjects.ListAllResources()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// report
|
||||
if err := submitInterfaces.Reporter.ActionSendReport(&cautils.OPASessionObj{PostureReport: postureReport, AllResources: allresources}); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Data has been submitted successfully")
|
||||
submitInterfaces.Reporter.DisplayReportURL()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SubmitExceptions(accountID, excPath string) error {
|
||||
logger.L().Info("submitting exceptions", helpers.String("path", excPath))
|
||||
|
||||
// load cached config
|
||||
tenantConfig := getTenantConfig(accountID, "", getKubernetesApi())
|
||||
if err := tenantConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
// load exceptions from file
|
||||
loader := getter.NewLoadPolicy([]string{excPath})
|
||||
exceptions, err := loader.GetExceptions("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// login kubescape SaaS
|
||||
armoAPI := getter.GetArmoAPIConnector()
|
||||
if err := armoAPI.Login(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := armoAPI.PostExceptions(exceptions); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Exceptions submitted successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package clihandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func CliView() error {
|
||||
tenant := getTenantConfig("", "", getKubernetesApi()) // change k8sinterface
|
||||
fmt.Fprintf(os.Stderr, "%s\n", tenant.GetConfigObj().Config())
|
||||
return nil
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// clusterCmd represents the cluster command
|
||||
var clusterCmd = &cobra.Command{
|
||||
Use: "cluster",
|
||||
Short: "Set configuration for cluster",
|
||||
Long: ``,
|
||||
Deprecated: "use the 'set' command instead",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(clusterCmd)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var getCmd = &cobra.Command{
|
||||
Use: "get <key>",
|
||||
Short: "Get configuration in cluster",
|
||||
Long: ``,
|
||||
Deprecated: "use the 'view' command instead",
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 || len(args) > 1 {
|
||||
return fmt.Errorf("requires one argument")
|
||||
}
|
||||
|
||||
keyValue := strings.Split(args[0], "=")
|
||||
if len(keyValue) != 1 {
|
||||
return fmt.Errorf("requires one argument")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
keyValue := strings.Split(args[0], "=")
|
||||
key := keyValue[0]
|
||||
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, "")
|
||||
val, err := clusterConfig.GetValueByKeyFromConfigMap(key)
|
||||
if err != nil {
|
||||
if err.Error() == "value does not exist." {
|
||||
return fmt.Errorf("failed to get value from configmap, reason: %s", err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
logger.L().Info(key + "=" + val)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
clusterCmd.AddCommand(getCmd)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var setClusterCmd = &cobra.Command{
|
||||
Use: "set <key>=<value>",
|
||||
Short: "Set configuration in cluster",
|
||||
Long: ``,
|
||||
Deprecated: "use the 'set' command instead",
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 || len(args) > 1 {
|
||||
return fmt.Errorf("requires one argument: <key>=<value>")
|
||||
}
|
||||
keyValue := strings.Split(args[0], "=")
|
||||
if len(keyValue) != 2 {
|
||||
return fmt.Errorf("requires one argument: <key>=<value>")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
keyValue := strings.Split(args[0], "=")
|
||||
key := keyValue[0]
|
||||
data := keyValue[1]
|
||||
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, "")
|
||||
if err := clusterConfig.SetKeyValueInConfigmap(key, data); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Info("value added successfully.")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
clusterCmd.AddCommand(setClusterCmd)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var completionCmdExamples = `
|
||||
|
||||
# Enable BASH shell autocompletion
|
||||
echo 'source <(kubescape completion bash)' >> ~/.bashrc
|
||||
|
||||
# Enable ZSH shell autocompletion
|
||||
echo 'source <(kubectl completion zsh)' >> "${fpath[1]}/_kubectl"
|
||||
|
||||
`
|
||||
var completionCmd = &cobra.Command{
|
||||
Use: "completion [bash|zsh|fish|powershell]",
|
||||
Short: "Generate autocompletion script",
|
||||
Long: "To load completions",
|
||||
Example: completionCmdExamples,
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||
Args: cobra.ExactValidArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "bash":
|
||||
cmd.Root().GenBashCompletion(os.Stdout)
|
||||
case "zsh":
|
||||
cmd.Root().GenZshCompletion(os.Stdout)
|
||||
case "fish":
|
||||
cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
case "powershell":
|
||||
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(completionCmd)
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/kubescape/clihandler/cliobjects"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
configExample = `
|
||||
# View cached configurations
|
||||
kubescape config view
|
||||
|
||||
# Delete cached configurations
|
||||
kubescape config delete
|
||||
|
||||
# Set cached configurations
|
||||
kubescape config set --help
|
||||
`
|
||||
setConfigExample = `
|
||||
# Set account id
|
||||
kubescape config set accountID <account id>
|
||||
|
||||
# Set client id
|
||||
kubescape config set clientID <client id>
|
||||
|
||||
# Set access key
|
||||
kubescape config set secretKey <access key>
|
||||
`
|
||||
)
|
||||
|
||||
// configCmd represents the config command
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "handle cached configurations",
|
||||
Example: configExample,
|
||||
}
|
||||
|
||||
var setConfig = cliobjects.SetConfig{}
|
||||
|
||||
// configCmd represents the config command
|
||||
var configSetCmd = &cobra.Command{
|
||||
Use: "set",
|
||||
Short: fmt.Sprintf("Set configurations, supported: %s", strings.Join(stringKeysToSlice(supportConfigSet), "/")),
|
||||
Example: setConfigExample,
|
||||
ValidArgs: stringKeysToSlice(supportConfigSet),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := parseSetArgs(args); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := clihandler.CliSetConfig(&setConfig); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var supportConfigSet = map[string]func(*cliobjects.SetConfig, string){
|
||||
"accountID": func(s *cliobjects.SetConfig, account string) { s.Account = account },
|
||||
"clientID": func(s *cliobjects.SetConfig, clientID string) { s.ClientID = clientID },
|
||||
"secretKey": func(s *cliobjects.SetConfig, secretKey string) { s.SecretKey = secretKey },
|
||||
}
|
||||
|
||||
func stringKeysToSlice(m map[string]func(*cliobjects.SetConfig, string)) []string {
|
||||
l := []string{}
|
||||
for i := range m {
|
||||
l = append(l, i)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func parseSetArgs(args []string) error {
|
||||
var key string
|
||||
var value string
|
||||
if len(args) == 1 {
|
||||
if keyValue := strings.Split(args[0], "="); len(keyValue) == 2 {
|
||||
key = keyValue[0]
|
||||
value = keyValue[1]
|
||||
}
|
||||
} else if len(args) == 2 {
|
||||
key = args[0]
|
||||
value = args[1]
|
||||
}
|
||||
if setConfigFunc, ok := supportConfigSet[key]; ok {
|
||||
setConfigFunc(&setConfig, value)
|
||||
} else {
|
||||
return fmt.Errorf("key '%s' unknown . supported: %s", key, strings.Join(stringKeysToSlice(supportConfigSet), "/"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var configDeleteCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete cached configurations",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := clihandler.CliDelete(); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// configCmd represents the config command
|
||||
var configViewCmd = &cobra.Command{
|
||||
Use: "view",
|
||||
Short: "View cached configurations",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := clihandler.CliView(); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(configCmd)
|
||||
configCmd.AddCommand(configSetCmd)
|
||||
configCmd.AddCommand(configDeleteCmd)
|
||||
configCmd.AddCommand(configViewCmd)
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
controlExample = `
|
||||
# Scan the 'privileged container' control
|
||||
kubescape scan control "privileged container"
|
||||
|
||||
# Scan list of controls separated with a comma
|
||||
kubescape scan control "privileged container","allowed hostpath"
|
||||
|
||||
# Scan list of controls using the control ID separated with a comma
|
||||
kubescape scan control C-0058,C-0057
|
||||
|
||||
Run 'kubescape list controls' for the list of supported controls
|
||||
|
||||
Control documentation:
|
||||
https://hub.armo.cloud/docs/controls
|
||||
`
|
||||
)
|
||||
|
||||
// controlCmd represents the control command
|
||||
var controlCmd = &cobra.Command{
|
||||
Use: "control <control names list>/<control ids list>",
|
||||
Short: "The controls you wish to use. Run 'kubescape list controls' for the list of supported controls",
|
||||
Example: controlExample,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
controls := strings.Split(args[0], ",")
|
||||
if len(controls) > 1 {
|
||||
for _, control := range controls {
|
||||
if control == "" {
|
||||
return fmt.Errorf("usage: <control-0>,<control-1>")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("requires at least one control name")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
flagValidationControl()
|
||||
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
|
||||
|
||||
if len(args) == 0 {
|
||||
scanInfo.ScanAll = true
|
||||
} else { // expected control or list of control sepparated by ","
|
||||
|
||||
// Read controls from input args
|
||||
scanInfo.SetPolicyIdentifiers(strings.Split(args[0], ","), reporthandling.KindControl)
|
||||
|
||||
if len(args) > 1 {
|
||||
if len(args[1:]) == 0 || args[1] != "-" {
|
||||
scanInfo.InputPatterns = args[1:]
|
||||
} else { // store stdin to file - do NOT move to separate function !!
|
||||
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
|
||||
return err
|
||||
}
|
||||
scanInfo.InputPatterns = []string{tempFile.Name()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scanInfo.FrameworkScan = false
|
||||
scanInfo.Init()
|
||||
cautils.SetSilentMode(scanInfo.Silent)
|
||||
err := clihandler.ScanCliSetup(&scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
scanInfo = cautils.ScanInfo{}
|
||||
scanCmd.AddCommand(controlCmd)
|
||||
}
|
||||
|
||||
func flagValidationControl() {
|
||||
if 100 < scanInfo.FailThreshold {
|
||||
logger.L().Fatal("bad argument: out of range threshold")
|
||||
}
|
||||
}
|
||||
|
||||
func setScanForFirstControl(controls []string) []reporthandling.PolicyIdentifier {
|
||||
newPolicy := reporthandling.PolicyIdentifier{}
|
||||
newPolicy.Kind = reporthandling.KindControl
|
||||
newPolicy.Name = controls[0]
|
||||
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
return scanInfo.PolicyIdentifier
|
||||
}
|
||||
|
||||
func SetScanForGivenControls(controls []string) []reporthandling.PolicyIdentifier {
|
||||
for _, control := range controls {
|
||||
control := strings.TrimLeft(control, " ")
|
||||
newPolicy := reporthandling.PolicyIdentifier{}
|
||||
newPolicy.Kind = reporthandling.KindControl
|
||||
newPolicy.Name = control
|
||||
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
}
|
||||
return scanInfo.PolicyIdentifier
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/kubescape/clihandler/cliobjects"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var deleteInfo cliobjects.Delete
|
||||
|
||||
var deleteExceptionsExamples = `
|
||||
# Delete single exception
|
||||
kubescape delete exceptions "exception name"
|
||||
|
||||
# Delete multiple exceptions
|
||||
kubescape delete exceptions "first exception;second exception;third exception"
|
||||
`
|
||||
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete <command>",
|
||||
Short: "Delete configurations in Kubescape SaaS version",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
|
||||
var deleteExceptionsCmd = &cobra.Command{
|
||||
Use: "exceptions <exception name>",
|
||||
Short: "Delete exceptions from Kubescape SaaS version. Run 'kubescape list exceptions' for all exceptions names",
|
||||
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) {
|
||||
exceptionsNames := strings.Split(args[0], ";")
|
||||
if len(exceptionsNames) == 0 {
|
||||
logger.L().Fatal("missing exceptions names")
|
||||
}
|
||||
if err := clihandler.DeleteExceptions(deleteInfo.Account, exceptionsNames); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
rootCmd.AddCommand(deleteCmd)
|
||||
|
||||
deleteCmd.AddCommand(deleteExceptionsCmd)
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var downloadInfo = cautils.DownloadInfo{}
|
||||
|
||||
var (
|
||||
downloadExample = `
|
||||
# Download all artifacts and save them in the default path (~/.kubescape)
|
||||
kubescape download artifacts
|
||||
|
||||
# Download all artifacts and save them in /tmp path
|
||||
kubescape download artifacts --output /tmp
|
||||
|
||||
# Download the NSA framework. Run 'kubescape list frameworks' for all frameworks names
|
||||
kubescape download framework nsa
|
||||
|
||||
# Download the "Allowed hostPath" control. Run 'kubescape list controls' for all controls names
|
||||
kubescape download control "Allowed hostPath"
|
||||
|
||||
# Download the "C-0001" control. Run 'kubescape list controls --id' for all controls ids
|
||||
kubescape download control C-0001
|
||||
|
||||
# Download the configured exceptions
|
||||
kubescape download exceptions
|
||||
|
||||
# Download the configured controls-inputs
|
||||
kubescape download controls-inputs
|
||||
|
||||
`
|
||||
)
|
||||
var downloadCmd = &cobra.Command{
|
||||
Use: "download <policy> <policy name>",
|
||||
Short: fmt.Sprintf("Download %s", strings.Join(clihandler.DownloadSupportCommands(), ",")),
|
||||
Long: ``,
|
||||
Example: downloadExample,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
supported := strings.Join(clihandler.DownloadSupportCommands(), ",")
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("policy type required, supported: %v", supported)
|
||||
}
|
||||
if cautils.StringInSlice(clihandler.DownloadSupportCommands(), args[0]) == cautils.ValueNotFound {
|
||||
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
downloadInfo.Target = args[0]
|
||||
if len(args) >= 2 {
|
||||
downloadInfo.Name = args[1]
|
||||
}
|
||||
if err := clihandler.CliDownload(&downloadInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initDownload)
|
||||
|
||||
rootCmd.AddCommand(downloadCmd)
|
||||
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
|
||||
|
||||
}
|
||||
|
||||
func initDownload() {
|
||||
if filepath.Ext(downloadInfo.Path) == ".json" {
|
||||
downloadInfo.Path, downloadInfo.FileName = filepath.Split(downloadInfo.Path)
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
frameworkExample = `
|
||||
# Scan all frameworks and submit the results
|
||||
kubescape scan framework all --submit
|
||||
|
||||
# Scan the NSA framework
|
||||
kubescape scan framework nsa
|
||||
|
||||
# Scan the NSA and MITRE framework
|
||||
kubescape scan framework nsa,mitre
|
||||
|
||||
# Scan all frameworks
|
||||
kubescape scan framework all
|
||||
|
||||
# Scan kubernetes YAML manifest files
|
||||
kubescape scan framework nsa *.yaml
|
||||
|
||||
Run 'kubescape list frameworks' for the list of supported frameworks
|
||||
`
|
||||
)
|
||||
var frameworkCmd = &cobra.Command{
|
||||
Use: "framework <framework names list> [`<glob pattern>`/`-`] [flags]",
|
||||
Short: "The framework you wish to use. Run 'kubescape list frameworks' for the list of supported frameworks",
|
||||
Example: frameworkExample,
|
||||
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
frameworks := strings.Split(args[0], ",")
|
||||
if len(frameworks) > 1 {
|
||||
for _, framework := range frameworks {
|
||||
if framework == "" {
|
||||
return fmt.Errorf("usage: <framework-0>,<framework-1>")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("requires at least one framework name")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
flagValidationFramework()
|
||||
var frameworks []string
|
||||
|
||||
if len(args) == 0 { // scan all frameworks
|
||||
scanInfo.ScanAll = true
|
||||
} else {
|
||||
// Read frameworks from input args
|
||||
frameworks = strings.Split(args[0], ",")
|
||||
if cautils.StringInSlice(frameworks, "all") != cautils.ValueNotFound {
|
||||
scanInfo.ScanAll = true
|
||||
frameworks = []string{}
|
||||
}
|
||||
if len(args) > 1 {
|
||||
if len(args[1:]) == 0 || args[1] != "-" {
|
||||
scanInfo.InputPatterns = args[1:]
|
||||
} else { // store stdin to file - do NOT move to separate function !!
|
||||
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
|
||||
return err
|
||||
}
|
||||
scanInfo.InputPatterns = []string{tempFile.Name()}
|
||||
}
|
||||
}
|
||||
}
|
||||
scanInfo.FrameworkScan = true
|
||||
|
||||
scanInfo.SetPolicyIdentifiers(frameworks, reporthandling.KindFramework)
|
||||
|
||||
scanInfo.Init()
|
||||
cautils.SetSilentMode(scanInfo.Silent)
|
||||
err := clihandler.ScanCliSetup(&scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
scanCmd.AddCommand(frameworkCmd)
|
||||
scanInfo = cautils.ScanInfo{}
|
||||
scanInfo.FrameworkScan = true
|
||||
}
|
||||
|
||||
// func SetScanForFirstFramework(frameworks []string) []reporthandling.PolicyIdentifier {
|
||||
// newPolicy := reporthandling.PolicyIdentifier{}
|
||||
// newPolicy.Kind = reporthandling.KindFramework
|
||||
// newPolicy.Name = frameworks[0]
|
||||
// scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
// return scanInfo.PolicyIdentifier
|
||||
// }
|
||||
|
||||
func flagValidationFramework() {
|
||||
if scanInfo.Submit && scanInfo.Local {
|
||||
logger.L().Fatal("you can use `keep-local` or `submit`, but not both")
|
||||
}
|
||||
if 100 < scanInfo.FailThreshold {
|
||||
logger.L().Fatal("bad argument: out of range threshold")
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/kubescape/clihandler/cliobjects"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
listExample = `
|
||||
# List default supported frameworks names
|
||||
kubescape list frameworks
|
||||
|
||||
# List all supported frameworks names
|
||||
kubescape list frameworks --account <account id>
|
||||
|
||||
# List all supported controls names
|
||||
kubescape list controls
|
||||
|
||||
# List all supported controls ids
|
||||
kubescape list controls --id
|
||||
|
||||
Control documentation:
|
||||
https://hub.armo.cloud/docs/controls
|
||||
`
|
||||
)
|
||||
var listPolicies = cliobjects.ListPolicies{}
|
||||
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list <policy> [flags]",
|
||||
Short: "List frameworks/controls will list the supported frameworks and controls",
|
||||
Long: ``,
|
||||
Example: listExample,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
supported := strings.Join(clihandler.ListSupportCommands(), ",")
|
||||
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("policy type requeued, supported: %s", supported)
|
||||
}
|
||||
if cautils.StringInSlice(clihandler.ListSupportCommands(), args[0]) == cautils.ValueNotFound {
|
||||
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
listPolicies.Target = args[0]
|
||||
|
||||
if err := clihandler.CliList(&listPolicies); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// cobra.OnInitialize(initConfig)
|
||||
|
||||
rootCmd.AddCommand(listCmd)
|
||||
listCmd.PersistentFlags().StringVar(&listPolicies.Account, "account", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-printer'/'json'")
|
||||
listCmd.PersistentFlags().BoolVarP(&listPolicies.ListIDs, "id", "", false, "List control ID's instead of controls names")
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var localCmd = &cobra.Command{
|
||||
Use: "local",
|
||||
Short: "Set configuration locally (for config.json)",
|
||||
Long: ``,
|
||||
Deprecated: "use the 'set' command instead",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(localCmd)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var localGetCmd = &cobra.Command{
|
||||
Use: "get <key>",
|
||||
Short: "Get configuration locally",
|
||||
Long: ``,
|
||||
Deprecated: "use the 'view' command instead",
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 || len(args) > 1 {
|
||||
return fmt.Errorf("requires one argument")
|
||||
}
|
||||
|
||||
keyValue := strings.Split(args[0], "=")
|
||||
if len(keyValue) != 1 {
|
||||
return fmt.Errorf("requires one argument")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
keyValue := strings.Split(args[0], "=")
|
||||
key := keyValue[0]
|
||||
|
||||
val, err := cautils.GetValueFromConfigJson(key)
|
||||
if err != nil {
|
||||
if err.Error() == "value does not exist." {
|
||||
return fmt.Errorf("failed to get value from: %s, reason: %s", cautils.ConfigFileFullPath(), err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
fmt.Println(key + "=" + val)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
localCmd.AddCommand(localGetCmd)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var localSetCmd = &cobra.Command{
|
||||
Use: "set <key>=<value>",
|
||||
Short: "Set configuration locally",
|
||||
Long: ``,
|
||||
Deprecated: "use the 'set' command instead",
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 || len(args) > 1 {
|
||||
return fmt.Errorf("requires one argument: <key>=<value>")
|
||||
}
|
||||
keyValue := strings.Split(args[0], "=")
|
||||
if len(keyValue) != 2 {
|
||||
return fmt.Errorf("requires one argument: <key>=<value>")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
keyValue := strings.Split(args[0], "=")
|
||||
key := keyValue[0]
|
||||
data := keyValue[1]
|
||||
|
||||
if err := cautils.SetKeyValueInConfigJson(key, data); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Value added successfully.")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
localCmd.AddCommand(localSetCmd)
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/kubescape/clihandler/cliinterfaces"
|
||||
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
|
||||
"github.com/armosec/rbac-utils/rbacscanner"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// rabcCmd represents the RBAC command
|
||||
var rabcCmd = &cobra.Command{
|
||||
Use: "rbac \nExample:\n$ kubescape submit rbac",
|
||||
Short: "Submit cluster's Role-Based Access Control(RBAC)",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
|
||||
// get config
|
||||
clusterConfig := getTenantConfig(submitInfo.Account, "", k8s)
|
||||
if err := clusterConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
// list RBAC
|
||||
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetAccountID(), clusterConfig.GetClusterName()))
|
||||
|
||||
// submit resources
|
||||
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
|
||||
|
||||
submitInterfaces := cliinterfaces.SubmitInterfaces{
|
||||
ClusterConfig: clusterConfig,
|
||||
SubmitObjects: rbacObjects,
|
||||
Reporter: r,
|
||||
}
|
||||
|
||||
if err := clihandler.Submit(submitInterfaces); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
submitCmd.AddCommand(rabcCmd)
|
||||
}
|
||||
|
||||
// getKubernetesApi
|
||||
func getKubernetesApi() *k8sinterface.KubernetesApi {
|
||||
if !k8sinterface.IsConnectedToCluster() {
|
||||
return nil
|
||||
}
|
||||
return k8sinterface.NewKubernetesApi()
|
||||
}
|
||||
func getTenantConfig(Account, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
|
||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||
return cautils.NewLocalConfig(getter.GetArmoAPIConnector(), Account, clusterName)
|
||||
}
|
||||
return cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), Account, clusterName)
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/kubescape/clihandler/cliinterfaces"
|
||||
"github.com/armosec/kubescape/resultshandling/reporter"
|
||||
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
|
||||
reporterv2 "github.com/armosec/kubescape/resultshandling/reporter/v2"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/google/uuid"
|
||||
"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() (*reporthandling.PostureReport, error) {
|
||||
// load framework results from json file
|
||||
frameworkReports, err := loadResultsFromFile(resultsObject.filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &reporthandling.PostureReport{
|
||||
FrameworkReports: frameworkReports,
|
||||
ReportID: uuid.NewString(),
|
||||
ReportGenerationTime: time.Now().UTC(),
|
||||
CustomerGUID: resultsObject.customerGUID,
|
||||
ClusterName: resultsObject.clusterName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
|
||||
return map[string]workloadinterface.IMetadata{}, nil
|
||||
}
|
||||
|
||||
var resultsCmd = &cobra.Command{
|
||||
Use: "results <json file>\nExample:\n$ kubescape submit results path/to/results.json --format-version v2",
|
||||
Short: "Submit a pre scanned results file. The file must be in json format",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("missing results file")
|
||||
}
|
||||
|
||||
k8s := getKubernetesApi()
|
||||
|
||||
// get config
|
||||
clusterConfig := getTenantConfig(submitInfo.Account, "", k8s)
|
||||
if err := clusterConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
resultsObjects := NewResultsObject(clusterConfig.GetAccountID(), clusterConfig.GetClusterName(), args[0])
|
||||
|
||||
// submit resources
|
||||
var r reporter.IReport
|
||||
switch formatVersion {
|
||||
case "v2":
|
||||
r = reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj())
|
||||
default:
|
||||
logger.L().Warning("Deprecated results version. run with '--format-version' flag", helpers.String("your version", formatVersion), helpers.String("latest version", "v2"))
|
||||
r = reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
|
||||
}
|
||||
|
||||
submitInterfaces := cliinterfaces.SubmitInterfaces{
|
||||
ClusterConfig: clusterConfig,
|
||||
SubmitObjects: resultsObjects,
|
||||
Reporter: r,
|
||||
}
|
||||
|
||||
if err := clihandler.Submit(submitInterfaces); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
submitCmd.AddCommand(resultsCmd)
|
||||
resultsCmd.PersistentFlags().StringVar(&formatVersion, "format-version", "v1", "Output object can be differnet between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
|
||||
}
|
||||
|
||||
func loadResultsFromFile(filePath string) ([]reporthandling.FrameworkReport, error) {
|
||||
frameworkReports := []reporthandling.FrameworkReport{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(f, &frameworkReports); err != nil {
|
||||
frameworkReport := reporthandling.FrameworkReport{}
|
||||
if err = json.Unmarshal(f, &frameworkReport); err != nil {
|
||||
return frameworkReports, err
|
||||
}
|
||||
frameworkReports = append(frameworkReports, frameworkReport)
|
||||
}
|
||||
return frameworkReports, nil
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var armoBEURLs = ""
|
||||
var armoBEURLsDep = ""
|
||||
var rootInfo cautils.RootInfo
|
||||
|
||||
const envFlagUsage = "Send report results to specific URL. Format:<ReportReceiver>,<Backend>,<Frontend>.\n\t\tExample:report.armo.cloud,api.armo.cloud,portal.armo.cloud"
|
||||
|
||||
var ksExamples = `
|
||||
# Scan command
|
||||
kubescape scan --submit
|
||||
|
||||
# List supported frameworks
|
||||
kubescape list frameworks
|
||||
|
||||
# Download artifacts (air-gapped environment support)
|
||||
kubescape download artifacts
|
||||
|
||||
# View cached configurations
|
||||
kubescape config view
|
||||
`
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "kubescape",
|
||||
Version: cautils.BuildNumber,
|
||||
Short: "Kubescape is a tool for testing Kubernetes security posture",
|
||||
Long: `Based on NSA \ MITRE ATT&CK® and other frameworks specifications`,
|
||||
Example: ksExamples,
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
rootCmd.Execute()
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
cobra.OnInitialize(initLogger, initLoggerLevel, initEnvironment, initCacheDir)
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&armoBEURLsDep, "environment", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().StringVar(&armoBEURLs, "env", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().MarkDeprecated("environment", "use 'env' instead")
|
||||
rootCmd.PersistentFlags().MarkHidden("environment")
|
||||
rootCmd.PersistentFlags().MarkHidden("env")
|
||||
|
||||
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]")
|
||||
}
|
||||
|
||||
func initLogger() {
|
||||
if l := os.Getenv("KS_LOGGER_NAME"); l != "" {
|
||||
logger.InitializeLogger(l)
|
||||
}
|
||||
}
|
||||
func initLoggerLevel() {
|
||||
if rootInfo.Logger != helpers.InfoLevel.String() {
|
||||
} else if l := os.Getenv("KS_LOGGER"); l != "" {
|
||||
rootInfo.Logger = l
|
||||
}
|
||||
if err := logger.L().SetLevel(rootInfo.Logger); err != nil {
|
||||
logger.L().Fatal(fmt.Sprintf("supported levels: %s", strings.Join(helpers.SupportedLevels(), "/")), helpers.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func initCacheDir() {
|
||||
if rootInfo.CacheDir != getter.DefaultLocalStore {
|
||||
getter.DefaultLocalStore = rootInfo.CacheDir
|
||||
} else if cacheDir := os.Getenv("KS_CACHE_DIR"); cacheDir != "" {
|
||||
getter.DefaultLocalStore = cacheDir
|
||||
} else {
|
||||
return // using default cache di location
|
||||
}
|
||||
|
||||
logger.L().Debug("cache dir updated", helpers.String("path", getter.DefaultLocalStore))
|
||||
}
|
||||
func initEnvironment() {
|
||||
if armoBEURLsDep != "" {
|
||||
armoBEURLs = armoBEURLsDep
|
||||
}
|
||||
urlSlices := strings.Split(armoBEURLs, ",")
|
||||
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.SetARMOAPIConnector(getter.NewARMOAPIDev())
|
||||
case "stage", "staging":
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPIStaging())
|
||||
case "":
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPIProd())
|
||||
default:
|
||||
logger.L().Fatal("--environment flag usage: " + envFlagUsage)
|
||||
}
|
||||
case 2:
|
||||
logger.L().Fatal("--environment flag usage: " + envFlagUsage)
|
||||
case 3, 4:
|
||||
var armoAUTHURL string
|
||||
armoERURL := urlSlices[0] // mandatory
|
||||
armoBEURL := urlSlices[1] // mandatory
|
||||
armoFEURL := urlSlices[2] // mandatory
|
||||
if len(urlSlices) <= 4 {
|
||||
armoAUTHURL = urlSlices[3]
|
||||
}
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPICustomized(armoERURL, armoBEURL, armoFEURL, armoAUTHURL))
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var scanInfo cautils.ScanInfo
|
||||
|
||||
var scanCmdExamples = `
|
||||
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defind frameworks
|
||||
|
||||
# Scan current cluster with all frameworks
|
||||
kubescape scan --submit --enable-host-scan
|
||||
|
||||
# Scan kubernetes YAML manifest files
|
||||
kubescape scan *.yaml
|
||||
|
||||
# Scan and save the results in the JSON format
|
||||
kubescape scan --format json --output results.json
|
||||
|
||||
# Display all resources
|
||||
kubescape scan --verbose
|
||||
|
||||
# Scan different clusters from the kubectl context
|
||||
kubescape scan --kube-context <kubernetes context>
|
||||
|
||||
`
|
||||
|
||||
// scanCmd represents the scan command
|
||||
var scanCmd = &cobra.Command{
|
||||
Use: "scan",
|
||||
Short: "Scan the current running cluster or yaml files",
|
||||
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" {
|
||||
scanInfo.ScanAll = true
|
||||
return frameworkCmd.RunE(cmd, append([]string{"all"}, args...))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
scanInfo.ScanAll = true
|
||||
return frameworkCmd.RunE(cmd, []string{"all"})
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func frameworkInitConfig() {
|
||||
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
|
||||
}
|
||||
func init() {
|
||||
|
||||
cobra.OnInitialize(frameworkInitConfig)
|
||||
|
||||
rootCmd.AddCommand(scanCmd)
|
||||
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
|
||||
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. Recommended: kube-system,kube-public")
|
||||
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().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer","json","junit","prometheus","pdf"`)
|
||||
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 Armo backend. Use this flag if you ran with the '--submit' flag in the past and you do not want to submit your current scan results")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
|
||||
scanCmd.PersistentFlags().BoolVar(&scanInfo.VerboseMode, "verbose", false, "Display all of the input resources and not only failed resources")
|
||||
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().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Send the scan results to Armo management portal 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().StringVar(&scanInfo.HostSensorYamlPath, "host-scan-yaml", "", "Override default host sensor DaemonSet. Use this flag cautiously")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.FormatVersion, "format-version", "v1", "Output object can be differnet between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
|
||||
|
||||
// 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 DaemoSet can not run pods on the nodes
|
||||
scanCmd.PersistentFlags().MarkHidden("silent") // this flag should be deprecated since we added the --logger support
|
||||
// scanCmd.PersistentFlags().MarkHidden("format-version") // meant for testing different output approaches and not for common use
|
||||
|
||||
hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensorEnabled, "enable-host-scan", "", "Deploy ARMO K8s host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valueable data from cluster nodes for certain controls. Yaml file: https://raw.githubusercontent.com/armosec/kubescape/master/hostsensorutils/hostsensor.yaml")
|
||||
hostF.NoOptDefVal = "true"
|
||||
hostF.DefValue = "false, for no TTY in stdin"
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/kubescape/clihandler/cliobjects"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var submitInfo cliobjects.Submit
|
||||
|
||||
var submitCmdExamples = `
|
||||
|
||||
`
|
||||
var submitCmd = &cobra.Command{
|
||||
Use: "submit <command>",
|
||||
Short: "Submit an object to the Kubescape SaaS version",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
|
||||
var submitExceptionsCmd = &cobra.Command{
|
||||
Use: "exceptions <full path to exceptins 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 := clihandler.SubmitExceptions(submitInfo.Account, args[0]); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
submitCmd.PersistentFlags().StringVarP(&submitInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
rootCmd.AddCommand(submitCmd)
|
||||
|
||||
submitCmd.AddCommand(submitExceptionsCmd)
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Get current version",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := cautils.NewIVersionCheckHandler()
|
||||
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
|
||||
fmt.Fprintln(os.Stdout, "Your current version is: "+cautils.BuildNumber)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
package clihandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/hostsensorutils"
|
||||
"github.com/armosec/kubescape/opaprocessor"
|
||||
"github.com/armosec/kubescape/policyhandler"
|
||||
"github.com/armosec/kubescape/resourcehandler"
|
||||
"github.com/armosec/kubescape/resultshandling"
|
||||
"github.com/armosec/kubescape/resultshandling/reporter"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
type componentInterfaces struct {
|
||||
tenantConfig cautils.ITenantConfig
|
||||
resourceHandler resourcehandler.IResourceHandler
|
||||
report reporter.IReport
|
||||
printerHandler printer.IPrinter
|
||||
hostSensorHandler hostsensorutils.IHostSensor
|
||||
}
|
||||
|
||||
func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
|
||||
|
||||
// ================== setup k8s interface object ======================================
|
||||
var k8s *k8sinterface.KubernetesApi
|
||||
if scanInfo.GetScanningEnvironment() == cautils.ScanCluster {
|
||||
k8s = getKubernetesApi()
|
||||
if k8s == nil {
|
||||
logger.L().Fatal("failed connecting to Kubernetes cluster")
|
||||
}
|
||||
}
|
||||
|
||||
// ================== setup tenant object ======================================
|
||||
|
||||
tenantConfig := getTenantConfig(scanInfo.Account, scanInfo.KubeContext, k8s)
|
||||
|
||||
// Set submit behavior AFTER loading tenant config
|
||||
setSubmitBehavior(scanInfo, tenantConfig)
|
||||
|
||||
// ================== version testing ======================================
|
||||
|
||||
v := cautils.NewIVersionCheckHandler()
|
||||
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, policyIdentifierNames(scanInfo.PolicyIdentifier), "", scanInfo.GetScanningEnvironment()))
|
||||
|
||||
// ================== setup host sensor object ======================================
|
||||
|
||||
hostSensorHandler := getHostSensorHandler(scanInfo, k8s)
|
||||
if err := hostSensorHandler.Init(); err != nil {
|
||||
logger.L().Error("failed to init host sensor", helpers.Error(err))
|
||||
hostSensorHandler = &hostsensorutils.HostSensorHandlerMock{}
|
||||
}
|
||||
// excluding hostsensor namespace
|
||||
if len(scanInfo.IncludeNamespaces) == 0 && hostSensorHandler.GetNamespace() != "" {
|
||||
scanInfo.ExcludedNamespaces = fmt.Sprintf("%s,%s", scanInfo.ExcludedNamespaces, hostSensorHandler.GetNamespace())
|
||||
}
|
||||
|
||||
// ================== setup registry adaptors ======================================
|
||||
|
||||
registryAdaptors, err := resourcehandler.NewRegistryAdaptors()
|
||||
if err != nil {
|
||||
logger.L().Error("failed to initialize registry adaptors", helpers.Error(err))
|
||||
}
|
||||
|
||||
// ================== setup resource collector object ======================================
|
||||
|
||||
resourceHandler := getResourceHandler(scanInfo, tenantConfig, k8s, hostSensorHandler, registryAdaptors)
|
||||
|
||||
// ================== setup reporter & printer objects ======================================
|
||||
|
||||
// reporting behavior - setup reporter
|
||||
reportHandler := getReporter(tenantConfig, scanInfo.Submit, scanInfo.FrameworkScan, len(scanInfo.InputPatterns) == 0)
|
||||
|
||||
// setup printer
|
||||
printerHandler := resultshandling.NewPrinter(scanInfo.Format, scanInfo.FormatVersion, scanInfo.VerboseMode)
|
||||
printerHandler.SetWriter(scanInfo.Output)
|
||||
|
||||
// ================== return interface ======================================
|
||||
|
||||
return componentInterfaces{
|
||||
tenantConfig: tenantConfig,
|
||||
resourceHandler: resourceHandler,
|
||||
report: reportHandler,
|
||||
printerHandler: printerHandler,
|
||||
hostSensorHandler: hostSensorHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func ScanCliSetup(scanInfo *cautils.ScanInfo) error {
|
||||
logger.L().Info("ARMO security scanner starting")
|
||||
|
||||
interfaces := getInterfaces(scanInfo)
|
||||
// setPolicyGetter(scanInfo, interfaces.clusterConfig.GetCustomerGUID())
|
||||
|
||||
processNotification := make(chan *cautils.OPASessionObj)
|
||||
reportResults := make(chan *cautils.OPASessionObj)
|
||||
|
||||
cautils.ClusterName = interfaces.tenantConfig.GetClusterName() // TODO - Deprecated
|
||||
cautils.CustomerGUID = interfaces.tenantConfig.GetAccountID() // TODO - Deprecated
|
||||
interfaces.report.SetClusterName(interfaces.tenantConfig.GetClusterName())
|
||||
interfaces.report.SetCustomerGUID(interfaces.tenantConfig.GetAccountID())
|
||||
|
||||
downloadReleasedPolicy := getter.NewDownloadReleasedPolicy() // download config inputs from github release
|
||||
|
||||
// set policy getter only after setting the customerGUID
|
||||
scanInfo.Getters.PolicyGetter = getPolicyGetter(scanInfo.UseFrom, interfaces.tenantConfig.GetAccountID(), scanInfo.FrameworkScan, downloadReleasedPolicy)
|
||||
scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(scanInfo.ControlsInputs, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
|
||||
scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(scanInfo.UseExceptions)
|
||||
|
||||
// TODO - list supported frameworks/controls
|
||||
if scanInfo.ScanAll {
|
||||
scanInfo.SetPolicyIdentifiers(listFrameworksNames(scanInfo.Getters.PolicyGetter), reporthandling.KindFramework)
|
||||
}
|
||||
|
||||
//
|
||||
defer func() {
|
||||
if err := interfaces.hostSensorHandler.TearDown(); err != nil {
|
||||
logger.L().Error("failed to tear down host sensor", helpers.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// cli handler setup
|
||||
go func() {
|
||||
// policy handler setup
|
||||
policyHandler := policyhandler.NewPolicyHandler(&processNotification, interfaces.resourceHandler)
|
||||
|
||||
if err := Scan(policyHandler, scanInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
// processor setup - rego run
|
||||
go func() {
|
||||
opaprocessorObj := opaprocessor.NewOPAProcessorHandler(&processNotification, &reportResults)
|
||||
opaprocessorObj.ProcessRulesListenner()
|
||||
}()
|
||||
|
||||
resultsHandling := resultshandling.NewResultsHandler(&reportResults, interfaces.report, interfaces.printerHandler)
|
||||
score := resultsHandling.HandleResults(scanInfo)
|
||||
|
||||
// print report url
|
||||
interfaces.report.DisplayReportURL()
|
||||
|
||||
if score > float32(scanInfo.FailThreshold) {
|
||||
return fmt.Errorf("scan risk-score %.2f is above permitted threshold %.2f", score, scanInfo.FailThreshold)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Scan(policyHandler *policyhandler.PolicyHandler, scanInfo *cautils.ScanInfo) error {
|
||||
policyNotification := &reporthandling.PolicyNotification{
|
||||
Rules: scanInfo.PolicyIdentifier,
|
||||
KubescapeNotification: reporthandling.KubescapeNotification{
|
||||
Designators: armotypes.PortalDesignator{},
|
||||
NotificationType: reporthandling.TypeExecPostureScan,
|
||||
},
|
||||
}
|
||||
switch policyNotification.KubescapeNotification.NotificationType {
|
||||
case reporthandling.TypeExecPostureScan:
|
||||
if err := policyHandler.HandleNotificationRequest(policyNotification, scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("notification type '%s' Unknown", policyNotification.KubescapeNotification.NotificationType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func askUserForHostSensor() bool {
|
||||
return false
|
||||
|
||||
if !isatty.IsTerminal(os.Stdin.Fd()) {
|
||||
return false
|
||||
}
|
||||
if ssss, err := os.Stdin.Stat(); err == nil {
|
||||
// fmt.Printf("Found stdin type: %s\n", ssss.Mode().Type())
|
||||
if ssss.Mode().Type()&(fs.ModeDevice|fs.ModeCharDevice) > 0 { //has TTY
|
||||
fmt.Fprintf(os.Stderr, "Would you like to scan K8s nodes? [y/N]. This is required to collect valuable data for certain controls\n")
|
||||
fmt.Fprintf(os.Stderr, "Use --enable-host-scan flag to suppress this message\n")
|
||||
var b []byte = make([]byte, 1)
|
||||
if n, err := os.Stdin.Read(b); err == nil {
|
||||
if n > 0 && len(b) > 0 && (b[0] == 'y' || b[0] == 'Y') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
package clihandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/hostsensorutils"
|
||||
"github.com/armosec/kubescape/resourcehandler"
|
||||
"github.com/armosec/kubescape/resultshandling/reporter"
|
||||
reporterv2 "github.com/armosec/kubescape/resultshandling/reporter/v2"
|
||||
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/rbac-utils/rbacscanner"
|
||||
)
|
||||
|
||||
// getKubernetesApi
|
||||
func getKubernetesApi() *k8sinterface.KubernetesApi {
|
||||
if !k8sinterface.IsConnectedToCluster() {
|
||||
return nil
|
||||
}
|
||||
return k8sinterface.NewKubernetesApi()
|
||||
}
|
||||
func getTenantConfig(Account, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
|
||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||
return cautils.NewLocalConfig(getter.GetArmoAPIConnector(), Account, clusterName)
|
||||
}
|
||||
return cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), Account, clusterName)
|
||||
}
|
||||
|
||||
func getExceptionsGetter(useExceptions string) getter.IExceptionsGetter {
|
||||
if useExceptions != "" {
|
||||
// load exceptions from file
|
||||
return getter.NewLoadPolicy([]string{useExceptions})
|
||||
} else {
|
||||
return getter.GetArmoAPIConnector()
|
||||
}
|
||||
}
|
||||
|
||||
func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, submit bool) *cautils.RBACObjects {
|
||||
if submit {
|
||||
return cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, tenantConfig.GetAccountID(), tenantConfig.GetClusterName()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getReporter(tenantConfig cautils.ITenantConfig, submit, fwScan, clusterScan bool) reporter.IReport {
|
||||
if submit && clusterScan {
|
||||
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj())
|
||||
}
|
||||
if tenantConfig.GetAccountID() == "" && fwScan && clusterScan {
|
||||
// Add link only when scanning a cluster using a framework
|
||||
return reporterv2.NewReportMock(reporterv2.NO_SUBMIT_QUERY, "run kubescape with the '--submit' flag")
|
||||
}
|
||||
var message string
|
||||
if !fwScan {
|
||||
message = "Kubescape does not submit scan results when scanning controls"
|
||||
}
|
||||
if !clusterScan {
|
||||
message = "Kubescape will submit scan results only when scanning a cluster (not YAML files)"
|
||||
}
|
||||
return reporterv2.NewReportMock("", message)
|
||||
}
|
||||
|
||||
func getResourceHandler(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, hostSensorHandler hostsensorutils.IHostSensor, registryAdaptors *resourcehandler.RegistryAdaptors) resourcehandler.IResourceHandler {
|
||||
if len(scanInfo.InputPatterns) > 0 || k8s == nil {
|
||||
// scanInfo.HostSensor.SetBool(false)
|
||||
return resourcehandler.NewFileResourceHandler(scanInfo.InputPatterns, registryAdaptors)
|
||||
}
|
||||
getter.GetArmoAPIConnector()
|
||||
rbacObjects := getRBACHandler(tenantConfig, k8s, scanInfo.Submit)
|
||||
return resourcehandler.NewK8sResourceHandler(k8s, getFieldSelector(scanInfo), hostSensorHandler, rbacObjects, registryAdaptors)
|
||||
}
|
||||
|
||||
func getHostSensorHandler(scanInfo *cautils.ScanInfo, k8s *k8sinterface.KubernetesApi) hostsensorutils.IHostSensor {
|
||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||
return &hostsensorutils.HostSensorHandlerMock{}
|
||||
}
|
||||
|
||||
hasHostSensorControls := true
|
||||
// we need to determined which controls needs host sensor
|
||||
if scanInfo.HostSensorEnabled.Get() == nil && hasHostSensorControls {
|
||||
scanInfo.HostSensorEnabled.SetBool(askUserForHostSensor())
|
||||
logger.L().Warning("Kubernetes cluster nodes scanning is disabled. This is required to collect valuable data for certain controls. You can enable it using the --enable-host-scan flag")
|
||||
}
|
||||
if hostSensorVal := scanInfo.HostSensorEnabled.Get(); hostSensorVal != nil && *hostSensorVal {
|
||||
hostSensorHandler, err := hostsensorutils.NewHostSensorHandler(k8s, scanInfo.HostSensorYamlPath)
|
||||
if err != nil {
|
||||
logger.L().Warning(fmt.Sprintf("failed to create host sensor: %s", err.Error()))
|
||||
return &hostsensorutils.HostSensorHandlerMock{}
|
||||
}
|
||||
return hostSensorHandler
|
||||
}
|
||||
return &hostsensorutils.HostSensorHandlerMock{}
|
||||
}
|
||||
func getFieldSelector(scanInfo *cautils.ScanInfo) resourcehandler.IFieldSelector {
|
||||
if scanInfo.IncludeNamespaces != "" {
|
||||
return resourcehandler.NewIncludeSelector(scanInfo.IncludeNamespaces)
|
||||
}
|
||||
if scanInfo.ExcludedNamespaces != "" {
|
||||
return resourcehandler.NewExcludeSelector(scanInfo.ExcludedNamespaces)
|
||||
}
|
||||
|
||||
return &resourcehandler.EmptySelector{}
|
||||
}
|
||||
|
||||
func policyIdentifierNames(pi []reporthandling.PolicyIdentifier) string {
|
||||
policiesNames := ""
|
||||
for i := range pi {
|
||||
policiesNames += pi[i].Name
|
||||
if i+1 < len(pi) {
|
||||
policiesNames += ","
|
||||
}
|
||||
}
|
||||
if policiesNames == "" {
|
||||
policiesNames = "all"
|
||||
}
|
||||
return policiesNames
|
||||
}
|
||||
|
||||
// setSubmitBehavior - Setup the desired cluster behavior regarding submittion to the Armo BE
|
||||
func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig) {
|
||||
|
||||
/*
|
||||
|
||||
If "First run (local config not found)" -
|
||||
Default/keep-local - Do not send report
|
||||
Submit - Create tenant & Submit report
|
||||
|
||||
If "Submitted" -
|
||||
keep-local - Do not send report
|
||||
Default/Submit - Submit report
|
||||
|
||||
*/
|
||||
|
||||
// do not submit control scanning
|
||||
if !scanInfo.FrameworkScan {
|
||||
scanInfo.Submit = false
|
||||
return
|
||||
}
|
||||
|
||||
if tenantConfig.IsConfigFound() { // config found in cache (submitted)
|
||||
if !scanInfo.Local {
|
||||
// Submit report
|
||||
scanInfo.Submit = true
|
||||
}
|
||||
} else { // config not found in cache (not submitted)
|
||||
if scanInfo.Submit {
|
||||
// submit - Create tenant & Submit report
|
||||
if err := tenantConfig.SetTenant(); err != nil {
|
||||
logger.L().Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setPolicyGetter set the policy getter - local file/github release/ArmoAPI
|
||||
func getPolicyGetter(loadPoliciesFromFile []string, accountID string, frameworkScope bool, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
|
||||
if len(loadPoliciesFromFile) > 0 {
|
||||
return getter.NewLoadPolicy(loadPoliciesFromFile)
|
||||
}
|
||||
if accountID != "" && frameworkScope {
|
||||
g := getter.GetArmoAPIConnector() // download policy from ARMO backend
|
||||
return g
|
||||
}
|
||||
if downloadReleasedPolicy == nil {
|
||||
downloadReleasedPolicy = getter.NewDownloadReleasedPolicy()
|
||||
}
|
||||
return getDownloadReleasedPolicy(downloadReleasedPolicy)
|
||||
|
||||
}
|
||||
|
||||
// func setGetArmoAPIConnector(scanInfo *cautils.ScanInfo, customerGUID string) {
|
||||
// g := getter.GetArmoAPIConnector() // download policy from ARMO backend
|
||||
// g.SetCustomerGUID(customerGUID)
|
||||
// scanInfo.PolicyGetter = g
|
||||
// if scanInfo.ScanAll {
|
||||
// frameworks, err := g.ListCustomFrameworks(customerGUID)
|
||||
// if err != nil {
|
||||
// glog.Error("failed to get custom frameworks") // handle error
|
||||
// return
|
||||
// }
|
||||
// scanInfo.SetPolicyIdentifiers(frameworks, reporthandling.KindFramework)
|
||||
// }
|
||||
// }
|
||||
|
||||
// setConfigInputsGetter sets the config input getter - local file/github release/ArmoAPI
|
||||
func getConfigInputsGetter(ControlsInputs string, accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IControlsInputsGetter {
|
||||
if len(ControlsInputs) > 0 {
|
||||
return getter.NewLoadPolicy([]string{ControlsInputs})
|
||||
}
|
||||
if accountID != "" {
|
||||
g := getter.GetArmoAPIConnector() // download config from ARMO backend
|
||||
return g
|
||||
}
|
||||
if downloadReleasedPolicy == nil {
|
||||
downloadReleasedPolicy = getter.NewDownloadReleasedPolicy()
|
||||
}
|
||||
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull config inputs, fallback to BE
|
||||
cautils.WarningDisplay(os.Stderr, "Warning: failed to get config inputs from github release, this may affect the scanning results\n")
|
||||
}
|
||||
return downloadReleasedPolicy
|
||||
}
|
||||
|
||||
func getDownloadReleasedPolicy(downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
|
||||
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull policy, fallback to cache
|
||||
logger.L().Warning("failed to get policies from github release, loading policies from cache", helpers.Error(err))
|
||||
return getter.NewLoadPolicy(getDefaultFrameworksPaths())
|
||||
} else {
|
||||
return downloadReleasedPolicy
|
||||
}
|
||||
}
|
||||
|
||||
func getDefaultFrameworksPaths() []string {
|
||||
fwPaths := []string{}
|
||||
for i := range getter.NativeFrameworks {
|
||||
fwPaths = append(fwPaths, getter.GetDefaultPath(getter.NativeFrameworks[i]))
|
||||
}
|
||||
return fwPaths
|
||||
}
|
||||
|
||||
func listFrameworksNames(policyGetter getter.IPolicyGetter) []string {
|
||||
fw, err := policyGetter.ListFrameworks()
|
||||
if err == nil {
|
||||
return fw
|
||||
}
|
||||
return getter.NativeFrameworks
|
||||
}
|
||||
45
cmd/completion/completion.go
Normal file
45
cmd/completion/completion.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package completion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var completionCmdExamples = fmt.Sprintf(`
|
||||
# Enable BASH shell autocompletion
|
||||
$ source <(%[1]s completion bash)
|
||||
$ echo 'source <(%[1]s completion bash)' >> ~/.bashrc
|
||||
|
||||
# Enable ZSH shell autocompletion
|
||||
$ source <(kubectl completion zsh)
|
||||
$ echo 'source <(kubectl completion zsh)' >> "${fpath[1]}/_kubectl"
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetCompletionCmd() *cobra.Command {
|
||||
completionCmd := &cobra.Command{
|
||||
Use: "completion [bash|zsh|fish|powershell]",
|
||||
Short: "Generate autocompletion script",
|
||||
Long: "To load completions",
|
||||
Example: completionCmdExamples,
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "bash":
|
||||
cmd.Root().GenBashCompletion(os.Stdout)
|
||||
case "zsh":
|
||||
cmd.Root().GenZshCompletion(os.Stdout)
|
||||
case "fish":
|
||||
cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
case "powershell":
|
||||
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||
}
|
||||
},
|
||||
}
|
||||
return completionCmd
|
||||
}
|
||||
51
cmd/config/config.go
Normal file
51
cmd/config/config.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
configExample = fmt.Sprintf(`
|
||||
# View cached configurations
|
||||
%[1]s config view
|
||||
|
||||
# Delete cached configurations
|
||||
%[1]s config delete
|
||||
|
||||
# Set cached configurations
|
||||
%[1]s config set --help
|
||||
`, cautils.ExecName())
|
||||
setConfigExample = fmt.Sprintf(`
|
||||
# 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>
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
func GetConfigCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
// configCmd represents the config command
|
||||
configCmd := &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Handle cached configurations",
|
||||
Example: configExample,
|
||||
}
|
||||
|
||||
configCmd.AddCommand(getDeleteCmd(ks))
|
||||
configCmd.AddCommand(getSetCmd(ks))
|
||||
configCmd.AddCommand(getViewCmd(ks))
|
||||
|
||||
return configCmd
|
||||
}
|
||||
23
cmd/config/delete.go
Normal file
23
cmd/config/delete.go
Normal file
@@ -0,0 +1,23 @@
|
||||
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/spf13/cobra"
|
||||
)
|
||||
|
||||
func getDeleteCmd(ks meta.IKubescape) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete cached configurations",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := ks.DeleteCachedConfig(context.TODO(), &v1.DeleteConfig{}); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
73
cmd/config/set.go
Normal file
73
cmd/config/set.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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/spf13/cobra"
|
||||
)
|
||||
|
||||
func getSetCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
// configCmd represents the config command
|
||||
configSetCmd := &cobra.Command{
|
||||
Use: "set",
|
||||
Short: fmt.Sprintf("Set configurations, supported: %s", strings.Join(stringKeysToSlice(supportConfigSet), "/")),
|
||||
Example: setConfigExample,
|
||||
ValidArgs: stringKeysToSlice(supportConfigSet),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
setConfig, err := parseSetArgs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ks.SetCachedConfig(setConfig); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return configSetCmd
|
||||
}
|
||||
|
||||
var supportConfigSet = map[string]func(*metav1.SetConfig, string){
|
||||
"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)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func parseSetArgs(args []string) (*metav1.SetConfig, error) {
|
||||
var key string
|
||||
var value string
|
||||
if len(args) == 1 {
|
||||
if keyValue := strings.Split(args[0], "="); len(keyValue) == 2 {
|
||||
key = keyValue[0]
|
||||
value = keyValue[1]
|
||||
}
|
||||
} else if len(args) == 2 {
|
||||
key = args[0]
|
||||
value = args[1]
|
||||
}
|
||||
setConfig := &metav1.SetConfig{}
|
||||
|
||||
if setConfigFunc, ok := supportConfigSet[key]; ok {
|
||||
setConfigFunc(setConfig, value)
|
||||
} else {
|
||||
return setConfig, fmt.Errorf("key '%s' unknown . supported: %s", key, strings.Join(stringKeysToSlice(supportConfigSet), "/"))
|
||||
}
|
||||
return setConfig, nil
|
||||
}
|
||||
25
cmd/config/view.go
Normal file
25
cmd/config/view.go
Normal file
@@ -0,0 +1,25 @@
|
||||
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/spf13/cobra"
|
||||
)
|
||||
|
||||
func getViewCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
// configCmd represents the config command
|
||||
return &cobra.Command{
|
||||
Use: "view",
|
||||
Short: "View cached configurations",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := ks.ViewCachedConfig(&v1.ViewConfig{Writer: os.Stdout}); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
37
cmd/delete/delete.go
Normal file
37
cmd/delete/delete.go
Normal file
@@ -0,0 +1,37 @@
|
||||
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
|
||||
}
|
||||
47
cmd/delete/exceptions.go
Normal file
47
cmd/delete/exceptions.go
Normal file
@@ -0,0 +1,47 @@
|
||||
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()
|
||||
}
|
||||
98
cmd/download/download.go
Normal file
98
cmd/download/download.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package download
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"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/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
downloadExample = fmt.Sprintf(`
|
||||
# Download all artifacts and save them in the default path (~/.kubescape)
|
||||
%[1]s download artifacts
|
||||
|
||||
# Download all artifacts and save them in /tmp path
|
||||
%[1]s download artifacts --output /tmp
|
||||
|
||||
# Download the NSA framework. Run '%[1]s list frameworks' for all frameworks names
|
||||
%[1]s download framework nsa
|
||||
|
||||
# Download the "C-0001" control. Run '%[1]s list controls --id' for all controls ids
|
||||
%[1]s download control "C-0001"
|
||||
|
||||
# Download the "C-0001" control. Run '%[1]s list controls --id' for all controls ids
|
||||
%[1]s download control C-0001
|
||||
|
||||
# Download the configured exceptions
|
||||
%[1]s download exceptions
|
||||
|
||||
# Download the configured controls-inputs
|
||||
%[1]s download controls-inputs
|
||||
|
||||
# Download the attack tracks
|
||||
%[1]s download attack-tracks
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var downloadInfo = v1.DownloadInfo{}
|
||||
|
||||
downloadCmd := &cobra.Command{
|
||||
Use: "download <policy> <policy name>",
|
||||
Short: fmt.Sprintf("Download %s", strings.Join(core.DownloadSupportCommands(), ",")),
|
||||
Long: ``,
|
||||
Example: downloadExample,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
supported := strings.Join(core.DownloadSupportCommands(), ",")
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("policy type required, supported: %v", supported)
|
||||
}
|
||||
if cautils.StringInSlice(core.DownloadSupportCommands(), args[0]) == cautils.ValueNotFound {
|
||||
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationDownload(&downloadInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if filepath.Ext(downloadInfo.Path) == ".json" {
|
||||
downloadInfo.Path, downloadInfo.FileName = filepath.Split(downloadInfo.Path)
|
||||
}
|
||||
downloadInfo.Target = args[0]
|
||||
if len(args) >= 2 {
|
||||
|
||||
downloadInfo.Identifier = args[1]
|
||||
|
||||
}
|
||||
if err := ks.Download(context.TODO(), &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.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
|
||||
|
||||
return downloadCmd
|
||||
}
|
||||
|
||||
// Check if the flag entered are valid
|
||||
func flagValidationDownload(downloadInfo *v1.DownloadInfo) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return downloadInfo.Credentials.Validate()
|
||||
}
|
||||
48
cmd/fix/fix.go
Normal file
48
cmd/fix/fix.go
Normal file
@@ -0,0 +1,48 @@
|
||||
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/spf13/cobra"
|
||||
)
|
||||
|
||||
var fixCmdExamples = fmt.Sprintf(`
|
||||
Fix command is for fixing kubernetes manifest files based on a scan command output.
|
||||
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
|
||||
2) %[1]s fix output.json
|
||||
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetFixCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var fixInfo metav1.FixInfo
|
||||
|
||||
fixCmd := &cobra.Command{
|
||||
Use: "fix <report output file>",
|
||||
Short: "Fix misconfiguration in files",
|
||||
Long: ``,
|
||||
Example: fixCmdExamples,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return errors.New("report output file is required")
|
||||
}
|
||||
fixInfo.ReportFile = args[0]
|
||||
|
||||
return ks.Fix(context.TODO(), &fixInfo)
|
||||
},
|
||||
}
|
||||
|
||||
fixCmd.PersistentFlags().BoolVar(&fixInfo.NoConfirm, "no-confirm", false, "No confirmation will be given to the user before applying the fix (default false)")
|
||||
fixCmd.PersistentFlags().BoolVar(&fixInfo.DryRun, "dry-run", false, "No changes will be applied (default false)")
|
||||
fixCmd.PersistentFlags().BoolVar(&fixInfo.SkipUserValues, "skip-user-values", true, "Changes which involve user-defined values will be skipped")
|
||||
|
||||
return fixCmd
|
||||
}
|
||||
79
cmd/list/list.go
Normal file
79
cmd/list/list.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
listExample = fmt.Sprintf(`
|
||||
# List default supported frameworks names
|
||||
%[1]s list frameworks
|
||||
|
||||
# List all supported frameworks names
|
||||
%[1]s list frameworks --account <account id>
|
||||
|
||||
# List all supported controls names with ids
|
||||
%[1]s list controls
|
||||
|
||||
Control documentation:
|
||||
https://hub.armosec.io/docs/controls
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var listPolicies = v1.ListPolicies{}
|
||||
|
||||
listCmd := &cobra.Command{
|
||||
Use: "list <policy> [flags]",
|
||||
Short: "List frameworks/controls will list the supported frameworks and controls",
|
||||
Long: ``,
|
||||
Example: listExample,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
supported := strings.Join(core.ListSupportActions(), ",")
|
||||
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("policy type requeued, supported: %s", supported)
|
||||
}
|
||||
if cautils.StringInSlice(core.ListSupportActions(), args[0]) == cautils.ValueNotFound {
|
||||
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationList(&listPolicies); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
listPolicies.Target = args[0]
|
||||
|
||||
if err := ks.List(context.TODO(), &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().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-print'/'json'")
|
||||
listCmd.PersistentFlags().MarkDeprecated("id", "Control ID's are included in list outputs")
|
||||
|
||||
return listCmd
|
||||
}
|
||||
|
||||
// Check if the flag entered are valid
|
||||
func flagValidationList(listPolicies *v1.ListPolicies) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return listPolicies.Credentials.Validate()
|
||||
}
|
||||
100
cmd/root.go
Normal file
100
cmd/root.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
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/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootInfo cautils.RootInfo
|
||||
|
||||
var ksExamples = fmt.Sprintf(`
|
||||
# Scan command
|
||||
%[1]s scan
|
||||
|
||||
# List supported frameworks
|
||||
%[1]s list frameworks
|
||||
|
||||
# Download artifacts (air-gapped environment support)
|
||||
%[1]s download artifacts
|
||||
|
||||
# View cached configurations
|
||||
%[1]s config view
|
||||
`, cautils.ExecName())
|
||||
|
||||
func NewDefaultKubescapeCommand() *cobra.Command {
|
||||
ks := core.NewKubescape()
|
||||
return getRootCmd(ks)
|
||||
}
|
||||
|
||||
func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "kubescape",
|
||||
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://hub.armosec.io/docs",
|
||||
Example: ksExamples,
|
||||
}
|
||||
|
||||
if cautils.IsKrewPlugin() {
|
||||
// Invoked as a kubectl plugin.
|
||||
|
||||
// Cobra doesn't have a way to specify a two word command (i.e. "kubectl kubescape"), so set a custom usage template
|
||||
// with kubectl in it. Cobra will use this template for the root and all child commands.
|
||||
oldUsageTemplate := rootCmd.UsageTemplate()
|
||||
newUsageTemplate := strings.NewReplacer("{{.UseLine}}", "kubectl {{.UseLine}}", "{{.CommandPath}}", "kubectl {{.CommandPath}}").Replace(oldUsageTemplate)
|
||||
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().MarkHidden("environment")
|
||||
rootCmd.PersistentFlags().MarkHidden("env")
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.LoggerName, "logger-name", "", fmt.Sprintf("Logger name. Supported: %s [$KS_LOGGER_NAME]", strings.Join(logger.ListLoggersNames(), "/")))
|
||||
rootCmd.PersistentFlags().MarkHidden("logger-name")
|
||||
|
||||
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)
|
||||
|
||||
// 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(config.GetConfigCmd(ks))
|
||||
rootCmd.AddCommand(update.GetUpdateCmd())
|
||||
rootCmd.AddCommand(fix.GetFixCmd(ks))
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
func Execute() error {
|
||||
ks := NewDefaultKubescapeCommand()
|
||||
return ks.Execute()
|
||||
}
|
||||
90
cmd/rootutils.go
Normal file
90
cmd/rootutils.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/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"
|
||||
} else {
|
||||
rootInfo.LoggerName = "zap"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.InitLogger(rootInfo.LoggerName)
|
||||
|
||||
}
|
||||
func initLoggerLevel() {
|
||||
if rootInfo.Logger == helpers.InfoLevel.String() {
|
||||
} else if l := os.Getenv("KS_LOGGER"); l != "" {
|
||||
rootInfo.Logger = l
|
||||
}
|
||||
|
||||
if err := logger.L().SetLevel(rootInfo.Logger); err != nil {
|
||||
logger.L().Fatal(fmt.Sprintf("supported levels: %s", strings.Join(helpers.SupportedLevels(), "/")), helpers.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func initCacheDir() {
|
||||
if rootInfo.CacheDir != getter.DefaultLocalStore {
|
||||
getter.DefaultLocalStore = rootInfo.CacheDir
|
||||
} else if cacheDir := os.Getenv("KS_CACHE_DIR"); cacheDir != "" {
|
||||
getter.DefaultLocalStore = cacheDir
|
||||
} else {
|
||||
return // using default cache dir location
|
||||
}
|
||||
|
||||
logger.L().Debug("cache dir updated", helpers.String("path", getter.DefaultLocalStore))
|
||||
}
|
||||
func initEnvironment() {
|
||||
if rootInfo.KSCloudBEURLs == "" {
|
||||
rootInfo.KSCloudBEURLs = rootInfo.KSCloudBEURLsDep
|
||||
}
|
||||
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(ksEventReceiverURL, ksBackendURL, ksFrontendURL, ksAuthURL))
|
||||
}
|
||||
}
|
||||
133
cmd/scan/control.go
Normal file
133
cmd/scan/control.go
Normal file
@@ -0,0 +1,133 @@
|
||||
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/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
|
||||
"github.com/enescakir/emoji"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
controlExample = fmt.Sprintf(`
|
||||
# Scan the 'privileged container' control
|
||||
%[1]s scan control "privileged container"
|
||||
|
||||
# Scan list of controls separated with a comma
|
||||
%[1]s scan control "privileged container","HostPath mount"
|
||||
|
||||
# Scan list of controls using the control ID separated with a comma
|
||||
%[1]s scan control C-0058,C-0057
|
||||
|
||||
Run '%[1]s list controls' for the list of supported controls
|
||||
|
||||
Control documentation:
|
||||
https://hub.armosec.io/docs/controls
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
// controlCmd represents the control command
|
||||
func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "control <control names list>/<control ids list>",
|
||||
Short: fmt.Sprintf("The controls you wish to use. Run '%[1]s list controls' for the list of supported controls", cautils.ExecName()),
|
||||
Example: controlExample,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
controls := strings.Split(args[0], ",")
|
||||
if len(controls) > 1 {
|
||||
for _, control := range controls {
|
||||
if control == "" {
|
||||
return fmt.Errorf("usage: <control-0>,<control-1>")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("requires at least one control name")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := validateFrameworkScanInfo(scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// flagValidationControl(scanInfo)
|
||||
scanInfo.PolicyIdentifier = []cautils.PolicyIdentifier{}
|
||||
|
||||
if len(args) == 0 {
|
||||
scanInfo.ScanAll = true
|
||||
} else { // expected control or list of control separated by ","
|
||||
|
||||
// Read controls from input args
|
||||
scanInfo.SetPolicyIdentifiers(strings.Split(args[0], ","), apisv1.KindControl)
|
||||
|
||||
if len(args) > 1 {
|
||||
if len(args[1:]) == 0 || args[1] != "-" {
|
||||
scanInfo.InputPatterns = args[1:]
|
||||
} else { // store stdin to file - do NOT move to separate function !!
|
||||
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
|
||||
return err
|
||||
}
|
||||
scanInfo.InputPatterns = []string{tempFile.Name()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scanInfo.FrameworkScan = false
|
||||
|
||||
if err := validateControlScanInfo(scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
results, err := ks.Scan(ctx, scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
if err := results.HandleResults(ctx); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
if !scanInfo.VerboseMode {
|
||||
cautils.SimpleDisplay(os.Stderr, "%s Run with '--verbose'/'-v' flag for detailed resources view\n\n", emoji.Detective)
|
||||
}
|
||||
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)))
|
||||
}
|
||||
enforceSeverityThresholds(results.GetResults().SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// validateControlScanInfo validates the ScanInfo struct for the `control` command
|
||||
func validateControlScanInfo(scanInfo *cautils.ScanInfo) error {
|
||||
severity := scanInfo.FailThresholdSeverity
|
||||
|
||||
if scanInfo.Submit && scanInfo.OmitRawResources {
|
||||
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
|
||||
}
|
||||
|
||||
if err := validateSeverity(severity); severity != "" && err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
215
cmd/scan/framework.go
Normal file
215
cmd/scan/framework.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
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/meta"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
frameworkExample = fmt.Sprintf(`
|
||||
# Scan all frameworks
|
||||
%[1]s scan framework all
|
||||
|
||||
# Scan the NSA framework
|
||||
%[1]s scan framework nsa
|
||||
|
||||
# Scan the NSA and MITRE framework
|
||||
%[1]s scan framework nsa,mitre
|
||||
|
||||
# Scan all frameworks
|
||||
%[1]s scan framework all
|
||||
|
||||
# Scan kubernetes YAML manifest files (single file or glob)
|
||||
%[1]s scan framework nsa .
|
||||
|
||||
Run '%[1]s list frameworks' for the list of supported frameworks
|
||||
`, cautils.ExecName())
|
||||
|
||||
ErrUnknownSeverity = errors.New("unknown severity")
|
||||
)
|
||||
|
||||
func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
|
||||
|
||||
return &cobra.Command{
|
||||
Use: "framework <framework names list> [`<glob pattern>`/`-`] [flags]",
|
||||
Short: fmt.Sprintf("The framework you wish to use. Run '%[1]s list frameworks' for the list of supported frameworks", cautils.ExecName()),
|
||||
Example: frameworkExample,
|
||||
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
frameworks := strings.Split(args[0], ",")
|
||||
if len(frameworks) > 1 {
|
||||
for _, framework := range frameworks {
|
||||
if framework == "" {
|
||||
return fmt.Errorf("usage: <framework-0>,<framework-1>")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("requires at least one framework name")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := validateFrameworkScanInfo(scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
scanInfo.FrameworkScan = true
|
||||
|
||||
var frameworks []string
|
||||
|
||||
if len(args) == 0 { // scan all frameworks
|
||||
scanInfo.ScanAll = true
|
||||
} else {
|
||||
// Read frameworks from input args
|
||||
frameworks = strings.Split(args[0], ",")
|
||||
if cautils.StringInSlice(frameworks, "all") != cautils.ValueNotFound {
|
||||
scanInfo.ScanAll = true
|
||||
frameworks = []string{}
|
||||
}
|
||||
if len(args) > 1 {
|
||||
if len(args[1:]) == 0 || args[1] != "-" {
|
||||
scanInfo.InputPatterns = args[1:]
|
||||
} else { // store stdin to file - do NOT move to separate function !!
|
||||
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
|
||||
return err
|
||||
}
|
||||
scanInfo.InputPatterns = []string{tempFile.Name()}
|
||||
}
|
||||
}
|
||||
}
|
||||
scanInfo.FrameworkScan = true
|
||||
|
||||
scanInfo.SetPolicyIdentifiers(frameworks, apisv1.KindFramework)
|
||||
|
||||
ctx := context.TODO()
|
||||
results, err := ks.Scan(ctx, scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
if err = results.HandleResults(ctx); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
if !scanInfo.VerboseMode {
|
||||
cautils.SimpleDisplay(os.Stderr, "Run with '--verbose'/'-v' flag for detailed resources view\n\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)))
|
||||
}
|
||||
|
||||
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 {
|
||||
return false, err
|
||||
}
|
||||
|
||||
getFailedResourcesFuncsBySeverity := []struct {
|
||||
SeverityName string
|
||||
GetFailedResources func() int
|
||||
}{
|
||||
{reporthandlingapis.SeverityLowString, severityCounters.NumberOfLowSeverity},
|
||||
{reporthandlingapis.SeverityMediumString, severityCounters.NumberOfMediumSeverity},
|
||||
{reporthandlingapis.SeverityHighString, severityCounters.NumberOfHighSeverity},
|
||||
{reporthandlingapis.SeverityCriticalString, severityCounters.NumberOfCriticalSeverity},
|
||||
}
|
||||
|
||||
targetSeverityIdx := 0
|
||||
for idx, description := range getFailedResourcesFuncsBySeverity {
|
||||
if strings.EqualFold(description.SeverityName, targetSeverity) {
|
||||
targetSeverityIdx = idx
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, description := range getFailedResourcesFuncsBySeverity[targetSeverityIdx:] {
|
||||
failedResourcesCount := description.GetFailedResources()
|
||||
if failedResourcesCount > 0 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
// enforceSeverityThresholds ensures that the scan results are below the defined severity threshold
|
||||
//
|
||||
// The function forces the application to terminate with an exit code 1 if at least one control failed control that exceeds the set severity threshold
|
||||
func enforceSeverityThresholds(severityCounters reportsummary.ISeverityCounters, scanInfo *cautils.ScanInfo, onExceed func(*cautils.ScanInfo, helpers.ILogger)) {
|
||||
// If a severity threshold is not set, we don’t need to enforce it
|
||||
if scanInfo.FailThresholdSeverity == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if val, err := countersExceedSeverityThreshold(severityCounters, scanInfo); val && err == nil {
|
||||
onExceed(scanInfo, logger.L())
|
||||
} else if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 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.Submit && scanInfo.Local {
|
||||
return fmt.Errorf("you can use `keep-local` or `submit`, but not both")
|
||||
}
|
||||
if 100 < scanInfo.FailThreshold || 0 > scanInfo.FailThreshold {
|
||||
return fmt.Errorf("bad argument: out of range threshold")
|
||||
}
|
||||
if scanInfo.Submit && scanInfo.OmitRawResources {
|
||||
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
|
||||
}
|
||||
severity := scanInfo.FailThresholdSeverity
|
||||
if err := validateSeverity(severity); severity != "" && err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate the user's credentials
|
||||
return scanInfo.Credentials.Validate()
|
||||
}
|
||||
113
cmd/scan/scan.go
Normal file
113
cmd/scan/scan.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"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 kubernetes YAML 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
|
||||
|
||||
# Display all resources
|
||||
%[1]s scan --verbose
|
||||
|
||||
# Scan different clusters from the kubectl context
|
||||
%[1]s scan --kube-context <kubernetes context>
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
var scanInfo cautils.ScanInfo
|
||||
|
||||
// scanCmd represents the scan command
|
||||
scanCmd := &cobra.Command{
|
||||
Use: "scan",
|
||||
Short: "Scan the current running cluster or yaml files",
|
||||
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" {
|
||||
scanInfo.ScanAll = true
|
||||
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{"all"}, args...))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if len(args) == 0 {
|
||||
scanInfo.ScanAll = true
|
||||
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, []string{"all"})
|
||||
}
|
||||
return nil
|
||||
},
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
|
||||
|
||||
},
|
||||
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")
|
||||
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().Float32VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above 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().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().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")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.FormatVersion, "format-version", "v2", "Output object can be different between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.CustomClusterName, "cluster-name", "", "Set the custom name of the cluster. Not same as the kube-context flag")
|
||||
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().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
|
||||
|
||||
// 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")
|
||||
|
||||
// Retrieve --kubeconfig flag from https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/cmd.go
|
||||
scanCmd.PersistentFlags().AddGoFlag(flag.Lookup("kubeconfig"))
|
||||
|
||||
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.AddCommand(getControlCmd(ks, &scanInfo))
|
||||
scanCmd.AddCommand(getFrameworkCmd(ks, &scanInfo))
|
||||
|
||||
return scanCmd
|
||||
}
|
||||
256
cmd/scan/scan_test.go
Normal file
256
cmd/scan/scan_test.go
Normal file
@@ -0,0 +1,256 @@
|
||||
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"
|
||||
)
|
||||
|
||||
func TestExceedsSeverity(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Description string
|
||||
ScanInfo *cautils.ScanInfo
|
||||
SeverityCounters reportsummary.ISeverityCounters
|
||||
Want bool
|
||||
Error error
|
||||
}{
|
||||
{
|
||||
Description: "Critical failed resource should exceed Critical threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "critical"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Critical failed resource should exceed Critical threshold set as constant",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: apis.SeverityCriticalString},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "High failed resource should not exceed Critical threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "critical"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
|
||||
Want: false,
|
||||
},
|
||||
{
|
||||
Description: "Critical failed resource exceeds High threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "High failed resource exceeds High threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Medium failed resource does not exceed High threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{MediumSeverityCounter: 1},
|
||||
Want: false,
|
||||
},
|
||||
{
|
||||
Description: "Critical failed resource exceeds Medium threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "High failed resource exceeds Medium threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Medium failed resource exceeds Medium threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{MediumSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Low failed resource does not exceed Medium threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
|
||||
Want: false,
|
||||
},
|
||||
{
|
||||
Description: "Critical failed resource exceeds Low threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "High failed resource exceeds Low threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Medium failed resource exceeds Low threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{MediumSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Low failed resource exceeds Low threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Unknown severity returns an error",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "unknown"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
|
||||
Want: false,
|
||||
Error: ErrUnknownSeverity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
got, err := countersExceedSeverityThreshold(testCase.SeverityCounters, testCase.ScanInfo)
|
||||
want := testCase.Want
|
||||
|
||||
if got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
|
||||
if err != testCase.Error {
|
||||
t.Errorf(`got error "%v", want "%v"`, err, testCase.Error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_enforceSeverityThresholds(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Description string
|
||||
SeverityCounters *reportsummary.SeverityCounters
|
||||
ScanInfo *cautils.ScanInfo
|
||||
Want bool
|
||||
}{
|
||||
{
|
||||
"Exceeding Critical severity counter should call the terminating function",
|
||||
&reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
|
||||
&cautils.ScanInfo{FailThresholdSeverity: apis.SeverityCriticalString},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Non-exceeding severity counter should call not the terminating function",
|
||||
&reportsummary.SeverityCounters{},
|
||||
&cautils.ScanInfo{FailThresholdSeverity: apis.SeverityCriticalString},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(
|
||||
tc.Description,
|
||||
func(t *testing.T) {
|
||||
severityCounters := tc.SeverityCounters
|
||||
scanInfo := tc.ScanInfo
|
||||
want := tc.Want
|
||||
|
||||
got := false
|
||||
onExceed := func(*cautils.ScanInfo, helpers.ILogger) {
|
||||
got = true
|
||||
}
|
||||
|
||||
enforceSeverityThresholds(severityCounters, scanInfo, onExceed)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("got: %v, want %v", got, want)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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) 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 Test_terminateOnExceedingSeverity(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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
116
cmd/scan/validators_test.go
Normal file
116
cmd/scan/validators_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
)
|
||||
|
||||
// Test_validateControlScanInfo tests how scan info is validated for the `scan control` command
|
||||
func Test_validateControlScanInfo(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Description string
|
||||
ScanInfo *cautils.ScanInfo
|
||||
Want error
|
||||
}{
|
||||
{
|
||||
"Empty severity should be valid for scan info",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: ""},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"High severity should be valid for scan info",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "High"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Unknown severity should be invalid for scan info",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
|
||||
ErrUnknownSeverity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(
|
||||
tc.Description,
|
||||
func(t *testing.T) {
|
||||
var want error = tc.Want
|
||||
|
||||
got := validateControlScanInfo(tc.ScanInfo)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Test_validateFrameworkScanInfo tests how scan info is validated for the `scan framework` command
|
||||
func Test_validateFrameworkScanInfo(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Description string
|
||||
ScanInfo *cautils.ScanInfo
|
||||
Want error
|
||||
}{
|
||||
{
|
||||
"Empty severity should be valid for scan info",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: ""},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"High severity should be valid for scan info",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "High"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Unknown severity should be invalid for scan info",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
|
||||
ErrUnknownSeverity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(
|
||||
tc.Description,
|
||||
func(t *testing.T) {
|
||||
var want error = tc.Want
|
||||
|
||||
got := validateFrameworkScanInfo(tc.ScanInfo)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validateSeverity(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Description string
|
||||
Input string
|
||||
Want error
|
||||
}{
|
||||
{"low should be a valid severity", "low", nil},
|
||||
{"Low should be a valid severity", "Low", nil},
|
||||
{"medium should be a valid severity", "medium", nil},
|
||||
{"Medium should be a valid severity", "Medium", nil},
|
||||
{"high should be a valid severity", "high", nil},
|
||||
{"Critical should be a valid severity", "Critical", nil},
|
||||
{"critical should be a valid severity", "critical", nil},
|
||||
{"Unknown should be an invalid severity", "Unknown", ErrUnknownSeverity},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
input := testCase.Input
|
||||
want := testCase.Want
|
||||
got := validateSeverity(input)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
35
cmd/submit/exceptions.go
Normal file
35
cmd/submit/exceptions.go
Normal file
@@ -0,0 +1,35 @@
|
||||
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())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user