mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
Compare commits
838 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56183ba369 | ||
|
|
a9c1ecd3b8 | ||
|
|
d900ce6146 | ||
|
|
3a80ff00b6 | ||
|
|
b989c4c21f | ||
|
|
915fa919b2 | ||
|
|
8102dd93ba | ||
|
|
35cafa9eb4 | ||
|
|
cc823d7559 | ||
|
|
eaa74487c2 | ||
|
|
e8a4c2033f | ||
|
|
8fd9258efa | ||
|
|
159d3907b5 | ||
|
|
cde916bec8 | ||
|
|
8d289bd924 | ||
|
|
fda1c83d01 | ||
|
|
31b6a3c571 | ||
|
|
31a693e9b6 | ||
|
|
5de228ce0f | ||
|
|
ed27641f04 | ||
|
|
df39e10300 | ||
|
|
c7d1292c7d | ||
|
|
a52f13b8c9 | ||
|
|
16e34002f5 | ||
|
|
3242de8a28 | ||
|
|
ca2730cd85 | ||
|
|
88b55cd6c3 | ||
|
|
46ca5036c4 | ||
|
|
d8f1a25ab7 | ||
|
|
56cfb4fcef | ||
|
|
894d436274 | ||
|
|
39166d40bf | ||
|
|
2ba3f78bfc | ||
|
|
1d68d1ba67 | ||
|
|
6cc5116999 | ||
|
|
7706c1264c | ||
|
|
2f299b6201 | ||
|
|
f1af9d5687 | ||
|
|
d3abd66aa3 | ||
|
|
2a0a2cf95a | ||
|
|
e90f08968f | ||
|
|
e6b7086961 | ||
|
|
4ea35eec00 | ||
|
|
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 | ||
|
|
2fb2ab02c4 | ||
|
|
f7445d1777 | ||
|
|
2ce6c1840b | ||
|
|
54020d317e | ||
|
|
91d1ec6c2f | ||
|
|
1d3fd0dc9d | ||
|
|
8a7511cecb | ||
|
|
0af0c01ec0 | ||
|
|
4f1971a63d | ||
|
|
afb6ea1d9c | ||
|
|
b3d16875d6 | ||
|
|
cf086e6614 | ||
|
|
a27d2d41f2 |
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">
|
||||
181
.github/workflows/a-pr-scanner.yaml
vendored
Normal file
181
.github/workflows/a-pr-scanner.yaml
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
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 pkg-config (macOS)
|
||||
run: brew install pkg-config
|
||||
if: matrix.os == 'macos-latest'
|
||||
|
||||
- name: Install libgit2 (Linux/macOS)
|
||||
run: make libgit2
|
||||
if: matrix.os != 'windows-latest'
|
||||
|
||||
- name: Test core pkg
|
||||
run: go test "-tags=static,gitenabled" -v ./...
|
||||
|
||||
- name: Test httphandler pkg
|
||||
run: cd httphandler && go test "-tags=static,gitenabled" -v ./...
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
CLIENT: ${{ inputs.CLIENT }}
|
||||
CGO_ENABLED: 1
|
||||
run: python3 --version && python3 build.py
|
||||
|
||||
- name: Smoke Testing (Windows / MacOS)
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/kubescape-${{ matrix.os }}
|
||||
if: matrix.os != 'ubuntu-20.04'
|
||||
|
||||
- name: Smoke Testing (Linux)
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/kubescape-ubuntu-latest
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
|
||||
- name: golangci-lint
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
continue-on-error: true
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout 10m --build-tags=static
|
||||
only-new-issues: true
|
||||
279
.github/workflows/b-binary-build-and-e2e-tests.yaml
vendored
Normal file
279
.github/workflows/b-binary-build-and-e2e-tests.yaml
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
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 pkg-config (macOS)
|
||||
run: brew install pkg-config
|
||||
if: matrix.os == 'macos-latest'
|
||||
|
||||
- name: Install libgit2 (Linux/macOS)
|
||||
run: make libgit2
|
||||
if: matrix.os != 'windows-latest'
|
||||
|
||||
- name: Test core pkg
|
||||
run: go test "-tags=static,gitenabled" -v ./...
|
||||
|
||||
- name: Test httphandler pkg
|
||||
run: cd httphandler && go test "-tags=static,gitenabled" -v ./...
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
CLIENT: ${{ inputs.CLIENT }}
|
||||
CGO_ENABLED: ${{ 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}}
|
||||
|
||||
|
||||
138
.github/workflows/build.yaml
vendored
138
.github/workflows/build.yaml
vendored
@@ -1,138 +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@v3
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
# - name: Test cmd pkg
|
||||
# run: cd cmd && go test -v ./...
|
||||
- name: Test core pkg
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Test httphandler pkg
|
||||
run: cd httphandler && go test -v ./...
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
CLIENT: release
|
||||
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: 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 the Docker image
|
||||
run: docker buildx build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:latest --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg client=image-release --push --platform linux/amd64,linux/arm64
|
||||
|
||||
# - name: Login to GitHub Container Registry
|
||||
# uses: docker/login-action@v1
|
||||
# with:
|
||||
# registry: ghcr.io
|
||||
# username: ${{ github.actor }}
|
||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# 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 }}
|
||||
|
||||
96
.github/workflows/build_dev.yaml
vendored
96
.github/workflows/build_dev.yaml
vendored
@@ -1,96 +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@v3
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
|
||||
# - name: Test cmd pkg
|
||||
# run: cd cmd && go test -v ./...
|
||||
|
||||
# - name: Test core pkg
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# run: cd core && go test -v ./...
|
||||
|
||||
# - name: Test cmd pkg
|
||||
# run: cd cmd && go test -v ./...
|
||||
|
||||
- name: Test core pkg
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Test httphandler pkg
|
||||
run: cd httphandler && go test -v ./...
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
CLIENT: release-dev
|
||||
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: 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 the Docker image
|
||||
run: docker buildx 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 }} --build-arg client=image-dev --push --platform linux/amd64,linux/arm64
|
||||
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 }}
|
||||
|
||||
48
.github/workflows/master_pr_checks.yaml
vendored
48
.github/workflows/master_pr_checks.yaml
vendored
@@ -1,48 +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@v3
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
|
||||
# - name: Test cmd pkg
|
||||
# run: cd cmd && go test -v ./...
|
||||
|
||||
- name: Test core pkg
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Test httphandler pkg
|
||||
run: cd httphandler && go test -v ./...
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
CLIENT: test
|
||||
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,8 +1,10 @@
|
||||
*.vs*
|
||||
*kubescape*
|
||||
*debug*
|
||||
*vender*
|
||||
*vendor*
|
||||
*.pyc*
|
||||
.idea
|
||||
.history
|
||||
ca.srl
|
||||
ca.srl
|
||||
*.out
|
||||
ks
|
||||
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
|
||||
30
.krew.yaml
Normal file
30
.krew.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
apiVersion: krew.googlecontainertools.github.com/v1alpha2
|
||||
kind: Plugin
|
||||
metadata:
|
||||
name: kubescape
|
||||
spec:
|
||||
homepage: https://github.com/kubescape/kubescape/
|
||||
shortDescription: Scan resources and cluster configs against security frameworks.
|
||||
version: {{ .TagName }}
|
||||
description: |
|
||||
It includes risk analysis, security compliance, and misconfiguration scanning
|
||||
with an easy-to-use CLI interface, flexible output formats, and automated scanning capabilities.
|
||||
platforms:
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-macos-latest.tar.gz" .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: amd64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-ubuntu-latest.tar.gz" .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: amd64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-windows-latest.tar.gz" .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).
|
||||
|
||||
122
CONTRIBUTING.md
122
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,74 @@ 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
|
||||
### Use semantic commit messages (optional)
|
||||
|
||||
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.
|
||||
When contributing, you could consider using [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/), in order to improve logs readability and help us to automatically generate `CHANGELOG`s.
|
||||
|
||||
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.
|
||||
Format: `<type>(<scope>): <subject>`
|
||||
|
||||
### Attribution
|
||||
`<scope>` is optional
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
#### Example
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
```
|
||||
feat(cmd): add kubectl plugin
|
||||
^--^ ^-^ ^----------------^
|
||||
| | |
|
||||
| | +-> subject: summary in present tense.
|
||||
| |
|
||||
| +-------> scope: point of interest
|
||||
|
|
||||
+-------> type: chore, docs, feat, fix, refactor, style, or test.
|
||||
```
|
||||
|
||||
More Examples:
|
||||
* `feat`: new feature for the user, not a new feature for build script
|
||||
* `fix`: bug fix for the user, not a fix to a build script
|
||||
* `docs`: changes to the documentation
|
||||
* `style`: formatting, missing semi colons, etc; no production code change
|
||||
* `refactor`: refactoring production code, eg. renaming a variable
|
||||
* `test`: adding missing tests, refactoring tests; no production code change
|
||||
* `chore`: updating grunt tasks etc; no production code change
|
||||
|
||||
## Fixing a commit where the DCO failed
|
||||
|
||||
Check out [this guide](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md).
|
||||
|
||||
@@ -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) ./...
|
||||
361
README.md
361
README.md
@@ -1,333 +1,94 @@
|
||||
<img src="docs/kubescape.png" width="300" alt="logo" align="center">
|
||||
[](https://github.com/kubescape/kubescape/releases)
|
||||
[](https://github.com/kubescape/kubescape/actions/workflows/02-release.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)).
|
||||
|
||||
<!-- # Kubescape Coverage
|
||||
<img src="docs/ksfromcodetodeploy.png">
|
||||
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/).
|
||||
|
||||
</br> -->
|
||||
## 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! 😀_
|
||||
|
||||
# Kubescape CLI:
|
||||
<img src="docs/demo.gif">
|
||||
## Getting started
|
||||
|
||||
</br>
|
||||
Experimenting with Kubescape is as easy as:
|
||||
|
||||
<!-- # Kubescape overview:
|
||||
<img src="docs/ARMO-header-2022.gif"> -->
|
||||
|
||||
# TL;DR
|
||||
## Install:
|
||||
```sh
|
||||
curl -s https://raw.githubusercontent.com/armosec/kubescape/master/install.sh | /bin/bash
|
||||
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)
|
||||
|
||||
[Install on NixOS or Linux/macOS via nix](#install-on-nixos-or-with-nix-community)
|
||||
_Did you know you can use Kubescape in all these places?_
|
||||
|
||||
[Install using Go](#install-using-go)
|
||||
<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>
|
||||
|
||||
## Run:
|
||||
```sh
|
||||
kubescape scan --submit --enable-host-scan --verbose
|
||||
```
|
||||
## Under the hood
|
||||
|
||||
<img src="docs/summary.png">
|
||||
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:
|
||||
|
||||
> 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.
|
||||
* 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).
|
||||
|
||||
### Click [👍](https://github.com/armosec/kubescape/stargazers) if you want us to continue to develop and improve Kubescape 😀
|
||||
## Community
|
||||
|
||||
</br>
|
||||
Kubescape is an open source project, we welcome your feedback and ideas for improvement. We are part of the Kubernetes community and are building more tests and controls as the ecosystem develops.
|
||||
|
||||
We hold [community meetings](https://us02web.zoom.us/j/84020231442) on Zoom, on the first Tuesday of every month, at 14:00 GMT.
|
||||
|
||||
# Being part of the team
|
||||
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
|
||||
We invite you to our team! We are excited about this project and want to return the love we get.
|
||||
## Contributions
|
||||
|
||||
Want to contribute? Want to discuss something? Have an issue?
|
||||
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 [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!
|
||||
* 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.
|
||||
|
||||
[<img src="docs/discord-banner.png" width="100" alt="logo" align="center">](https://armosec.github.io/kubescape/)
|
||||

|
||||
<br>
|
||||
|
||||
|
||||
# Options and examples
|
||||
|
||||
[Kubescape docs](https://hub.armosec.io/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. ```sh
|
||||
brew tap armosec/kubescape
|
||||
```
|
||||
2. ```sh
|
||||
brew install kubescape
|
||||
```
|
||||
|
||||
## Install on NixOS or with nix (Community)
|
||||
|
||||
Direct issues installing `kubescape` via `nix` through the channels mentioned [here](https://nixos.wiki/wiki/Support)
|
||||
|
||||
You can use `nix` on Linux or macOS and on other platforms unofficially.
|
||||
|
||||
Try it out in an ephemeral shell: `nix-shell -p kubescape`
|
||||
|
||||
Install declarative as usual
|
||||
|
||||
NixOS:
|
||||
|
||||
```nix
|
||||
# your other config ...
|
||||
environment.systemPackages = with pkgs; [
|
||||
# your other packages ...
|
||||
kubescape
|
||||
];
|
||||
```
|
||||
|
||||
home-manager:
|
||||
|
||||
```nix
|
||||
# your other config ...
|
||||
home.packages = with pkgs; [
|
||||
# your other packages ...
|
||||
kubescape
|
||||
];
|
||||
```
|
||||
|
||||
Or to your profile (not preferred): `nix-env --install -A nixpkgs.kubescape`
|
||||
|
||||
## Install using Go
|
||||
|
||||
With a sufficient version of `go` you can install and build with `go install github.com/armosec/kubescape/v2@latest`
|
||||
|
||||
## 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 --verbose
|
||||
```
|
||||
|
||||
> Read [here](https://hub.armosec.io/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.armosec.io/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) Submit the results in case the directory is a git repo. [docs](https://hub.armosec.io/docs/repository-scanning)
|
||||
```
|
||||
kubescape scan *.yaml --submit
|
||||
```
|
||||
|
||||
#### Scan kubernetes manifest files from a git repository [and submit the results](https://hub.armosec.io/docs/repository-scanning)
|
||||
```
|
||||
kubescape scan https://github.com/armosec/kubescape --submit
|
||||
```
|
||||
|
||||
#### 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
|
||||
```
|
||||
kubescape scan </path/to/directory> --submit
|
||||
```
|
||||
> Kubescape will load the default values file
|
||||
|
||||
### 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.armosec.io/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
|
||||
```
|
||||
|
||||
|
||||
# Integrations
|
||||
|
||||
## VS Code Extension
|
||||
|
||||
 
|
||||
|
||||
Scan the YAML files while writing them using the [vs code extension](https://github.com/armosec/vscode-kubescape/blob/master/README.md)
|
||||
|
||||
## Lens Extension
|
||||
|
||||
View Kubescape scan results directly in [Lens IDE](https://k8slens.dev/) using kubescape [Lens extension](https://github.com/armosec/lens-kubescape/blob/master/README.md)
|
||||
|
||||
# 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
|
||||
62
build.py
62
build.py
@@ -3,12 +3,15 @@ import sys
|
||||
import hashlib
|
||||
import platform
|
||||
import subprocess
|
||||
import tarfile
|
||||
|
||||
BASE_GETTER_CONST = "github.com/armosec/kubescape/v2/core/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"
|
||||
|
||||
platformSuffixes = {
|
||||
"Windows": "windows-latest",
|
||||
"Linux": "ubuntu-latest",
|
||||
"Darwin": "macos-latest",
|
||||
}
|
||||
|
||||
def check_status(status, msg):
|
||||
if status != 0:
|
||||
@@ -17,21 +20,15 @@ def check_status(status, msg):
|
||||
|
||||
|
||||
def get_build_dir():
|
||||
current_platform = platform.system()
|
||||
build_dir = "./build/"
|
||||
return "build"
|
||||
|
||||
if current_platform == "Windows": build_dir += "windows-latest"
|
||||
elif current_platform == "Linux": build_dir += "ubuntu-latest"
|
||||
elif current_platform == "Darwin": build_dir += "macos-latest"
|
||||
else: raise OSError("Platform %s is not supported!" % (current_platform))
|
||||
|
||||
return build_dir
|
||||
|
||||
def get_package_name():
|
||||
package_name = "kubescape"
|
||||
# if platform.system() == "Windows": package_name += ".exe"
|
||||
current_platform = platform.system()
|
||||
|
||||
return package_name
|
||||
if current_platform not in platformSuffixes: raise OSError("Platform %s is not supported!" % (current_platform))
|
||||
|
||||
return "kubescape-" + platformSuffixes[current_platform]
|
||||
|
||||
|
||||
def main():
|
||||
@@ -39,21 +36,18 @@ def main():
|
||||
|
||||
# Set some variables
|
||||
package_name = get_package_name()
|
||||
build_url = "github.com/armosec/kubescape/v2/core/cautils.BuildNumber"
|
||||
build_url = "github.com/kubescape/kubescape/v2/core/cautils.BuildNumber"
|
||||
release_version = os.getenv("RELEASE")
|
||||
armo_be_server = os.getenv("ArmoBEServer")
|
||||
armo_er_server = os.getenv("ArmoERServer")
|
||||
armo_website = os.getenv("ArmoWebsite")
|
||||
armo_auth_server = os.getenv("ArmoAuthServer")
|
||||
|
||||
client_var = "github.com/armosec/kubescape/v2/core/cautils.Client"
|
||||
client_var = "github.com/kubescape/kubescape/v2/core/cautils.Client"
|
||||
client_name = os.getenv("CLIENT")
|
||||
|
||||
|
||||
# Create build directory
|
||||
build_dir = get_build_dir()
|
||||
|
||||
ks_file = os.path.join(build_dir, package_name)
|
||||
hash_file = ks_file + ".sha256"
|
||||
tar_file = ks_file + ".tar.gz"
|
||||
|
||||
if not os.path.isdir(build_dir):
|
||||
os.makedirs(build_dir)
|
||||
@@ -64,23 +58,15 @@ def main():
|
||||
ldflags += " -X {}={}".format(build_url, release_version)
|
||||
if client_name:
|
||||
ldflags += " -X {}={}".format(client_var, client_name)
|
||||
if armo_be_server:
|
||||
ldflags += " -X {}={}".format(BE_SERVER_CONST, armo_be_server)
|
||||
if armo_er_server:
|
||||
ldflags += " -X {}={}".format(ER_SERVER_CONST, armo_er_server)
|
||||
if armo_website:
|
||||
ldflags += " -X {}={}".format(WEBSITE_CONST, armo_website)
|
||||
if armo_auth_server:
|
||||
ldflags += " -X {}={}".format(AUTH_SERVER_CONST, armo_auth_server)
|
||||
|
||||
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)
|
||||
check_status(status, "Failed to build kubescape")
|
||||
|
||||
|
||||
sha256 = hashlib.sha256()
|
||||
with open(ks_file, "rb") as kube:
|
||||
sha256.update(kube.read())
|
||||
@@ -89,8 +75,12 @@ 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")
|
||||
archive.add("LICENSE", "LICENSE")
|
||||
|
||||
print("Build Done")
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.18-alpine as builder
|
||||
FROM golang:1.19-alpine as builder
|
||||
|
||||
ARG image_version
|
||||
ARG client
|
||||
@@ -8,41 +8,44 @@ 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/ubuntu-latest
|
||||
RUN ls -ltr build/
|
||||
|
||||
# build kubescape cmd
|
||||
WORKDIR /work
|
||||
RUN python build.py
|
||||
|
||||
RUN /work/build/ubuntu-latest/kubescape download artifacts -o /work/artifacts
|
||||
RUN /work/build/kubescape-ubuntu-latest download artifacts -o /work/artifacts
|
||||
|
||||
FROM alpine
|
||||
FROM alpine:3.16.2
|
||||
|
||||
RUN addgroup -S armo && adduser -S armo -G armo
|
||||
RUN addgroup -S ks && adduser -S ks -G ks
|
||||
|
||||
RUN mkdir /home/armo/.kubescape
|
||||
COPY --from=builder /work/artifacts/ /home/armo/.kubescape
|
||||
COPY --from=builder /work/artifacts/ /home/ks/.kubescape
|
||||
|
||||
RUN chown -R armo:armo /home/armo/.kubescape
|
||||
RUN chown -R ks:ks /home/ks/.kubescape
|
||||
|
||||
USER armo
|
||||
WORKDIR /home/armo
|
||||
USER ks
|
||||
|
||||
COPY --from=builder /work/httphandler/build/ubuntu-latest/kubescape /usr/bin/ksserver
|
||||
COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kubescape
|
||||
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,23 +1,23 @@
|
||||
package completion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var completionCmdExamples = `
|
||||
var completionCmdExamples = fmt.Sprintf(`
|
||||
# Enable BASH shell autocompletion
|
||||
$ source <(%[1]s completion bash)
|
||||
$ echo 'source <(%[1]s completion bash)' >> ~/.bashrc
|
||||
|
||||
# Enable BASH shell autocompletion
|
||||
$ source <(kubescape completion bash)
|
||||
$ echo 'source <(kubescape completion bash)' >> ~/.bashrc
|
||||
|
||||
# Enable ZSH shell autocompletion
|
||||
# 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{
|
||||
@@ -27,7 +27,7 @@ func GetCompletionCmd() *cobra.Command {
|
||||
Example: completionCmdExamples,
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||
Args: cobra.ExactValidArgs(1),
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "bash":
|
||||
@@ -43,7 +43,3 @@ func GetCompletionCmd() *cobra.Command {
|
||||
}
|
||||
return completionCmd
|
||||
}
|
||||
|
||||
// func init() {
|
||||
// rootCmd.AddCommand(completionCmd)
|
||||
// }
|
||||
|
||||
@@ -1,31 +1,37 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
configExample = `
|
||||
configExample = fmt.Sprintf(`
|
||||
# View cached configurations
|
||||
kubescape config view
|
||||
%[1]s config view
|
||||
|
||||
# Delete cached configurations
|
||||
kubescape config delete
|
||||
%[1]s config delete
|
||||
|
||||
# Set cached configurations
|
||||
kubescape config set --help
|
||||
`
|
||||
setConfigExample = `
|
||||
%[1]s config set --help
|
||||
`, cautils.ExecName())
|
||||
setConfigExample = fmt.Sprintf(`
|
||||
# Set account id
|
||||
kubescape config set accountID <account id>
|
||||
%[1]s config set accountID <account id>
|
||||
|
||||
# Set client id
|
||||
kubescape config set clientID <client id>
|
||||
%[1]s config set clientID <client id>
|
||||
|
||||
# Set access key
|
||||
kubescape config set secretKey <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 {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -13,7 +15,7 @@ func getDeleteCmd(ks meta.IKubescape) *cobra.Command {
|
||||
Short: "Delete cached configurations",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := ks.DeleteCachedConfig(&v1.DeleteConfig{}); err != nil {
|
||||
if err := ks.DeleteCachedConfig(context.TODO(), &v1.DeleteConfig{}); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
metav1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -33,9 +33,13 @@ func getSetCmd(ks meta.IKubescape) *cobra.Command {
|
||||
}
|
||||
|
||||
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 },
|
||||
"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 {
|
||||
|
||||
@@ -3,9 +3,9 @@ package config
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
|
||||
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"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
package delete
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
|
||||
"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 = `
|
||||
var deleteExceptionsExamples = fmt.Sprintf(`
|
||||
# Delete single exception
|
||||
kubescape delete exceptions "exception name"
|
||||
%[1]s delete exceptions "exception name"
|
||||
|
||||
# Delete multiple exceptions
|
||||
kubescape delete exceptions "first exception;second exception;third exception"
|
||||
`
|
||||
%[1]s delete exceptions "first exception;second exception;third exception"
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetDeleteCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var deleteInfo v1.Delete
|
||||
|
||||
@@ -4,16 +4,17 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
|
||||
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: "Delete exceptions from Kubescape SaaS version. Run 'kubescape list exceptions' for all exceptions names",
|
||||
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 {
|
||||
@@ -22,6 +23,11 @@ func getExceptionsCmd(ks meta.IKubescape, deleteInfo *v1.Delete) *cobra.Command
|
||||
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")
|
||||
@@ -32,3 +38,10 @@ func getExceptionsCmd(ks meta.IKubescape, deleteInfo *v1.Delete) *cobra.Command
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the flag entered are valid
|
||||
func flagValidationDelete(deleteInfo *v1.Delete) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return deleteInfo.Credentials.Validate()
|
||||
}
|
||||
|
||||
@@ -1,45 +1,48 @@
|
||||
package download
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/core"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
|
||||
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 = `
|
||||
downloadExample = fmt.Sprintf(`
|
||||
# Download all artifacts and save them in the default path (~/.kubescape)
|
||||
kubescape download artifacts
|
||||
download
|
||||
# Download all artifacts and save them in /tmp path
|
||||
kubescape download artifacts --output /tmp
|
||||
%[1]s download artifacts
|
||||
|
||||
# Download the NSA framework. Run 'kubescape list frameworks' for all frameworks names
|
||||
kubescape download framework nsa
|
||||
# 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 "Allowed hostPath" control. Run 'kubescape list controls' for all controls names
|
||||
kubescape download control "Allowed hostPath"
|
||||
# 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 'kubescape list controls --id' for all controls ids
|
||||
kubescape 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
|
||||
kubescape download exceptions
|
||||
%[1]s download exceptions
|
||||
|
||||
# Download the configured controls-inputs
|
||||
kubescape download controls-inputs
|
||||
%[1]s download controls-inputs
|
||||
|
||||
`
|
||||
# Download the attack tracks
|
||||
%[1]s download attack-tracks
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
func GeDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var downloadInfo = v1.DownloadInfo{}
|
||||
|
||||
downloadCmd := &cobra.Command{
|
||||
@@ -59,14 +62,20 @@ func GeDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
},
|
||||
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.Name = args[1]
|
||||
|
||||
downloadInfo.Identifier = args[1]
|
||||
|
||||
}
|
||||
if err := ks.Download(&downloadInfo); err != nil {
|
||||
if err := ks.Download(context.TODO(), &downloadInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
@@ -80,3 +89,10 @@ func GeDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,34 +1,32 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/core"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
|
||||
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 = `
|
||||
listExample = fmt.Sprintf(`
|
||||
# List default supported frameworks names
|
||||
kubescape list frameworks
|
||||
%[1]s list frameworks
|
||||
|
||||
# List all supported frameworks names
|
||||
kubescape list frameworks --account <account id>
|
||||
%[1]s list frameworks --account <account id>
|
||||
|
||||
# List all supported controls names
|
||||
kubescape list controls
|
||||
|
||||
# List all supported controls ids
|
||||
kubescape list controls --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 {
|
||||
@@ -51,9 +49,14 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
||||
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(&listPolicies); err != nil {
|
||||
if err := ks.List(context.TODO(), &listPolicies); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
@@ -62,8 +65,15 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
||||
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-printer'/'json'")
|
||||
listCmd.PersistentFlags().BoolVarP(&listPolicies.ListIDs, "id", "", false, "List control ID's instead of controls names")
|
||||
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()
|
||||
}
|
||||
|
||||
62
cmd/root.go
62
cmd/root.go
@@ -4,39 +4,41 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/v2/cmd/completion"
|
||||
"github.com/armosec/kubescape/v2/cmd/config"
|
||||
"github.com/armosec/kubescape/v2/cmd/delete"
|
||||
"github.com/armosec/kubescape/v2/cmd/download"
|
||||
"github.com/armosec/kubescape/v2/cmd/list"
|
||||
"github.com/armosec/kubescape/v2/cmd/scan"
|
||||
"github.com/armosec/kubescape/v2/cmd/submit"
|
||||
"github.com/armosec/kubescape/v2/cmd/version"
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/getter"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/v2/core/core"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
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 = `
|
||||
var ksExamples = fmt.Sprintf(`
|
||||
# Scan command
|
||||
kubescape scan --submit
|
||||
%[1]s scan
|
||||
|
||||
# List supported frameworks
|
||||
kubescape list frameworks
|
||||
%[1]s list frameworks
|
||||
|
||||
# Download artifacts (air-gapped environment support)
|
||||
kubescape download artifacts
|
||||
%[1]s download artifacts
|
||||
|
||||
# View cached configurations
|
||||
kubescape config view
|
||||
`
|
||||
%[1]s config view
|
||||
`, cautils.ExecName())
|
||||
|
||||
func NewDefaultKubescapeCommand() *cobra.Command {
|
||||
ks := core.NewKubescape()
|
||||
@@ -47,13 +49,22 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "kubescape",
|
||||
Version: cautils.BuildNumber,
|
||||
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://hub.armosec.io/docs",
|
||||
Example: ksExamples,
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.ArmoBEURLsDep, "environment", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.ArmoBEURLs, "env", "", envFlagUsage)
|
||||
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")
|
||||
@@ -64,18 +75,21 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
rootCmd.PersistentFlags().StringVarP(&rootInfo.Logger, "logger", "l", helpers.InfoLevel.String(), fmt.Sprintf("Logger level. Supported: %s [$KS_LOGGER]", strings.Join(helpers.SupportedLevels(), "/")))
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.CacheDir, "cache-dir", getter.DefaultLocalStore, "Cache directory [$KS_CACHE_DIR]")
|
||||
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable Color output for logging")
|
||||
rootCmd.PersistentFlags().BoolVarP(&rootInfo.EnableColor, "enable-color", "", false, "Force enable Color output for logging")
|
||||
|
||||
cobra.OnInitialize(initLogger, initLoggerLevel, initEnvironment, initCacheDir)
|
||||
|
||||
// Supported commands
|
||||
rootCmd.AddCommand(scan.GetScanCommand(ks))
|
||||
rootCmd.AddCommand(download.GeDownloadCmd(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
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils/getter"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
|
||||
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"
|
||||
)
|
||||
@@ -16,6 +16,7 @@ const envFlagUsage = "Send report results to specific URL. Format:<ReportReceive
|
||||
|
||||
func initLogger() {
|
||||
logger.DisableColor(rootInfo.DisableColor)
|
||||
logger.EnableColor(rootInfo.EnableColor)
|
||||
|
||||
if rootInfo.LoggerName == "" {
|
||||
if l := os.Getenv("KS_LOGGER_NAME"); l != "" {
|
||||
@@ -55,10 +56,10 @@ func initCacheDir() {
|
||||
logger.L().Debug("cache dir updated", helpers.String("path", getter.DefaultLocalStore))
|
||||
}
|
||||
func initEnvironment() {
|
||||
if rootInfo.ArmoBEURLs == "" {
|
||||
rootInfo.ArmoBEURLs = rootInfo.ArmoBEURLsDep
|
||||
if rootInfo.KSCloudBEURLs == "" {
|
||||
rootInfo.KSCloudBEURLs = rootInfo.KSCloudBEURLsDep
|
||||
}
|
||||
urlSlices := strings.Split(rootInfo.ArmoBEURLs, ",")
|
||||
urlSlices := strings.Split(rootInfo.KSCloudBEURLs, ",")
|
||||
if len(urlSlices) != 1 && len(urlSlices) < 3 {
|
||||
logger.L().Fatal("expected at least 3 URLs (report, api, frontend, auth)")
|
||||
}
|
||||
@@ -66,24 +67,24 @@ func initEnvironment() {
|
||||
case 1:
|
||||
switch urlSlices[0] {
|
||||
case "dev", "development":
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPIDev())
|
||||
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIDev())
|
||||
case "stage", "staging":
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPIStaging())
|
||||
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIStaging())
|
||||
case "":
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPIProd())
|
||||
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 armoAUTHURL string
|
||||
armoERURL := urlSlices[0] // mandatory
|
||||
armoBEURL := urlSlices[1] // mandatory
|
||||
armoFEURL := urlSlices[2] // mandatory
|
||||
var ksAuthURL string
|
||||
ksEventReceiverURL := urlSlices[0] // mandatory
|
||||
ksBackendURL := urlSlices[1] // mandatory
|
||||
ksFrontendURL := urlSlices[2] // mandatory
|
||||
if len(urlSlices) >= 4 {
|
||||
armoAUTHURL = urlSlices[3]
|
||||
ksAuthURL = urlSlices[3]
|
||||
}
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPICustomized(armoERURL, armoBEURL, armoFEURL, armoAUTHURL))
|
||||
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPICustomized(ksEventReceiverURL, ksBackendURL, ksFrontendURL, ksAuthURL))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,46 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
apisv1 "github.com/armosec/opa-utils/httpserver/apis/v1"
|
||||
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/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
"github.com/enescakir/emoji"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
controlExample = `
|
||||
controlExample = fmt.Sprintf(`
|
||||
# Scan the 'privileged container' control
|
||||
kubescape scan control "privileged container"
|
||||
%[1]s scan control "privileged container"
|
||||
|
||||
# Scan list of controls separated with a comma
|
||||
kubescape scan control "privileged container","allowed hostpath"
|
||||
%[1]s scan control "privileged container","HostPath mount"
|
||||
|
||||
# Scan list of controls using the control ID separated with a comma
|
||||
kubescape scan control C-0058,C-0057
|
||||
%[1]s scan control C-0058,C-0057
|
||||
|
||||
Run 'kubescape list controls' for the list of supported controls
|
||||
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: "The controls you wish to use. Run 'kubescape list controls' for the list of supported controls",
|
||||
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 {
|
||||
@@ -57,19 +59,23 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
||||
},
|
||||
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 sepparated by ","
|
||||
} 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 = []string{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 {
|
||||
@@ -87,11 +93,16 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
||||
|
||||
scanInfo.FrameworkScan = false
|
||||
|
||||
results, err := ks.Scan(scanInfo)
|
||||
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(); err != nil {
|
||||
if err := results.HandleResults(ctx); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
if !scanInfo.VerboseMode {
|
||||
@@ -100,7 +111,23 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
||||
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
|
||||
logger.L().Fatal("scan risk-score is above permitted threshold", helpers.String("risk-score", fmt.Sprintf("%.2f", results.GetRiskScore())), helpers.String("fail-threshold", fmt.Sprintf("%.2f", scanInfo.FailThreshold)))
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,47 +1,54 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
apisv1 "github.com/armosec/opa-utils/httpserver/apis/v1"
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
reporthandlingapis "github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
"github.com/enescakir/emoji"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
frameworkExample = `
|
||||
# Scan all frameworks and submit the results
|
||||
kubescape scan framework all --submit
|
||||
frameworkExample = fmt.Sprintf(`
|
||||
# Scan all frameworks
|
||||
%[1]s scan framework all
|
||||
|
||||
# Scan the NSA framework
|
||||
kubescape scan framework nsa
|
||||
%[1]s scan framework nsa
|
||||
|
||||
# Scan the NSA and MITRE framework
|
||||
kubescape scan framework nsa,mitre
|
||||
%[1]s scan framework nsa,mitre
|
||||
|
||||
# Scan all frameworks
|
||||
kubescape scan framework all
|
||||
%[1]s scan framework all
|
||||
|
||||
# Scan kubernetes YAML manifest files (single file or glob)
|
||||
kubescape scan framework nsa *.yaml
|
||||
%[1]s scan framework nsa .
|
||||
|
||||
Run 'kubescape list frameworks' for the list of supported frameworks
|
||||
`
|
||||
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: "The framework you wish to use. Run 'kubescape list frameworks' for the list of supported frameworks",
|
||||
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 {
|
||||
@@ -61,11 +68,14 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationFramework(scanInfo); err != nil {
|
||||
if err := validateFrameworkScanInfo(scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
scanInfo.FrameworkScan = true
|
||||
|
||||
// We do not scan all frameworks by default when triggering scan from the CLI
|
||||
scanInfo.ScanAll = false
|
||||
|
||||
var frameworks []string
|
||||
|
||||
if len(args) == 0 { // scan all frameworks
|
||||
@@ -75,11 +85,12 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
||||
frameworks = strings.Split(args[0], ",")
|
||||
if cautils.StringInSlice(frameworks, "all") != cautils.ValueNotFound {
|
||||
scanInfo.ScanAll = true
|
||||
frameworks = []string{}
|
||||
frameworks = getter.NativeFrameworks
|
||||
}
|
||||
if len(args) > 1 {
|
||||
if len(args[1:]) == 0 || args[1] != "-" {
|
||||
scanInfo.InputPatterns = []string{args[1]}
|
||||
scanInfo.InputPatterns = args[1:]
|
||||
logger.L().Debug("List of input files", helpers.Interface("patterns", scanInfo.InputPatterns))
|
||||
} else { // store stdin to file - do NOT move to separate function !!
|
||||
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
|
||||
if err != nil {
|
||||
@@ -98,31 +109,112 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
||||
|
||||
scanInfo.SetPolicyIdentifiers(frameworks, apisv1.KindFramework)
|
||||
|
||||
results, err := ks.Scan(scanInfo)
|
||||
ctx := context.TODO()
|
||||
results, err := ks.Scan(ctx, scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
if err = results.HandleResults(); err != nil {
|
||||
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)
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func flagValidationFramework(scanInfo *cautils.ScanInfo) error {
|
||||
// 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")
|
||||
}
|
||||
return nil
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var scanCmdExamples = `
|
||||
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defind frameworks
|
||||
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
|
||||
kubescape scan --submit --enable-host-scan --verbose
|
||||
%[1]s scan --enable-host-scan --verbose
|
||||
|
||||
# Scan kubernetes YAML manifest files
|
||||
kubescape scan *.yaml
|
||||
%[1]s scan .
|
||||
|
||||
# Scan and save the results in the JSON format
|
||||
kubescape scan --format json --output results.json
|
||||
%[1]s scan --format json --output results.json --format-version=v2
|
||||
|
||||
# Display all resources
|
||||
kubescape scan --verbose
|
||||
%[1]s scan --verbose
|
||||
|
||||
# Scan different clusters from the kubectl context
|
||||
kubescape scan --kube-context <kubernetes context>
|
||||
|
||||
`
|
||||
%[1]s scan --kube-context <kubernetes context>
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
var scanInfo cautils.ScanInfo
|
||||
@@ -41,7 +43,6 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
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...))
|
||||
}
|
||||
}
|
||||
@@ -50,13 +51,13 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if len(args) == 0 {
|
||||
scanInfo.ScanAll = true
|
||||
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, []string{"all"})
|
||||
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, []string{strings.Join(getter.NativeFrameworks, ",")})
|
||||
}
|
||||
return nil
|
||||
},
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
|
||||
|
||||
},
|
||||
PostRun: func(cmd *cobra.Command, args []string) {
|
||||
// TODO - revert context
|
||||
@@ -64,36 +65,44 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
}
|
||||
|
||||
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. Recommended: kube-system,kube-public")
|
||||
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().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html"`)
|
||||
|
||||
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 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().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().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 scanner 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'")
|
||||
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")
|
||||
|
||||
// Deprecated flags - remove 1.May.2022
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages")
|
||||
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("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
|
||||
scanCmd.PersistentFlags().MarkHidden("omit-raw-resources")
|
||||
scanCmd.PersistentFlags().MarkHidden("print-attack-tree")
|
||||
|
||||
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 valuable data from cluster nodes for certain controls. Yaml file: https://github.com/armosec/kubescape/blob/master/core/pkg/hostsensorutils/hostsensor.yaml")
|
||||
// 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"
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
package submit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
metav1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
|
||||
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 exceptins file>",
|
||||
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 {
|
||||
@@ -21,7 +22,12 @@ func getExceptionsCmd(ks meta.IKubescape, submitInfo *metav1.Submit) *cobra.Comm
|
||||
return nil
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := ks.SubmitExceptions(&submitInfo.Credentials, args[0]); err != nil {
|
||||
|
||||
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
if err := ks.SubmitExceptions(context.TODO(), &submitInfo.Credentials, args[0]); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,42 +1,65 @@
|
||||
package submit
|
||||
|
||||
import (
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/getter"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
"github.com/armosec/kubescape/v2/core/meta/cliinterfaces"
|
||||
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
reporterv1 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v1"
|
||||
"github.com/google/uuid"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
reporterv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter/v2"
|
||||
|
||||
"github.com/armosec/rbac-utils/rbacscanner"
|
||||
"github.com/kubescape/rbac-utils/rbacscanner"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
rbacExamples = fmt.Sprintf(`
|
||||
# Submit cluster's Role-Based Access Control(RBAC)
|
||||
%[1]s submit rbac
|
||||
|
||||
# Submit cluster's Role-Based Access Control(RBAC) with account ID
|
||||
%[1]s submit rbac --account <account-id>
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
// getRBACCmd represents the RBAC command
|
||||
func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "rbac \nExample:\n$ kubescape submit rbac",
|
||||
Short: "Submit cluster's Role-Based Access Control(RBAC)",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
Use: "rbac",
|
||||
Deprecated: "This command is deprecated and will not be supported after 1/Jan/2023. Please use the 'scan' command instead.",
|
||||
Example: rbacExamples,
|
||||
Short: "Submit cluster's Role-Based Access Control(RBAC)",
|
||||
Long: ``,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
|
||||
// get config
|
||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", k8s)
|
||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
|
||||
if err := clusterConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
if clusterConfig.GetAccountID() == "" {
|
||||
return fmt.Errorf("account ID is not set, run '%[1]s submit rbac --account <account-id>'", cautils.ExecName())
|
||||
}
|
||||
|
||||
// list RBAC
|
||||
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetAccountID(), clusterConfig.GetContextName()))
|
||||
|
||||
// submit resources
|
||||
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
|
||||
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextRBAC)
|
||||
|
||||
submitInterfaces := cliinterfaces.SubmitInterfaces{
|
||||
ClusterConfig: clusterConfig,
|
||||
@@ -44,7 +67,7 @@ func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
Reporter: r,
|
||||
}
|
||||
|
||||
if err := ks.Submit(submitInterfaces); err != nil {
|
||||
if err := ks.Submit(context.TODO(), submitInterfaces); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
@@ -60,9 +83,16 @@ func getKubernetesApi() *k8sinterface.KubernetesApi {
|
||||
}
|
||||
return k8sinterface.NewKubernetesApi()
|
||||
}
|
||||
func getTenantConfig(credentials *cautils.Credentials, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
|
||||
func getTenantConfig(credentials *cautils.Credentials, clusterName string, customClusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
|
||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||
return cautils.NewLocalConfig(getter.GetArmoAPIConnector(), credentials, clusterName)
|
||||
return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
|
||||
}
|
||||
return cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), credentials, clusterName)
|
||||
return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
|
||||
}
|
||||
|
||||
// Check if the flag entered are valid
|
||||
func flagValidationSubmit(submitInfo *v1.Submit) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return submitInfo.Credentials.Validate()
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
package submit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
"github.com/armosec/kubescape/v2/core/meta/cliinterfaces"
|
||||
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter"
|
||||
reporterv1 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v1"
|
||||
reporterv2 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v2"
|
||||
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/google/uuid"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
reporterv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter/v2"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -37,19 +37,13 @@ func NewResultsObject(customerGUID, clusterName, filePath string) *ResultsObject
|
||||
}
|
||||
}
|
||||
|
||||
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandling.PostureReport, error) {
|
||||
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandlingv2.PostureReport, error) {
|
||||
// load framework results from json file
|
||||
frameworkReports, err := loadResultsFromFile(resultsObject.filePath)
|
||||
report, 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
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
|
||||
@@ -58,10 +52,15 @@ func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinter
|
||||
|
||||
func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
var resultsCmd = &cobra.Command{
|
||||
Use: "results <json file>\nExample:\n$ kubescape submit results path/to/results.json --format-version v2",
|
||||
Use: fmt.Sprintf("results <json file>\nExample:\n$ %[1]s submit results path/to/results.json --format-version v2", cautils.ExecName()),
|
||||
Short: "Submit a pre scanned results file. The file must be in json format",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("missing results file")
|
||||
}
|
||||
@@ -69,22 +68,14 @@ func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
k8s := getKubernetesApi()
|
||||
|
||||
// get config
|
||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", k8s)
|
||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
|
||||
if err := clusterConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
resultsObjects := NewResultsObject(clusterConfig.GetAccountID(), clusterConfig.GetContextName(), args[0])
|
||||
|
||||
// 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())
|
||||
}
|
||||
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextScan)
|
||||
|
||||
submitInterfaces := cliinterfaces.SubmitInterfaces{
|
||||
ClusterConfig: clusterConfig,
|
||||
@@ -92,28 +83,24 @@ func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
Reporter: r,
|
||||
}
|
||||
|
||||
if err := ks.Submit(submitInterfaces); err != nil {
|
||||
if err := ks.Submit(context.TODO(), submitInterfaces); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
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'")
|
||||
resultsCmd.PersistentFlags().StringVar(&formatVersion, "format-version", "v2", "Output object can be different between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
|
||||
|
||||
return resultsCmd
|
||||
}
|
||||
func loadResultsFromFile(filePath string) ([]reporthandling.FrameworkReport, error) {
|
||||
frameworkReports := []reporthandling.FrameworkReport{}
|
||||
func loadResultsFromFile(filePath string) (*reporthandlingv2.PostureReport, error) {
|
||||
report := &reporthandlingv2.PostureReport{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(f, &frameworkReports); err != nil {
|
||||
frameworkReport := reporthandling.FrameworkReport{}
|
||||
if err = json.Unmarshal(f, &frameworkReport); err != nil {
|
||||
return frameworkReports, err
|
||||
}
|
||||
frameworkReports = append(frameworkReports, frameworkReport)
|
||||
if err = json.Unmarshal(f, report); err != nil {
|
||||
return report, fmt.Errorf("failed to unmarshal results file: %s, make sure you run kubescape with '--format=json --format-version=v2'", err.Error())
|
||||
}
|
||||
return frameworkReports, nil
|
||||
return report, nil
|
||||
}
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
package submit
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
metav1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var submitCmdExamples = `
|
||||
var submitCmdExamples = fmt.Sprintf(`
|
||||
# Submit Kubescape scan results file
|
||||
%[1]s submit results
|
||||
|
||||
`
|
||||
# Submit exceptions file to Kubescape SaaS
|
||||
%[1]s submit exceptions
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetSubmitCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var submitInfo metav1.Submit
|
||||
|
||||
submitCmd := &cobra.Command{
|
||||
Use: "submit <command>",
|
||||
Short: "Submit an object to the Kubescape SaaS version",
|
||||
Long: ``,
|
||||
Use: "submit <command>",
|
||||
Short: "Submit an object to the Kubescape SaaS version",
|
||||
Long: ``,
|
||||
Example: submitCmdExamples,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
|
||||
66
cmd/update/update.go
Normal file
66
cmd/update/update.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package update
|
||||
|
||||
//This update command updates to the latest kubescape release.
|
||||
//Example:-
|
||||
// kubescape update
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var updateCmdExamples = fmt.Sprintf(`
|
||||
# Update to the latest kubescape release
|
||||
%[1]s update
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetUpdateCmd() *cobra.Command {
|
||||
updateCmd := &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update your version",
|
||||
Long: ``,
|
||||
Example: updateCmdExamples,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
//Checking the user's version of kubescape to the latest release
|
||||
if cautils.BuildNumber == cautils.LatestReleaseVersion {
|
||||
//your version == latest version
|
||||
logger.L().Info(("You are in the latest version"))
|
||||
} else {
|
||||
|
||||
const OSTYPE string = runtime.GOOS
|
||||
var ShellToUse string
|
||||
switch OSTYPE {
|
||||
|
||||
case "windows":
|
||||
cautils.StartSpinner()
|
||||
//run the installation command for windows
|
||||
ShellToUse = "powershell"
|
||||
_, err := exec.Command(ShellToUse, "-c", "iwr -useb https://raw.githubusercontent.com/kubescape/kubescape/master/install.ps1 | iex").Output()
|
||||
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
cautils.StopSpinner()
|
||||
|
||||
default:
|
||||
ShellToUse = "bash"
|
||||
cautils.StartSpinner()
|
||||
//run the installation command for linux and macOS
|
||||
_, err := exec.Command(ShellToUse, "-c", "curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash").Output()
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
cautils.StopSpinner()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return updateCmd
|
||||
}
|
||||
7
cmd/version/git_native_disabled.go
Normal file
7
cmd/version/git_native_disabled.go
Normal file
@@ -0,0 +1,7 @@
|
||||
//go:build !gitenabled
|
||||
|
||||
package version
|
||||
|
||||
func isGitEnabled() bool {
|
||||
return false
|
||||
}
|
||||
7
cmd/version/git_native_enabled.go
Normal file
7
cmd/version/git_native_enabled.go
Normal file
@@ -0,0 +1,7 @@
|
||||
//go:build gitenabled
|
||||
|
||||
package version
|
||||
|
||||
func isGitEnabled() bool {
|
||||
return true
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -14,9 +15,14 @@ func GetVersionCmd() *cobra.Command {
|
||||
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)
|
||||
ctx := context.TODO()
|
||||
v := cautils.NewIVersionCheckHandler(ctx)
|
||||
v.CheckLatestVersion(ctx, cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
|
||||
fmt.Fprintf(os.Stdout,
|
||||
"Your current version is: %s [git enabled in build: %t]\n",
|
||||
cautils.BuildNumber,
|
||||
isGitEnabled(),
|
||||
)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
12
core/cautils/controllink.go
Normal file
12
core/cautils/controllink.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetControlLink(controlID string) string {
|
||||
// For CIS Controls, cis-1.1.3 will be transformed to cis-1-1-3 in documentation link.
|
||||
docLinkID := strings.ReplaceAll(controlID, ".", "-")
|
||||
return fmt.Sprintf("https://hub.armosec.io/docs/%s", strings.ToLower(docLinkID))
|
||||
}
|
||||
@@ -5,13 +5,15 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/getter"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
@@ -31,6 +33,10 @@ type ConfigObj struct {
|
||||
Token string `json:"invitationParam,omitempty"`
|
||||
CustomerAdminEMail string `json:"adminMail,omitempty"`
|
||||
ClusterName string `json:"clusterName,omitempty"`
|
||||
CloudReportURL string `json:"cloudReportURL,omitempty"`
|
||||
CloudAPIURL string `json:"cloudAPIURL,omitempty"`
|
||||
CloudUIURL string `json:"cloudUIURL,omitempty"`
|
||||
CloudAuthURL string `json:"cloudAuthURL,omitempty"`
|
||||
}
|
||||
|
||||
// Config - convert ConfigObj to config file
|
||||
@@ -64,7 +70,7 @@ type ITenantConfig interface {
|
||||
// set
|
||||
SetTenant() error
|
||||
UpdateCachedConfig() error
|
||||
DeleteCachedConfig() error
|
||||
DeleteCachedConfig(ctx context.Context) error
|
||||
|
||||
// getters
|
||||
GetContextName() string
|
||||
@@ -74,6 +80,10 @@ type ITenantConfig interface {
|
||||
GetClientID() string
|
||||
GetSecretKey() string
|
||||
GetConfigObj() *ConfigObj
|
||||
GetCloudReportURL() string
|
||||
GetCloudAPIURL() string
|
||||
GetCloudUIURL() string
|
||||
GetCloudAuthURL() string
|
||||
// GetBackendAPI() getter.IBackend
|
||||
// GenerateURL()
|
||||
|
||||
@@ -90,7 +100,7 @@ type LocalConfig struct {
|
||||
}
|
||||
|
||||
func NewLocalConfig(
|
||||
backendAPI getter.IBackend, credentials *Credentials, clusterName string) *LocalConfig {
|
||||
backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *LocalConfig {
|
||||
|
||||
lc := &LocalConfig{
|
||||
backendAPI: backendAPI,
|
||||
@@ -102,29 +112,58 @@ func NewLocalConfig(
|
||||
}
|
||||
|
||||
updateCredentials(lc.configObj, credentials)
|
||||
updateCloudURLs(lc.configObj)
|
||||
|
||||
if clusterName != "" {
|
||||
// If a custom cluster name is provided then set that name, else use the cluster's original name
|
||||
if customClusterName != "" {
|
||||
lc.configObj.ClusterName = AdoptClusterName(customClusterName)
|
||||
} else if clusterName != "" {
|
||||
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
|
||||
}
|
||||
|
||||
lc.backendAPI.SetAccountID(lc.configObj.AccountID)
|
||||
lc.backendAPI.SetClientID(lc.configObj.ClientID)
|
||||
lc.backendAPI.SetSecretKey(lc.configObj.SecretKey)
|
||||
if lc.configObj.CloudAPIURL != "" {
|
||||
lc.backendAPI.SetCloudAPIURL(lc.configObj.CloudAPIURL)
|
||||
} else {
|
||||
lc.configObj.CloudAPIURL = lc.backendAPI.GetCloudAPIURL()
|
||||
}
|
||||
if lc.configObj.CloudAuthURL != "" {
|
||||
lc.backendAPI.SetCloudAuthURL(lc.configObj.CloudAuthURL)
|
||||
} else {
|
||||
lc.configObj.CloudAuthURL = lc.backendAPI.GetCloudAuthURL()
|
||||
}
|
||||
if lc.configObj.CloudReportURL != "" {
|
||||
lc.backendAPI.SetCloudReportURL(lc.configObj.CloudReportURL)
|
||||
} else {
|
||||
lc.configObj.CloudReportURL = lc.backendAPI.GetCloudReportURL()
|
||||
}
|
||||
if lc.configObj.CloudUIURL != "" {
|
||||
lc.backendAPI.SetCloudUIURL(lc.configObj.CloudUIURL)
|
||||
} else {
|
||||
lc.configObj.CloudUIURL = lc.backendAPI.GetCloudUIURL()
|
||||
}
|
||||
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", lc.backendAPI.GetCloudAPIURL()), helpers.String("auth", lc.backendAPI.GetCloudAuthURL()), helpers.String("report", lc.backendAPI.GetCloudReportURL()), helpers.String("UI", lc.backendAPI.GetCloudUIURL()))
|
||||
|
||||
return lc
|
||||
}
|
||||
|
||||
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
|
||||
func (lc *LocalConfig) GetTenantEmail() string { return lc.configObj.CustomerAdminEMail }
|
||||
func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountID }
|
||||
func (lc *LocalConfig) GetClientID() string { return lc.configObj.ClientID }
|
||||
func (lc *LocalConfig) GetSecretKey() string { return lc.configObj.SecretKey }
|
||||
func (lc *LocalConfig) GetContextName() string { return lc.configObj.ClusterName }
|
||||
func (lc *LocalConfig) GetToken() string { return lc.configObj.Token }
|
||||
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
|
||||
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
|
||||
func (lc *LocalConfig) GetTenantEmail() string { return lc.configObj.CustomerAdminEMail }
|
||||
func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountID }
|
||||
func (lc *LocalConfig) GetClientID() string { return lc.configObj.ClientID }
|
||||
func (lc *LocalConfig) GetSecretKey() string { return lc.configObj.SecretKey }
|
||||
func (lc *LocalConfig) GetContextName() string { return lc.configObj.ClusterName }
|
||||
func (lc *LocalConfig) GetToken() string { return lc.configObj.Token }
|
||||
func (lc *LocalConfig) GetCloudReportURL() string { return lc.configObj.CloudReportURL }
|
||||
func (lc *LocalConfig) GetCloudAPIURL() string { return lc.configObj.CloudAPIURL }
|
||||
func (lc *LocalConfig) GetCloudUIURL() string { return lc.configObj.CloudUIURL }
|
||||
func (lc *LocalConfig) GetCloudAuthURL() string { return lc.configObj.CloudAuthURL }
|
||||
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
|
||||
func (lc *LocalConfig) SetTenant() error {
|
||||
|
||||
// ARMO tenant GUID
|
||||
// Kubescape Cloud tenant GUID
|
||||
if err := getTenantConfigFromBE(lc.backendAPI, lc.configObj); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -136,16 +175,16 @@ func (lc *LocalConfig) UpdateCachedConfig() error {
|
||||
return updateConfigFile(lc.configObj)
|
||||
}
|
||||
|
||||
func (lc *LocalConfig) DeleteCachedConfig() error {
|
||||
func (lc *LocalConfig) DeleteCachedConfig(ctx context.Context) error {
|
||||
if err := DeleteConfigFile(); err != nil {
|
||||
logger.L().Warning(err.Error())
|
||||
logger.L().Ctx(ctx).Warning(err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTenantConfigFromBE(backendAPI getter.IBackend, configObj *ConfigObj) error {
|
||||
|
||||
// get from armoBE
|
||||
// get from Kubescape Cloud API
|
||||
tenantResponse, err := backendAPI.GetTenant()
|
||||
if err == nil && tenantResponse != nil {
|
||||
if tenantResponse.AdminMail != "" { // registered tenant
|
||||
@@ -178,18 +217,18 @@ KS_ACCOUNT_ID
|
||||
KS_CLIENT_ID
|
||||
KS_SECRET_KEY
|
||||
|
||||
TODO - supprot:
|
||||
TODO - support:
|
||||
KS_CACHE // path to cached files
|
||||
*/
|
||||
type ClusterConfig struct {
|
||||
backendAPI getter.IBackend
|
||||
k8s *k8sinterface.KubernetesApi
|
||||
configObj *ConfigObj
|
||||
configMapName string
|
||||
configMapNamespace string
|
||||
backendAPI getter.IBackend
|
||||
configObj *ConfigObj
|
||||
}
|
||||
|
||||
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, credentials *Credentials, clusterName string) *ClusterConfig {
|
||||
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *ClusterConfig {
|
||||
// var configObj *ConfigObj
|
||||
c := &ClusterConfig{
|
||||
k8s: k8s,
|
||||
@@ -209,8 +248,12 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
|
||||
loadConfigFromFile(c.configObj)
|
||||
}
|
||||
updateCredentials(c.configObj, credentials)
|
||||
updateCloudURLs(c.configObj)
|
||||
|
||||
if clusterName != "" {
|
||||
// If a custom cluster name is provided then set that name, else use the cluster's original name
|
||||
if customClusterName != "" {
|
||||
c.configObj.ClusterName = AdoptClusterName(customClusterName)
|
||||
} else if clusterName != "" {
|
||||
c.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
|
||||
}
|
||||
|
||||
@@ -223,18 +266,44 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
|
||||
c.backendAPI.SetAccountID(c.configObj.AccountID)
|
||||
c.backendAPI.SetClientID(c.configObj.ClientID)
|
||||
c.backendAPI.SetSecretKey(c.configObj.SecretKey)
|
||||
if c.configObj.CloudAPIURL != "" {
|
||||
c.backendAPI.SetCloudAPIURL(c.configObj.CloudAPIURL)
|
||||
} else {
|
||||
c.configObj.CloudAPIURL = c.backendAPI.GetCloudAPIURL()
|
||||
}
|
||||
if c.configObj.CloudAuthURL != "" {
|
||||
c.backendAPI.SetCloudAuthURL(c.configObj.CloudAuthURL)
|
||||
} else {
|
||||
c.configObj.CloudAuthURL = c.backendAPI.GetCloudAuthURL()
|
||||
}
|
||||
if c.configObj.CloudReportURL != "" {
|
||||
c.backendAPI.SetCloudReportURL(c.configObj.CloudReportURL)
|
||||
} else {
|
||||
c.configObj.CloudReportURL = c.backendAPI.GetCloudReportURL()
|
||||
}
|
||||
if c.configObj.CloudUIURL != "" {
|
||||
c.backendAPI.SetCloudUIURL(c.configObj.CloudUIURL)
|
||||
} else {
|
||||
c.configObj.CloudUIURL = c.backendAPI.GetCloudUIURL()
|
||||
}
|
||||
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", c.backendAPI.GetCloudAPIURL()), helpers.String("auth", c.backendAPI.GetCloudAuthURL()), helpers.String("report", c.backendAPI.GetCloudReportURL()), helpers.String("UI", c.backendAPI.GetCloudUIURL()))
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
|
||||
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
|
||||
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
|
||||
func (c *ClusterConfig) GetClientID() string { return c.configObj.ClientID }
|
||||
func (c *ClusterConfig) GetSecretKey() string { return c.configObj.SecretKey }
|
||||
func (c *ClusterConfig) GetTenantEmail() string { return c.configObj.CustomerAdminEMail }
|
||||
func (c *ClusterConfig) GetToken() string { return c.configObj.Token }
|
||||
func (c *ClusterConfig) IsConfigFound() bool { return existsConfigFile() || c.existsConfigMap() }
|
||||
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
|
||||
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
|
||||
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
|
||||
func (c *ClusterConfig) GetClientID() string { return c.configObj.ClientID }
|
||||
func (c *ClusterConfig) GetSecretKey() string { return c.configObj.SecretKey }
|
||||
func (c *ClusterConfig) GetTenantEmail() string { return c.configObj.CustomerAdminEMail }
|
||||
func (c *ClusterConfig) GetToken() string { return c.configObj.Token }
|
||||
func (c *ClusterConfig) GetCloudReportURL() string { return c.configObj.CloudReportURL }
|
||||
func (c *ClusterConfig) GetCloudAPIURL() string { return c.configObj.CloudAPIURL }
|
||||
func (c *ClusterConfig) GetCloudUIURL() string { return c.configObj.CloudUIURL }
|
||||
func (c *ClusterConfig) GetCloudAuthURL() string { return c.configObj.CloudAuthURL }
|
||||
|
||||
func (c *ClusterConfig) IsConfigFound() bool { return existsConfigFile() || c.existsConfigMap() }
|
||||
|
||||
func (c *ClusterConfig) SetTenant() error {
|
||||
|
||||
@@ -261,12 +330,12 @@ func (c *ClusterConfig) UpdateCachedConfig() error {
|
||||
return updateConfigFile(c.configObj)
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) DeleteCachedConfig() error {
|
||||
func (c *ClusterConfig) DeleteCachedConfig(ctx context.Context) error {
|
||||
if err := c.deleteConfigMap(); err != nil {
|
||||
logger.L().Warning(err.Error())
|
||||
logger.L().Ctx(ctx).Warning(err.Error())
|
||||
}
|
||||
if err := DeleteConfigFile(); err != nil {
|
||||
logger.L().Warning(err.Error())
|
||||
logger.L().Ctx(ctx).Warning(err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -401,10 +470,7 @@ func (c *ClusterConfig) updateConfigMap() error {
|
||||
}
|
||||
|
||||
func updateConfigFile(configObj *ConfigObj) error {
|
||||
if err := os.WriteFile(ConfigFileFullPath(), configObj.Config(), 0664); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return os.WriteFile(ConfigFileFullPath(), configObj.Config(), 0664) //nolint:gosec
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {
|
||||
@@ -468,7 +534,11 @@ func DeleteConfigFile() error {
|
||||
}
|
||||
|
||||
func AdoptClusterName(clusterName string) string {
|
||||
return strings.ReplaceAll(clusterName, "/", "-")
|
||||
re, err := regexp.Compile(`[^\w]+`)
|
||||
if err != nil {
|
||||
return clusterName
|
||||
}
|
||||
return re.ReplaceAllString(clusterName, "-")
|
||||
}
|
||||
|
||||
func getConfigMapName() string {
|
||||
@@ -487,13 +557,13 @@ func getConfigMapNamespace() string {
|
||||
|
||||
func getAccountFromEnv(credentials *Credentials) {
|
||||
// load from env
|
||||
if accountID := os.Getenv("KS_ACCOUNT_ID"); credentials.Account != "" && accountID != "" {
|
||||
if accountID := os.Getenv("KS_ACCOUNT_ID"); credentials.Account == "" && accountID != "" {
|
||||
credentials.Account = accountID
|
||||
}
|
||||
if clientID := os.Getenv("KS_CLIENT_ID"); credentials.ClientID != "" && clientID != "" {
|
||||
if clientID := os.Getenv("KS_CLIENT_ID"); credentials.ClientID == "" && clientID != "" {
|
||||
credentials.ClientID = clientID
|
||||
}
|
||||
if secretKey := os.Getenv("KS_SECRET_KEY"); credentials.SecretKey != "" && secretKey != "" {
|
||||
if secretKey := os.Getenv("KS_SECRET_KEY"); credentials.SecretKey == "" && secretKey != "" {
|
||||
credentials.SecretKey = secretKey
|
||||
}
|
||||
}
|
||||
@@ -516,3 +586,39 @@ func updateCredentials(configObj *ConfigObj, credentials *Credentials) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getCloudURLsFromEnv(cloudURLs *CloudURLs) {
|
||||
// load from env
|
||||
if cloudAPIURL := os.Getenv("KS_CLOUD_API_URL"); cloudAPIURL != "" {
|
||||
cloudURLs.CloudAPIURL = cloudAPIURL
|
||||
}
|
||||
if cloudAuthURL := os.Getenv("KS_CLOUD_AUTH_URL"); cloudAuthURL != "" {
|
||||
cloudURLs.CloudAuthURL = cloudAuthURL
|
||||
}
|
||||
if cloudReportURL := os.Getenv("KS_CLOUD_REPORT_URL"); cloudReportURL != "" {
|
||||
cloudURLs.CloudReportURL = cloudReportURL
|
||||
}
|
||||
if cloudUIURL := os.Getenv("KS_CLOUD_UI_URL"); cloudUIURL != "" {
|
||||
cloudURLs.CloudUIURL = cloudUIURL
|
||||
}
|
||||
}
|
||||
|
||||
func updateCloudURLs(configObj *ConfigObj) {
|
||||
cloudURLs := &CloudURLs{}
|
||||
|
||||
getCloudURLsFromEnv(cloudURLs)
|
||||
|
||||
if cloudURLs.CloudAPIURL != "" {
|
||||
configObj.CloudAPIURL = cloudURLs.CloudAPIURL // override config CloudAPIURL
|
||||
}
|
||||
if cloudURLs.CloudAuthURL != "" {
|
||||
configObj.CloudAuthURL = cloudURLs.CloudAuthURL // override config CloudAuthURL
|
||||
}
|
||||
if cloudURLs.CloudReportURL != "" {
|
||||
configObj.CloudReportURL = cloudURLs.CloudReportURL // override config CloudReportURL
|
||||
}
|
||||
if cloudURLs.CloudUIURL != "" {
|
||||
configObj.CloudUIURL = cloudURLs.CloudUIURL // override config CloudUIURL
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package cautils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -16,6 +17,10 @@ func mockConfigObj() *ConfigObj {
|
||||
ClusterName: "ddd",
|
||||
CustomerAdminEMail: "ab@cd",
|
||||
Token: "eee",
|
||||
CloudReportURL: "report.armo.cloud",
|
||||
CloudAPIURL: "api.armosec.io",
|
||||
CloudUIURL: "cloud.armosec.io",
|
||||
CloudAuthURL: "auth.armosec.io",
|
||||
}
|
||||
}
|
||||
func mockLocalConfig() *LocalConfig {
|
||||
@@ -39,6 +44,10 @@ func TestConfig(t *testing.T) {
|
||||
assert.Equal(t, co.AccountID, cop.AccountID)
|
||||
assert.Equal(t, co.ClientID, cop.ClientID)
|
||||
assert.Equal(t, co.SecretKey, cop.SecretKey)
|
||||
assert.Equal(t, co.CloudReportURL, cop.CloudReportURL)
|
||||
assert.Equal(t, co.CloudAPIURL, cop.CloudAPIURL)
|
||||
assert.Equal(t, co.CloudUIURL, cop.CloudUIURL)
|
||||
assert.Equal(t, co.CloudAuthURL, cop.CloudAuthURL)
|
||||
assert.Equal(t, "", cop.ClusterName) // Not copied to bytes
|
||||
assert.Equal(t, "", cop.CustomerAdminEMail) // Not copied to bytes
|
||||
assert.Equal(t, "", cop.Token) // Not copied to bytes
|
||||
@@ -60,6 +69,10 @@ func TestITenantConfig(t *testing.T) {
|
||||
assert.Equal(t, co.ClusterName, lc.GetContextName())
|
||||
assert.Equal(t, co.CustomerAdminEMail, lc.GetTenantEmail())
|
||||
assert.Equal(t, co.Token, lc.GetToken())
|
||||
assert.Equal(t, co.CloudReportURL, lc.GetCloudReportURL())
|
||||
assert.Equal(t, co.CloudAPIURL, lc.GetCloudAPIURL())
|
||||
assert.Equal(t, co.CloudUIURL, lc.GetCloudUIURL())
|
||||
assert.Equal(t, co.CloudAuthURL, lc.GetCloudAuthURL())
|
||||
|
||||
// test ClusterConfig methods
|
||||
assert.Equal(t, co.AccountID, c.GetAccountID())
|
||||
@@ -68,6 +81,10 @@ func TestITenantConfig(t *testing.T) {
|
||||
assert.Equal(t, co.ClusterName, c.GetContextName())
|
||||
assert.Equal(t, co.CustomerAdminEMail, c.GetTenantEmail())
|
||||
assert.Equal(t, co.Token, c.GetToken())
|
||||
assert.Equal(t, co.CloudReportURL, c.GetCloudReportURL())
|
||||
assert.Equal(t, co.CloudAPIURL, c.GetCloudAPIURL())
|
||||
assert.Equal(t, co.CloudUIURL, c.GetCloudUIURL())
|
||||
assert.Equal(t, co.CloudAuthURL, c.GetCloudAuthURL())
|
||||
}
|
||||
|
||||
func TestUpdateConfigData(t *testing.T) {
|
||||
@@ -80,6 +97,10 @@ func TestUpdateConfigData(t *testing.T) {
|
||||
assert.Equal(t, c.GetAccountID(), configMap.Data["accountID"])
|
||||
assert.Equal(t, c.GetClientID(), configMap.Data["clientID"])
|
||||
assert.Equal(t, c.GetSecretKey(), configMap.Data["secretKey"])
|
||||
assert.Equal(t, c.GetCloudReportURL(), configMap.Data["cloudReportURL"])
|
||||
assert.Equal(t, c.GetCloudAPIURL(), configMap.Data["cloudAPIURL"])
|
||||
assert.Equal(t, c.GetCloudUIURL(), configMap.Data["cloudUIURL"])
|
||||
assert.Equal(t, c.GetCloudAuthURL(), configMap.Data["cloudAuthURL"])
|
||||
}
|
||||
|
||||
func TestReadConfig(t *testing.T) {
|
||||
@@ -97,6 +118,10 @@ func TestReadConfig(t *testing.T) {
|
||||
assert.Equal(t, com.ClusterName, co.ClusterName)
|
||||
assert.Equal(t, com.CustomerAdminEMail, co.CustomerAdminEMail)
|
||||
assert.Equal(t, com.Token, co.Token)
|
||||
assert.Equal(t, com.CloudReportURL, co.CloudReportURL)
|
||||
assert.Equal(t, com.CloudAPIURL, co.CloudAPIURL)
|
||||
assert.Equal(t, com.CloudUIURL, co.CloudUIURL)
|
||||
assert.Equal(t, com.CloudAuthURL, co.CloudAuthURL)
|
||||
}
|
||||
|
||||
func TestLoadConfigFromData(t *testing.T) {
|
||||
@@ -120,6 +145,10 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
assert.Equal(t, c.GetContextName(), co.ClusterName)
|
||||
assert.Equal(t, c.GetTenantEmail(), co.CustomerAdminEMail)
|
||||
assert.Equal(t, c.GetToken(), co.Token)
|
||||
assert.Equal(t, c.GetCloudReportURL(), co.CloudReportURL)
|
||||
assert.Equal(t, c.GetCloudAPIURL(), co.CloudAPIURL)
|
||||
assert.Equal(t, c.GetCloudUIURL(), co.CloudUIURL)
|
||||
assert.Equal(t, c.GetCloudAuthURL(), co.CloudAuthURL)
|
||||
}
|
||||
|
||||
// use case: all data is in config.json
|
||||
@@ -139,6 +168,10 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
assert.Equal(t, c.GetAccountID(), co.AccountID)
|
||||
assert.Equal(t, c.GetClientID(), co.ClientID)
|
||||
assert.Equal(t, c.GetSecretKey(), co.SecretKey)
|
||||
assert.Equal(t, c.GetCloudReportURL(), co.CloudReportURL)
|
||||
assert.Equal(t, c.GetCloudAPIURL(), co.CloudAPIURL)
|
||||
assert.Equal(t, c.GetCloudUIURL(), co.CloudUIURL)
|
||||
assert.Equal(t, c.GetCloudAuthURL(), co.CloudAuthURL)
|
||||
}
|
||||
|
||||
// use case: some data is in config.json
|
||||
@@ -151,10 +184,12 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
// add to map
|
||||
configMap.Data["clientID"] = c.configObj.ClientID
|
||||
configMap.Data["secretKey"] = c.configObj.SecretKey
|
||||
configMap.Data["cloudReportURL"] = c.configObj.CloudReportURL
|
||||
|
||||
// delete the content
|
||||
c.configObj.ClientID = ""
|
||||
c.configObj.SecretKey = ""
|
||||
c.configObj.CloudReportURL = ""
|
||||
|
||||
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
|
||||
loadConfigFromData(c.configObj, configMap.Data)
|
||||
@@ -162,6 +197,7 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
assert.NotEmpty(t, c.GetAccountID())
|
||||
assert.NotEmpty(t, c.GetClientID())
|
||||
assert.NotEmpty(t, c.GetSecretKey())
|
||||
assert.NotEmpty(t, c.GetCloudReportURL())
|
||||
}
|
||||
|
||||
// use case: some data is in config.json
|
||||
@@ -191,3 +227,44 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAdoptClusterName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
clusterName string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "replace 1",
|
||||
clusterName: "my-name__is--ks",
|
||||
want: "my-name__is-ks",
|
||||
},
|
||||
{
|
||||
name: "replace 2",
|
||||
clusterName: "my-name1",
|
||||
want: "my-name1",
|
||||
},
|
||||
{
|
||||
name: "replace 3",
|
||||
clusterName: "my:name",
|
||||
want: "my-name",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := AdoptClusterName(tt.clusterName); got != tt.want {
|
||||
t.Errorf("AdoptClusterName() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateCloudURLs(t *testing.T) {
|
||||
co := mockConfigObj()
|
||||
mockCloudAPIURL := "1-2-3-4.com"
|
||||
os.Setenv("KS_CLOUD_API_URL", mockCloudAPIURL)
|
||||
|
||||
assert.NotEqual(t, co.CloudAPIURL, mockCloudAPIURL)
|
||||
updateCloudURLs(co)
|
||||
assert.Equal(t, co.CloudAPIURL, mockCloudAPIURL)
|
||||
}
|
||||
|
||||
@@ -1,67 +1,85 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
apis "github.com/armosec/opa-utils/reporthandling/apis"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
apis "github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/prioritization"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
)
|
||||
|
||||
// K8SResources map[<api group>/<api version>/<resource>][]<resourceID>
|
||||
type K8SResources map[string][]string
|
||||
type ArmoResources map[string][]string
|
||||
type KSResources map[string][]string
|
||||
|
||||
type OPASessionObj struct {
|
||||
K8SResources *K8SResources // input k8s objects
|
||||
ArmoResource *ArmoResources // input ARMO objects
|
||||
Policies []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>
|
||||
ResourceSource map[string]reporthandling.Source // resources sources, map[<rtesource ID>]<resource result>
|
||||
PostureReport *reporthandling.PostureReport // scan results v1 - Remove
|
||||
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
|
||||
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
|
||||
RegoInputData RegoInputData // input passed to rgo for scanning. map[<control name>][<input arguments>]
|
||||
K8SResources *K8SResources // input k8s objects
|
||||
ArmoResource *KSResources // input ARMO objects
|
||||
AllPolicies *Policies // list of all frameworks
|
||||
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<resource ID>]<resource>
|
||||
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<resource ID>]<resource result>
|
||||
ResourceSource map[string]reporthandling.Source // resources sources, map[<resource ID>]<resource result>
|
||||
ResourcesPrioritized map[string]prioritization.PrioritizedResource // resources prioritization information, map[<resource ID>]<prioritized resource>
|
||||
ResourceAttackTracks map[string]v1alpha1.IAttackTrack // resources attack tracks, map[<resource ID>]<attack track>
|
||||
AttackTracks map[string]v1alpha1.IAttackTrack
|
||||
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
|
||||
RegoInputData RegoInputData // input passed to rego for scanning. map[<control name>][<input arguments>]
|
||||
Metadata *reporthandlingv2.Metadata
|
||||
InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo
|
||||
ResourceToControlsMap map[string][]string // map[<apigroup/apiversion/resource>] = [<control_IDs>]
|
||||
SessionID string // SessionID
|
||||
InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo
|
||||
ResourceToControlsMap map[string][]string // map[<apigroup/apiversion/resource>] = [<control_IDs>]
|
||||
SessionID string // SessionID
|
||||
Policies []reporthandling.Framework // list of frameworks to scan
|
||||
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
|
||||
OmitRawResources bool // omit raw resources from output
|
||||
}
|
||||
|
||||
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources, scanInfo *ScanInfo) *OPASessionObj {
|
||||
func NewOPASessionObj(ctx context.Context, frameworks []reporthandling.Framework, k8sResources *K8SResources, scanInfo *ScanInfo) *OPASessionObj {
|
||||
return &OPASessionObj{
|
||||
Report: &reporthandlingv2.PostureReport{},
|
||||
Policies: frameworks,
|
||||
K8SResources: k8sResources,
|
||||
AllResources: make(map[string]workloadinterface.IMetadata),
|
||||
ResourcesResult: make(map[string]resourcesresults.Result),
|
||||
ResourcesPrioritized: make(map[string]prioritization.PrioritizedResource),
|
||||
InfoMap: make(map[string]apis.StatusInfo),
|
||||
ResourceToControlsMap: make(map[string][]string),
|
||||
ResourceSource: make(map[string]reporthandling.Source),
|
||||
SessionID: scanInfo.ScanID,
|
||||
PostureReport: &reporthandling.PostureReport{
|
||||
ClusterName: ClusterName,
|
||||
CustomerGUID: CustomerGUID,
|
||||
},
|
||||
Metadata: scanInfoToScanMetadata(scanInfo),
|
||||
Metadata: scanInfoToScanMetadata(ctx, scanInfo),
|
||||
OmitRawResources: scanInfo.OmitRawResources,
|
||||
}
|
||||
}
|
||||
|
||||
func (sessionObj *OPASessionObj) SetMapNamespaceToNumberOfResources(mapNamespaceToNumberOfResources map[string]int) {
|
||||
if sessionObj.Metadata.ContextMetadata.ClusterContextMetadata == nil {
|
||||
sessionObj.Metadata.ContextMetadata.ClusterContextMetadata = &reporthandlingv2.ClusterMetadata{}
|
||||
}
|
||||
if sessionObj.Metadata.ContextMetadata.ClusterContextMetadata.MapNamespaceToNumberOfResources == nil {
|
||||
sessionObj.Metadata.ContextMetadata.ClusterContextMetadata.MapNamespaceToNumberOfResources = make(map[string]int)
|
||||
}
|
||||
sessionObj.Metadata.ContextMetadata.ClusterContextMetadata.MapNamespaceToNumberOfResources = mapNamespaceToNumberOfResources
|
||||
}
|
||||
|
||||
func (sessionObj *OPASessionObj) SetNumberOfWorkerNodes(n int) {
|
||||
if sessionObj.Metadata.ContextMetadata.ClusterContextMetadata == nil {
|
||||
sessionObj.Metadata.ContextMetadata.ClusterContextMetadata = &reporthandlingv2.ClusterMetadata{}
|
||||
}
|
||||
sessionObj.Metadata.ContextMetadata.ClusterContextMetadata.NumberOfWorkerNodes = n
|
||||
}
|
||||
|
||||
func NewOPASessionObjMock() *OPASessionObj {
|
||||
return &OPASessionObj{
|
||||
Policies: 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: "",
|
||||
},
|
||||
Policies: nil,
|
||||
K8SResources: nil,
|
||||
AllResources: make(map[string]workloadinterface.IMetadata),
|
||||
ResourcesResult: make(map[string]resourcesresults.Result),
|
||||
ResourcesPrioritized: make(map[string]prioritization.PrioritizedResource),
|
||||
Report: &reporthandlingv2.PostureReport{},
|
||||
Metadata: &reporthandlingv2.Metadata{
|
||||
ScanMetadata: reporthandlingv2.ScanMetadata{
|
||||
ScanningTarget: 0,
|
||||
@@ -83,11 +101,12 @@ type Exception struct {
|
||||
|
||||
type RegoInputData struct {
|
||||
PostureControlInputs map[string][]string `json:"postureControlInputs"`
|
||||
DataControlInputs map[string]string `json:"dataControlInputs"`
|
||||
// ClusterName string `json:"clusterName"`
|
||||
// K8sConfig RegoK8sConfig `json:"k8sconfig"`
|
||||
}
|
||||
|
||||
type Policies struct {
|
||||
Frameworks []string
|
||||
Controls map[string]reporthandling.Control // map[<control ID>]<control>
|
||||
Frameworks []string
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ package cautils
|
||||
import (
|
||||
"golang.org/x/mod/semver"
|
||||
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/utils-go/boolutils"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
)
|
||||
|
||||
func NewPolicies() *Policies {
|
||||
@@ -22,20 +23,29 @@ func (policies *Policies) Set(frameworks []reporthandling.Framework, version str
|
||||
for j := range frameworks[i].Controls {
|
||||
compatibleRules := []reporthandling.PolicyRule{}
|
||||
for r := range frameworks[i].Controls[j].Rules {
|
||||
if !ruleWithArmoOpaDependency(frameworks[i].Controls[j].Rules[r].Attributes) && isRuleKubescapeVersionCompatible(frameworks[i].Controls[j].Rules[r].Attributes, version) {
|
||||
if !ruleWithKSOpaDependency(frameworks[i].Controls[j].Rules[r].Attributes) && isRuleKubescapeVersionCompatible(frameworks[i].Controls[j].Rules[r].Attributes, version) {
|
||||
compatibleRules = append(compatibleRules, frameworks[i].Controls[j].Rules[r])
|
||||
}
|
||||
}
|
||||
if len(compatibleRules) > 0 {
|
||||
frameworks[i].Controls[j].Rules = compatibleRules
|
||||
policies.Controls[frameworks[i].Controls[j].ControlID] = frameworks[i].Controls[j]
|
||||
} else { // if the control type is manual review, add it to the list of controls
|
||||
actionRequiredStr := frameworks[i].Controls[j].GetActionRequiredAttribute()
|
||||
if actionRequiredStr == "" {
|
||||
continue
|
||||
}
|
||||
if actionRequiredStr == string(apis.SubStatusManualReview) {
|
||||
policies.Controls[frameworks[i].Controls[j].ControlID] = frameworks[i].Controls[j]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func ruleWithArmoOpaDependency(attributes map[string]interface{}) bool {
|
||||
func ruleWithKSOpaDependency(attributes map[string]interface{}) bool {
|
||||
if attributes == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
spinnerpkg "github.com/briandowns/spinner"
|
||||
"github.com/fatih/color"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
)
|
||||
|
||||
var FailureDisplay = color.New(color.Bold, color.FgHiRed).FprintfFunc()
|
||||
@@ -39,3 +40,28 @@ func StopSpinner() {
|
||||
}
|
||||
spinner.Stop()
|
||||
}
|
||||
|
||||
type ProgressHandler struct {
|
||||
title string
|
||||
pb *progressbar.ProgressBar
|
||||
}
|
||||
|
||||
func NewProgressHandler(title string) *ProgressHandler {
|
||||
return &ProgressHandler{title: title}
|
||||
}
|
||||
|
||||
func (p *ProgressHandler) Start(allSteps int) {
|
||||
if isatty.IsTerminal(os.Stderr.Fd()) {
|
||||
p.pb = progressbar.Default(int64(allSteps), p.title)
|
||||
} else {
|
||||
p.pb = progressbar.DefaultSilent(int64(allSteps), p.title)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProgressHandler) ProgressJob(step int, message string) {
|
||||
p.pb.Add(step)
|
||||
p.pb.Describe(message)
|
||||
}
|
||||
|
||||
func (p *ProgressHandler) Stop() {
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package cautils
|
||||
|
||||
// CA environment vars
|
||||
// Kubescape Cloud environment vars
|
||||
var (
|
||||
CustomerGUID = ""
|
||||
ClusterName = ""
|
||||
|
||||
@@ -2,19 +2,21 @@ package cautils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes/localworkload"
|
||||
"gopkg.in/yaml.v2"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -29,8 +31,8 @@ const (
|
||||
JSON_FILE_FORMAT FileFormat = "json"
|
||||
)
|
||||
|
||||
// LoadResourcesFromHelmCharts scans a given path (recuresively) for helm charts, renders the templates and returns a list of workloads
|
||||
func LoadResourcesFromHelmCharts(basePath string) map[string][]workloadinterface.IMetadata {
|
||||
// LoadResourcesFromHelmCharts scans a given path (recursively) for helm charts, renders the templates and returns a map of workloads and a map of chart names
|
||||
func LoadResourcesFromHelmCharts(ctx context.Context, basePath string) (map[string][]workloadinterface.IMetadata, map[string]string) {
|
||||
directories, _ := listDirs(basePath)
|
||||
helmDirectories := make([]string, 0)
|
||||
for _, dir := range directories {
|
||||
@@ -39,28 +41,66 @@ func LoadResourcesFromHelmCharts(basePath string) map[string][]workloadinterface
|
||||
}
|
||||
}
|
||||
|
||||
result := map[string][]workloadinterface.IMetadata{}
|
||||
sourceToWorkloads := map[string][]workloadinterface.IMetadata{}
|
||||
sourceToChartName := map[string]string{}
|
||||
for _, helmDir := range helmDirectories {
|
||||
chart, err := NewHelmChart(helmDir)
|
||||
if err == nil {
|
||||
wls, errs := chart.GetWorkloadsWithDefaultValues()
|
||||
if len(errs) > 0 {
|
||||
logger.L().Error(fmt.Sprintf("Rendering of Helm chart template failed: %v", errs))
|
||||
logger.L().Ctx(ctx).Error(fmt.Sprintf("Rendering of Helm chart template '%s', failed: %v", chart.GetName(), errs))
|
||||
continue
|
||||
}
|
||||
|
||||
chartName := chart.GetName()
|
||||
for k, v := range wls {
|
||||
result[k] = v
|
||||
sourceToWorkloads[k] = v
|
||||
sourceToChartName[k] = chartName
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
return sourceToWorkloads, sourceToChartName
|
||||
}
|
||||
|
||||
func LoadResourcesFromFiles(input, rootPath string) map[string][]workloadinterface.IMetadata {
|
||||
// If the contents at given path is a Kustomize Directory, LoadResourcesFromKustomizeDirectory will
|
||||
// generate yaml files using "Kustomize" & renders a map of workloads from those yaml files
|
||||
func LoadResourcesFromKustomizeDirectory(ctx context.Context, basePath string) (map[string][]workloadinterface.IMetadata, string) {
|
||||
isKustomizeDirectory := IsKustomizeDirectory(basePath)
|
||||
isKustomizeFile := IsKustomizeFile(basePath)
|
||||
if ok := isKustomizeDirectory || isKustomizeFile; !ok {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
sourceToWorkloads := map[string][]workloadinterface.IMetadata{}
|
||||
kustomizeDirectory := NewKustomizeDirectory(basePath)
|
||||
|
||||
var newBasePath string
|
||||
|
||||
if isKustomizeFile {
|
||||
newBasePath = filepath.Dir(basePath)
|
||||
logger.L().Info("Kustomize File Detected, Scanning the rendered Kubernetes Objects...")
|
||||
} else {
|
||||
newBasePath = basePath
|
||||
logger.L().Info("Kustomize Directory Detected, Scanning the rendered Kubernetes Objects...")
|
||||
}
|
||||
|
||||
wls, errs := kustomizeDirectory.GetWorkloads(newBasePath)
|
||||
kustomizeDirectoryName := GetKustomizeDirectoryName(newBasePath)
|
||||
|
||||
if len(errs) > 0 {
|
||||
logger.L().Ctx(ctx).Error(fmt.Sprintf("Rendering yaml from Kustomize failed: %v", errs))
|
||||
}
|
||||
|
||||
for k, v := range wls {
|
||||
sourceToWorkloads[k] = v
|
||||
}
|
||||
return sourceToWorkloads, kustomizeDirectoryName
|
||||
}
|
||||
|
||||
func LoadResourcesFromFiles(ctx context.Context, input, rootPath string) map[string][]workloadinterface.IMetadata {
|
||||
files, errs := listFiles(input)
|
||||
if len(errs) > 0 {
|
||||
logger.L().Error(fmt.Sprintf("%v", errs))
|
||||
logger.L().Ctx(ctx).Error(fmt.Sprintf("%v", errs))
|
||||
}
|
||||
if len(files) == 0 {
|
||||
return nil
|
||||
@@ -68,7 +108,7 @@ func LoadResourcesFromFiles(input, rootPath string) map[string][]workloadinterfa
|
||||
|
||||
workloads, errs := loadFiles(rootPath, files)
|
||||
if len(errs) > 0 {
|
||||
logger.L().Error(fmt.Sprintf("%v", errs))
|
||||
logger.L().Ctx(ctx).Error(fmt.Sprintf("%v", errs))
|
||||
}
|
||||
|
||||
return workloads
|
||||
@@ -100,9 +140,9 @@ func loadFiles(rootPath string, filePaths []string) (map[string][]workloadinterf
|
||||
for j := range w {
|
||||
lw := localworkload.NewLocalWorkload(w[j].GetObject())
|
||||
if relPath, err := filepath.Rel(rootPath, path); err == nil {
|
||||
lw.SetPath(relPath)
|
||||
lw.SetPath(fmt.Sprintf("%s:%d", relPath, j))
|
||||
} else {
|
||||
lw.SetPath(path)
|
||||
lw.SetPath(fmt.Sprintf("%s:%d", path, j))
|
||||
}
|
||||
wSlice = append(wSlice, lw)
|
||||
}
|
||||
@@ -186,8 +226,10 @@ func readYamlFile(yamlFile []byte) ([]workloadinterface.IMetadata, error) {
|
||||
}
|
||||
if obj, ok := j.(map[string]interface{}); ok {
|
||||
if o := objectsenvelopes.NewObject(obj); o != nil {
|
||||
if o.GetKind() == "List" {
|
||||
yamlObjs = append(yamlObjs, handleListObject(o)...)
|
||||
if o.GetObjectType() == workloadinterface.TypeListWorkloads {
|
||||
if list := workloadinterface.NewListWorkloadsObj(o.GetObject()); list != nil {
|
||||
yamlObjs = append(yamlObjs, list.GetItems()...)
|
||||
}
|
||||
} else {
|
||||
yamlObjs = append(yamlObjs, o)
|
||||
}
|
||||
@@ -256,7 +298,7 @@ func glob(root, pattern string, onlyDirectories bool) ([]string, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// listing only directotries
|
||||
// listing only directories
|
||||
if onlyDirectories {
|
||||
if info.IsDir() {
|
||||
if matched, err := filepath.Match(pattern, filepath.Base(path)); err != nil {
|
||||
@@ -319,20 +361,3 @@ func GetFileFormat(filePath string) FileFormat {
|
||||
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,12 +1,13 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/armosec/opa-utils/objectsenvelopes/localworkload"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,7 @@ func TestListFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadResourcesFromFiles(t *testing.T) {
|
||||
workloads := LoadResourcesFromFiles(onlineBoutiquePath(), "")
|
||||
workloads := LoadResourcesFromFiles(context.TODO(), onlineBoutiquePath(), "")
|
||||
assert.Equal(t, 12, len(workloads))
|
||||
|
||||
for i, w := range workloads {
|
||||
@@ -44,7 +45,7 @@ func TestLoadResourcesFromFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadResourcesFromHelmCharts(t *testing.T) {
|
||||
sourceToWorkloads := LoadResourcesFromHelmCharts(helmChartPath())
|
||||
sourceToWorkloads, sourceToChartName := LoadResourcesFromHelmCharts(context.TODO(), helmChartPath())
|
||||
assert.Equal(t, 6, len(sourceToWorkloads))
|
||||
|
||||
for file, workloads := range sourceToWorkloads {
|
||||
@@ -52,6 +53,7 @@ func TestLoadResourcesFromHelmCharts(t *testing.T) {
|
||||
|
||||
w := workloads[0]
|
||||
assert.True(t, localworkload.IsTypeLocalWorkload(w.GetObject()), "Expected localworkload as object type")
|
||||
assert.Equal(t, "kubescape", sourceToChartName[file])
|
||||
|
||||
switch filepath.Base(file) {
|
||||
case "serviceaccount.yaml":
|
||||
|
||||
@@ -1,371 +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/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/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 CAREFUL
|
||||
armoERURL = "report.armo.cloud"
|
||||
armoBEURL = "api.armosec.io"
|
||||
armoFEURL = "cloud.armosec.io"
|
||||
armoAUTHURL = "auth.armosec.io"
|
||||
|
||||
armoStageERURL = "report-ks.eustage2.cyberarmorsoft.com"
|
||||
armoStageBEURL = "api-stage.armosec.io"
|
||||
armoStageFEURL = "armoui-stage.armosec.io"
|
||||
armoStageAUTHURL = "eggauth-stage.armosec.io"
|
||||
|
||||
armoDevERURL = "report.eudev3.cyberarmorsoft.com"
|
||||
armoDevBEURL = "api-dev.armosec.io"
|
||||
armoDevFEURL = "cloud-dev.armosec.io"
|
||||
armoDevAUTHURL = "eggauth-dev.armosec.io"
|
||||
)
|
||||
|
||||
// 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")
|
||||
SetARMOAPIConnector(NewARMOAPIProd())
|
||||
}
|
||||
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) GetAuthURL() string { return armoAPI.authURL }
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
// try with default scope
|
||||
respStr, err = armoAPI.Get(armoAPI.getAccountConfigDefault(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
|
||||
}
|
||||
@@ -8,11 +8,11 @@ type FeLoginData struct {
|
||||
type FeLoginResponse struct {
|
||||
Token string `json:"accessToken"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
ExpiresIn int32 `json:"expiresIn"`
|
||||
Expires string `json:"expires"`
|
||||
ExpiresIn int32 `json:"expiresIn"`
|
||||
}
|
||||
|
||||
type ArmoSelectCustomer struct {
|
||||
type KSCloudSelectCustomer struct {
|
||||
SelectedCustomerGuid string `json:"selectedCustomer"`
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/opa-utils/gitregostore"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
|
||||
"github.com/kubescape/regolibrary/gitregostore"
|
||||
)
|
||||
|
||||
// =======================================================================================================================
|
||||
@@ -22,11 +26,11 @@ func NewDownloadReleasedPolicy() *DownloadReleasedPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) GetControl(policyName string) (*reporthandling.Control, error) {
|
||||
func (drp *DownloadReleasedPolicy) GetControl(ID string) (*reporthandling.Control, error) {
|
||||
var control *reporthandling.Control
|
||||
var err error
|
||||
|
||||
control, err = drp.gs.GetOPAControl(policyName)
|
||||
control, err = drp.gs.GetOPAControlByID(ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -53,13 +57,29 @@ func (drp *DownloadReleasedPolicy) ListFrameworks() ([]string, error) {
|
||||
return drp.gs.GetOPAFrameworksNamesList()
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) ListControls(listType ListType) ([]string, error) {
|
||||
switch listType {
|
||||
case ListID:
|
||||
return drp.gs.GetOPAControlsIDsList()
|
||||
default:
|
||||
return drp.gs.GetOPAControlsNamesList()
|
||||
func (drp *DownloadReleasedPolicy) ListControls() ([]string, error) {
|
||||
controlsIDsList, err := drp.gs.GetOPAControlsIDsList()
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
controlsNamesList, err := drp.gs.GetOPAControlsNamesList()
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
controls, err := drp.gs.GetOPAControls()
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
var controlsFrameworksList [][]string
|
||||
for _, control := range controls {
|
||||
controlsFrameworksList = append(controlsFrameworksList, control.FrameworkNames)
|
||||
}
|
||||
controlsNamesWithIDsandFrameworksList := make([]string, len(controlsIDsList))
|
||||
// by design all slices have the same lengt
|
||||
for i := range controlsIDsList {
|
||||
controlsNamesWithIDsandFrameworksList[i] = fmt.Sprintf("%v|%v|%v", controlsIDsList[i], controlsNamesList[i], strings.Join(controlsFrameworksList[i], ","))
|
||||
}
|
||||
return controlsNamesWithIDsandFrameworksList, nil
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
||||
@@ -70,6 +90,14 @@ func (drp *DownloadReleasedPolicy) GetControlsInputs(clusterName string) (map[st
|
||||
return defaultConfigInputs.Settings.PostureControlInputs, err
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) GetAttackTracks() ([]v1alpha1.AttackTrack, error) {
|
||||
attackTracks, err := drp.gs.GetAttackTracks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return attackTracks, err
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) SetRegoObjects() error {
|
||||
fwNames, err := drp.gs.GetOPAFrameworksNamesList()
|
||||
if len(fwNames) != 0 && err == nil {
|
||||
@@ -78,15 +106,10 @@ func (drp *DownloadReleasedPolicy) SetRegoObjects() error {
|
||||
return drp.gs.SetRegoObjects()
|
||||
}
|
||||
|
||||
func isNativeFramework(framework string) bool {
|
||||
return contains(NativeFrameworks, framework)
|
||||
}
|
||||
|
||||
func contains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if strings.EqualFold(v, str) {
|
||||
return true
|
||||
}
|
||||
func (drp *DownloadReleasedPolicy) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||
exceptions, err := drp.gs.GetSystemPostureExceptionPolicies()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return false
|
||||
return exceptions, nil
|
||||
}
|
||||
|
||||
170
core/cautils/getter/downloadreleasedpolicy_test.go
Normal file
170
core/cautils/getter/downloadreleasedpolicy_test.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestReleasedPolicy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := NewDownloadReleasedPolicy()
|
||||
|
||||
t.Run("should initialize objects", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// acquire from github or from local fixture
|
||||
hydrateReleasedPolicyFromMock(t, p)
|
||||
|
||||
require.NoError(t, p.SetRegoObjects())
|
||||
|
||||
t.Run("with ListControls", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
controlIDs, err := p.ListControls()
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, controlIDs)
|
||||
|
||||
sampleSize := min(len(controlIDs), 10)
|
||||
|
||||
for _, toPin := range controlIDs[:sampleSize] {
|
||||
// Example of a returned "ID": `C-0154|Ensure_that_the_--client-cert-auth_argument_is_set_to_true|`
|
||||
controlString := toPin
|
||||
parts := strings.Split(controlString, "|")
|
||||
controlID := parts[0]
|
||||
|
||||
t.Run(fmt.Sprintf("with GetControl(%q)", controlID), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctrl, err := p.GetControl(controlID)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, ctrl)
|
||||
require.Equal(t, controlID, ctrl.ControlID)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("with unknown GetControl()", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctrl, err := p.GetControl("zork")
|
||||
require.Error(t, err)
|
||||
require.Nil(t, ctrl)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with GetFrameworks", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
frameworks, err := p.GetFrameworks()
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, frameworks)
|
||||
|
||||
for _, toPin := range frameworks {
|
||||
framework := toPin
|
||||
require.NotEmpty(t, framework)
|
||||
require.NotEmpty(t, framework.Name)
|
||||
|
||||
t.Run(fmt.Sprintf("with GetFramework(%q)", framework.Name), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fw, err := p.GetFramework(framework.Name)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, fw)
|
||||
|
||||
require.EqualValues(t, framework, *fw)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("with unknown GetFramework()", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctrl, err := p.GetFramework("zork")
|
||||
require.Error(t, err)
|
||||
require.Nil(t, ctrl)
|
||||
})
|
||||
|
||||
t.Run("with ListFrameworks", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
frameworkIDs, err := p.ListFrameworks()
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, frameworkIDs)
|
||||
|
||||
require.Len(t, frameworkIDs, len(frameworks))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
t.Run("with GetControlsInput", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
controlInputs, err := p.GetControlsInputs("") // NOTE: cluster name currently unused
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, controlInputs)
|
||||
})
|
||||
|
||||
t.Run("with GetAttackTracks", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
attackTracks, err := p.GetAttackTracks()
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, attackTracks)
|
||||
})
|
||||
|
||||
t.Run("with GetExceptions", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
exceptions, err := p.GetExceptions("") // NOTE: cluster name currently unused
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, exceptions)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func hydrateReleasedPolicyFromMock(t testing.TB, p *DownloadReleasedPolicy) {
|
||||
regoFile := testRegoFile("policy")
|
||||
|
||||
if _, err := os.Stat(regoFile); errors.Is(err, fs.ErrNotExist) {
|
||||
// retrieve fixture from latest released policy from github.
|
||||
//
|
||||
// NOTE: to update the mock, just delete the testdata/policy.json file and run the tests again.
|
||||
t.Logf("updating fixture file %q from github", regoFile)
|
||||
|
||||
require.NoError(t, p.SetRegoObjects())
|
||||
require.NotNil(t, p.gs)
|
||||
|
||||
require.NoError(t,
|
||||
SaveInFile(p.gs, regoFile),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// we have a mock fixture: load this rather than calling github
|
||||
t.Logf("populating rego policy from fixture file %q", regoFile)
|
||||
buf, err := os.ReadFile(regoFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t,
|
||||
json.Unmarshal(buf, p.gs),
|
||||
)
|
||||
}
|
||||
|
||||
func testRegoFile(framework string) string {
|
||||
return filepath.Join(currentDir(), "testdata", fmt.Sprintf("%s.json", framework))
|
||||
}
|
||||
42
core/cautils/getter/gcpcloudapi.go
Normal file
42
core/cautils/getter/gcpcloudapi.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
containeranalysis "cloud.google.com/go/containeranalysis/apiv1"
|
||||
)
|
||||
|
||||
type GCPCloudAPI struct {
|
||||
credentialsPath string
|
||||
context context.Context
|
||||
client *containeranalysis.Client
|
||||
projectID string
|
||||
credentialsCheck bool
|
||||
}
|
||||
|
||||
func GetGlobalGCPCloudAPIConnector() *GCPCloudAPI {
|
||||
|
||||
if os.Getenv("KS_GCP_CREDENTIALS_PATH") == "" || os.Getenv("KS_GCP_PROJECT_ID") == "" {
|
||||
return &GCPCloudAPI{
|
||||
credentialsCheck: false,
|
||||
}
|
||||
} else {
|
||||
return &GCPCloudAPI{
|
||||
context: context.Background(),
|
||||
credentialsPath: os.Getenv("KS_GCP_CREDENTIALS_PATH"),
|
||||
projectID: os.Getenv("KS_GCP_PROJECT_ID"),
|
||||
credentialsCheck: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *GCPCloudAPI) SetClient(client *containeranalysis.Client) {
|
||||
api.client = client
|
||||
}
|
||||
|
||||
func (api *GCPCloudAPI) GetCredentialsPath() string { return api.credentialsPath }
|
||||
func (api *GCPCloudAPI) GetClient() *containeranalysis.Client { return api.client }
|
||||
func (api *GCPCloudAPI) GetProjectID() string { return api.projectID }
|
||||
func (api *GCPCloudAPI) GetCredentialsCheck() bool { return api.credentialsCheck }
|
||||
func (api *GCPCloudAPI) GetContext() context.Context { return api.context }
|
||||
@@ -2,22 +2,17 @@ package getter
|
||||
|
||||
import (
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
)
|
||||
|
||||
// supported listing
|
||||
type ListType string
|
||||
|
||||
const ListID ListType = "id"
|
||||
const ListName ListType = "name"
|
||||
|
||||
type IPolicyGetter interface {
|
||||
GetFramework(name string) (*reporthandling.Framework, error)
|
||||
GetFrameworks() ([]reporthandling.Framework, error)
|
||||
GetControl(name string) (*reporthandling.Control, error)
|
||||
GetControl(ID string) (*reporthandling.Control, error)
|
||||
|
||||
ListFrameworks() ([]string, error)
|
||||
ListControls(ListType) ([]string, error)
|
||||
ListControls() ([]string, error)
|
||||
}
|
||||
|
||||
type IExceptionsGetter interface {
|
||||
@@ -27,10 +22,18 @@ type IBackend interface {
|
||||
GetAccountID() string
|
||||
GetClientID() string
|
||||
GetSecretKey() string
|
||||
GetCloudReportURL() string
|
||||
GetCloudAPIURL() string
|
||||
GetCloudUIURL() string
|
||||
GetCloudAuthURL() string
|
||||
|
||||
SetAccountID(accountID string)
|
||||
SetClientID(clientID string)
|
||||
SetSecretKey(secretKey string)
|
||||
SetCloudReportURL(cloudReportURL string)
|
||||
SetCloudAPIURL(cloudAPIURL string)
|
||||
SetCloudUIURL(cloudUIURL string)
|
||||
SetCloudAuthURL(cloudAuthURL string)
|
||||
|
||||
GetTenant() (*TenantResponse, error)
|
||||
}
|
||||
@@ -38,3 +41,7 @@ type IBackend interface {
|
||||
type IControlsInputsGetter interface {
|
||||
GetControlsInputs(clusterName string) (map[string][]string, error)
|
||||
}
|
||||
|
||||
type IAttackTracksGetter interface {
|
||||
GetAttackTracks() ([]v1alpha1.AttackTrack, error)
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@ package getter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
@@ -21,18 +19,19 @@ func SaveInFile(policy interface{}, pathStr string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
|
||||
err = os.WriteFile(pathStr, encodedData, 0644) //nolint:gosec
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
pathDir := path.Dir(pathStr)
|
||||
if err := os.Mkdir(pathDir, 0744); err != nil {
|
||||
return err
|
||||
pathDir := filepath.Dir(pathStr)
|
||||
// pathDir could contain subdirectories
|
||||
if erm := os.MkdirAll(pathDir, 0755); erm != nil {
|
||||
return erm
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
|
||||
}
|
||||
err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
|
||||
err = os.WriteFile(pathStr, encodedData, 0644) //nolint:gosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -40,13 +39,6 @@ func SaveInFile(policy interface{}, pathStr string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// JSONDecoder returns JSON decoder for given string
|
||||
func JSONDecoder(origin string) *json.Decoder {
|
||||
dec := json.NewDecoder(strings.NewReader(origin))
|
||||
dec.UseNumber()
|
||||
return dec
|
||||
}
|
||||
|
||||
func HttpDelete(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) {
|
||||
|
||||
req, err := http.NewRequest("DELETE", fullURL, nil)
|
||||
@@ -65,6 +57,7 @@ func HttpDelete(httpClient *http.Client, fullURL string, headers map[string]stri
|
||||
}
|
||||
return respStr, nil
|
||||
}
|
||||
|
||||
func HttpGetter(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) {
|
||||
|
||||
req, err := http.NewRequest("GET", fullURL, nil)
|
||||
|
||||
68
core/cautils/getter/getpoliciesutils_test.go
Normal file
68
core/cautils/getter/getpoliciesutils_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetDefaultPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const name = "mine"
|
||||
|
||||
pth := GetDefaultPath(name)
|
||||
require.Equal(t, name, filepath.Base(pth))
|
||||
require.Equal(t, ".kubescape", filepath.Base(filepath.Dir(pth)))
|
||||
}
|
||||
|
||||
func TestSaveInFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir, err := os.MkdirTemp(".", "test")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = os.RemoveAll(dir)
|
||||
}()
|
||||
|
||||
policy := map[string]interface{}{
|
||||
"key": "value",
|
||||
"number": 1.00,
|
||||
}
|
||||
|
||||
t.Run("should save data as JSON (target folder exists)", func(t *testing.T) {
|
||||
target := filepath.Join(dir, "target.json")
|
||||
require.NoError(t, SaveInFile(policy, target))
|
||||
|
||||
buf, err := os.ReadFile(target)
|
||||
require.NoError(t, err)
|
||||
var retrieved interface{}
|
||||
require.NoError(t, json.Unmarshal(buf, &retrieved))
|
||||
|
||||
require.EqualValues(t, policy, retrieved)
|
||||
})
|
||||
|
||||
t.Run("should save data as JSON (new target folder)", func(t *testing.T) {
|
||||
target := filepath.Join(dir, "subdir", "target.json")
|
||||
require.NoError(t, SaveInFile(policy, target))
|
||||
|
||||
buf, err := os.ReadFile(target)
|
||||
require.NoError(t, err)
|
||||
var retrieved interface{}
|
||||
require.NoError(t, json.Unmarshal(buf, &retrieved))
|
||||
|
||||
require.EqualValues(t, policy, retrieved)
|
||||
})
|
||||
|
||||
t.Run("should error", func(t *testing.T) {
|
||||
badPolicy := map[string]interface{}{
|
||||
"key": "value",
|
||||
"number": 1.00,
|
||||
"err": func() {},
|
||||
}
|
||||
target := filepath.Join(dir, "error.json")
|
||||
require.Error(t, SaveInFile(badPolicy, target))
|
||||
})
|
||||
}
|
||||
26
core/cautils/getter/json.go
Normal file
26
core/cautils/getter/json.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
stdjson "encoding/json"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
var (
|
||||
json jsoniter.API
|
||||
)
|
||||
|
||||
func init() {
|
||||
// NOTE(fredbi): attention, this configuration rounds floats down to 6 digits
|
||||
// For finer-grained config, see: https://pkg.go.dev/github.com/json-iterator/go#section-readme
|
||||
json = jsoniter.ConfigFastest
|
||||
}
|
||||
|
||||
// JSONDecoder returns JSON decoder for given string
|
||||
func JSONDecoder(origin string) *stdjson.Decoder {
|
||||
dec := stdjson.NewDecoder(strings.NewReader(origin))
|
||||
dec.UseNumber()
|
||||
return dec
|
||||
}
|
||||
32
core/cautils/getter/json_test.go
Normal file
32
core/cautils/getter/json_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestJSONDecoder(t *testing.T) {
|
||||
t.Run("should decode json string", func(t *testing.T) {
|
||||
const input = `"xyz"`
|
||||
d := JSONDecoder(input)
|
||||
var receiver string
|
||||
require.NoError(t, d.Decode(&receiver))
|
||||
require.Equal(t, "xyz", receiver)
|
||||
})
|
||||
|
||||
t.Run("should decode json number", func(t *testing.T) {
|
||||
const input = `123.01`
|
||||
d := JSONDecoder(input)
|
||||
var receiver float64
|
||||
require.NoError(t, d.Decode(&receiver))
|
||||
require.Equal(t, 123.01, receiver)
|
||||
})
|
||||
|
||||
t.Run("requires json quotes", func(t *testing.T) {
|
||||
const input = `xyz`
|
||||
d := JSONDecoder(input)
|
||||
var receiver string
|
||||
require.Error(t, d.Decode(&receiver))
|
||||
})
|
||||
}
|
||||
379
core/cautils/getter/kscloudapi.go
Normal file
379
core/cautils/getter/kscloudapi.go
Normal file
@@ -0,0 +1,379 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
ksCloudERURL = "report.armo.cloud"
|
||||
ksCloudBEURL = "api.armosec.io"
|
||||
ksCloudFEURL = "cloud.armosec.io"
|
||||
ksCloudAUTHURL = "auth.armosec.io"
|
||||
|
||||
ksCloudStageERURL = "report-ks.eustage2.cyberarmorsoft.com"
|
||||
ksCloudStageBEURL = "api-stage.armosec.io"
|
||||
ksCloudStageFEURL = "armoui-stage.armosec.io"
|
||||
ksCloudStageAUTHURL = "eggauth-stage.armosec.io"
|
||||
|
||||
ksCloudDevERURL = "report.eudev3.cyberarmorsoft.com"
|
||||
ksCloudDevBEURL = "api-dev.armosec.io"
|
||||
ksCloudDevFEURL = "cloud-dev.armosec.io"
|
||||
ksCloudDevAUTHURL = "eggauth-dev.armosec.io"
|
||||
)
|
||||
|
||||
// KSCloudAPI allows accessing the API of the Kubescape Cloud offering
|
||||
type KSCloudAPI struct {
|
||||
httpClient *http.Client
|
||||
cloudAPIURL string
|
||||
cloudAuthURL string
|
||||
cloudReportURL string
|
||||
cloudUIURL string
|
||||
accountID string
|
||||
clientID string
|
||||
secretKey string
|
||||
authCookie string
|
||||
feToken FeLoginResponse
|
||||
loggedIn bool
|
||||
}
|
||||
|
||||
var globalKSCloudAPIConnector *KSCloudAPI
|
||||
|
||||
func SetKSCloudAPIConnector(ksCloudAPI *KSCloudAPI) {
|
||||
globalKSCloudAPIConnector = ksCloudAPI
|
||||
}
|
||||
|
||||
func GetKSCloudAPIConnector() *KSCloudAPI {
|
||||
if globalKSCloudAPIConnector == nil {
|
||||
SetKSCloudAPIConnector(NewKSCloudAPIProd())
|
||||
}
|
||||
return globalKSCloudAPIConnector
|
||||
}
|
||||
|
||||
func NewKSCloudAPIDev() *KSCloudAPI {
|
||||
apiObj := newKSCloudAPI()
|
||||
|
||||
apiObj.cloudAPIURL = ksCloudDevBEURL
|
||||
apiObj.cloudAuthURL = ksCloudDevAUTHURL
|
||||
apiObj.cloudReportURL = ksCloudDevERURL
|
||||
apiObj.cloudUIURL = ksCloudDevFEURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func NewKSCloudAPIProd() *KSCloudAPI {
|
||||
apiObj := newKSCloudAPI()
|
||||
|
||||
apiObj.cloudAPIURL = ksCloudBEURL
|
||||
apiObj.cloudReportURL = ksCloudERURL
|
||||
apiObj.cloudUIURL = ksCloudFEURL
|
||||
apiObj.cloudAuthURL = ksCloudAUTHURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func NewKSCloudAPIStaging() *KSCloudAPI {
|
||||
apiObj := newKSCloudAPI()
|
||||
|
||||
apiObj.cloudAPIURL = ksCloudStageBEURL
|
||||
apiObj.cloudReportURL = ksCloudStageERURL
|
||||
apiObj.cloudUIURL = ksCloudStageFEURL
|
||||
apiObj.cloudAuthURL = ksCloudStageAUTHURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func NewKSCloudAPICustomized(ksCloudERURL, ksCloudBEURL, ksCloudFEURL, ksCloudAUTHURL string) *KSCloudAPI {
|
||||
apiObj := newKSCloudAPI()
|
||||
|
||||
apiObj.cloudReportURL = ksCloudERURL
|
||||
apiObj.cloudAPIURL = ksCloudBEURL
|
||||
apiObj.cloudUIURL = ksCloudFEURL
|
||||
apiObj.cloudAuthURL = ksCloudAUTHURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func newKSCloudAPI() *KSCloudAPI {
|
||||
return &KSCloudAPI{
|
||||
httpClient: &http.Client{Timeout: time.Duration(61) * time.Second},
|
||||
loggedIn: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) Post(fullURL string, headers map[string]string, body []byte) (string, error) {
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
api.appendAuthHeaders(headers)
|
||||
return HttpPost(api.httpClient, fullURL, headers, body)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) Delete(fullURL string, headers map[string]string) (string, error) {
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
api.appendAuthHeaders(headers)
|
||||
return HttpDelete(api.httpClient, fullURL, headers)
|
||||
}
|
||||
func (api *KSCloudAPI) Get(fullURL string, headers map[string]string) (string, error) {
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
api.appendAuthHeaders(headers)
|
||||
return HttpGetter(api.httpClient, fullURL, headers)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) GetAccountID() string { return api.accountID }
|
||||
func (api *KSCloudAPI) IsLoggedIn() bool { return api.loggedIn }
|
||||
func (api *KSCloudAPI) GetClientID() string { return api.clientID }
|
||||
func (api *KSCloudAPI) GetSecretKey() string { return api.secretKey }
|
||||
func (api *KSCloudAPI) GetCloudReportURL() string { return api.cloudReportURL }
|
||||
func (api *KSCloudAPI) GetCloudAPIURL() string { return api.cloudAPIURL }
|
||||
func (api *KSCloudAPI) GetCloudUIURL() string { return api.cloudUIURL }
|
||||
func (api *KSCloudAPI) GetCloudAuthURL() string { return api.cloudAuthURL }
|
||||
|
||||
func (api *KSCloudAPI) SetAccountID(accountID string) { api.accountID = accountID }
|
||||
func (api *KSCloudAPI) SetClientID(clientID string) { api.clientID = clientID }
|
||||
func (api *KSCloudAPI) SetSecretKey(secretKey string) { api.secretKey = secretKey }
|
||||
func (api *KSCloudAPI) SetCloudReportURL(cloudReportURL string) { api.cloudReportURL = cloudReportURL }
|
||||
func (api *KSCloudAPI) SetCloudAPIURL(cloudAPIURL string) { api.cloudAPIURL = cloudAPIURL }
|
||||
func (api *KSCloudAPI) SetCloudUIURL(cloudUIURL string) { api.cloudUIURL = cloudUIURL }
|
||||
func (api *KSCloudAPI) SetCloudAuthURL(cloudAuthURL string) { api.cloudAuthURL = cloudAuthURL }
|
||||
|
||||
func (api *KSCloudAPI) GetAttackTracks() ([]v1alpha1.AttackTrack, error) {
|
||||
respStr, err := api.Get(api.getAttackTracksURL(), nil)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
attackTracks := []v1alpha1.AttackTrack{}
|
||||
if err = JSONDecoder(respStr).Decode(&attackTracks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return attackTracks, err
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) GetFramework(name string) (*reporthandling.Framework, error) {
|
||||
respStr, err := api.Get(api.getFrameworkURL(name), nil)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
framework := &reporthandling.Framework{}
|
||||
if err = JSONDecoder(respStr).Decode(framework); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return framework, err
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) GetFrameworks() ([]reporthandling.Framework, error) {
|
||||
respStr, err := api.Get(api.getListFrameworkURL(), nil)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
frameworks := []reporthandling.Framework{}
|
||||
if err = JSONDecoder(respStr).Decode(&frameworks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return frameworks, err
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) GetControl(ID string) (*reporthandling.Control, error) {
|
||||
return nil, fmt.Errorf("control api is not public")
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||
exceptions := []armotypes.PostureExceptionPolicy{}
|
||||
|
||||
respStr, err := api.Get(api.getExceptionsURL(clusterName), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = JSONDecoder(respStr).Decode(&exceptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exceptions, nil
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) GetTenant() (*TenantResponse, error) {
|
||||
url := api.getAccountURL()
|
||||
if api.accountID != "" {
|
||||
url = fmt.Sprintf("%s?customerGUID=%s", url, api.accountID)
|
||||
}
|
||||
respStr, err := api.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 != "" {
|
||||
api.accountID = tenant.TenantID
|
||||
}
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
// ControlsInputs // map[<control name>][<input arguments>]
|
||||
func (api *KSCloudAPI) GetAccountConfig(clusterName string) (*armotypes.CustomerConfig, error) {
|
||||
accountConfig := &armotypes.CustomerConfig{}
|
||||
if api.accountID == "" {
|
||||
return accountConfig, nil
|
||||
}
|
||||
respStr, err := api.Get(api.getAccountConfig(clusterName), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = JSONDecoder(respStr).Decode(&accountConfig); err != nil {
|
||||
// try with default scope
|
||||
respStr, err = api.Get(api.getAccountConfigDefault(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 (api *KSCloudAPI) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
||||
accountConfig, err := api.GetAccountConfig(clusterName)
|
||||
if err == nil {
|
||||
return accountConfig.Settings.PostureControlInputs, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) ListCustomFrameworks() ([]string, error) {
|
||||
respStr, err := api.Get(api.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 (api *KSCloudAPI) ListFrameworks() ([]string, error) {
|
||||
respStr, err := api.Get(api.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 (api *KSCloudAPI) ListControls() ([]string, error) {
|
||||
return nil, fmt.Errorf("control api is not public")
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) PostExceptions(exceptions []armotypes.PostureExceptionPolicy) error {
|
||||
|
||||
for i := range exceptions {
|
||||
ex, err := json.Marshal(exceptions[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = api.Post(api.exceptionsURL(""), map[string]string{"Content-Type": "application/json"}, ex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) DeleteException(exceptionName string) error {
|
||||
|
||||
_, err := api.Delete(api.exceptionsURL(exceptionName), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (api *KSCloudAPI) Login() error {
|
||||
if api.accountID == "" {
|
||||
return fmt.Errorf("failed to login, missing accountID")
|
||||
}
|
||||
if api.clientID == "" {
|
||||
return fmt.Errorf("failed to login, missing clientID")
|
||||
}
|
||||
if api.secretKey == "" {
|
||||
return fmt.Errorf("failed to login, missing secretKey")
|
||||
}
|
||||
|
||||
// init URLs
|
||||
feLoginData := FeLoginData{ClientId: api.clientID, Secret: api.secretKey}
|
||||
body, _ := json.Marshal(feLoginData)
|
||||
|
||||
resp, err := http.Post(api.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 := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var feLoginResponse FeLoginResponse
|
||||
|
||||
if err = json.Unmarshal(responseBody, &feLoginResponse); err != nil {
|
||||
return err
|
||||
}
|
||||
api.feToken = feLoginResponse
|
||||
|
||||
/* Now we have JWT */
|
||||
|
||||
api.authCookie, err = api.getAuthCookie()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
api.loggedIn = true
|
||||
return nil
|
||||
}
|
||||
273
core/cautils/getter/kscloudapi_mocks_test.go
Normal file
273
core/cautils/getter/kscloudapi_mocks_test.go
Normal file
@@ -0,0 +1,273 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
)
|
||||
|
||||
func mockAttackTracks() []v1alpha1.AttackTrack {
|
||||
return []v1alpha1.AttackTrack{
|
||||
{
|
||||
ApiVersion: "v1",
|
||||
Kind: "track",
|
||||
Metadata: map[string]interface{}{"label": "name"},
|
||||
Spec: v1alpha1.AttackTrackSpecification{
|
||||
Version: "v2",
|
||||
Description: "a mock",
|
||||
Data: v1alpha1.AttackTrackStep{
|
||||
Name: "track1",
|
||||
Description: "mock-step",
|
||||
SubSteps: []v1alpha1.AttackTrackStep{
|
||||
{
|
||||
Name: "track1",
|
||||
Description: "mock-step",
|
||||
Controls: []v1alpha1.IAttackTrackControl{
|
||||
mockControlPtr("control-1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Controls: []v1alpha1.IAttackTrackControl{
|
||||
mockControlPtr("control-2"),
|
||||
mockControlPtr("control-3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ApiVersion: "v1",
|
||||
Kind: "track",
|
||||
Metadata: map[string]interface{}{"label": "stuff"},
|
||||
Spec: v1alpha1.AttackTrackSpecification{
|
||||
Version: "v1",
|
||||
Description: "another mock",
|
||||
Data: v1alpha1.AttackTrackStep{
|
||||
Name: "track2",
|
||||
Description: "mock-step2",
|
||||
SubSteps: []v1alpha1.AttackTrackStep{
|
||||
{
|
||||
Name: "track3",
|
||||
Description: "mock-step",
|
||||
Controls: []v1alpha1.IAttackTrackControl{
|
||||
mockControlPtr("control-4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Controls: []v1alpha1.IAttackTrackControl{
|
||||
mockControlPtr("control-5"),
|
||||
mockControlPtr("control-6"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mockFrameworks() []reporthandling.Framework {
|
||||
id1s := []string{"control-1", "control-2"}
|
||||
id2s := []string{"control-3", "control-4"}
|
||||
id3s := []string{"control-5", "control-6"}
|
||||
|
||||
return []reporthandling.Framework{
|
||||
{
|
||||
PortalBase: armotypes.PortalBase{
|
||||
Name: "mock-1",
|
||||
},
|
||||
CreationTime: "now",
|
||||
Description: "mock-1",
|
||||
Controls: []reporthandling.Control{
|
||||
mockControl("control-1"),
|
||||
mockControl("control-2"),
|
||||
},
|
||||
ControlsIDs: &id1s,
|
||||
SubSections: map[string]*reporthandling.FrameworkSubSection{
|
||||
"section1": {
|
||||
ID: "section-id",
|
||||
ControlIDs: id1s,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
PortalBase: armotypes.PortalBase{
|
||||
Name: "mock-2",
|
||||
},
|
||||
CreationTime: "then",
|
||||
Description: "mock-2",
|
||||
Controls: []reporthandling.Control{
|
||||
mockControl("control-3"),
|
||||
mockControl("control-4"),
|
||||
},
|
||||
ControlsIDs: &id2s,
|
||||
SubSections: map[string]*reporthandling.FrameworkSubSection{
|
||||
"section2": {
|
||||
ID: "section-id",
|
||||
ControlIDs: id2s,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
PortalBase: armotypes.PortalBase{
|
||||
Name: "nsa",
|
||||
},
|
||||
CreationTime: "tomorrow",
|
||||
Description: "nsa mock",
|
||||
Controls: []reporthandling.Control{
|
||||
mockControl("control-5"),
|
||||
mockControl("control-6"),
|
||||
},
|
||||
ControlsIDs: &id3s,
|
||||
SubSections: map[string]*reporthandling.FrameworkSubSection{
|
||||
"section2": {
|
||||
ID: "section-id",
|
||||
ControlIDs: id3s,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mockControl(controlID string) reporthandling.Control {
|
||||
return reporthandling.Control{
|
||||
ControlID: controlID,
|
||||
}
|
||||
}
|
||||
func mockControlPtr(controlID string) *reporthandling.Control {
|
||||
val := mockControl(controlID)
|
||||
|
||||
return &val
|
||||
}
|
||||
|
||||
func mockExceptions() []armotypes.PostureExceptionPolicy {
|
||||
return []armotypes.PostureExceptionPolicy{
|
||||
{
|
||||
PolicyType: "postureExceptionPolicy",
|
||||
CreationTime: "now",
|
||||
Actions: []armotypes.PostureExceptionPolicyActions{
|
||||
"alertOnly",
|
||||
},
|
||||
Resources: []armotypes.PortalDesignator{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: map[string]string{
|
||||
"kind": "Pod",
|
||||
"name": "coredns-[A-Za-z0-9]+-[A-Za-z0-9]+",
|
||||
"namespace": "kube-system",
|
||||
},
|
||||
},
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: map[string]string{
|
||||
"kind": "Pod",
|
||||
"name": "etcd-.*",
|
||||
"namespace": "kube-system",
|
||||
},
|
||||
},
|
||||
},
|
||||
PosturePolicies: []armotypes.PosturePolicy{
|
||||
{
|
||||
FrameworkName: "MITRE",
|
||||
ControlID: "C-.*",
|
||||
},
|
||||
{
|
||||
FrameworkName: "another-framework",
|
||||
ControlID: "a regexp",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
PolicyType: "postureExceptionPolicy",
|
||||
CreationTime: "then",
|
||||
Actions: []armotypes.PostureExceptionPolicyActions{
|
||||
"alertOnly",
|
||||
},
|
||||
Resources: []armotypes.PortalDesignator{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: map[string]string{
|
||||
"kind": "Deployment",
|
||||
"name": "my-regexp",
|
||||
},
|
||||
},
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: map[string]string{
|
||||
"kind": "Secret",
|
||||
"name": "another-regexp",
|
||||
},
|
||||
},
|
||||
},
|
||||
PosturePolicies: []armotypes.PosturePolicy{
|
||||
{
|
||||
FrameworkName: "yet-another-framework",
|
||||
ControlID: "a regexp",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mockTenantResponse() *TenantResponse {
|
||||
return &TenantResponse{
|
||||
TenantID: "id",
|
||||
Token: "token",
|
||||
Expires: "expiry-time",
|
||||
AdminMail: "admin@example.com",
|
||||
}
|
||||
}
|
||||
|
||||
func mockCustomerConfig(cluster, scope string) func() *armotypes.CustomerConfig {
|
||||
if cluster == "" {
|
||||
cluster = "my-cluster"
|
||||
}
|
||||
|
||||
if scope == "" {
|
||||
scope = "default"
|
||||
}
|
||||
|
||||
return func() *armotypes.CustomerConfig {
|
||||
return &armotypes.CustomerConfig{
|
||||
Name: "user",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "value",
|
||||
},
|
||||
Scope: armotypes.PortalDesignator{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: map[string]string{
|
||||
"kind": "Cluster",
|
||||
"name": cluster,
|
||||
"scope": scope,
|
||||
},
|
||||
},
|
||||
Settings: armotypes.Settings{
|
||||
PostureControlInputs: map[string][]string{
|
||||
"inputs-1": {"x1", "y2"},
|
||||
"inputs-2": {"x2", "y2"},
|
||||
},
|
||||
PostureScanConfig: armotypes.PostureScanConfig{
|
||||
ScanFrequency: armotypes.ScanFrequency("weekly"),
|
||||
},
|
||||
VulnerabilityScanConfig: armotypes.VulnerabilityScanConfig{
|
||||
ScanFrequency: armotypes.ScanFrequency("daily"),
|
||||
CriticalPriorityThreshold: 1,
|
||||
HighPriorityThreshold: 2,
|
||||
MediumPriorityThreshold: 3,
|
||||
ScanNewDeployment: true,
|
||||
AllowlistRegistries: []string{"a", "b"},
|
||||
BlocklistRegistries: []string{"c", "d"},
|
||||
},
|
||||
SlackConfigurations: armotypes.SlackSettings{
|
||||
Token: "slack-token",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mockLoginResponse() *FeLoginResponse {
|
||||
return &FeLoginResponse{
|
||||
Token: "access-token",
|
||||
RefreshToken: "refresh-token",
|
||||
Expires: "expiry-time",
|
||||
ExpiresIn: 123,
|
||||
}
|
||||
}
|
||||
1096
core/cautils/getter/kscloudapi_test.go
Normal file
1096
core/cautils/getter/kscloudapi_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,21 +2,20 @@ package getter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var NativeFrameworks = []string{"nsa", "mitre", "armobest", "devopsbest"}
|
||||
var NativeFrameworks = []string{"allcontrols", "nsa", "mitre"}
|
||||
|
||||
func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
|
||||
func (api *KSCloudAPI) getFrameworkURL(frameworkName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/armoFrameworks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
q.Add("customerGUID", api.getCustomerGUIDFallBack())
|
||||
if isNativeFramework(frameworkName) {
|
||||
q.Add("frameworkName", strings.ToUpper(frameworkName))
|
||||
} else {
|
||||
@@ -28,23 +27,34 @@ func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getListFrameworkURL() string {
|
||||
func (api *KSCloudAPI) getAttackTracksURL() string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
|
||||
u.Path = "api/v1/armoFrameworks"
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/attackTracks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
q.Add("customerGUID", api.getCustomerGUIDFallBack())
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
func (armoAPI *ArmoAPI) getExceptionsURL(clusterName string) string {
|
||||
|
||||
func (api *KSCloudAPI) getListFrameworkURL() string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/armoFrameworks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", api.getCustomerGUIDFallBack())
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
func (api *KSCloudAPI) getExceptionsURL(clusterName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/armoPostureExceptions"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
q.Add("customerGUID", api.getCustomerGUIDFallBack())
|
||||
// if clusterName != "" { // TODO - fix customer name support in Armo BE
|
||||
// q.Add("clusterName", clusterName)
|
||||
// }
|
||||
@@ -53,13 +63,13 @@ func (armoAPI *ArmoAPI) getExceptionsURL(clusterName string) string {
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) exceptionsURL(exceptionsPolicyName string) string {
|
||||
func (api *KSCloudAPI) exceptionsURL(exceptionsPolicyName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/postureExceptionPolicy"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
q.Add("customerGUID", api.getCustomerGUIDFallBack())
|
||||
if exceptionsPolicyName != "" { // for delete
|
||||
q.Add("policyName", exceptionsPolicyName)
|
||||
}
|
||||
@@ -69,19 +79,19 @@ func (armoAPI *ArmoAPI) exceptionsURL(exceptionsPolicyName string) string {
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getAccountConfigDefault(clusterName string) string {
|
||||
config := armoAPI.getAccountConfig(clusterName)
|
||||
func (api *KSCloudAPI) getAccountConfigDefault(clusterName string) string {
|
||||
config := api.getAccountConfig(clusterName)
|
||||
url := config + "&scope=customer"
|
||||
return url
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getAccountConfig(clusterName string) string {
|
||||
func (api *KSCloudAPI) getAccountConfig(clusterName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/armoCustomerConfiguration"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
q.Add("customerGUID", api.getCustomerGUIDFallBack())
|
||||
if clusterName != "" { // TODO - fix customer name support in Armo BE
|
||||
q.Add("clusterName", clusterName)
|
||||
}
|
||||
@@ -90,49 +100,49 @@ func (armoAPI *ArmoAPI) getAccountConfig(clusterName string) string {
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getAccountURL() string {
|
||||
func (api *KSCloudAPI) getAccountURL() string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/createTenant"
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getApiToken() string {
|
||||
func (api *KSCloudAPI) getApiToken() string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(armoAPI.GetAuthURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAuthURL())
|
||||
u.Path = "identity/resources/auth/v1/api-token"
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getOpenidCustomers() string {
|
||||
func (api *KSCloudAPI) getOpenidCustomers() string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/openid_customers"
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getAuthCookie() (string, error) {
|
||||
selectCustomer := ArmoSelectCustomer{SelectedCustomerGuid: armoAPI.accountID}
|
||||
func (api *KSCloudAPI) getAuthCookie() (string, error) {
|
||||
selectCustomer := KSCloudSelectCustomer{SelectedCustomerGuid: api.accountID}
|
||||
requestBody, _ := json.Marshal(selectCustomer)
|
||||
client := &http.Client{}
|
||||
httpRequest, err := http.NewRequest(http.MethodPost, armoAPI.getOpenidCustomers(), bytes.NewBuffer(requestBody))
|
||||
httpRequest, err := http.NewRequest(http.MethodPost, api.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))
|
||||
httpRequest.Header.Set("Authorization", fmt.Sprintf("Bearer %s", api.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)
|
||||
return "", fmt.Errorf("failed to get cookie from %s: status %d", api.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())
|
||||
return "", fmt.Errorf("no cookie field in response from %s", api.getOpenidCustomers())
|
||||
}
|
||||
|
||||
authCookie := ""
|
||||
@@ -144,24 +154,24 @@ func (armoAPI *ArmoAPI) getAuthCookie() (string, error) {
|
||||
}
|
||||
|
||||
if len(authCookie) == 0 {
|
||||
return "", fmt.Errorf("no auth cookie field in response from %s", armoAPI.getOpenidCustomers())
|
||||
return "", fmt.Errorf("no auth cookie field in response from %s", api.getOpenidCustomers())
|
||||
}
|
||||
|
||||
return authCookie, nil
|
||||
}
|
||||
func (armoAPI *ArmoAPI) appendAuthHeaders(headers map[string]string) {
|
||||
func (api *KSCloudAPI) appendAuthHeaders(headers map[string]string) {
|
||||
|
||||
if armoAPI.feToken.Token != "" {
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", armoAPI.feToken.Token)
|
||||
if api.feToken.Token != "" {
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", api.feToken.Token)
|
||||
}
|
||||
if armoAPI.authCookie != "" {
|
||||
headers["Cookie"] = fmt.Sprintf("auth=%s", armoAPI.authCookie)
|
||||
if api.authCookie != "" {
|
||||
headers["Cookie"] = fmt.Sprintf("auth=%s", api.authCookie)
|
||||
}
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getCustomerGUIDFallBack() string {
|
||||
if armoAPI.accountID != "" {
|
||||
return armoAPI.accountID
|
||||
func (api *KSCloudAPI) getCustomerGUIDFallBack() string {
|
||||
if api.accountID != "" {
|
||||
return api.accountID
|
||||
}
|
||||
return "11111111-1111-1111-1111-111111111111"
|
||||
}
|
||||
@@ -174,3 +184,16 @@ func parseHost(host string) (string, string) {
|
||||
// default scheme
|
||||
return "https", strings.Replace(host, "https://", "", 1)
|
||||
}
|
||||
|
||||
func isNativeFramework(framework string) bool {
|
||||
return contains(NativeFrameworks, framework)
|
||||
}
|
||||
|
||||
func contains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if strings.EqualFold(v, str) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,20 +1,33 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
)
|
||||
|
||||
// =======================================================================================================================
|
||||
// ============================================== LoadPolicy =============================================================
|
||||
// =======================================================================================================================
|
||||
var DefaultLocalStore = getCacheDir()
|
||||
var (
|
||||
DefaultLocalStore = getCacheDir()
|
||||
|
||||
ErrNotImplemented = errors.New("feature is currently not supported")
|
||||
ErrNotFound = errors.New("name not found")
|
||||
ErrNameRequired = errors.New("missing required input framework name")
|
||||
ErrIDRequired = errors.New("missing required input control ID")
|
||||
ErrFrameworkNotMatching = errors.New("framework from file not matching")
|
||||
ErrControlNotMatching = errors.New("framework from file not matching")
|
||||
|
||||
_ IPolicyGetter = &LoadPolicy{}
|
||||
_ IExceptionsGetter = &LoadPolicy{}
|
||||
)
|
||||
|
||||
func getCacheDir() string {
|
||||
defaultDirPath := ".kubescape"
|
||||
@@ -24,120 +37,225 @@ func getCacheDir() string {
|
||||
return defaultDirPath
|
||||
}
|
||||
|
||||
// Load policies from a local repository
|
||||
// LoadPolicy loads policies from a local repository.
|
||||
type LoadPolicy struct {
|
||||
filePaths []string
|
||||
}
|
||||
|
||||
// NewLoadPolicy builds a LoadPolicy.
|
||||
func NewLoadPolicy(filePaths []string) *LoadPolicy {
|
||||
return &LoadPolicy{
|
||||
filePaths: filePaths,
|
||||
}
|
||||
}
|
||||
|
||||
// Return control from file
|
||||
func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, error) {
|
||||
// GetControl returns a control from the policy file.
|
||||
func (lp *LoadPolicy) GetControl(controlID string) (*reporthandling.Control, error) {
|
||||
if controlID == "" {
|
||||
return nil, ErrIDRequired
|
||||
}
|
||||
|
||||
control := &reporthandling.Control{}
|
||||
// NOTE: this assumes that only the first path contains either a valid control descriptor or a framework descriptor
|
||||
filePath := lp.filePath()
|
||||
f, err := os.ReadFile(filePath)
|
||||
buf, err := os.ReadFile(filePath)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(f, control); err != nil {
|
||||
return control, err
|
||||
// check if the file is a control descriptor: a ControlID field is populated.
|
||||
var control reporthandling.Control
|
||||
if err = json.Unmarshal(buf, &control); err == nil && control.ControlID != "" {
|
||||
if strings.EqualFold(controlID, control.ControlID) {
|
||||
return &control, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("controlID: %s: %w", controlID, ErrControlNotMatching)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// check if the file is a framework descriptor
|
||||
var framework reporthandling.Framework
|
||||
if err = json.Unmarshal(buf, &framework); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, toPin := range framework.Controls {
|
||||
ctrl := toPin
|
||||
|
||||
if strings.EqualFold(ctrl.ControlID, controlID) {
|
||||
return &ctrl, nil
|
||||
}
|
||||
}
|
||||
return control, err
|
||||
|
||||
return nil, fmt.Errorf("controlID: %s: %w", controlID, ErrControlNotMatching)
|
||||
}
|
||||
|
||||
// GetFramework retrieves a framework configuration from the policy paths.
|
||||
func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) {
|
||||
framework := &reporthandling.Framework{}
|
||||
var err error
|
||||
if frameworkName == "" {
|
||||
return nil, ErrNameRequired
|
||||
}
|
||||
|
||||
for _, filePath := range lp.filePaths {
|
||||
f, err := os.ReadFile(filePath)
|
||||
buf, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(f, framework); err != nil {
|
||||
return framework, err
|
||||
var framework reporthandling.Framework
|
||||
if err = json.Unmarshal(buf, &framework); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.EqualFold(frameworkName, framework.Name) {
|
||||
break
|
||||
return &framework, nil
|
||||
}
|
||||
}
|
||||
if frameworkName != "" && !strings.EqualFold(frameworkName, framework.Name) {
|
||||
|
||||
return nil, fmt.Errorf("framework from file not matching")
|
||||
}
|
||||
return framework, err
|
||||
return nil, fmt.Errorf("framework: %s: %w", frameworkName, ErrFrameworkNotMatching)
|
||||
}
|
||||
|
||||
// GetFrameworks returns all configured framework descriptors.
|
||||
func (lp *LoadPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
|
||||
frameworks := []reporthandling.Framework{}
|
||||
var err error
|
||||
return frameworks, err
|
||||
}
|
||||
frameworks := make([]reporthandling.Framework, 0, 10)
|
||||
seenFws := make(map[string]struct{})
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
buf, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var framework reporthandling.Framework
|
||||
if err = json.Unmarshal(buf, &framework); err != nil {
|
||||
// ignore invalid framework files
|
||||
continue
|
||||
}
|
||||
|
||||
// dedupe
|
||||
_, alreadyLoaded := seenFws[framework.Name]
|
||||
if alreadyLoaded {
|
||||
continue
|
||||
}
|
||||
|
||||
seenFws[framework.Name] = struct{}{}
|
||||
frameworks = append(frameworks, framework)
|
||||
}
|
||||
return fwNames, nil
|
||||
|
||||
return frameworks, nil
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) ListControls(listType ListType) ([]string, error) {
|
||||
// TODO - Support
|
||||
return []string{}, fmt.Errorf("loading controls list from file is not supported")
|
||||
// ListFrameworks lists the names of all configured frameworks in this policy.
|
||||
func (lp *LoadPolicy) ListFrameworks() ([]string, error) {
|
||||
frameworkNames := make([]string, 0, 10)
|
||||
|
||||
for _, f := range lp.filePaths {
|
||||
buf, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var framework reporthandling.Framework
|
||||
if err := json.Unmarshal(buf, &framework); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if framework.Name == "" || contains(frameworkNames, framework.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
frameworkNames = append(frameworkNames, framework.Name)
|
||||
}
|
||||
|
||||
return frameworkNames, nil
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||
// ListControls returns the list of controls for this framework.
|
||||
//
|
||||
// At this moment, controls are listed for one single configured framework.
|
||||
func (lp *LoadPolicy) ListControls() ([]string, error) {
|
||||
controlIDs := make([]string, 0, 100)
|
||||
filePath := lp.filePath()
|
||||
exception := []armotypes.PostureExceptionPolicy{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
buf, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(f, &exception)
|
||||
var framework reporthandling.Framework
|
||||
if err = json.Unmarshal(buf, &framework); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ctrl := range framework.Controls {
|
||||
controlIDs = append(controlIDs, ctrl.ControlID)
|
||||
}
|
||||
|
||||
return controlIDs, nil
|
||||
}
|
||||
|
||||
// GetExceptions retrieves configured exceptions.
|
||||
//
|
||||
// NOTE: the cluster parameter is not used at this moment.
|
||||
func (lp *LoadPolicy) GetExceptions(_ /* clusterName */ string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||
// NOTE: this assumes that the first path contains a valid exceptions descriptor
|
||||
filePath := lp.filePath()
|
||||
|
||||
buf, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exception := make([]armotypes.PostureExceptionPolicy, 0, 300)
|
||||
err = json.Unmarshal(buf, &exception)
|
||||
|
||||
return exception, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
||||
// GetControlsInputs retrieves the map of control configs.
|
||||
//
|
||||
// NOTE: the cluster parameter is not used at this moment.
|
||||
func (lp *LoadPolicy) GetControlsInputs(_ /* clusterName */ string) (map[string][]string, error) {
|
||||
// NOTE: this assumes that only the first path contains a valid control inputs descriptor
|
||||
filePath := lp.filePath()
|
||||
accountConfig := &armotypes.CustomerConfig{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
fileName := filepath.Base(filePath)
|
||||
|
||||
buf, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
formattedError := fmt.Errorf(
|
||||
`Error opening %s file, "controls-config" will be downloaded from ARMO management portal`,
|
||||
fileName,
|
||||
)
|
||||
|
||||
return nil, formattedError
|
||||
}
|
||||
|
||||
controlInputs := make(map[string][]string, 100) // from armotypes.Settings.PostureControlInputs
|
||||
if err = json.Unmarshal(buf, &controlInputs); err != nil {
|
||||
formattedError := fmt.Errorf(
|
||||
`Error reading %s file, %v, "controls-config" will be downloaded from ARMO management portal`,
|
||||
fileName, err,
|
||||
)
|
||||
|
||||
return nil, formattedError
|
||||
}
|
||||
|
||||
return controlInputs, nil
|
||||
}
|
||||
|
||||
// GetAttackTracks yields the attack tracks from a config file.
|
||||
func (lp *LoadPolicy) GetAttackTracks() ([]v1alpha1.AttackTrack, error) {
|
||||
attackTracks := make([]v1alpha1.AttackTrack, 0, 20)
|
||||
|
||||
buf, err := os.ReadFile(lp.filePath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(f, &accountConfig.Settings.PostureControlInputs); err == nil {
|
||||
return accountConfig.Settings.PostureControlInputs, nil
|
||||
if err = json.Unmarshal(buf, &attackTracks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, err
|
||||
|
||||
return attackTracks, nil
|
||||
}
|
||||
|
||||
// temporary support for a list of files
|
||||
|
||||
@@ -1,13 +1,416 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
var mockFrameworkBasePath = filepath.Join("examples", "mocks", "frameworks")
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func MockNewLoadPolicy() *LoadPolicy {
|
||||
return &LoadPolicy{
|
||||
filePaths: []string{""},
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPolicy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
testFramework = "MITRE"
|
||||
testControl = "C-0053"
|
||||
)
|
||||
|
||||
t.Run("with GetFramework", func(t *testing.T) {
|
||||
t.Run("should retrieve named framework", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
|
||||
fw, err := p.GetFramework(testFramework)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, fw)
|
||||
|
||||
require.Equal(t, testFramework, fw.Name)
|
||||
})
|
||||
|
||||
t.Run("should fail to retrieve framework", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
|
||||
fw, err := p.GetFramework("wrong")
|
||||
require.Error(t, err)
|
||||
require.Nil(t, fw)
|
||||
})
|
||||
|
||||
t.Run("edge case: should error on empty framework", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
|
||||
fw, err := p.GetFramework("")
|
||||
require.ErrorIs(t, err, ErrNameRequired)
|
||||
require.Nil(t, fw)
|
||||
})
|
||||
|
||||
t.Run("edge case: corrupted json", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const invalidFramework = "invalid-fw"
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(invalidFramework)})
|
||||
fw, err := p.GetFramework(invalidFramework)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, fw)
|
||||
})
|
||||
|
||||
t.Run("edge case: missing json", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const invalidFramework = "nowheretobefound"
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(invalidFramework)})
|
||||
_, err := p.GetFramework(invalidFramework)
|
||||
require.Error(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with GetControl", func(t *testing.T) {
|
||||
t.Run("should retrieve named control from framework", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
expectedControlName = "Access container service account"
|
||||
)
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
|
||||
ctrl, err := p.GetControl(testControl)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ctrl)
|
||||
|
||||
require.Equal(t, testControl, ctrl.ControlID)
|
||||
require.Equal(t, expectedControlName, ctrl.Name)
|
||||
})
|
||||
|
||||
t.Run("with single control descriptor", func(t *testing.T) {
|
||||
const (
|
||||
singleControl = "C-0001"
|
||||
expectedControlName = "Forbidden Container Registries"
|
||||
)
|
||||
|
||||
t.Run("should retrieve named control from control descriptor", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(singleControl)})
|
||||
ctrl, err := p.GetControl(singleControl)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ctrl)
|
||||
|
||||
require.Equal(t, singleControl, ctrl.ControlID)
|
||||
require.Equal(t, expectedControlName, ctrl.Name)
|
||||
})
|
||||
|
||||
t.Run("should fail to retrieve named control from control descriptor", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(singleControl)})
|
||||
ctrl, err := p.GetControl("wrong")
|
||||
require.Error(t, err)
|
||||
require.Nil(t, ctrl)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with framework descriptor", func(t *testing.T) {
|
||||
t.Run("should fail to retrieve named control", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const testControl = "wrong"
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
|
||||
ctrl, err := p.GetControl(testControl)
|
||||
require.ErrorIs(t, err, ErrControlNotMatching)
|
||||
require.Nil(t, ctrl)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("edge case: corrupted json", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const invalidControl = "invalid-fw"
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(invalidControl)})
|
||||
_, err := p.GetControl(invalidControl)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("edge case: missing json", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const invalidControl = "nowheretobefound"
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(invalidControl)})
|
||||
_, err := p.GetControl(invalidControl)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("edge case: should error on empty control", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
|
||||
ctrl, err := p.GetControl("")
|
||||
require.ErrorIs(t, err, ErrIDRequired)
|
||||
require.Nil(t, ctrl)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with ListFrameworks", func(t *testing.T) {
|
||||
t.Run("should return all frameworks in the policy path", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
extraFramework = "NSA"
|
||||
attackTracks = "attack-tracks"
|
||||
)
|
||||
p := NewLoadPolicy([]string{
|
||||
testFrameworkFile(testFramework),
|
||||
testFrameworkFile(extraFramework),
|
||||
testFrameworkFile(extraFramework), // should be deduped
|
||||
testFrameworkFile(attackTracks), // should be ignored
|
||||
})
|
||||
fws, err := p.ListFrameworks()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, fws, 2)
|
||||
|
||||
require.Equal(t, testFramework, fws[0])
|
||||
require.Equal(t, extraFramework, fws[1])
|
||||
})
|
||||
|
||||
t.Run("should not return an empty framework", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
extraFramework = "NSA"
|
||||
attackTracks = "attack-tracks"
|
||||
controlsInputs = "controls-inputs"
|
||||
)
|
||||
p := NewLoadPolicy([]string{
|
||||
testFrameworkFile(testFramework),
|
||||
testFrameworkFile(extraFramework),
|
||||
testFrameworkFile(attackTracks), // should be ignored
|
||||
testFrameworkFile(controlsInputs), // should be ignored
|
||||
})
|
||||
fws, err := p.ListFrameworks()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, fws, 2)
|
||||
require.NotContains(t, fws, "")
|
||||
|
||||
require.Equal(t, testFramework, fws[0])
|
||||
require.Equal(t, extraFramework, fws[1])
|
||||
})
|
||||
|
||||
t.Run("should fail on file error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
extraFramework = "NSA"
|
||||
nowhere = "nowheretobeseen"
|
||||
)
|
||||
p := NewLoadPolicy([]string{
|
||||
testFrameworkFile(testFramework),
|
||||
testFrameworkFile(extraFramework),
|
||||
testFrameworkFile(nowhere), // should raise an error
|
||||
})
|
||||
fws, err := p.ListFrameworks()
|
||||
require.Error(t, err)
|
||||
require.Nil(t, fws)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("edge case: policy without path", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := NewLoadPolicy([]string{})
|
||||
require.Empty(t, p.filePath())
|
||||
})
|
||||
|
||||
t.Run("with GetFrameworks", func(t *testing.T) {
|
||||
const extraFramework = "NSA"
|
||||
|
||||
t.Run("should return all configured frameworks", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := NewLoadPolicy([]string{
|
||||
testFrameworkFile(testFramework),
|
||||
testFrameworkFile(extraFramework),
|
||||
})
|
||||
fws, err := p.GetFrameworks()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, fws, 2)
|
||||
|
||||
require.Equal(t, testFramework, fws[0].Name)
|
||||
require.Equal(t, extraFramework, fws[1].Name)
|
||||
})
|
||||
|
||||
t.Run("should return dedupe configured frameworks", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const attackTracks = "attack-tracks"
|
||||
p := NewLoadPolicy([]string{
|
||||
testFrameworkFile(testFramework),
|
||||
testFrameworkFile(extraFramework),
|
||||
testFrameworkFile(extraFramework),
|
||||
testFrameworkFile(attackTracks), // should be ignored
|
||||
})
|
||||
fws, err := p.GetFrameworks()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, fws, 2)
|
||||
|
||||
require.Equal(t, testFramework, fws[0].Name)
|
||||
require.Equal(t, extraFramework, fws[1].Name)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with ListControls", func(t *testing.T) {
|
||||
t.Run("should return controls", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
|
||||
controlIDs, err := p.ListControls()
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(controlIDs), 0)
|
||||
require.Equal(t, testControl, controlIDs[0])
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with GetAttackTracks", func(t *testing.T) {
|
||||
t.Run("should return attack tracks", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const attackTracks = "attack-tracks"
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(attackTracks)})
|
||||
tracks, err := p.GetAttackTracks()
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(tracks), 0)
|
||||
|
||||
for _, track := range tracks {
|
||||
require.Equal(t, "AttackTrack", track.Kind)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("edge case: corrupted json", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const invalidTracks = "invalid-fw"
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(invalidTracks)})
|
||||
_, err := p.GetAttackTracks()
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("edge case: missing json", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const invalidTracks = "nowheretobefound"
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(invalidTracks)})
|
||||
_, err := p.GetAttackTracks()
|
||||
require.Error(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with GetControlsInputs", func(t *testing.T) {
|
||||
const cluster = "dummy" // unused parameter at the moment
|
||||
|
||||
t.Run("should return control inputs for a cluster", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fixture, expected := writeTempJSONControlInputs(t)
|
||||
t.Cleanup(func() {
|
||||
_ = os.Remove(fixture)
|
||||
})
|
||||
|
||||
p := NewLoadPolicy([]string{fixture})
|
||||
inputs, err := p.GetControlsInputs(cluster)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, expected, inputs)
|
||||
})
|
||||
|
||||
t.Run("edge case: corrupted json", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const invalidInputs = "invalid-fw"
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(invalidInputs)})
|
||||
_, err := p.GetControlsInputs(cluster)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("edge case: missing json", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const invalidInputs = "nowheretobefound"
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(invalidInputs)})
|
||||
_, err := p.GetControlsInputs(cluster)
|
||||
require.Error(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with GetExceptions", func(t *testing.T) {
|
||||
const cluster = "dummy" // unused parameter at the moment
|
||||
|
||||
t.Run("should return exceptions", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const exceptions = "exceptions"
|
||||
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(exceptions)})
|
||||
exceptionPolicies, err := p.GetExceptions(cluster)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Greater(t, len(exceptionPolicies), 0)
|
||||
t.Logf("len=%d", len(exceptionPolicies))
|
||||
for _, policy := range exceptionPolicies {
|
||||
require.NotEmpty(t, policy.Name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("edge case: corrupted json", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const invalidInputs = "invalid-fw"
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(invalidInputs)})
|
||||
_, err := p.GetExceptions(cluster)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("edge case: missing json", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const invalidInputs = "nowheretobefound"
|
||||
p := NewLoadPolicy([]string{testFrameworkFile(invalidInputs)})
|
||||
_, err := p.GetExceptions(cluster)
|
||||
require.Error(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testFrameworkFile(framework string) string {
|
||||
return filepath.Join(currentDir(), "testdata", fmt.Sprintf("%s.json", framework))
|
||||
}
|
||||
|
||||
func writeTempJSONControlInputs(t testing.TB) (string, map[string][]string) {
|
||||
fileName := testFrameworkFile("control-inputs")
|
||||
mock := map[string][]string{
|
||||
"key1": {
|
||||
"val1", "val2",
|
||||
},
|
||||
"key2": {
|
||||
"val3", "val4",
|
||||
},
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(mock)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, os.WriteFile(fileName, buf, 0600))
|
||||
|
||||
return fileName, mock
|
||||
}
|
||||
|
||||
func currentDir() string {
|
||||
_, filename, _, _ := runtime.Caller(1)
|
||||
|
||||
return filepath.Dir(filename)
|
||||
}
|
||||
|
||||
85
core/cautils/getter/testdata/C-0001.json
vendored
Normal file
85
core/cautils/getter/testdata/C-0001.json
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"guid": "",
|
||||
"name": "Forbidden Container Registries",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "container",
|
||||
"categories": [
|
||||
"Initial access"
|
||||
]
|
||||
}
|
||||
],
|
||||
"controlTypeTags": [
|
||||
"security",
|
||||
"compliance"
|
||||
],
|
||||
"microsoftMitreColumns": [
|
||||
"Initial Access"
|
||||
]
|
||||
},
|
||||
"id": "C-0001",
|
||||
"controlID": "C-0001",
|
||||
"creationTime": "",
|
||||
"description": "In cases where the Kubernetes cluster is provided by a CSP (e.g., AKS in Azure, GKE in GCP, or EKS in AWS), compromised cloud credential can lead to the cluster takeover. Attackers may abuse cloud account credentials or IAM mechanism to the cluster’s management layer.",
|
||||
"remediation": "Limit the registries from which you pull container images from",
|
||||
"rules": [
|
||||
{
|
||||
"guid": "",
|
||||
"name": "rule-identify-blocklisted-image-registries",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Initial Access::Compromised images in registry"
|
||||
},
|
||||
"creationTime": "",
|
||||
"rule": "package armo_builtins\nimport data\n# Check for images from blocklisted repos\n\nuntrustedImageRepo[msga] {\n\tpod := input[_]\n\tk := pod.kind\n\tk == \"Pod\"\n\tcontainer := pod.spec.containers[i]\n\tpath := sprintf(\"spec.containers[%v].image\", [format_int(i, 10)])\n\timage := container.image\n untrusted_or_public_registries(image)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"image '%v' in container '%s' comes from untrusted registry\", [image, container.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 2,\n\t\t\"fixPaths\": [],\n\t\t\"failedPaths\": [path],\n \"alertObject\": {\n\t\t\t\"k8sApiObjects\": [pod]\n\t\t}\n }\n}\n\nuntrustedImageRepo[msga] {\n\twl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n\tcontainer := wl.spec.template.spec.containers[i]\n\tpath := sprintf(\"spec.template.spec.containers[%v].image\", [format_int(i, 10)])\n\timage := container.image\n untrusted_or_public_registries(image)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"image '%v' in container '%s' comes from untrusted registry\", [image, container.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 2,\n\t\t\"fixPaths\": [],\n\t\t\"failedPaths\": [path],\n \"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n }\n}\n\nuntrustedImageRepo[msga] {\n\twl := input[_]\n\twl.kind == \"CronJob\"\n\tcontainer := wl.spec.jobTemplate.spec.template.spec.containers[i]\n\tpath := sprintf(\"spec.jobTemplate.spec.template.spec.containers[%v].image\", [format_int(i, 10)])\n\timage := container.image\n untrusted_or_public_registries(image)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"image '%v' in container '%s' comes from untrusted registry\", [image, container.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 2,\n\t\t\"fixPaths\": [],\n\t\t\"failedPaths\": [path],\n \"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n }\n}\n\nuntrusted_or_public_registries(image){\n\t# see default-config-inputs.json for list values\n\tuntrusted_registries := data.postureControlInputs.untrustedRegistries\n\trepo_prefix := untrusted_registries[_]\n\tstartswith(image, repo_prefix)\n}\n\nuntrusted_or_public_registries(image){\n\t# see default-config-inputs.json for list values\n\tpublic_registries := data.postureControlInputs.publicRegistries\n\trepo_prefix := public_registries[_]\n\tstartswith(image, repo_prefix)\n}",
|
||||
"resourceEnumerator": "",
|
||||
"ruleLanguage": "Rego",
|
||||
"match": [
|
||||
{
|
||||
"apiGroups": [
|
||||
"*"
|
||||
],
|
||||
"apiVersions": [
|
||||
"*"
|
||||
],
|
||||
"resources": [
|
||||
"Pod",
|
||||
"Deployment",
|
||||
"ReplicaSet",
|
||||
"DaemonSet",
|
||||
"StatefulSet",
|
||||
"Job",
|
||||
"CronJob"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ruleDependencies": [],
|
||||
"configInputs": [
|
||||
"settings.postureControlInputs.publicRegistries",
|
||||
"settings.postureControlInputs.untrustedRegistries"
|
||||
],
|
||||
"controlConfigInputs": [
|
||||
{
|
||||
"path": "settings.postureControlInputs.publicRegistries",
|
||||
"name": "Public registries",
|
||||
"description": "Kubescape checks none of these public registries are in use."
|
||||
},
|
||||
{
|
||||
"path": "settings.postureControlInputs.untrustedRegistries",
|
||||
"name": "Registries block list",
|
||||
"description": "Kubescape checks none of the following registries are in use."
|
||||
}
|
||||
],
|
||||
"description": "Identifying if pod container images are from unallowed registries",
|
||||
"remediation": "Use images from safe registry",
|
||||
"ruleQuery": "",
|
||||
"relevantCloudProviders": null
|
||||
}
|
||||
],
|
||||
"rulesIDs": [
|
||||
""
|
||||
],
|
||||
"baseScore": 7
|
||||
}
|
||||
2832
core/cautils/getter/testdata/MITRE.json
vendored
Normal file
2832
core/cautils/getter/testdata/MITRE.json
vendored
Normal file
File diff suppressed because one or more lines are too long
2249
core/cautils/getter/testdata/NSA.json
vendored
Normal file
2249
core/cautils/getter/testdata/NSA.json
vendored
Normal file
File diff suppressed because one or more lines are too long
136
core/cautils/getter/testdata/attack-tracks.json
vendored
Normal file
136
core/cautils/getter/testdata/attack-tracks.json
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
[
|
||||
{
|
||||
"apiVersion": "regolibrary.kubescape/v1alpha1",
|
||||
"kind": "AttackTrack",
|
||||
"metadata": {
|
||||
"name": "node"
|
||||
},
|
||||
"spec": {
|
||||
"data": {
|
||||
"name": "Initial access",
|
||||
"subSteps": [
|
||||
{
|
||||
"name": "Execution",
|
||||
"subSteps": [
|
||||
{
|
||||
"name": "Persistence"
|
||||
},
|
||||
{
|
||||
"name": "Credential access"
|
||||
},
|
||||
{
|
||||
"name": "Defense evasion"
|
||||
},
|
||||
{
|
||||
"name": "Discovery"
|
||||
},
|
||||
{
|
||||
"name": "Lateral movement"
|
||||
},
|
||||
{
|
||||
"name": "Impact - data theft"
|
||||
},
|
||||
{
|
||||
"name": "Impact - data destruction"
|
||||
},
|
||||
{
|
||||
"name": "Impact - service injection"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "regolibrary.kubescape/v1alpha1",
|
||||
"kind": "AttackTrack",
|
||||
"metadata": {
|
||||
"name": "kubeapi"
|
||||
},
|
||||
"spec": {
|
||||
"data": {
|
||||
"name": "Initial access",
|
||||
"subSteps": [
|
||||
{
|
||||
"name": "Persistence"
|
||||
},
|
||||
{
|
||||
"name": "Privilege escalation"
|
||||
},
|
||||
{
|
||||
"name": "Credential access"
|
||||
},
|
||||
{
|
||||
"name": "Discovery"
|
||||
},
|
||||
{
|
||||
"name": "Lateral movement"
|
||||
},
|
||||
{
|
||||
"name": "Defense evasion"
|
||||
},
|
||||
{
|
||||
"name": "Impact - data destruction"
|
||||
},
|
||||
{
|
||||
"name": "Impact - service injection"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "regolibrary.kubescape/v1alpha1",
|
||||
"kind": "AttackTrack",
|
||||
"metadata": {
|
||||
"name": "container"
|
||||
},
|
||||
"spec": {
|
||||
"data": {
|
||||
"name": "Initial access",
|
||||
"subSteps": [
|
||||
{
|
||||
"name": "Execution",
|
||||
"subSteps": [
|
||||
{
|
||||
"name": "Privilege escalation"
|
||||
},
|
||||
{
|
||||
"name": "Credential access",
|
||||
"subSteps": [
|
||||
{
|
||||
"name": "Impact - service access"
|
||||
},
|
||||
{
|
||||
"name": "Impact - K8s API access",
|
||||
"subSteps": [
|
||||
{
|
||||
"name": "Defense evasion - KubeAPI"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Discovery"
|
||||
},
|
||||
{
|
||||
"name": "Lateral movement"
|
||||
},
|
||||
{
|
||||
"name": "Impact - Data access in container"
|
||||
},
|
||||
{
|
||||
"name": "Persistence"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Impact - service destruction"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
125
core/cautils/getter/testdata/controls-inputs.json
vendored
Normal file
125
core/cautils/getter/testdata/controls-inputs.json
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"publicRegistries": [],
|
||||
"untrustedRegistries": [],
|
||||
"listOfDangerousArtifacts": [
|
||||
"bin/bash",
|
||||
"sbin/sh",
|
||||
"bin/ksh",
|
||||
"bin/tcsh",
|
||||
"bin/zsh",
|
||||
"usr/bin/scsh",
|
||||
"bin/csh",
|
||||
"bin/busybox",
|
||||
"usr/bin/busybox"
|
||||
],
|
||||
"sensitiveKeyNames": [
|
||||
"aws_access_key_id",
|
||||
"aws_secret_access_key",
|
||||
"azure_batchai_storage_account",
|
||||
"azure_batchai_storage_key",
|
||||
"azure_batch_account",
|
||||
"azure_batch_key",
|
||||
"secret",
|
||||
"key",
|
||||
"password",
|
||||
"pwd",
|
||||
"token",
|
||||
"jwt",
|
||||
"bearer",
|
||||
"credential"
|
||||
],
|
||||
"servicesNames": [
|
||||
"nifi-service",
|
||||
"argo-server",
|
||||
"minio",
|
||||
"postgres",
|
||||
"workflow-controller-metrics",
|
||||
"weave-scope-app",
|
||||
"kubernetes-dashboard"
|
||||
],
|
||||
"memory_limit_max": [],
|
||||
"cpu_request_min": [],
|
||||
"wlKnownNames": [
|
||||
"coredns",
|
||||
"kube-proxy",
|
||||
"event-exporter-gke",
|
||||
"kube-dns",
|
||||
"17-default-backend",
|
||||
"metrics-server",
|
||||
"ca-audit",
|
||||
"ca-dashboard-aggregator",
|
||||
"ca-notification-server",
|
||||
"ca-ocimage",
|
||||
"ca-oracle",
|
||||
"ca-posture",
|
||||
"ca-rbac",
|
||||
"ca-vuln-scan",
|
||||
"ca-webhook",
|
||||
"ca-websocket",
|
||||
"clair-clair"
|
||||
],
|
||||
"sensitiveInterfaces": [
|
||||
"nifi",
|
||||
"argo-server",
|
||||
"weave-scope-app",
|
||||
"kubeflow",
|
||||
"kubernetes-dashboard",
|
||||
"jenkins",
|
||||
"prometheus-deployment"
|
||||
],
|
||||
"max_high_vulnerabilities": [
|
||||
"10"
|
||||
],
|
||||
"sensitiveValues": [
|
||||
"BEGIN \\w+ PRIVATE KEY",
|
||||
"PRIVATE KEY",
|
||||
"eyJhbGciO",
|
||||
"JWT",
|
||||
"Bearer",
|
||||
"_key_",
|
||||
"_secret_"
|
||||
],
|
||||
"memory_request_max": [],
|
||||
"memory_request_min": [],
|
||||
"cpu_request_max": [],
|
||||
"cpu_limit_max": [],
|
||||
"cpu_limit_min": [],
|
||||
"insecureCapabilities": [
|
||||
"SETPCAP",
|
||||
"NET_ADMIN",
|
||||
"NET_RAW",
|
||||
"SYS_MODULE",
|
||||
"SYS_RAWIO",
|
||||
"SYS_PTRACE",
|
||||
"SYS_ADMIN",
|
||||
"SYS_BOOT",
|
||||
"MAC_OVERRIDE",
|
||||
"MAC_ADMIN",
|
||||
"PERFMON",
|
||||
"ALL",
|
||||
"BPF"
|
||||
],
|
||||
"max_critical_vulnerabilities": [
|
||||
"5"
|
||||
],
|
||||
"sensitiveValuesAllowed": [],
|
||||
"memory_limit_min": [],
|
||||
"recommendedLabels": [
|
||||
"app",
|
||||
"tier",
|
||||
"phase",
|
||||
"version",
|
||||
"owner",
|
||||
"env"
|
||||
],
|
||||
"k8sRecommendedLabels": [
|
||||
"app.kubernetes.io/name",
|
||||
"app.kubernetes.io/instance",
|
||||
"app.kubernetes.io/version",
|
||||
"app.kubernetes.io/component",
|
||||
"app.kubernetes.io/part-of",
|
||||
"app.kubernetes.io/managed-by",
|
||||
"app.kubernetes.io/created-by"
|
||||
],
|
||||
"imageRepositoryAllowList": []
|
||||
}
|
||||
6407
core/cautils/getter/testdata/exceptions.json
vendored
Normal file
6407
core/cautils/getter/testdata/exceptions.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3
core/cautils/getter/testdata/invalid-fw.json
vendored
Normal file
3
core/cautils/getter/testdata/invalid-fw.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"guid": "",
|
||||
}
|
||||
25821
core/cautils/getter/testdata/policy.json
vendored
Normal file
25821
core/cautils/getter/testdata/policy.json
vendored
Normal file
File diff suppressed because one or more lines are too long
22
core/cautils/git_native_disabled.go
Normal file
22
core/cautils/git_native_disabled.go
Normal file
@@ -0,0 +1,22 @@
|
||||
//go:build !gitenabled
|
||||
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kubescape/go-git-url/apis"
|
||||
)
|
||||
|
||||
var ErrFatalNotSupportedByBuild = errors.New(`git scan not supported by this build. Build with tag "gitenabled" to enable the git scan feature`)
|
||||
|
||||
type gitRepository struct {
|
||||
}
|
||||
|
||||
func newGitRepository(root string) (*gitRepository, error) {
|
||||
return &gitRepository{}, ErrWarnNotSupportedByBuild
|
||||
}
|
||||
|
||||
func (g *gitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) {
|
||||
return nil, ErrFatalNotSupportedByBuild
|
||||
}
|
||||
11
core/cautils/git_native_disabled_test.go
Normal file
11
core/cautils/git_native_disabled_test.go
Normal file
@@ -0,0 +1,11 @@
|
||||
//go:build !gitenabled
|
||||
|
||||
package cautils
|
||||
|
||||
func (s *LocalGitRepositoryTestSuite) TestGetLastCommit() {
|
||||
s.T().Log("warn: skipped testing native git functionality [GetLastCommit]")
|
||||
}
|
||||
|
||||
func (s *LocalGitRepositoryTestSuite) TestGetFileLastCommit() {
|
||||
s.T().Log("warn: skipped testing native git functionality [GetFileLastCommit]")
|
||||
}
|
||||
141
core/cautils/git_native_enabled.go
Normal file
141
core/cautils/git_native_enabled.go
Normal file
@@ -0,0 +1,141 @@
|
||||
//go:build gitenabled
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/kubescape/go-git-url/apis"
|
||||
git2go "github.com/libgit2/git2go/v33"
|
||||
)
|
||||
|
||||
type gitRepository struct {
|
||||
git2GoRepo *git2go.Repository
|
||||
fileToLastCommit map[string]*git2go.Commit
|
||||
}
|
||||
|
||||
func newGitRepository(root string) (*gitRepository, error) {
|
||||
git2GoRepo, err := git2go.OpenRepository(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gitRepository{
|
||||
git2GoRepo: git2GoRepo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *gitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) {
|
||||
if len(g.fileToLastCommit) == 0 {
|
||||
filePathToCommitTime := map[string]time.Time{}
|
||||
filePathToCommit := map[string]*git2go.Commit{}
|
||||
allCommits, _ := g.getAllCommits()
|
||||
|
||||
// builds a map of all files to their last commit
|
||||
for _, commit := range allCommits {
|
||||
// Ignore merge commits (2+ parents)
|
||||
if commit.ParentCount() <= 1 {
|
||||
tree, err := commit.Tree()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// ParentCount can be either 1 or 0 (initial commit)
|
||||
// In case it's the initial commit, prevTree is nil
|
||||
var prevTree *git2go.Tree
|
||||
if commit.ParentCount() == 1 {
|
||||
prevCommit := commit.Parent(0)
|
||||
prevTree, err = prevCommit.Tree()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
diff, err := g.git2GoRepo.DiffTreeToTree(prevTree, tree, nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
numDeltas, err := diff.NumDeltas()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for i := 0; i < numDeltas; i++ {
|
||||
delta, err := diff.Delta(i)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
deltaFilePath := delta.NewFile.Path
|
||||
commitTime := commit.Author().When
|
||||
|
||||
// In case we have the commit information for the file which is not the latest - we override it
|
||||
if currentCommitTime, exists := filePathToCommitTime[deltaFilePath]; exists {
|
||||
if currentCommitTime.Before(commitTime) {
|
||||
filePathToCommitTime[deltaFilePath] = commitTime
|
||||
filePathToCommit[deltaFilePath] = commit
|
||||
}
|
||||
} else {
|
||||
filePathToCommitTime[deltaFilePath] = commitTime
|
||||
filePathToCommit[deltaFilePath] = commit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g.fileToLastCommit = filePathToCommit
|
||||
}
|
||||
|
||||
if relevantCommit, exists := g.fileToLastCommit[filePath]; exists {
|
||||
return g.getCommit(relevantCommit), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to get commit information for file: %s", filePath)
|
||||
}
|
||||
|
||||
func (g *gitRepository) getAllCommits() ([]*git2go.Commit, error) {
|
||||
logItr, itrErr := g.git2GoRepo.Walk()
|
||||
if itrErr != nil {
|
||||
|
||||
return nil, itrErr
|
||||
}
|
||||
|
||||
pushErr := logItr.PushHead()
|
||||
if pushErr != nil {
|
||||
return nil, pushErr
|
||||
}
|
||||
|
||||
var allCommits []*git2go.Commit
|
||||
err := logItr.Iterate(func(commit *git2go.Commit) bool {
|
||||
if commit != nil {
|
||||
allCommits = append(allCommits, commit)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return allCommits, nil
|
||||
}
|
||||
|
||||
func (g *gitRepository) getCommit(commit *git2go.Commit) *apis.Commit {
|
||||
return &apis.Commit{
|
||||
SHA: commit.Id().String(),
|
||||
Author: apis.Committer{
|
||||
Name: commit.Author().Name,
|
||||
Email: commit.Author().Email,
|
||||
Date: commit.Author().When,
|
||||
},
|
||||
Message: commit.Message(),
|
||||
Committer: apis.Committer{},
|
||||
Files: []apis.Files{},
|
||||
}
|
||||
}
|
||||
44
core/cautils/git_native_enabled_test.go
Normal file
44
core/cautils/git_native_enabled_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
//go:build gitenabled
|
||||
package cautils
|
||||
|
||||
func (s *LocalGitRepositoryTestSuite) TestGetLastCommit() {
|
||||
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
|
||||
if commit, err := localRepo.GetLastCommit(); s.NoError(err) {
|
||||
s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA)
|
||||
s.Equal("Amir Malka", commit.Author.Name)
|
||||
s.Equal("amirm@armosec.io", commit.Author.Email)
|
||||
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
|
||||
s.Equal("added file B\n", commit.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LocalGitRepositoryTestSuite) TestGetFileLastCommit() {
|
||||
s.Run("fileA", func() {
|
||||
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
|
||||
|
||||
if commit, err := localRepo.GetFileLastCommit("fileA"); s.NoError(err) {
|
||||
s.Equal("9fae4be19624297947d2b605cefbff516628612d", commit.SHA)
|
||||
s.Equal("Amir Malka", commit.Author.Name)
|
||||
s.Equal("amirm@armosec.io", commit.Author.Email)
|
||||
s.Equal("2022-05-22 18:55:48 +0300 +0300", commit.Author.Date.String())
|
||||
s.Equal("added file A\n", commit.Message)
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
s.Run("fileB", func() {
|
||||
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
|
||||
|
||||
if commit, err := localRepo.GetFileLastCommit("dirA/fileB"); s.NoError(err) {
|
||||
s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA)
|
||||
s.Equal("Amir Malka", commit.Author.Name)
|
||||
s.Equal("amirm@armosec.io", commit.Author.Email)
|
||||
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
|
||||
s.Equal("added file B\n", commit.Message)
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
16
core/cautils/gitparse_test.go
Normal file
16
core/cautils/gitparse_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
giturl "github.com/kubescape/go-git-url"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEnsureRemoteParsed(t *testing.T) {
|
||||
const remote = "git@gitlab.com:foobar/gitlab-tests/sample-project.git"
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
_, _ = giturl.NewGitURL(remote)
|
||||
})
|
||||
}
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes/localworkload"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
||||
|
||||
helmchart "helm.sh/helm/v3/pkg/chart"
|
||||
helmloader "helm.sh/helm/v3/pkg/chart/loader"
|
||||
helmchartutil "helm.sh/helm/v3/pkg/chartutil"
|
||||
|
||||
@@ -3,13 +3,12 @@ package cautils
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/armosec/opa-utils/objectsenvelopes/localworkload"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
@@ -39,7 +38,7 @@ func (s *HelmChartTestSuite) SetupSuite() {
|
||||
}
|
||||
|
||||
var obj interface{}
|
||||
file, _ := ioutil.ReadFile(filepath.Join("testdata", "helm_expected_default_values.json"))
|
||||
file, _ := os.ReadFile(filepath.Join("testdata", "helm_expected_default_values.json"))
|
||||
_ = json.Unmarshal([]byte(file), &obj)
|
||||
s.expectedDefaultValues = obj.(map[string]interface{})
|
||||
}
|
||||
|
||||
20
core/cautils/krewutils.go
Normal file
20
core/cautils/krewutils.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ExecName returns the correct name to use in examples depending on how kubescape is invoked
|
||||
func ExecName() string {
|
||||
n := "kubescape"
|
||||
if IsKrewPlugin() {
|
||||
return "kubectl " + n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func IsKrewPlugin() bool {
|
||||
return strings.HasPrefix(filepath.Base(os.Args[0]), "kubectl-")
|
||||
}
|
||||
115
core/cautils/kustomizedirectory.go
Normal file
115
core/cautils/kustomizedirectory.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/krusty"
|
||||
"sigs.k8s.io/kustomize/kyaml/filesys"
|
||||
)
|
||||
|
||||
type KustomizeDirectory struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// Used for checking if there is "Kustomization" file in the given Directory
|
||||
var kustomizationFileMatchers = [3]string{"kustomization.yml", "kustomization.yaml", "Kustomization"}
|
||||
|
||||
func IsKustomizeDirectory(path string) bool {
|
||||
if isDir := IsDir(path); !isDir {
|
||||
return false
|
||||
}
|
||||
|
||||
if lastChar := path[len(path)-1:]; lastChar != "/" {
|
||||
path += "/"
|
||||
}
|
||||
|
||||
matches := 0
|
||||
for _, kustomizationFileMatcher := range kustomizationFileMatchers {
|
||||
checkPath := path + kustomizationFileMatcher
|
||||
if _, err := os.Stat(checkPath); err == nil {
|
||||
matches++
|
||||
}
|
||||
}
|
||||
|
||||
switch matches {
|
||||
case 0:
|
||||
return false
|
||||
case 1:
|
||||
return true
|
||||
default:
|
||||
logger.L().Info("Multiple kustomize files found while checking Kustomize Directory")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Used for checking if the path is Kustomization file.
|
||||
func IsKustomizeFile(path string) bool {
|
||||
fileName := filepath.Base(path)
|
||||
|
||||
for _, kustomizationFileMatcher := range kustomizationFileMatchers {
|
||||
if fileName == kustomizationFileMatcher {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func NewKustomizeDirectory(path string) *KustomizeDirectory {
|
||||
return &KustomizeDirectory{
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
func GetKustomizeDirectoryName(path string) string {
|
||||
if isKustomizeDirectory := IsKustomizeDirectory(path); !isKustomizeDirectory {
|
||||
return ""
|
||||
}
|
||||
return filepath.Dir(path)
|
||||
}
|
||||
|
||||
// Get Workloads, creates the yaml files(K8s resources) using Kustomize and
|
||||
// renders the workloads from the yaml files (k8s resources)
|
||||
func (kd *KustomizeDirectory) GetWorkloads(kustomizeDirectoryPath string) (map[string][]workloadinterface.IMetadata, []error) {
|
||||
|
||||
fSys := filesys.MakeFsOnDisk()
|
||||
kustomizer := krusty.MakeKustomizer(krusty.MakeDefaultOptions())
|
||||
resmap, err := kustomizer.Run(fSys, kustomizeDirectoryPath)
|
||||
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
|
||||
yml, err := resmap.AsYaml()
|
||||
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
|
||||
workloads := make(map[string][]workloadinterface.IMetadata, 0)
|
||||
errs := []error{}
|
||||
|
||||
wls, e := ReadFile(yml, YAML_FILE_FORMAT)
|
||||
|
||||
if e != nil {
|
||||
logger.L().Debug("failed to read rendered yaml file", helpers.String("file", kustomizeDirectoryPath), helpers.Error(e))
|
||||
}
|
||||
|
||||
if len(wls) != 0 {
|
||||
workloads[kustomizeDirectoryPath] = []workloadinterface.IMetadata{}
|
||||
for i := range wls {
|
||||
lw := localworkload.NewLocalWorkload(wls[i].GetObject())
|
||||
lw.SetPath(kustomizeDirectoryPath)
|
||||
workloads[kustomizeDirectoryPath] = append(workloads[kustomizeDirectoryPath], lw)
|
||||
}
|
||||
}
|
||||
|
||||
return workloads, errs
|
||||
|
||||
}
|
||||
@@ -1,29 +1,33 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/go-git-url/apis"
|
||||
gitv5 "github.com/go-git/go-git/v5"
|
||||
configv5 "github.com/go-git/go-git/v5/config"
|
||||
plumbingv5 "github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/kubescape/go-git-url/apis"
|
||||
)
|
||||
|
||||
type LocalGitRepository struct {
|
||||
repo *gitv5.Repository
|
||||
head *plumbingv5.Reference
|
||||
config *configv5.Config
|
||||
*gitRepository
|
||||
goGitRepo *gitv5.Repository
|
||||
head *plumbingv5.Reference
|
||||
config *configv5.Config
|
||||
}
|
||||
|
||||
var ErrWarnNotSupportedByBuild = errors.New(`git commits retrieval not supported by this build. Build with tag "gitenabled" to enable the full git scan feature`)
|
||||
|
||||
func NewLocalGitRepository(path string) (*LocalGitRepository, error) {
|
||||
gitRepo, err := gitv5.PlainOpenWithOptions(path, &gitv5.PlainOpenOptions{DetectDotGit: true})
|
||||
goGitRepo, err := gitv5.PlainOpenWithOptions(path, &gitv5.PlainOpenOptions{DetectDotGit: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
head, err := gitRepo.Head()
|
||||
head, err := goGitRepo.Head()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -32,16 +36,31 @@ func NewLocalGitRepository(path string) (*LocalGitRepository, error) {
|
||||
return nil, fmt.Errorf("current HEAD reference is not a branch")
|
||||
}
|
||||
|
||||
config, err := gitRepo.Config()
|
||||
config, err := goGitRepo.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LocalGitRepository{
|
||||
repo: gitRepo,
|
||||
head: head,
|
||||
config: config,
|
||||
}, nil
|
||||
if len(config.Remotes) == 0 {
|
||||
return nil, fmt.Errorf("no remotes found")
|
||||
}
|
||||
|
||||
l := &LocalGitRepository{
|
||||
goGitRepo: goGitRepo,
|
||||
head: head,
|
||||
config: config,
|
||||
}
|
||||
|
||||
if repoRoot, err := l.GetRootDir(); err == nil {
|
||||
gitRepository, err := newGitRepository(repoRoot)
|
||||
if err != nil && !errors.Is(err, ErrWarnNotSupportedByBuild) {
|
||||
return l, err
|
||||
}
|
||||
|
||||
l.gitRepository = gitRepository
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// GetBranchName get current branch name
|
||||
@@ -54,6 +73,10 @@ func (g *LocalGitRepository) GetRemoteUrl() (string, error) {
|
||||
branchName := g.GetBranchName()
|
||||
if branchRef, branchFound := g.config.Branches[branchName]; branchFound {
|
||||
remoteName := branchRef.Remote
|
||||
// branchRef.Remote can be a reference to a config.Remotes entry or directly a gitUrl
|
||||
if _, found := g.config.Remotes[remoteName]; !found {
|
||||
return remoteName, nil
|
||||
}
|
||||
if len(g.config.Remotes[remoteName].URLs) == 0 {
|
||||
return "", fmt.Errorf("expected to find URLs for remote '%s', branch '%s'", remoteName, branchName)
|
||||
}
|
||||
@@ -61,10 +84,13 @@ func (g *LocalGitRepository) GetRemoteUrl() (string, error) {
|
||||
}
|
||||
|
||||
const defaultRemoteName string = "origin"
|
||||
if len(g.config.Remotes[defaultRemoteName].URLs) == 0 {
|
||||
defaultRemote, ok := g.config.Remotes[defaultRemoteName]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("did not find a default remote with name '%s'", defaultRemoteName)
|
||||
} else if len(defaultRemote.URLs) == 0 {
|
||||
return "", fmt.Errorf("expected to find URLs for remote '%s'", defaultRemoteName)
|
||||
}
|
||||
return g.config.Remotes[defaultRemoteName].URLs[0], nil
|
||||
return defaultRemote.URLs[0], nil
|
||||
}
|
||||
|
||||
// GetName get origin name without the .git suffix
|
||||
@@ -80,20 +106,7 @@ func (g *LocalGitRepository) GetName() (string, error) {
|
||||
|
||||
// GetLastCommit get latest commit object
|
||||
func (g *LocalGitRepository) GetLastCommit() (*apis.Commit, error) {
|
||||
return g.GetFileLastCommit("")
|
||||
}
|
||||
|
||||
// GetFileLastCommit get file latest commit object, if empty will return latest commit
|
||||
func (g *LocalGitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) {
|
||||
// By default, returns commit information from current HEAD
|
||||
logOptions := &gitv5.LogOptions{}
|
||||
|
||||
if filePath != "" {
|
||||
logOptions.FileName = &filePath
|
||||
logOptions.Order = gitv5.LogOrderCommitterTime // faster -> LogOrderDFSPost
|
||||
}
|
||||
|
||||
cIter, err := g.repo.Log(logOptions)
|
||||
cIter, err := g.goGitRepo.Log(&gitv5.LogOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -118,7 +131,7 @@ func (g *LocalGitRepository) GetFileLastCommit(filePath string) (*apis.Commit, e
|
||||
}
|
||||
|
||||
func (g *LocalGitRepository) GetRootDir() (string, error) {
|
||||
wt, err := g.repo.Worktree()
|
||||
wt, err := g.goGitRepo.Worktree()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get repo root")
|
||||
}
|
||||
|
||||
@@ -9,14 +9,18 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
configv5 "github.com/go-git/go-git/v5/config"
|
||||
plumbingv5 "github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
var TEST_REPOS = [...]string{"localrepo", "withoutremotes"}
|
||||
|
||||
type LocalGitRepositoryTestSuite struct {
|
||||
suite.Suite
|
||||
archive *zip.ReadCloser
|
||||
gitRepositoryPath string
|
||||
destinationPath string
|
||||
archives map[string]*zip.ReadCloser
|
||||
gitRepositoryPaths map[string]string
|
||||
destinationPath string
|
||||
}
|
||||
|
||||
func unzipFile(zipPath, destinationFolder string) (*zip.ReadCloser, error) {
|
||||
@@ -24,54 +28,76 @@ func unzipFile(zipPath, destinationFolder string) (*zip.ReadCloser, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, f := range archive.File {
|
||||
filePath := filepath.Join(destinationFolder, f.Name)
|
||||
filePath := filepath.Join(destinationFolder, f.Name) //nolint:gosec
|
||||
if !strings.HasPrefix(filePath, filepath.Clean(destinationFolder)+string(os.PathSeparator)) {
|
||||
return nil, fmt.Errorf("invalid file path")
|
||||
}
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
os.MkdirAll(filePath, os.ModePerm)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
if erc := copyFileInFolder(filePath, f); erc != nil {
|
||||
return nil, erc
|
||||
}
|
||||
|
||||
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileInArchive, err := f.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(dstFile, fileInArchive); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dstFile.Close()
|
||||
fileInArchive.Close()
|
||||
}
|
||||
|
||||
return archive, err
|
||||
}
|
||||
|
||||
func copyFileInFolder(filePath string, f *zip.File) (err error) {
|
||||
if err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = dstFile.Close()
|
||||
}()
|
||||
|
||||
fileInArchive, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = fileInArchive.Close()
|
||||
}()
|
||||
|
||||
_, err = io.Copy(dstFile, fileInArchive) //nolint:gosec
|
||||
|
||||
if err = dstFile.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = fileInArchive.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *LocalGitRepositoryTestSuite) SetupSuite() {
|
||||
zippedFixturePath := filepath.Join(".", "testdata", "localrepo.git")
|
||||
s.archives = make(map[string]*zip.ReadCloser)
|
||||
s.gitRepositoryPaths = make(map[string]string)
|
||||
|
||||
destinationPath := filepath.Join(".", "testdata", "temp")
|
||||
gitRepositoryPath := filepath.Join(destinationPath, "localrepo")
|
||||
|
||||
s.destinationPath = destinationPath
|
||||
os.RemoveAll(destinationPath)
|
||||
archive, err := unzipFile(zippedFixturePath, destinationPath)
|
||||
for _, repo := range TEST_REPOS {
|
||||
zippedFixturePath := filepath.Join(".", "testdata", repo+".git")
|
||||
gitRepositoryPath := filepath.Join(destinationPath, repo)
|
||||
archive, err := unzipFile(zippedFixturePath, destinationPath)
|
||||
|
||||
if err == nil {
|
||||
s.archive = archive
|
||||
s.gitRepositoryPath = gitRepositoryPath
|
||||
s.destinationPath = destinationPath
|
||||
if err == nil {
|
||||
s.archives[repo] = archive
|
||||
s.gitRepositoryPaths[repo] = gitRepositoryPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,9 +106,14 @@ func TestLocalGitRepositoryTestSuite(t *testing.T) {
|
||||
}
|
||||
|
||||
func (s *LocalGitRepositoryTestSuite) TearDownSuite() {
|
||||
if s.archive != nil {
|
||||
s.archive.Close()
|
||||
if s.archives != nil {
|
||||
for _, archive := range s.archives {
|
||||
if archive != nil {
|
||||
archive.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
os.RemoveAll(s.destinationPath)
|
||||
}
|
||||
|
||||
@@ -92,14 +123,20 @@ func (s *LocalGitRepositoryTestSuite) TestInvalidRepositoryPath() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LocalGitRepositoryTestSuite) TestRepositoryWithoutRemotes() {
|
||||
if _, err := NewLocalGitRepository(s.gitRepositoryPaths["withoutremotes"]); s.Error(err) {
|
||||
s.Equal("no remotes found", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LocalGitRepositoryTestSuite) TestGetBranchName() {
|
||||
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPath); s.NoError(err) {
|
||||
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
|
||||
s.Equal("master", localRepo.GetBranchName())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LocalGitRepositoryTestSuite) TestGetName() {
|
||||
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPath); s.NoError(err) {
|
||||
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
|
||||
if name, err := localRepo.GetName(); s.NoError(err) {
|
||||
s.Equal("localrepo", name)
|
||||
}
|
||||
@@ -108,47 +145,56 @@ func (s *LocalGitRepositoryTestSuite) TestGetName() {
|
||||
}
|
||||
|
||||
func (s *LocalGitRepositoryTestSuite) TestGetOriginUrl() {
|
||||
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPath); s.NoError(err) {
|
||||
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
|
||||
if url, err := localRepo.GetRemoteUrl(); s.NoError(err) {
|
||||
s.Equal("git@github.com:testuser/localrepo", url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LocalGitRepositoryTestSuite) TestGetLastCommit() {
|
||||
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPath); s.NoError(err) {
|
||||
if commit, err := localRepo.GetLastCommit(); s.NoError(err) {
|
||||
s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA)
|
||||
s.Equal("Amir Malka", commit.Author.Name)
|
||||
s.Equal("amirm@armosec.io", commit.Author.Email)
|
||||
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
|
||||
s.Equal("added file B\n", commit.Message)
|
||||
}
|
||||
func TestGetRemoteUrl(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Name string
|
||||
LocalRepo LocalGitRepository
|
||||
Want string
|
||||
WantErr error
|
||||
}{
|
||||
{
|
||||
Name: "Branch with missing upstream and missing 'origin' fallback should return an error",
|
||||
LocalRepo: LocalGitRepository{
|
||||
config: &configv5.Config{
|
||||
Branches: make(map[string]*configv5.Branch),
|
||||
Remotes: make(map[string]*configv5.RemoteConfig),
|
||||
},
|
||||
head: plumbingv5.NewReferenceFromStrings("HEAD", "ref: refs/heads/v4"),
|
||||
},
|
||||
Want: "",
|
||||
WantErr: fmt.Errorf("did not find a default remote with name 'origin'"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
localRepo := LocalGitRepository{
|
||||
config: &configv5.Config{
|
||||
Branches: make(map[string]*configv5.Branch),
|
||||
Remotes: make(map[string]*configv5.RemoteConfig),
|
||||
},
|
||||
head: plumbingv5.NewReferenceFromStrings("HEAD", "ref: refs/heads/v4"),
|
||||
}
|
||||
|
||||
want := tc.Want
|
||||
wantErr := tc.WantErr
|
||||
got, gotErr := localRepo.GetRemoteUrl()
|
||||
|
||||
if got != want {
|
||||
t.Errorf("Remote URLs don’t match: got '%s', want '%s'", got, want)
|
||||
}
|
||||
|
||||
if gotErr.Error() != wantErr.Error() {
|
||||
t.Errorf("Errors don’t match: got '%v', want '%v'", gotErr, wantErr)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LocalGitRepositoryTestSuite) TestGetFileLastCommit() {
|
||||
s.Run("fileA", func() {
|
||||
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPath); s.NoError(err) {
|
||||
if commit, err := localRepo.GetFileLastCommit("fileA"); s.NoError(err) {
|
||||
s.Equal("9fae4be19624297947d2b605cefbff516628612d", commit.SHA)
|
||||
s.Equal("Amir Malka", commit.Author.Name)
|
||||
s.Equal("amirm@armosec.io", commit.Author.Email)
|
||||
s.Equal("2022-05-22 18:55:48 +0300 +0300", commit.Author.Date.String())
|
||||
s.Equal("added file A\n", commit.Message)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
s.Run("fileB", func() {
|
||||
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPath); s.NoError(err) {
|
||||
if commit, err := localRepo.GetFileLastCommit("dirA/fileB"); s.NoError(err) {
|
||||
s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA)
|
||||
s.Equal("Amir Malka", commit.Author.Name)
|
||||
s.Equal("amirm@armosec.io", commit.Author.Email)
|
||||
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
|
||||
s.Equal("added file B\n", commit.Message)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user