mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
Compare commits
899 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ae21b064a | ||
|
|
0b01eb5ee4 | ||
|
|
feb9e3af10 | ||
|
|
ec30ed8439 | ||
|
|
cda9bb0e45 | ||
|
|
17f1c6b647 | ||
|
|
98079ec1ec | ||
|
|
16aaf9b5f8 | ||
|
|
ff0264ee15 | ||
|
|
bea9bd64a4 | ||
|
|
544a19906e | ||
|
|
208bb25118 | ||
|
|
fdb7e278c1 | ||
|
|
a132a49d57 | ||
|
|
23e73f5e88 | ||
|
|
fdcc5e9a66 | ||
|
|
77e7b1a2cb | ||
|
|
db95da3742 | ||
|
|
dc172a1476 | ||
|
|
8694a929cf | ||
|
|
36b3840362 | ||
|
|
d5fcbe842f | ||
|
|
155349dac0 | ||
|
|
7956a849d9 | ||
|
|
0d1c4cdc02 | ||
|
|
8c833a5df8 | ||
|
|
2c5bb977cb | ||
|
|
cddf7dd8f6 | ||
|
|
306c18147e | ||
|
|
84815eb97d | ||
|
|
890c13a91f | ||
|
|
3887ec8091 | ||
|
|
726b06bb70 | ||
|
|
c8e07c283e | ||
|
|
88a5128c03 | ||
|
|
01f2d3b04f | ||
|
|
fef85a4467 | ||
|
|
e0eadc1f2d | ||
|
|
a881b73e8d | ||
|
|
606f5cfb62 | ||
|
|
40737d545b | ||
|
|
990be3afe8 | ||
|
|
7020c2d025 | ||
|
|
a6497c1252 | ||
|
|
9d528a8075 | ||
|
|
5aec8b6f28 | ||
|
|
830ee27169 | ||
|
|
5f2e5c6f4e | ||
|
|
cf4317b5f6 | ||
|
|
2453aea6f3 | ||
|
|
e95b0f840a | ||
|
|
83680d1207 | ||
|
|
fd135e9e49 | ||
|
|
e47eb9cb4e | ||
|
|
d288fdc7f2 | ||
|
|
9de73dab29 | ||
|
|
f0afc20ec6 | ||
|
|
bf75059347 | ||
|
|
78835a58c4 | ||
|
|
fdccae9a1e | ||
|
|
6d97d42f67 | ||
|
|
46001e4761 | ||
|
|
b4d712fcb1 | ||
|
|
7847a4593b | ||
|
|
b2036e64f1 | ||
|
|
fd0bbcccfe | ||
|
|
7caa47f949 | ||
|
|
06b171901d | ||
|
|
e685fe2b7d | ||
|
|
7177e77a8d | ||
|
|
4cda32771b | ||
|
|
f896b65a87 | ||
|
|
3fff1b750a | ||
|
|
2380317953 | ||
|
|
bd9ade4d15 | ||
|
|
659d3533ee | ||
|
|
37c242576e | ||
|
|
e9a22a23e7 | ||
|
|
ae3816c1e0 | ||
|
|
e4661a5ae2 | ||
|
|
539d1889fe | ||
|
|
2dd5f05f1a | ||
|
|
60c9b38de4 | ||
|
|
8b66b068ea | ||
|
|
1507bc3f04 | ||
|
|
1e0baba919 | ||
|
|
4c9f47b1e1 | ||
|
|
b66446b7eb | ||
|
|
f1726e21ae | ||
|
|
8d48f8ad86 | ||
|
|
8b280f272e | ||
|
|
b92d4256ad | ||
|
|
914a04a386 | ||
|
|
12f3dd7db6 | ||
|
|
427032ab94 | ||
|
|
b55aaaa34d | ||
|
|
7cde877452 | ||
|
|
e399012f73 | ||
|
|
fe1d2646bd | ||
|
|
ea98bfbe9a | ||
|
|
7bc3277634 | ||
|
|
22e94c5a29 | ||
|
|
aa8cf0ff15 | ||
|
|
a22f97bd13 | ||
|
|
3fd2d1629d | ||
|
|
cd04204a5c | ||
|
|
eee55376e7 | ||
|
|
d3c0972d70 | ||
|
|
e3f5fa8e35 | ||
|
|
03c540b68c | ||
|
|
7f2f53b06c | ||
|
|
8064826b53 | ||
|
|
8bdff31693 | ||
|
|
6f05b4137b | ||
|
|
4207f3d6d1 | ||
|
|
98dbda696d | ||
|
|
7a34c94542 | ||
|
|
789b93776d | ||
|
|
c93ee64630 | ||
|
|
f54c3ad85c | ||
|
|
a9fcd00723 | ||
|
|
fb7cc4284e | ||
|
|
e7a0755c25 | ||
|
|
d1e02dc298 | ||
|
|
69814039ca | ||
|
|
2ffb7fcdb4 | ||
|
|
d1695b7f10 | ||
|
|
5f0f9a9eae | ||
|
|
aa2dedb76f | ||
|
|
d630811386 | ||
|
|
407e35c9d8 | ||
|
|
bb7f38ce31 | ||
|
|
1ffb2d360a | ||
|
|
5cbadc02c5 | ||
|
|
9aa8d9edf0 | ||
|
|
db84380844 | ||
|
|
bbb0d2154f | ||
|
|
2a937ac7c0 | ||
|
|
a96652094e | ||
|
|
dbf3de57f6 | ||
|
|
c00bc0ebbb | ||
|
|
b08e5a2c32 | ||
|
|
09db5d94e1 | ||
|
|
033e8f6b44 | ||
|
|
bef40f0e6c | ||
|
|
aa2f69125f | ||
|
|
d30f3960a7 | ||
|
|
5f43da94ba | ||
|
|
2aa8a0c935 | ||
|
|
c02f8c6cb5 | ||
|
|
aa0be474e2 | ||
|
|
c0161c9b33 | ||
|
|
71404f2205 | ||
|
|
514da1e2db | ||
|
|
75dfceb5da | ||
|
|
1ae76b4377 | ||
|
|
b6f90cba8e | ||
|
|
62af441a1d | ||
|
|
228b8957d3 | ||
|
|
b4ce999ab3 | ||
|
|
cc06a414fe | ||
|
|
d3c37c4e5f | ||
|
|
3b448b62b1 | ||
|
|
6a3f5658b1 | ||
|
|
f65e791522 | ||
|
|
d91304f9ad | ||
|
|
61ce00108e | ||
|
|
a4eb773eee | ||
|
|
cfc69f5a0f | ||
|
|
a44823c3ed | ||
|
|
8a166e5ba5 | ||
|
|
9a7aeff870 | ||
|
|
cb3bdb9df2 | ||
|
|
0be8d57eaa | ||
|
|
79b9cbf1d6 | ||
|
|
500df8737e | ||
|
|
b8acbd1bee | ||
|
|
0bde8a65ba | ||
|
|
d2884b8936 | ||
|
|
e692359b47 | ||
|
|
473746eab0 | ||
|
|
050878cbd6 | ||
|
|
e100f18bb0 | ||
|
|
05c82fc166 | ||
|
|
839c3e261f | ||
|
|
95b579d191 | ||
|
|
8656715753 | ||
|
|
05b6394c5c | ||
|
|
72860deb0f | ||
|
|
d3bdbf31ac | ||
|
|
639c694c13 | ||
|
|
f34f6dc51e | ||
|
|
b93e7b9abf | ||
|
|
995f615b10 | ||
|
|
39b95eff4f | ||
|
|
392625b774 | ||
|
|
306b9d28ca | ||
|
|
6fe87bba20 | ||
|
|
c0d534072d | ||
|
|
009221aa98 | ||
|
|
46e5aff5f9 | ||
|
|
59498361e7 | ||
|
|
c652da130d | ||
|
|
83246a1802 | ||
|
|
f255df0198 | ||
|
|
9e524ffc34 | ||
|
|
004cc0c469 | ||
|
|
52b78a7e73 | ||
|
|
bd089d76af | ||
|
|
d5025b54bf | ||
|
|
740497047d | ||
|
|
3f6cbd57b2 | ||
|
|
2c9524ed45 | ||
|
|
384922680a | ||
|
|
d2e9f8f4f8 | ||
|
|
b4f10f854e | ||
|
|
8ce64d2a7f | ||
|
|
d917e21364 | ||
|
|
32cedaf565 | ||
|
|
4c2a5e9a11 | ||
|
|
a41d2a46ff | ||
|
|
4794cbfb36 | ||
|
|
d021217cf7 | ||
|
|
4573d83831 | ||
|
|
2bb612ca3f | ||
|
|
35534112c6 | ||
|
|
f51e531f3a | ||
|
|
2490856ccb | ||
|
|
9a5a87b027 | ||
|
|
45b8c89865 | ||
|
|
e68e6dcd3d | ||
|
|
670ff4a15d | ||
|
|
b616a37800 | ||
|
|
ce488a3645 | ||
|
|
fb47a9c742 | ||
|
|
80ace81a12 | ||
|
|
1efdae5197 | ||
|
|
a4c88edfca | ||
|
|
8f38c2f627 | ||
|
|
bbf68d4ce8 | ||
|
|
e1eec47a22 | ||
|
|
fc05075817 | ||
|
|
5bb64b634a | ||
|
|
7bc2c2be13 | ||
|
|
27e2c044da | ||
|
|
1213e8d6ac | ||
|
|
3f58d68d2a | ||
|
|
803e62020e | ||
|
|
fde437312f | ||
|
|
18425c915b | ||
|
|
0de6892ddd | ||
|
|
dfb92ffec3 | ||
|
|
b7842f98f0 | ||
|
|
85317f1ee1 | ||
|
|
f22f60508f | ||
|
|
716bdaaf38 | ||
|
|
1b0e2b87de | ||
|
|
2c57b809d2 | ||
|
|
d9c96db212 | ||
|
|
5f7391a76b | ||
|
|
accd80eda8 | ||
|
|
e49499f085 | ||
|
|
521f8930d7 | ||
|
|
11b9a8eb6e | ||
|
|
0d4350ae24 | ||
|
|
62a6a25aa1 | ||
|
|
14a74e7312 | ||
|
|
3fad2f3430 | ||
|
|
c35d1e8791 | ||
|
|
0367255a2a | ||
|
|
f5f5552ecd | ||
|
|
046a22bd2b | ||
|
|
ad94ac7595 | ||
|
|
cfa3993b79 | ||
|
|
972793b98a | ||
|
|
35682bf5b8 | ||
|
|
b023f592aa | ||
|
|
a1c34646f1 | ||
|
|
9ac3768f1d | ||
|
|
ff7881130f | ||
|
|
37effda7c5 | ||
|
|
0cac7cb1a5 | ||
|
|
8d41d11ca3 | ||
|
|
0ef516d147 | ||
|
|
f57a30898c | ||
|
|
a10c67555d | ||
|
|
14d0df3926 | ||
|
|
c085aeaa68 | ||
|
|
8543afccca | ||
|
|
61b5603a3b | ||
|
|
e3efffb2ec | ||
|
|
fe9a342b42 | ||
|
|
c7668b4436 | ||
|
|
ccdf6b227f | ||
|
|
0aea384f41 | ||
|
|
467059cd26 | ||
|
|
f41af36ea9 | ||
|
|
e2f8902222 | ||
|
|
52bfd4cadc | ||
|
|
7cdc556292 | ||
|
|
039bda9eaf | ||
|
|
a6d73d6f8b | ||
|
|
8e5af59153 | ||
|
|
278467518e | ||
|
|
a7080a5778 | ||
|
|
6a71ef6745 | ||
|
|
10eb576260 | ||
|
|
f14acb79bf | ||
|
|
b8e011bd27 | ||
|
|
f6295308cd | ||
|
|
f981675850 | ||
|
|
93bb7610e6 | ||
|
|
23975ee359 | ||
|
|
14eaedf375 | ||
|
|
ced0b741b9 | ||
|
|
13e805b213 | ||
|
|
c424c1e394 | ||
|
|
77d68bdc73 | ||
|
|
a1555bb9cd | ||
|
|
3ca61b218e | ||
|
|
e7917277e7 | ||
|
|
aa18be17fa | ||
|
|
39c7af5f8d | ||
|
|
a5f7f8bbe4 | ||
|
|
420e491963 | ||
|
|
36f2ff997a | ||
|
|
c33807d052 | ||
|
|
fb3946b64f | ||
|
|
51322e7270 | ||
|
|
3f084d8525 | ||
|
|
b1f4002036 | ||
|
|
bb1cbe0902 | ||
|
|
a095634755 | ||
|
|
1b9ff074af | ||
|
|
f8361446a4 | ||
|
|
5713490f14 | ||
|
|
1ceac2a0a0 | ||
|
|
8a2967a0db | ||
|
|
86297720d5 | ||
|
|
1aeb2b96e2 | ||
|
|
4ee8b9d7f6 | ||
|
|
1d208ed5ec | ||
|
|
3883aaabab | ||
|
|
6fb3c070d0 | ||
|
|
d8d8b4ed73 | ||
|
|
907f46769f | ||
|
|
1ffdb717f7 | ||
|
|
9080603bce | ||
|
|
5796ae9084 | ||
|
|
50636e3a7e | ||
|
|
501d4c9dfc | ||
|
|
84cbc4ae04 | ||
|
|
cbb2a3e46f | ||
|
|
493197c073 | ||
|
|
31a2952101 | ||
|
|
acaccc23e8 | ||
|
|
70e339164d | ||
|
|
0de5d72d75 | ||
|
|
d604cc7faf | ||
|
|
d843a3e359 | ||
|
|
37586662b3 | ||
|
|
193687418f | ||
|
|
72e6bb9537 | ||
|
|
d69e790c61 | ||
|
|
01d41520d4 | ||
|
|
aea9eb9e01 | ||
|
|
26717b13e9 | ||
|
|
5f36417bd9 | ||
|
|
021ea34814 | ||
|
|
4a08fbdf28 | ||
|
|
268753091d | ||
|
|
ec688829b5 | ||
|
|
ec5bf58b0f | ||
|
|
f877d821f0 | ||
|
|
6c22cfef1e | ||
|
|
05305d858b | ||
|
|
e094237bbf | ||
|
|
77eb52bc51 | ||
|
|
c79834cec7 | ||
|
|
aefc5fded7 | ||
|
|
5fd5a5d4fa | ||
|
|
0368ecf7f3 | ||
|
|
d9ec5dcb56 | ||
|
|
030bc6c6b6 | ||
|
|
c1dd2fe0f4 | ||
|
|
4e0851868e | ||
|
|
276178c27c | ||
|
|
3006e6bcbf | ||
|
|
3a50c5686e | ||
|
|
f8eea4d082 | ||
|
|
8a42d77990 | ||
|
|
3980d1a9b0 | ||
|
|
53741ec26e | ||
|
|
c398cf46c9 | ||
|
|
e869ce4a64 | ||
|
|
4064be6577 | ||
|
|
1f00cf4151 | ||
|
|
bae0ca62b8 | ||
|
|
b7a51a2495 | ||
|
|
4f6a3e39d0 | ||
|
|
528f6b7402 | ||
|
|
c252f29e6d | ||
|
|
fea84c9652 | ||
|
|
9b9940f708 | ||
|
|
a34ab17307 | ||
|
|
477a3e7263 | ||
|
|
f94c9496df | ||
|
|
1c31281b7b | ||
|
|
0e5204ecb4 | ||
|
|
f3dc6235d7 | ||
|
|
37cdf1a19e | ||
|
|
1fb642c777 | ||
|
|
8f791ceb12 | ||
|
|
f40eaa0f56 | ||
|
|
cb34d17ba1 | ||
|
|
328ba82007 | ||
|
|
010ed1b047 | ||
|
|
5a81a77d92 | ||
|
|
c7ea10d206 | ||
|
|
a37d00b40a | ||
|
|
0168b768d2 | ||
|
|
9a85b57ba4 | ||
|
|
eafece6497 | ||
|
|
8f08271664 | ||
|
|
da0271e624 | ||
|
|
94f52fb4ac | ||
|
|
524c2922a4 | ||
|
|
0891d64654 | ||
|
|
d1c23f7442 | ||
|
|
8cbbe35f24 | ||
|
|
a21e9d706e | ||
|
|
57160c4d04 | ||
|
|
8b46a49e23 | ||
|
|
c11ebb49f7 | ||
|
|
e4c3935a1b | ||
|
|
ade062fdd3 | ||
|
|
b0f6357482 | ||
|
|
38a9c11286 | ||
|
|
0d95f02e60 | ||
|
|
1c30528eea | ||
|
|
d1b116d314 | ||
|
|
9d20fd41a8 | ||
|
|
54648bb973 | ||
|
|
fc4edb12f9 | ||
|
|
9a1b8d7ce2 | ||
|
|
6909975503 | ||
|
|
5d94bd990a | ||
|
|
1b2514e3ec | ||
|
|
0da4f40b48 | ||
|
|
67c8719f34 | ||
|
|
d5b60c6ac8 | ||
|
|
a99d2e9e26 | ||
|
|
5c7d89cb9e | ||
|
|
ae7810f0d3 | ||
|
|
5a90dc46f0 | ||
|
|
294f886588 | ||
|
|
17aec665cf | ||
|
|
959b25e8b7 | ||
|
|
9fd2bf3480 | ||
|
|
7b061a4e51 | ||
|
|
4fcd89390b | ||
|
|
667ffe9cd3 | ||
|
|
6f4086cd8c | ||
|
|
2a45a1a400 | ||
|
|
eee201de1e | ||
|
|
6be24bd22a | ||
|
|
ca927dec30 | ||
|
|
3a78ef46a3 | ||
|
|
bdb1cd0905 | ||
|
|
ffb556a637 | ||
|
|
40acfb5e9d | ||
|
|
de8bcfa0d2 | ||
|
|
9439f407da | ||
|
|
5095e62961 | ||
|
|
3301907864 | ||
|
|
151175c40f | ||
|
|
234d4fa537 | ||
|
|
f384e8a6e3 | ||
|
|
66068757e1 | ||
|
|
8a7cda5dd1 | ||
|
|
8e67104ba4 | ||
|
|
0c9da9ddc8 | ||
|
|
a5ef6aa126 | ||
|
|
c133b7a2c2 | ||
|
|
a0ca68cc41 | ||
|
|
41cae0bc93 | ||
|
|
b4198fde8c | ||
|
|
bd24f35738 | ||
|
|
6fcbb757b5 | ||
|
|
3b8825e5d2 | ||
|
|
5cf3244918 | ||
|
|
934c9ccc8b | ||
|
|
41dfdfd1e8 | ||
|
|
427fb59c99 | ||
|
|
ae825800f6 | ||
|
|
d72700acf6 | ||
|
|
3310a6a26f | ||
|
|
740b5aa772 | ||
|
|
04b55e764a | ||
|
|
beb4062bb1 | ||
|
|
5d4cd4acdc | ||
|
|
aec8198131 | ||
|
|
0a850e47df | ||
|
|
5544820c5e | ||
|
|
4f466d517a | ||
|
|
cd0f20ca2f | ||
|
|
b3661848dc | ||
|
|
548201c256 | ||
|
|
a54e5d9f8b | ||
|
|
536257afa1 | ||
|
|
5a71c3270a | ||
|
|
d194dd173f | ||
|
|
be03a9e984 | ||
|
|
a90177e7c0 | ||
|
|
be9e8ca47d | ||
|
|
eb9fe85c75 | ||
|
|
47183c405f | ||
|
|
2725923b9b | ||
|
|
f6c03ed7a2 | ||
|
|
76b5548216 | ||
|
|
cc57a34a32 | ||
|
|
f7099b62e6 | ||
|
|
093ee8916e | ||
|
|
ac0259157b | ||
|
|
9cb937798f | ||
|
|
11d4926c85 | ||
|
|
836211ae2b | ||
|
|
fddf3d3f58 | ||
|
|
b036d1079e | ||
|
|
137c39e918 | ||
|
|
8778d022cf | ||
|
|
043bdbacec | ||
|
|
e0e19b0258 | ||
|
|
c72e0f790a | ||
|
|
87e79110a2 | ||
|
|
faeae1af60 | ||
|
|
b371fbad01 | ||
|
|
90831e153d | ||
|
|
009d8275c1 | ||
|
|
05c88e0ffc | ||
|
|
7d12552932 | ||
|
|
b761505bb1 | ||
|
|
63367f4f31 | ||
|
|
6f9d6b4af3 | ||
|
|
6ed8287b01 | ||
|
|
d948e20682 | ||
|
|
42929dac58 | ||
|
|
74449c64a2 | ||
|
|
0bd164c69e | ||
|
|
d44c082134 | ||
|
|
d756b9bfe4 | ||
|
|
6144050212 | ||
|
|
b5fe456b0d | ||
|
|
37791ff391 | ||
|
|
c2d99163a6 | ||
|
|
d948353b99 | ||
|
|
2649cb75f6 | ||
|
|
7c8da4a4b9 | ||
|
|
b72f5d75f7 | ||
|
|
947826a764 | ||
|
|
906c69a86d | ||
|
|
9c65aadcc7 | ||
|
|
70a9a7bbbd | ||
|
|
09879e00ba | ||
|
|
5cbb7d940d | ||
|
|
050976d7a3 | ||
|
|
6a96d9f8b5 | ||
|
|
8c55dfbcf6 | ||
|
|
78c5b49b5a | ||
|
|
c33ca04a4f | ||
|
|
f12e66d315 | ||
|
|
8e4bb36df8 | ||
|
|
034412f6fe | ||
|
|
88d83ba72d | ||
|
|
829e7a33aa | ||
|
|
1dcde1538e | ||
|
|
b876cd0975 | ||
|
|
a06d11dc17 | ||
|
|
b1f5cd45c4 | ||
|
|
2c44c0e1f0 | ||
|
|
37c429b264 | ||
|
|
82994ce754 | ||
|
|
621f64c363 | ||
|
|
5591bf09d9 | ||
|
|
da94651656 | ||
|
|
419a77f144 | ||
|
|
f043358a59 | ||
|
|
24b17a8c27 | ||
|
|
26a64b788b | ||
|
|
89a05d247b | ||
|
|
718d549bb3 | ||
|
|
54a6a8324a | ||
|
|
7faf24cf88 | ||
|
|
7bebc7a814 | ||
|
|
8647a087dd | ||
|
|
78670665c4 | ||
|
|
1dd587cd83 | ||
|
|
d69cccf821 | ||
|
|
8ae3b9c28f | ||
|
|
9b6ad102b1 | ||
|
|
e6787b77fb | ||
|
|
46449045a6 | ||
|
|
d81984b4c6 | ||
|
|
22b463f306 | ||
|
|
475e45b848 | ||
|
|
32fac97d21 | ||
|
|
f9f32a1062 | ||
|
|
c90c3bbd05 | ||
|
|
ea42d9a061 | ||
|
|
cbae9a087b | ||
|
|
4da199c43e | ||
|
|
00d8660a91 | ||
|
|
76c2f6afe0 | ||
|
|
efa53bd83c | ||
|
|
01f6a1e1c0 | ||
|
|
1d1344ebc1 | ||
|
|
9f78703dee | ||
|
|
1a0e96338e | ||
|
|
eff4690e0e | ||
|
|
266480c234 | ||
|
|
a922d01005 | ||
|
|
b053b84197 | ||
|
|
0d9711c8bb | ||
|
|
edab68f4fb | ||
|
|
08f04e19ef | ||
|
|
e62234a6ac | ||
|
|
5499c7a96f | ||
|
|
7f9c5c25ae | ||
|
|
b1276d56f7 | ||
|
|
b53bf320a6 | ||
|
|
81a4c168ed | ||
|
|
512a1a806e | ||
|
|
c95ef05177 | ||
|
|
563bd8a6a3 | ||
|
|
b444542f4d | ||
|
|
6eded41eee | ||
|
|
de91ce182d | ||
|
|
afc7f85460 | ||
|
|
c1b4d7de39 | ||
|
|
cde5b83bca | ||
|
|
f00106a502 | ||
|
|
3ae2742717 | ||
|
|
8deac19945 | ||
|
|
2ad469a5f4 | ||
|
|
c67b111c77 | ||
|
|
ee770e7429 | ||
|
|
197a3adf6a | ||
|
|
269d39497b | ||
|
|
0c3a7ac02b | ||
|
|
07443548c9 | ||
|
|
e561f78ada | ||
|
|
bb88272251 | ||
|
|
ce2f3dafae | ||
|
|
488c67056c | ||
|
|
351e51208d | ||
|
|
0c79df7299 | ||
|
|
b9a5c5af35 | ||
|
|
59741b84d1 | ||
|
|
25efcdf750 | ||
|
|
49dfa6c2a6 | ||
|
|
659647353e | ||
|
|
0eba51c80b | ||
|
|
35996d11f4 | ||
|
|
e0e0a811eb | ||
|
|
76f400d0aa | ||
|
|
8e950e0f54 | ||
|
|
0adb9dd540 | ||
|
|
5825555534 | ||
|
|
306a8c6201 | ||
|
|
123b620085 | ||
|
|
831e7814be | ||
|
|
7c85199ac2 | ||
|
|
efec8e4f2f | ||
|
|
af4faef9cf | ||
|
|
90052ad9e3 | ||
|
|
35c7b16e4a | ||
|
|
7ac75acfc2 | ||
|
|
22662fddcd | ||
|
|
6df1cdf5d8 | ||
|
|
2287c51d73 | ||
|
|
372942a14f | ||
|
|
6362246da4 | ||
|
|
9986d69215 | ||
|
|
21644e5cba | ||
|
|
ad93217bf6 | ||
|
|
ca49bc1ddd | ||
|
|
1229c73ddc | ||
|
|
7416202555 | ||
|
|
a0ba683eea | ||
|
|
89654eb26f | ||
|
|
9d1736a141 | ||
|
|
eaa4ed3da5 | ||
|
|
0db3f65312 | ||
|
|
1ea0a3ccc5 | ||
|
|
16cd30bea8 | ||
|
|
075ba4c603 | ||
|
|
2d898822df | ||
|
|
25b8ec82e8 | ||
|
|
44b74e2681 | ||
|
|
485e171008 | ||
|
|
c12eb83b4b | ||
|
|
84060e7823 | ||
|
|
d80d50b59d | ||
|
|
f11f054fea | ||
|
|
4c1d491d5a | ||
|
|
2b67cc520c | ||
|
|
2a5712bd3c | ||
|
|
ccbc11408b | ||
|
|
22bae315be | ||
|
|
740ab7cb46 | ||
|
|
cdaa9aa1b0 | ||
|
|
875f82dcbb | ||
|
|
e8f6bdd64a | ||
|
|
25247491ee | ||
|
|
57ae3dc3a7 | ||
|
|
27d00b58d7 | ||
|
|
263821ce67 | ||
|
|
3c12247b00 | ||
|
|
56d41596f6 | ||
|
|
f0cd1965b4 | ||
|
|
86b6a1d88a | ||
|
|
f903e13d7b | ||
|
|
015206a760 | ||
|
|
0aff119260 | ||
|
|
ddb8608501 | ||
|
|
0d75a273f0 | ||
|
|
4f07d23dd6 | ||
|
|
79baa0d66e | ||
|
|
d5ca49ef9b | ||
|
|
536d7fb3c5 | ||
|
|
f66fd1f38c | ||
|
|
3a4c06a818 | ||
|
|
fd8dd7ab8a | ||
|
|
0434f6a935 | ||
|
|
b51a26442c | ||
|
|
6462ac0f0e | ||
|
|
fc01dbbac9 | ||
|
|
df8576c066 | ||
|
|
ca8adf28cf | ||
|
|
f70c6c566e | ||
|
|
056d4411b7 | ||
|
|
602b83b0e5 | ||
|
|
1cfcc6d930 | ||
|
|
8c6f618743 | ||
|
|
6adf1c3162 | ||
|
|
9f5f4f1832 | ||
|
|
9f49cc83e9 | ||
|
|
67972199ce | ||
|
|
9d5db86bf3 | ||
|
|
39efed5fc1 | ||
|
|
21c2bf22dd | ||
|
|
6c94b3a423 | ||
|
|
683248db0b | ||
|
|
9032400528 | ||
|
|
1a925b1acf | ||
|
|
dadc8c2c60 | ||
|
|
01c1b44bfc | ||
|
|
a394a99d8f | ||
|
|
4213707b7f | ||
|
|
d53b8272ee | ||
|
|
fdd688ac68 | ||
|
|
5bb961bdc6 | ||
|
|
9e7cc06f97 | ||
|
|
1d184d9000 | ||
|
|
a5e2ebf647 | ||
|
|
29eb573de5 | ||
|
|
ec6c3da5ec | ||
|
|
e2d4f8961e | ||
|
|
a48c680201 | ||
|
|
c869f2c962 | ||
|
|
f77fc9a06d | ||
|
|
e12eae93b9 | ||
|
|
d92fb32574 | ||
|
|
541dba3d79 | ||
|
|
033ed17125 | ||
|
|
aaeb663d15 | ||
|
|
c337005985 | ||
|
|
192eeee348 | ||
|
|
27c97684b9 | ||
|
|
41d5fa70ed | ||
|
|
4206e9c175 | ||
|
|
8658bb05dd | ||
|
|
9b707016a9 | ||
|
|
4b02826883 | ||
|
|
b29774ea71 | ||
|
|
bf68e90a8e | ||
|
|
cc5cdcd831 | ||
|
|
07f23ff7d9 | ||
|
|
2985da6dc9 | ||
|
|
1523973749 | ||
|
|
ccafd78a14 | ||
|
|
4f71fe0d55 | ||
|
|
7bd6b6b4d1 | ||
|
|
2b976489a2 | ||
|
|
1440f20f95 | ||
|
|
941e7e27c0 | ||
|
|
5428c6ab2f | ||
|
|
851bb65d17 | ||
|
|
a3ce04b7e8 | ||
|
|
4d68ca6aa2 | ||
|
|
29c6767d3c | ||
|
|
d8f5f7975c | ||
|
|
4cd8476837 | ||
|
|
112257449f | ||
|
|
ff9cf4adf0 | ||
|
|
f2d387bc9c | ||
|
|
2ceb5150e2 | ||
|
|
00006ec721 | ||
|
|
00aa6948ab | ||
|
|
aad32ec965 | ||
|
|
bd24ed3af7 | ||
|
|
9fc455bcec | ||
|
|
0f8ba1e7e8 | ||
|
|
d3137af3d7 | ||
|
|
775dd037d6 | ||
|
|
49cbfe130c | ||
|
|
9cd61dd996 | ||
|
|
292c4aa060 | ||
|
|
56a265930d | ||
|
|
4171d110a4 | ||
|
|
2568241ef8 | ||
|
|
d0775565e9 | ||
|
|
3c6b2db919 | ||
|
|
ca4d4a096c | ||
|
|
ff27db6b83 | ||
|
|
c9ecb6c563 | ||
|
|
b9e5782264 | ||
|
|
d852f81cb0 | ||
|
|
6137aa5d8e | ||
|
|
131b67ee83 | ||
|
|
db6f00be08 | ||
|
|
2ecc80985a | ||
|
|
93dbfd5110 | ||
|
|
ae0c384c85 | ||
|
|
f60ff1fb26 | ||
|
|
08a81696a1 | ||
|
|
8375a8ae63 | ||
|
|
31d8cf5118 | ||
|
|
597b967e55 | ||
|
|
679238ec13 | ||
|
|
94884ac3d7 | ||
|
|
0ef8f20c50 | ||
|
|
82f3d62de5 | ||
|
|
46f1e6a83b | ||
|
|
65841a014f | ||
|
|
985c6868c1 | ||
|
|
fca862b2c7 | ||
|
|
77a9956d91 | ||
|
|
3a4a58fdd5 | ||
|
|
a1e639453d | ||
|
|
7da23c111e | ||
|
|
768556251d | ||
|
|
00fcc565b5 | ||
|
|
9c74e5c93b | ||
|
|
6a0ee6e0d7 | ||
|
|
93bb09d78e | ||
|
|
228e7703a8 | ||
|
|
4b15a3b8e0 | ||
|
|
80c5fd7439 | ||
|
|
504c4acc42 | ||
|
|
573d85d770 | ||
|
|
4247f66378 | ||
|
|
7d6a10e787 | ||
|
|
bad303692e | ||
|
|
af3b33f7b0 | ||
|
|
fd66b2eba5 | ||
|
|
157ba1a08d | ||
|
|
6b15e6575b | ||
|
|
53f3229e9f | ||
|
|
186435de69 | ||
|
|
4d027d691f | ||
|
|
3f84ee3fcc | ||
|
|
38103ac90b | ||
|
|
13d27697e1 | ||
|
|
942f356d19 | ||
|
|
b87b687e2f | ||
|
|
2e313719bb | ||
|
|
0c5eb48fdb | ||
|
|
2ae2c81e0b | ||
|
|
23090fbb9f | ||
|
|
fcb6255f75 | ||
|
|
abbc9c3e2e | ||
|
|
75f76fcecd | ||
|
|
a00bba5fe4 | ||
|
|
96473188ed | ||
|
|
56264df047 | ||
|
|
c3008981e4 | ||
|
|
572130d797 | ||
|
|
7d8cf37532 | ||
|
|
ebd9661255 | ||
|
|
05e108b47b | ||
|
|
8cb6824f3c | ||
|
|
304017fc41 | ||
|
|
b82673f694 | ||
|
|
ac7e5219f1 | ||
|
|
a4c4f9c6ed | ||
|
|
222b154505 | ||
|
|
67c2de74f1 | ||
|
|
571a15bee8 |
100
.github/workflows/build.yaml
vendored
100
.github/workflows/build.yaml
vendored
@@ -3,10 +3,6 @@ name: build
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
types: [ closed ]
|
||||
|
||||
jobs:
|
||||
once:
|
||||
name: Create release
|
||||
@@ -20,8 +16,8 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: v1.0.${{ github.run_number }}
|
||||
release_name: Release v1.0.${{ github.run_number }}
|
||||
tag_name: v2.0.${{ github.run_number }}
|
||||
release_name: Release v2.0.${{ github.run_number }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
build:
|
||||
@@ -33,16 +29,31 @@ jobs:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.17
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Build
|
||||
run: mkdir -p build/${{ matrix.os }} && go mod tidy && go build -ldflags "-w -s" -o build/${{ matrix.os }}/kubescape
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
ArmoBEServer: api.armo.cloud
|
||||
ArmoAuthServer: auth.armo.cloud
|
||||
ArmoERServer: report.armo.cloud
|
||||
ArmoWebsite: portal.armo.cloud
|
||||
CGO_ENABLED: 0
|
||||
run: python3 --version && python3 build.py
|
||||
|
||||
- name: Upload Release Asset
|
||||
- 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:
|
||||
@@ -52,3 +63,72 @@ jobs:
|
||||
asset_path: build/${{ matrix.os }}/kubescape
|
||||
asset_name: kubescape-${{ matrix.os }}
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload release hash
|
||||
id: upload-release-hash
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.once.outputs.upload_url }}
|
||||
asset_path: build/${{ matrix.os }}/kubescape.sha256
|
||||
asset_name: kubescape-${{ matrix.os }}-sha256
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
|
||||
|
||||
build-docker:
|
||||
name: Build docker container, tag and upload to registry
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'armosec/kubescape' }} # TODO
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set image version
|
||||
id: image-version
|
||||
run: echo '::set-output name=IMAGE_VERSION::v2.0.${{ github.run_number }}'
|
||||
|
||||
- name: Set image name
|
||||
id: image-name
|
||||
run: echo '::set-output name=IMAGE_NAME::quay.io/${{ github.repository_owner }}/kubescape'
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
|
||||
- name: Re-Tag Image to latest
|
||||
run: docker tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} ${{ steps.image-name.outputs.IMAGE_NAME }}:latest
|
||||
|
||||
- name: Login to Quay.io
|
||||
env: # Or as an environment variable
|
||||
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
||||
# - name: Login to GitHub Container Registry
|
||||
# uses: docker/login-action@v1
|
||||
# with:
|
||||
# registry: ghcr.io
|
||||
# username: ${{ github.actor }}
|
||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Push Docker image
|
||||
run: |
|
||||
docker push ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
docker push ${{ steps.image-name.outputs.IMAGE_NAME }}:latest
|
||||
|
||||
# TODO - Wait for casign to support fixed tags -> https://github.com/sigstore/cosign/issues/1424
|
||||
# - name: Install cosign
|
||||
# uses: sigstore/cosign-installer@main
|
||||
# with:
|
||||
# cosign-release: 'v1.5.1' # optional
|
||||
# - name: sign kubescape container image
|
||||
# env:
|
||||
# COSIGN_EXPERIMENTAL: "true"
|
||||
# run: |
|
||||
# cosign sign --force ${{ steps.image-name.outputs.IMAGE_NAME }}:latest
|
||||
# cosign sign --force ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
|
||||
|
||||
84
.github/workflows/build_dev.yaml
vendored
Normal file
84
.github/workflows/build_dev.yaml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
name: build-dev
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
jobs:
|
||||
build:
|
||||
name: Create cross-platform dev build
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
ArmoBEServer: api.armo.cloud
|
||||
ArmoAuthServer: auth.armo.cloud
|
||||
ArmoERServer: report.armo.cloud
|
||||
ArmoWebsite: portal.armo.cloud
|
||||
CGO_ENABLED: 0
|
||||
run: python3 --version && python3 build.py
|
||||
|
||||
- name: Smoke Testing
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: kubescape-${{ matrix.os }}
|
||||
path: build/${{ matrix.os }}/kubescape
|
||||
|
||||
build-docker:
|
||||
name: Build docker container, tag and upload to registry
|
||||
needs: build
|
||||
if: ${{ github.repository == 'armosec/kubescape' }} # TODO
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set image version
|
||||
id: image-version
|
||||
run: echo '::set-output name=IMAGE_VERSION::dev-v2.0.${{ github.run_number }}'
|
||||
|
||||
- name: Set image name
|
||||
id: image-name
|
||||
run: echo '::set-output name=IMAGE_NAME::quay.io/${{ github.repository_owner }}/kubescape'
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
|
||||
- name: Login to Quay.io
|
||||
env:
|
||||
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
||||
# - name: Login to GitHub Container Registry
|
||||
# uses: docker/login-action@v1
|
||||
# with:
|
||||
# registry: ghcr.io
|
||||
# username: ${{ github.actor }}
|
||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Push Docker image
|
||||
run: |
|
||||
docker push ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
|
||||
40
.github/workflows/master_pr_checks.yaml
vendored
Normal file
40
.github/workflows/master_pr_checks.yaml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: master-pr
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
types: [ edited, opened, synchronize, reopened ]
|
||||
jobs:
|
||||
build:
|
||||
name: Create cross-platform build
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
ArmoBEServer: api.armo.cloud
|
||||
ArmoAuthServer: auth.armo.cloud
|
||||
ArmoERServer: report.armo.cloud
|
||||
ArmoWebsite: portal.armo.cloud
|
||||
CGO_ENABLED: 0
|
||||
run: python3 --version && python3 build.py
|
||||
|
||||
- name: Smoke Testing
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
|
||||
|
||||
17
.github/workflows/post-release.yaml
vendored
Normal file
17
.github/workflows/post-release.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: create release digests
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published]
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
once:
|
||||
name: Creating digests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Digest
|
||||
uses: MCJack123/ghaction-generate-release-hashes@v1
|
||||
with:
|
||||
hash-type: sha1
|
||||
file-name: kubescape-release-digests
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,4 +1,7 @@
|
||||
*.vs*
|
||||
*go.sum*
|
||||
*kubescape*
|
||||
*debug*
|
||||
*debug*
|
||||
*vender*
|
||||
*.pyc*
|
||||
.idea
|
||||
ca.srl
|
||||
127
CODE_OF_CONDUCT.md
Normal file
127
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Contributor Covenant 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.
|
||||
100
CONTRIBUTING.md
Normal file
100
CONTRIBUTING.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Contributing
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
Please note we have a code of conduct, please follow it in all your interactions with the project.
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
|
||||
build.
|
||||
2. Update the README.md with details of changes to the interface, this includes new environment
|
||||
variables, exposed ports, useful file locations and container parameters.
|
||||
3. Open Pull Request to `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.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
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.
|
||||
|
||||
### Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* 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
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* 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
|
||||
|
||||
We will distance those who are constantly adhere to unacceptable behavior.
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### Scope
|
||||
|
||||
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.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
9
MAINTAINERS.md
Normal file
9
MAINTAINERS.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Maintainers
|
||||
|
||||
The following table lists Kubescape project maintainers
|
||||
|
||||
| Name | GitHub | Email | Organization | Repositories/Area of Expertise | Added/Renewed On |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Ben Hirschberg | @slashben | ben@armosec.io | ARMO | Kubescape CLI | 2021-09-01 |
|
||||
| Rotem Refael | @rotemamsa | rrefael@armosec.io | ARMO | Kubescape CLI | 2021-10-11 |
|
||||
| David Wertenteil | @dwertent | dwertent@armosec.io | ARMO | Kubescape CLI | 2021-09-01 |
|
||||
279
README.md
279
README.md
@@ -1,138 +1,273 @@
|
||||
<img src="docs/kubescape.png" width="300" alt="logo" align="center">
|
||||
|
||||
[](https://github.com/armosec/kubescape/actions/workflows/build.yaml)
|
||||
[](https://github.com/armosec/kubescape)
|
||||
[](https://goreportcard.com/report/github.com/armosec/kubescape)
|
||||
|
||||
Kubescape is the first tool for testing if Kubernetes is deployed securely as defined in [Kubernetes Hardening Guidance by NSA and CISA](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
|
||||
|
||||
Use Kubescape to test clusters or scan single YAML files and integrate it to your processes.
|
||||
|
||||
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.
|
||||
|
||||
</br>
|
||||
|
||||
<img src="docs/demo.gif">
|
||||
|
||||
# TL;DR
|
||||
## Install & Run
|
||||
|
||||
### Install:
|
||||
## Install:
|
||||
```
|
||||
curl -s https://raw.githubusercontent.com/armosec/kubescape/master/install.sh | /bin/bash
|
||||
```
|
||||
|
||||
### Run:
|
||||
```
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
|
||||
```
|
||||
[Install on windows](#install-on-windows)
|
||||
|
||||
If you wish to scan all namespaces in your cluster, remove the `--exclude-namespaces` flag.
|
||||
[Install on macOS](#install-on-macos)
|
||||
|
||||
## Run:
|
||||
```
|
||||
kubescape scan --submit --enable-host-scan
|
||||
```
|
||||
|
||||
<img src="docs/summary.png">
|
||||
|
||||
</br>
|
||||
|
||||
### Flags
|
||||
> 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.
|
||||
|
||||
| flag | default | description | options |
|
||||
| --- | --- | --- | --- |
|
||||
| `-e`/`--exclude-namespaces` | Scan all namespaces | Namespaces to exclude from scanning, Recommended to exclude `kube-system` and `kube-public` namespaces |
|
||||
| `-s`/`--silent` | Display progress messages | Silent progress messages |
|
||||
| `-f`/`--format` | `pretty-printer` | Output format | `pretty-printer`/`json`/`junit` |
|
||||
| `-o`/`--output` | print to stdout | Save scan result in file |
|
||||
</br>
|
||||
|
||||
### Click [👍](https://github.com/armosec/kubescape/stargazers) if you want us to continue to develop and improve Kubescape 😀
|
||||
|
||||
</br>
|
||||
|
||||
|
||||
# Being part of the team
|
||||
|
||||
We invite you to our team! We are excited about this project and want to return the love we get.
|
||||
|
||||
Want to contribute? Want to discuss something? Have an issue?
|
||||
|
||||
* 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!
|
||||
|
||||
|
||||
[<img src="docs/discord-banner.png" width="100" alt="logo" align="center">](https://armosec.github.io/kubescape/)
|
||||

|
||||
|
||||
|
||||
# Options and examples
|
||||
|
||||
[Kubescape docs](https://hub.armo.cloud/docs)
|
||||
|
||||
## Playground
|
||||
* [Kubescape playground](https://www.katacoda.com/pathaksaiyam/scenarios/kubescape)
|
||||
|
||||
## Tutorials
|
||||
|
||||
* [Overview](https://youtu.be/wdBkt_0Qhbg)
|
||||
* [How To Secure Kubernetes Clusters With Kubescape And Armo](https://youtu.be/ZATGiDIDBQk)
|
||||
* [Scan Kubernetes YAML files](https://youtu.be/Ox6DaR7_4ZI)
|
||||
* [Scan Kubescape on an air-gapped environment (offline support)](https://youtu.be/IGXL9s37smM)
|
||||
* [Managing exceptions in the Kubescape SaaS version](https://youtu.be/OzpvxGmCR80)
|
||||
* [Configure and run customized frameworks](https://youtu.be/12Sanq_rEhs)
|
||||
* Customize controls configurations. [Kubescape CLI](https://youtu.be/955psg6TVu4), [Kubescape SaaS](https://youtu.be/lIMVSVhH33o)
|
||||
|
||||
## Install on Windows
|
||||
|
||||
**Requires powershell v5.0+**
|
||||
|
||||
``` powershell
|
||||
iwr -useb https://raw.githubusercontent.com/armosec/kubescape/master/install.ps1 | iex
|
||||
```
|
||||
|
||||
Note: if you get an error you might need to change the execution policy (i.e. enable Powershell) with
|
||||
|
||||
``` powershell
|
||||
Set-ExecutionPolicy RemoteSigned -scope CurrentUser
|
||||
```
|
||||
|
||||
## Install on macOS
|
||||
|
||||
1. ```
|
||||
brew tap armosec/kubescape
|
||||
```
|
||||
2. ```
|
||||
brew install kubescape
|
||||
```
|
||||
|
||||
## Usage & Examples
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
* Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework
|
||||
|
||||
#### Scan a running Kubernetes cluster and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
|
||||
```
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
|
||||
kubescape scan --submit --enable-host-scan
|
||||
```
|
||||
|
||||
* Scan local `yaml`/`json` files before deploying <img src="docs/new-feature.svg">
|
||||
> Read [here](https://hub.armo.cloud/docs/host-sensor) more about the `enable-host-scan` flag
|
||||
|
||||
#### Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
|
||||
```
|
||||
kubescape scan framework nsa *.yaml
|
||||
kubescape scan framework nsa --submit
|
||||
```
|
||||
|
||||
|
||||
* Scan `yaml`/`json` files from url <img src="docs/new-feature.svg">
|
||||
#### 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 nsa https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/master/release/kubernetes-manifests.yaml
|
||||
kubescape scan framework mitre --submit
|
||||
```
|
||||
|
||||
* Output in `json` format <img src="docs/new-feature.svg">
|
||||
|
||||
#### Scan a running Kubernetes cluster with a specific control using the control name or control ID. [List of controls](https://hub.armo.cloud/docs/controls)
|
||||
```
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format json --output results.json
|
||||
kubescape scan control "Privileged container"
|
||||
```
|
||||
|
||||
* Output in `junit xml` format <img src="docs/new-feature.svg">
|
||||
#### Scan specific namespaces
|
||||
```
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format junit --output results.xml
|
||||
kubescape scan --include-namespaces development,staging,production
|
||||
```
|
||||
|
||||
### Helm Support
|
||||
|
||||
* Render the helm chart using [`helm template`](https://helm.sh/docs/helm/helm_template/) and pass to stdout <img src="docs/new-feature.svg">
|
||||
#### Scan cluster and exclude some namespaces
|
||||
```
|
||||
helm template [NAME] [CHART] [flags] --dry-run | kubescape scan framework nsa -
|
||||
kubescape scan --exclude-namespaces kube-system,kube-public
|
||||
```
|
||||
|
||||
for example:
|
||||
#### Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI)
|
||||
```
|
||||
helm template bitnami/mysql --generate-name --dry-run | kubescape scan framework nsa -
|
||||
kubescape scan *.yaml
|
||||
```
|
||||
|
||||
# How to build
|
||||
|
||||
Note: development (and the release process) is done with Go `1.16`
|
||||
|
||||
1. Clone Project
|
||||
#### Scan kubernetes manifest files from a public github repository
|
||||
```
|
||||
git clone git@github.com:armosec/kubescape.git kubescape && cd "$_"
|
||||
kubescape scan https://github.com/armosec/kubescape
|
||||
```
|
||||
|
||||
2. Build
|
||||
#### Display all scanned resources (including the resources who passed)
|
||||
```
|
||||
go mod tidy && go build -o kubescape .
|
||||
kubescape scan --verbose
|
||||
```
|
||||
|
||||
3. Run
|
||||
#### Output in `json` format
|
||||
|
||||
> Add the `--format-version v2` flag
|
||||
|
||||
```
|
||||
./kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
|
||||
kubescape scan --format json --format-version v2 --output results.json
|
||||
```
|
||||
|
||||
4. Enjoy :zany_face:
|
||||
#### Output in `junit xml` format
|
||||
```
|
||||
kubescape scan --format junit --output results.xml
|
||||
```
|
||||
|
||||
#### Output in `pdf` format - Contributed by [@alegrey91](https://github.com/alegrey91)
|
||||
|
||||
```
|
||||
kubescape scan --format pdf --output results.pdf
|
||||
```
|
||||
|
||||
#### Output in `prometheus` metrics format - Contributed by [@Joibel](https://github.com/Joibel)
|
||||
|
||||
```
|
||||
kubescape scan --format prometheus
|
||||
```
|
||||
|
||||
#### Scan with exceptions, objects with exceptions will be presented as `exclude` and not `fail`
|
||||
[Full documentation](examples/exceptions/README.md)
|
||||
```
|
||||
kubescape scan --exceptions examples/exceptions/exclude-kube-namespaces.json
|
||||
```
|
||||
|
||||
#### Scan Helm charts - Render the helm chart using [`helm template`](https://helm.sh/docs/helm/helm_template/) and pass to stdout
|
||||
```
|
||||
helm template [NAME] [CHART] [flags] --dry-run | kubescape scan -
|
||||
```
|
||||
|
||||
e.g.
|
||||
```
|
||||
helm template bitnami/mysql --generate-name --dry-run | kubescape scan -
|
||||
```
|
||||
|
||||
|
||||
### Offline/Air-gaped Environment Support
|
||||
|
||||
[Video tutorial](https://youtu.be/IGXL9s37smM)
|
||||
|
||||
It is possible to run Kubescape offline!
|
||||
#### Download all artifacts
|
||||
|
||||
1. Download and save in local directory, if path not specified, will save all in `~/.kubescape`
|
||||
```
|
||||
kubescape download artifacts --output path/to/local/dir
|
||||
```
|
||||
2. Copy the downloaded artifacts to the air-gaped/offline environment
|
||||
|
||||
3. Scan using the downloaded artifacts
|
||||
```
|
||||
kubescape scan --use-artifacts-from path/to/local/dir
|
||||
```
|
||||
|
||||
#### Download a single artifacts
|
||||
|
||||
You can also download a single artifacts and scan with the `--use-from` flag
|
||||
|
||||
1. Download and save in file, if file name not specified, will save in `~/.kubescape/<framework name>.json`
|
||||
```
|
||||
kubescape download framework nsa --output /path/nsa.json
|
||||
```
|
||||
2. Copy the downloaded artifacts to the air-gaped/offline environment
|
||||
|
||||
3. Scan using the downloaded framework
|
||||
```
|
||||
kubescape scan framework nsa --use-from /path/nsa.json
|
||||
```
|
||||
|
||||
|
||||
## Scan Periodically using Helm - Contributed by [@yonahd](https://github.com/yonahd)
|
||||
[Please follow the instructions here](https://hub.armo.cloud/docs/installation-of-armo-in-cluster)
|
||||
[helm chart repo](https://github.com/armosec/armo-helm)
|
||||
|
||||
## Scan using docker image
|
||||
|
||||
Official Docker image `quay.io/armosec/kubescape`
|
||||
|
||||
```
|
||||
docker run -v "$(pwd)/example.yaml:/app/example.yaml quay.io/armosec/kubescape scan /app/example.yaml
|
||||
```
|
||||
|
||||
If you wish, you can [build the docker image on your own](build/README.md)
|
||||
|
||||
# Submit data manually
|
||||
|
||||
Use the `submit` command if you wish to submit data manually
|
||||
|
||||
## Submit scan results manually
|
||||
|
||||
> Support forward compatibility by using the `--format-version v2` flag
|
||||
|
||||
First, scan your cluster using the `json` format flag: `kubescape scan framework <name> --format json --format-version v2 --output path/to/results.json`.
|
||||
|
||||
Now you can submit the results to the Kubescape SaaS version -
|
||||
```
|
||||
kubescape submit results path/to/results.json
|
||||
```
|
||||
|
||||
# Under the hood
|
||||
|
||||
## Tests
|
||||
Kubescape is running the following tests according to what is defined by [Kubernetes Hardening Guidance by NSA and CISA](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
|
||||
* Non-root containers
|
||||
* Immutable container filesystem
|
||||
* Privileged containers
|
||||
* hostPID, hostIPC privileges
|
||||
* hostNetwork access
|
||||
* allowedHostPaths field
|
||||
* Protecting pod service account tokens
|
||||
* Resource policies
|
||||
* Control plane hardening
|
||||
* Exposed dashboard
|
||||
* Allow privilege escalation
|
||||
* Applications credentials in configuration files
|
||||
* Cluster-admin binding
|
||||
* Exec into container
|
||||
* Dangerous capabilities
|
||||
* Insecure capabilities
|
||||
* Linux hardening
|
||||
* Ingress and Egress blocked
|
||||
* Container hostPort
|
||||
|
||||
|
||||
|
||||
## Technology
|
||||
Kubescape based on OPA engine: https://github.com/open-policy-agent/opa and ARMO's posture controls.
|
||||
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 [regos snippets](https://www.openpolicyagent.org/docs/latest/policy-language/) developed by [ARMO](https://www.armosec.io/).
|
||||
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>
|
||||
|
||||
|
||||
94
build.py
Normal file
94
build.py
Normal file
@@ -0,0 +1,94 @@
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
BASE_GETTER_CONST = "github.com/armosec/kubescape/cautils/getter"
|
||||
BE_SERVER_CONST = BASE_GETTER_CONST + ".ArmoBEURL"
|
||||
ER_SERVER_CONST = BASE_GETTER_CONST + ".ArmoERURL"
|
||||
WEBSITE_CONST = BASE_GETTER_CONST + ".ArmoFEURL"
|
||||
AUTH_SERVER_CONST = BASE_GETTER_CONST + ".armoAUTHURL"
|
||||
|
||||
def checkStatus(status, msg):
|
||||
if status != 0:
|
||||
sys.stderr.write(msg)
|
||||
exit(status)
|
||||
|
||||
|
||||
def getBuildDir():
|
||||
currentPlatform = platform.system()
|
||||
buildDir = "build/"
|
||||
|
||||
if currentPlatform == "Windows": buildDir += "windows-latest"
|
||||
elif currentPlatform == "Linux": buildDir += "ubuntu-latest"
|
||||
elif currentPlatform == "Darwin": buildDir += "macos-latest"
|
||||
else: raise OSError("Platform %s is not supported!" % (currentPlatform))
|
||||
|
||||
return buildDir
|
||||
|
||||
def getPackageName():
|
||||
packageName = "kubescape"
|
||||
# if platform.system() == "Windows": packageName += ".exe"
|
||||
|
||||
return packageName
|
||||
|
||||
|
||||
def main():
|
||||
print("Building Kubescape")
|
||||
|
||||
# print environment variables
|
||||
# print(os.environ)
|
||||
|
||||
# Set some variables
|
||||
packageName = getPackageName()
|
||||
buildUrl = "github.com/armosec/kubescape/cautils.BuildNumber"
|
||||
releaseVersion = os.getenv("RELEASE")
|
||||
ArmoBEServer = os.getenv("ArmoBEServer")
|
||||
ArmoERServer = os.getenv("ArmoERServer")
|
||||
ArmoWebsite = os.getenv("ArmoWebsite")
|
||||
ArmoAuthServer = os.getenv("ArmoAuthServer")
|
||||
|
||||
# Create build directory
|
||||
buildDir = getBuildDir()
|
||||
|
||||
ks_file = os.path.join(buildDir, packageName)
|
||||
hash_file = ks_file + ".sha256"
|
||||
|
||||
if not os.path.isdir(buildDir):
|
||||
os.makedirs(buildDir)
|
||||
|
||||
# Build kubescape
|
||||
ldflags = "-w -s"
|
||||
if releaseVersion:
|
||||
ldflags += " -X {}={}".format(buildUrl, releaseVersion)
|
||||
if ArmoBEServer:
|
||||
ldflags += " -X {}={}".format(BE_SERVER_CONST, ArmoBEServer)
|
||||
if ArmoERServer:
|
||||
ldflags += " -X {}={}".format(ER_SERVER_CONST, ArmoERServer)
|
||||
if ArmoWebsite:
|
||||
ldflags += " -X {}={}".format(WEBSITE_CONST, ArmoWebsite)
|
||||
if ArmoAuthServer:
|
||||
ldflags += " -X {}={}".format(AUTH_SERVER_CONST, ArmoAuthServer)
|
||||
|
||||
build_command = ["go", "build", "-o", ks_file, "-ldflags" ,ldflags]
|
||||
|
||||
print("Building kubescape and saving here: {}".format(ks_file))
|
||||
print("Build command: {}".format(" ".join(build_command)))
|
||||
|
||||
status = subprocess.call(build_command)
|
||||
checkStatus(status, "Failed to build kubescape")
|
||||
|
||||
sha256 = hashlib.sha256()
|
||||
with open(ks_file, "rb") as kube:
|
||||
sha256.update(kube.read())
|
||||
with open(hash_file, "w") as kube_sha:
|
||||
hash = sha256.hexdigest()
|
||||
print("kubescape hash: {}, file: {}".format(hash, hash_file))
|
||||
kube_sha.write(sha256.hexdigest())
|
||||
|
||||
print("Build Done")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
31
build/Dockerfile
Normal file
31
build/Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
||||
FROM golang:1.17-alpine as builder
|
||||
#ENV GOPROXY=https://goproxy.io,direct
|
||||
|
||||
ARG image_version
|
||||
|
||||
ENV RELEASE=$image_version
|
||||
|
||||
ENV GO111MODULE=
|
||||
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
# Install required python/pip
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
|
||||
RUN python3 -m ensurepip
|
||||
RUN pip3 install --no-cache --upgrade pip setuptools
|
||||
|
||||
WORKDIR /work
|
||||
ADD . .
|
||||
|
||||
RUN python build.py
|
||||
|
||||
RUN ls -ltr build/ubuntu-latest
|
||||
|
||||
FROM alpine
|
||||
COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kubescape
|
||||
|
||||
# # Download the frameworks. Use the "--use-default" flag when running kubescape
|
||||
# RUN kubescape download framework nsa && kubescape download framework mitre
|
||||
|
||||
ENTRYPOINT ["kubescape"]
|
||||
13
build/README.md
Normal file
13
build/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## Docker Build
|
||||
|
||||
### Build your own Docker image
|
||||
|
||||
1. Clone Project
|
||||
```
|
||||
git clone https://github.com/armosec/kubescape.git kubescape && cd "$_"
|
||||
```
|
||||
|
||||
2. Build
|
||||
```
|
||||
docker build -t kubescape -f build/Dockerfile .
|
||||
```
|
||||
@@ -1,101 +0,0 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HTTPReqFunc allows you to insert query params and more to aggregation message while using update aggregator
|
||||
type HTTPReqFunc func(req *http.Request, qryData interface{})
|
||||
|
||||
func BasicBEQuery(req *http.Request, qryData interface{}) {
|
||||
|
||||
q := req.URL.Query()
|
||||
|
||||
if notificationData, isok := qryData.(*LoginObject); isok {
|
||||
q.Add("customerGUID", notificationData.GUID)
|
||||
}
|
||||
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
func EmptyQuery(req *http.Request, qryData interface{}) {
|
||||
q := req.URL.Query()
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
func MapQuery(req *http.Request, qryData interface{}) {
|
||||
q := req.URL.Query()
|
||||
if qryMap, isok := qryData.(map[string]string); isok {
|
||||
for k, v := range qryMap {
|
||||
q.Add(k, v)
|
||||
}
|
||||
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
func BEHttpRequest(loginobj *LoginObject, beURL,
|
||||
httpverb string,
|
||||
endpoint string,
|
||||
payload []byte,
|
||||
f HTTPReqFunc,
|
||||
qryData interface{}) ([]byte, error) {
|
||||
client := &http.Client{}
|
||||
|
||||
beURL = fmt.Sprintf("%v/%v", beURL, endpoint)
|
||||
req, err := http.NewRequest(httpverb, beURL, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", loginobj.Authorization)
|
||||
f(req, qryData)
|
||||
|
||||
for _, cookie := range loginobj.Cookies {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
fmt.Printf("req:\n%v\nresp:%v\n", req, resp)
|
||||
return nil, fmt.Errorf("Error #%v Due to: %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
type BELoginResponse struct {
|
||||
Name string `json:"name"`
|
||||
PreferredUsername string `json:"preferred_username"`
|
||||
Email string `json:"email"`
|
||||
CustomerGuid string `json:"customerGuid"`
|
||||
Expires string `json:"expires"`
|
||||
Authorization string `json:"authorization"`
|
||||
Cookies []*http.Cookie
|
||||
}
|
||||
|
||||
func (r *BELoginResponse) ToLoginObject() *LoginObject {
|
||||
l := &LoginObject{}
|
||||
l.Authorization = r.Authorization
|
||||
l.Cookies = r.Cookies
|
||||
l.Expires = r.Expires
|
||||
l.GUID = r.CustomerGuid
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
type BackendConnector struct {
|
||||
BaseURL string
|
||||
BELoginResponse *BELoginResponse
|
||||
Credentials *CustomerLoginDetails
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MakeBackendConnector(client *http.Client, baseURL string, loginDetails *CustomerLoginDetails) (*BackendConnector, error) {
|
||||
if err := ValidateBEConnectorMakerInput(client, baseURL, loginDetails); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn := &BackendConnector{BaseURL: baseURL, Credentials: loginDetails, HTTPClient: client}
|
||||
err := conn.Login()
|
||||
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func ValidateBEConnectorMakerInput(client *http.Client, baseURL string, loginDetails *CustomerLoginDetails) error {
|
||||
if client == nil {
|
||||
fmt.Errorf("You must provide an initialized httpclient")
|
||||
}
|
||||
if len(baseURL) == 0 {
|
||||
return fmt.Errorf("you must provide a valid backend url")
|
||||
}
|
||||
|
||||
if loginDetails == nil || (len(loginDetails.Email) == 0 && len(loginDetails.Password) == 0) {
|
||||
return fmt.Errorf("you must provide valid login details")
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (r *BackendConnector) Login() error {
|
||||
if !r.IsExpired() {
|
||||
return nil
|
||||
}
|
||||
|
||||
loginInfoBytes, err := json.Marshal(r.Credentials)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshal credentials properly")
|
||||
}
|
||||
|
||||
beURL := fmt.Sprintf("%v/%v", r.BaseURL, "login")
|
||||
|
||||
req, err := http.NewRequest("POST", beURL, bytes.NewReader(loginInfoBytes))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Referer", strings.Replace(beURL, "dashbe", "cpanel", 1))
|
||||
resp, err := r.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read login response")
|
||||
}
|
||||
|
||||
loginS := &BELoginResponse{}
|
||||
json.Unmarshal(body, &loginS)
|
||||
|
||||
loginS.Cookies = resp.Cookies()
|
||||
r.BELoginResponse = loginS
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *BackendConnector) IsExpired() bool {
|
||||
return r.BELoginResponse == nil || r.BELoginResponse.ToLoginObject().IsExpired()
|
||||
}
|
||||
|
||||
func (r *BackendConnector) GetBaseURL() string {
|
||||
return r.BaseURL
|
||||
}
|
||||
func (r *BackendConnector) GetLoginObj() *LoginObject {
|
||||
return r.BELoginResponse.ToLoginObject()
|
||||
}
|
||||
func (r *BackendConnector) GetClient() *http.Client {
|
||||
return r.HTTPClient
|
||||
}
|
||||
|
||||
func (r *BackendConnector) HTTPSend(httpverb string,
|
||||
endpoint string,
|
||||
payload []byte,
|
||||
f HTTPReqFunc,
|
||||
qryData interface{}) ([]byte, error) {
|
||||
|
||||
beURL := fmt.Sprintf("%v/%v", r.GetBaseURL(), endpoint)
|
||||
req, err := http.NewRequest(httpverb, beURL, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.IsExpired() {
|
||||
r.Login()
|
||||
}
|
||||
|
||||
loginobj := r.GetLoginObj()
|
||||
req.Header.Set("Authorization", loginobj.Authorization)
|
||||
f(req, qryData)
|
||||
q := req.URL.Query()
|
||||
q.Set("customerGUID", loginobj.GUID)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
for _, cookie := range loginobj.Cookies {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
resp, err := r.GetClient().Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
fmt.Printf("req:\n%v\nresp:%v\n", req, resp)
|
||||
return nil, fmt.Errorf("Error #%v Due to: %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package apis
|
||||
|
||||
// WebsocketScanCommand api
|
||||
const (
|
||||
WebsocketScanCommandVersion string = "v1"
|
||||
WebsocketScanCommandPath string = "scanImage"
|
||||
)
|
||||
|
||||
// commands send via websocket
|
||||
const (
|
||||
UPDATE string = "update"
|
||||
ATTACH string = "Attach"
|
||||
REMOVE string = "remove"
|
||||
DETACH string = "Detach"
|
||||
INCOMPATIBLE string = "Incompatible"
|
||||
REPLACE_HEADERS string = "ReplaceHeaders"
|
||||
IMAGE_UNREACHABLE string = "ImageUnreachable"
|
||||
SIGN string = "sign"
|
||||
UNREGISTERED string = "unregistered"
|
||||
INJECT string = "inject"
|
||||
RESTART string = "restart"
|
||||
ENCRYPT string = "encryptSecret"
|
||||
DECRYPT string = "decryptSecret"
|
||||
SCAN string = "scan"
|
||||
)
|
||||
@@ -1,78 +0,0 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
// WebsocketScanCommand trigger scan thru the websocket
|
||||
type WebsocketScanCommand struct {
|
||||
// CustomerGUID string `json:"customerGUID"`
|
||||
ImageTag string `json:"imageTag"`
|
||||
Wlid string `json:"wlid"`
|
||||
IsScanned bool `json:"isScanned"`
|
||||
ContainerName string `json:"containerName"`
|
||||
JobID string `json:"jobID,omitempty"`
|
||||
LastAction int `json:"actionIDN"`
|
||||
// ImageHash string `json:"imageHash"`
|
||||
Credentials *types.AuthConfig `json:"credentials,omitempty"`
|
||||
}
|
||||
|
||||
//taken from BE
|
||||
// ElasticRespTotal holds the total struct in Elastic array response
|
||||
type ElasticRespTotal struct {
|
||||
Value int `json:"value"`
|
||||
Relation string `json:"relation"`
|
||||
}
|
||||
|
||||
// V2ListResponse holds the response of some list request with some metadata
|
||||
type V2ListResponse struct {
|
||||
Total ElasticRespTotal `json:"total"`
|
||||
Response interface{} `json:"response"`
|
||||
// Cursor for quick access to the next page. Not supported yet
|
||||
Cursor string `json:"cursor"`
|
||||
}
|
||||
|
||||
// Oauth2Customer returns inside the "ca_groups" field in claims section of
|
||||
// Oauth2 verification process
|
||||
type Oauth2Customer struct {
|
||||
CustomerName string `json:"customerName"`
|
||||
CustomerGUID string `json:"customerGUID"`
|
||||
}
|
||||
|
||||
type LoginObject struct {
|
||||
Authorization string `json:"authorization"`
|
||||
GUID string
|
||||
Cookies []*http.Cookie
|
||||
Expires string
|
||||
}
|
||||
|
||||
type SafeMode struct {
|
||||
Reporter string `json:"reporter"` // "Agent"
|
||||
Action string `json:"action,omitempty"` // "action"
|
||||
Wlid string `json:"wlid"` // CAA_WLID
|
||||
PodName string `json:"podName"` // CAA_POD_NAME
|
||||
InstanceID string `json:"instanceID"` // CAA_POD_NAME
|
||||
ContainerName string `json:"containerName,omitempty"` // CAA_CONTAINER_NAME
|
||||
ProcessName string `json:"processName,omitempty"`
|
||||
ProcessID int `json:"processID,omitempty"`
|
||||
ProcessCMD string `json:"processCMD,omitempty"`
|
||||
ComponentGUID string `json:"componentGUID,omitempty"` // CAA_GUID
|
||||
StatusCode int `json:"statusCode"` // 0/1/2
|
||||
ProcessExitCode int `json:"processExitCode"` // 0 +
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Message string `json:"message,omitempty"` // any string
|
||||
JobID string `json:"jobID,omitempty"` // any string
|
||||
Compatible *bool `json:"compatible,omitempty"`
|
||||
}
|
||||
|
||||
func (safeMode *SafeMode) Json() string {
|
||||
b, err := json.Marshal(*safeMode)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s", b)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package apis
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
// "net/http"
|
||||
// "testing"
|
||||
// )
|
||||
|
||||
// func TestAuditStructure(t *testing.T) {
|
||||
// c := http.Client{}
|
||||
// be, err := MakeBackendConnector(&c, "https://dashbe.eudev3.cyberarmorsoft.com", &CustomerLoginDetails{Email: "lalafi@cyberarmor.io", Password: "*", CustomerName: "CyberArmorTests"})
|
||||
// if err != nil {
|
||||
// t.Errorf("sad1")
|
||||
|
||||
// }
|
||||
|
||||
// b, err := be.HTTPSend("GET", "v1/microservicesOverview", nil, MapQuery, map[string]string{"wlid": "wlid://cluster-childrenofbodom/namespace-default/deployment-pos"})
|
||||
// if err != nil {
|
||||
// t.Errorf("sad2")
|
||||
|
||||
// }
|
||||
// fmt.Printf("%v", string(b))
|
||||
|
||||
// t.Errorf("sad")
|
||||
|
||||
// }
|
||||
@@ -1,27 +0,0 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// type Dashboard interface {
|
||||
// OPAFRAMEWORKGet(string, bool) ([]opapolicy.Framework, error)
|
||||
// }
|
||||
|
||||
// Connector - interface for any connector (BE/Portal and so on)
|
||||
type Connector interface {
|
||||
|
||||
//may used for a more generic httpsend interface based method
|
||||
GetBaseURL() string
|
||||
GetLoginObj() *LoginObject
|
||||
GetClient() *http.Client
|
||||
|
||||
Login() error
|
||||
IsExpired() bool
|
||||
|
||||
HTTPSend(httpverb string,
|
||||
endpoint string,
|
||||
payload []byte,
|
||||
f HTTPReqFunc,
|
||||
qryData interface{}) ([]byte, error)
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"io/ioutil"
|
||||
|
||||
oidc "github.com/coreos/go-oidc"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
|
||||
// "go.uber.org/zap"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func GetOauth2TokenURL() string {
|
||||
return "https://idens.eudev3.cyberarmorsoft.com/auth/realms/CyberArmorSites"
|
||||
}
|
||||
|
||||
func GetLoginStruct() (LoginAux, error) {
|
||||
|
||||
return LoginAux{Referer: "https://cpanel.eudev3.cyberarmorsoft.com/login", Url: "https://cpanel.eudev3.cyberarmorsoft.com/login"}, nil
|
||||
}
|
||||
|
||||
func LoginWithKeycloak(loginDetails CustomerLoginDetails) ([]uuid.UUID, *oidc.IDToken, error) {
|
||||
// var custGUID uuid.UUID
|
||||
// config.Oauth2TokenURL
|
||||
if GetOauth2TokenURL() == "" {
|
||||
return nil, nil, fmt.Errorf("missing oauth2 token URL")
|
||||
}
|
||||
urlaux, _ := GetLoginStruct()
|
||||
conf, err := getOauth2Config(urlaux)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ctx := context.Background()
|
||||
provider, err := oidc.NewProvider(ctx, GetOauth2TokenURL())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// "Oauth2ClientID": "golang-client"
|
||||
oidcConfig := &oidc.Config{
|
||||
ClientID: "golang-client",
|
||||
SkipClientIDCheck: true,
|
||||
}
|
||||
|
||||
verifier := provider.Verifier(oidcConfig)
|
||||
ouToken, err := conf.PasswordCredentialsToken(ctx, loginDetails.Email, loginDetails.Password)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// "Authorization",
|
||||
authorization := fmt.Sprintf("%s %s", ouToken.Type(), ouToken.AccessToken)
|
||||
// oidc.IDTokenVerifier
|
||||
tkn, err := verifier.Verify(ctx, ouToken.AccessToken)
|
||||
if err != nil {
|
||||
return nil, tkn, err
|
||||
}
|
||||
tkn.Nonce = authorization
|
||||
if loginDetails.CustomerName == "" {
|
||||
customers, err := getCustomersNames(tkn)
|
||||
if err != nil {
|
||||
return nil, tkn, err
|
||||
}
|
||||
if len(customers) == 1 {
|
||||
loginDetails.CustomerName = customers[0]
|
||||
} else {
|
||||
return nil, tkn, fmt.Errorf("login with one of the following customers: %v", customers)
|
||||
}
|
||||
}
|
||||
custGUID, err := getCustomerGUID(tkn, &loginDetails)
|
||||
if err != nil {
|
||||
return nil, tkn, err
|
||||
}
|
||||
return []uuid.UUID{custGUID}, tkn, nil
|
||||
}
|
||||
|
||||
func getOauth2Config(urlaux LoginAux) (*oauth2.Config, error) {
|
||||
reURLSlices := strings.Split(urlaux.Referer, "/")
|
||||
if len(reURLSlices) == 0 {
|
||||
reURLSlices = strings.Split(urlaux.Url, "/")
|
||||
}
|
||||
// zapLogger.With(zap.Strings("referer", reURLSlices)).Info("Searching oauth2Config for")
|
||||
if len(reURLSlices) < 3 {
|
||||
reURLSlices = []string{reURLSlices[0], reURLSlices[0], reURLSlices[0]}
|
||||
}
|
||||
lg, _ := GetLoginStruct()
|
||||
provider, _ := oidc.NewProvider(context.Background(), GetOauth2TokenURL())
|
||||
//provider.Endpoint {"AuthURL":"https://idens.eudev3.cyberarmorsoft.com/auth/realms/CyberArmorSites/protocol/openid-connect/auth","TokenURL":"https://idens.eudev3.cyberarmorsoft.com/auth/realms/CyberArmorSites/protocol/openid-connect/token","AuthStyle":0}
|
||||
conf := oauth2.Config{
|
||||
ClientID: "golang-client",
|
||||
ClientSecret: "4e33bad2-3491-41a6-b486-93c492cfb4a2",
|
||||
RedirectURL: lg.Referer,
|
||||
// Discovery returns the OAuth2 endpoints.
|
||||
Endpoint: provider.Endpoint(),
|
||||
// "openid" is a required scope for OpenID Connect flows.
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
}
|
||||
return &conf, nil
|
||||
// return nil, fmt.Errorf("canno't find oauth2Config for referer '%+v'.\nPlease set referer or origin headers", reURLSlices)
|
||||
}
|
||||
|
||||
func getCustomersNames(oauth2Details *oidc.IDToken) ([]string, error) {
|
||||
var claimsJSON Oauth2Claims
|
||||
if err := oauth2Details.Claims(&claimsJSON); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customersList := make([]string, 0, len(claimsJSON.CAGroups))
|
||||
for _, v := range claimsJSON.CAGroups {
|
||||
var caCustomer Oauth2Customer
|
||||
if err := json.Unmarshal([]byte(v), &caCustomer); err == nil {
|
||||
customersList = append(customersList, caCustomer.CustomerName)
|
||||
}
|
||||
}
|
||||
return customersList, nil
|
||||
}
|
||||
|
||||
func getCustomerGUID(tkn *oidc.IDToken, loginDetails *CustomerLoginDetails) (uuid.UUID, error) {
|
||||
|
||||
customers, err := getCustomersList(tkn)
|
||||
if err != nil {
|
||||
return uuid.UUID{}, err
|
||||
}
|
||||
|
||||
// if customer name not provided - use default customer
|
||||
if loginDetails.CustomerName == "" && len(customers) > 0 {
|
||||
return uuid.FromString(customers[0].CustomerGUID)
|
||||
}
|
||||
|
||||
for _, i := range customers {
|
||||
if i.CustomerName == loginDetails.CustomerName {
|
||||
return uuid.FromString(i.CustomerGUID)
|
||||
}
|
||||
}
|
||||
return uuid.UUID{}, fmt.Errorf("customer name not found in customer list")
|
||||
}
|
||||
|
||||
func getCustomersList(oauth2Details *oidc.IDToken) ([]Oauth2Customer, error) {
|
||||
var claimsJSON Oauth2Claims
|
||||
if err := oauth2Details.Claims(&claimsJSON); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customersList := make([]Oauth2Customer, 0, len(claimsJSON.CAGroups))
|
||||
for _, v := range claimsJSON.CAGroups {
|
||||
var caCustomer Oauth2Customer
|
||||
if err := json.Unmarshal([]byte(v), &caCustomer); err == nil {
|
||||
customersList = append(customersList, caCustomer)
|
||||
}
|
||||
}
|
||||
return customersList, nil
|
||||
}
|
||||
|
||||
// func MakeAuthCookies(custGUID uuid.UUID, ouToken *oidc.IDToken) (*http.Cookie, error) {
|
||||
// var ccc http.Cookie
|
||||
// var responseData AuthenticationCookie
|
||||
// expireDate := time.Now().UTC().Add(time.Duration(config.CookieExpirationHours) * time.Hour)
|
||||
// if ouToken != nil {
|
||||
// expireDate = ouToken.Expiry
|
||||
// }
|
||||
// ccc.Expires = expireDate
|
||||
// responseData.CustomerGUID = custGUID
|
||||
// responseData.Expires = ccc.Expires
|
||||
// responseData.Version = 0
|
||||
// authorizationStr := ""
|
||||
// if ouToken != nil {
|
||||
// authorizationStr = ouToken.Nonce
|
||||
// if err := ouToken.Claims(&responseData.Oauth2Claims); err != nil {
|
||||
// errStr := fmt.Sprintf("failed to get claims from JWT")
|
||||
// return nil, fmt.Errorf("%v", errStr)
|
||||
// }
|
||||
// }
|
||||
// jsonBytes, err := json.Marshal(responseData)
|
||||
// if err != nil {
|
||||
// errStr := fmt.Sprintf("failed to get claims from JWT")
|
||||
// return nil, fmt.Errorf("%v", errStr)
|
||||
// }
|
||||
// ccc.Name = "auth"
|
||||
// ccc.Value = hex.EncodeToString(jsonBytes) + "." + cacheaccess.CalcHmac256(jsonBytes)
|
||||
// // TODO: HttpOnly for security...
|
||||
// ccc.HttpOnly = false
|
||||
// ccc.Path = "/"
|
||||
// ccc.Secure = true
|
||||
// ccc.SameSite = http.SameSiteNoneMode
|
||||
// http.SetCookie(w, &ccc)
|
||||
// responseData.Authorization = authorizationStr
|
||||
// jsonBytes, err = json.Marshal(responseData)
|
||||
// if err != nil {
|
||||
// w.WriteHeader(http.StatusInternalServerError)
|
||||
// fmt.Fprintf(w, "error while marshaling response(2) %s", err)
|
||||
// return
|
||||
// }
|
||||
// w.Write(jsonBytes)
|
||||
// }
|
||||
|
||||
func Login(loginDetails CustomerLoginDetails) (*LoginObject, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func GetBEInfo(cfgFile string) string {
|
||||
return "https://dashbe.eudev3.cyberarmorsoft.com"
|
||||
}
|
||||
|
||||
func BELogin(loginDetails *CustomerLoginDetails, login string, cfg string) (*BELoginResponse, error) {
|
||||
client := &http.Client{}
|
||||
|
||||
basebeURL := GetBEInfo(cfg)
|
||||
beURL := fmt.Sprintf("%v/%v", basebeURL, login)
|
||||
|
||||
loginInfoBytes, err := json.Marshal(loginDetails)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest("POST", beURL, bytes.NewReader(loginInfoBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Referer", strings.Replace(beURL, "dashbe", "cpanel", 1))
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loginS := &BELoginResponse{}
|
||||
json.Unmarshal(body, &loginS)
|
||||
|
||||
loginS.Cookies = resp.Cookies()
|
||||
return loginS, nil
|
||||
}
|
||||
|
||||
func (r *LoginObject) IsExpired() bool {
|
||||
if r == nil {
|
||||
return true
|
||||
}
|
||||
t, err := time.Parse(time.RFC3339, r.Expires)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return t.UTC().Before(time.Now().UTC())
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package apis
|
||||
|
||||
// func TestLogin2BE(t *testing.T) {
|
||||
|
||||
// loginDetails := CustomerLoginDetails{Email: "lalafi@cyberarmor.io", Password: "***", CustomerName: "CyberArmorTests"}
|
||||
// res, err := BELogin(loginDetails, "login")
|
||||
// if err != nil {
|
||||
// t.Errorf("failed to get raw audit is different ")
|
||||
// }
|
||||
// k := res.ToLoginObject()
|
||||
|
||||
// fmt.Printf("%v\n", k)
|
||||
|
||||
// }
|
||||
|
||||
// func TestGetMicroserviceOverview(t *testing.T) {
|
||||
// // client := &http.Client{}
|
||||
// loginDetails := CustomerLoginDetails{Email: "lalafi@cyberarmor.io", Password: "***", CustomerName: "CyberArmorTests"}
|
||||
// loginobj, err := BELogin(loginDetails, "login")
|
||||
// if err != nil {
|
||||
// t.Errorf("failed to get raw audit is different ")
|
||||
// }
|
||||
// k := loginobj.ToLoginObject()
|
||||
// beURL := GetBEInfo("")
|
||||
|
||||
// res, err := BEHttpRequest(k, beURL,
|
||||
// "GET",
|
||||
// "v1/microservicesOverview",
|
||||
// nil,
|
||||
// BasicBEQuery,
|
||||
// k)
|
||||
|
||||
// if err != nil {
|
||||
// t.Errorf("failed to get raw audit is different ")
|
||||
// }
|
||||
|
||||
// s := string(res)
|
||||
|
||||
// fmt.Printf("%v\n", s)
|
||||
|
||||
// }
|
||||
@@ -1,38 +0,0 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
// AuthenticationCookie is what it is
|
||||
type AuthenticationCookie struct {
|
||||
Oauth2Claims `json:",inline"`
|
||||
CustomerGUID uuid.UUID `json:"customerGuid"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Version int `json:"version"`
|
||||
Authorization string `json:"authorization,omitempty"`
|
||||
}
|
||||
|
||||
type LoginAux struct {
|
||||
Referer string
|
||||
Url string
|
||||
}
|
||||
|
||||
// CustomerLoginDetails is what it is
|
||||
type CustomerLoginDetails struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
CustomerName string `json:"customer,omitempty"`
|
||||
CustomerGUID uuid.UUID `json:"customerGuid,omitempty"`
|
||||
}
|
||||
|
||||
// Oauth2Claims returns in claims section of Oauth2 verification process
|
||||
type Oauth2Claims struct {
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
PreferredUserName string `json:"preferred_username"`
|
||||
CAGroups []string `json:"ca_groups"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Commands list of commands received from websocket
|
||||
type Commands struct {
|
||||
Commands []Command `json:"commands"`
|
||||
}
|
||||
|
||||
// Command structure of command received from websocket
|
||||
type Command struct {
|
||||
CommandName string `json:"commandName"`
|
||||
ResponseID string `json:"responseID"`
|
||||
Wlid string `json:"wlid,omitempty"`
|
||||
WildWlid string `json:"wildWlid,omitempty"`
|
||||
Sid string `json:"sid,omitempty"`
|
||||
WildSid string `json:"wildSid,omitempty"`
|
||||
JobTracking JobTracking `json:"jobTracking"`
|
||||
Args map[string]interface{} `json:"args,omitempty"`
|
||||
}
|
||||
|
||||
type JobTracking struct {
|
||||
JobID string `json:"jobID,omitempty"`
|
||||
ParentID string `json:"parentAction,omitempty"`
|
||||
LastActionNumber int `json:"numSeq,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Command) DeepCopy() *Command {
|
||||
newCommand := &Command{}
|
||||
newCommand.CommandName = c.CommandName
|
||||
newCommand.ResponseID = c.ResponseID
|
||||
newCommand.Wlid = c.Wlid
|
||||
newCommand.WildWlid = c.WildWlid
|
||||
if c.Args != nil {
|
||||
newCommand.Args = make(map[string]interface{})
|
||||
for i, j := range c.Args {
|
||||
newCommand.Args[i] = j
|
||||
}
|
||||
}
|
||||
return newCommand
|
||||
}
|
||||
|
||||
func (c *Command) GetLabels() map[string]string {
|
||||
if c.Args != nil {
|
||||
if ilabels, ok := c.Args["labels"]; ok {
|
||||
labels := map[string]string{}
|
||||
if b, e := json.Marshal(ilabels); e == nil {
|
||||
if e = json.Unmarshal(b, &labels); e == nil {
|
||||
return labels
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (c *Command) SetLabels(labels map[string]string) {
|
||||
if c.Args == nil {
|
||||
c.Args = make(map[string]interface{})
|
||||
}
|
||||
c.Args["labels"] = labels
|
||||
}
|
||||
|
||||
func (c *Command) GetFieldSelector() map[string]string {
|
||||
if c.Args != nil {
|
||||
if ilabels, ok := c.Args["fieldSelector"]; ok {
|
||||
labels := map[string]string{}
|
||||
if b, e := json.Marshal(ilabels); e == nil {
|
||||
if e = json.Unmarshal(b, &labels); e == nil {
|
||||
return labels
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (c *Command) SetFieldSelector(labels map[string]string) {
|
||||
if c.Args == nil {
|
||||
c.Args = make(map[string]interface{})
|
||||
}
|
||||
c.Args["fieldSelector"] = labels
|
||||
}
|
||||
|
||||
func (c *Command) GetID() string {
|
||||
if c.WildWlid != "" {
|
||||
return c.WildWlid
|
||||
}
|
||||
if c.WildSid != "" {
|
||||
return c.WildSid
|
||||
}
|
||||
if c.Wlid != "" {
|
||||
return c.Wlid
|
||||
}
|
||||
if c.Sid != "" {
|
||||
return c.Sid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *Command) Json() string {
|
||||
b, _ := json.Marshal(*c)
|
||||
return fmt.Sprintf("%s", b)
|
||||
}
|
||||
|
||||
func SIDFallback(c *Command) {
|
||||
if c.GetID() == "" {
|
||||
sid, err := getSIDFromArgs(c.Args)
|
||||
if err != nil || sid == "" {
|
||||
return
|
||||
}
|
||||
c.Sid = sid
|
||||
}
|
||||
}
|
||||
|
||||
func getSIDFromArgs(args map[string]interface{}) (string, error) {
|
||||
sidInterface, ok := args["sid"]
|
||||
if !ok {
|
||||
return "", nil
|
||||
}
|
||||
sid, ok := sidInterface.(string)
|
||||
if !ok || sid == "" {
|
||||
return "", fmt.Errorf("sid found in args but empty")
|
||||
}
|
||||
// if _, err := secrethandling.SplitSecretID(sid); err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
return sid, nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package armotypes
|
||||
|
||||
type EnforcmentsRule struct {
|
||||
MonitoredObject []string `json:"monitoredObject"`
|
||||
MonitoredObjectExistence []string `json:"objectExistence"`
|
||||
MonitoredObjectEvent []string `json:"event"`
|
||||
Action []string `json:"action"`
|
||||
}
|
||||
|
||||
type ExecutionPolicy struct {
|
||||
PortalBase `json:",inline"`
|
||||
Designators []PortalDesignator `json:"designators"`
|
||||
PolicyType string `json:"policyType"`
|
||||
CreationTime string `json:"creation_time"`
|
||||
ExecutionEnforcmentsRules []EnforcmentsRule `json:"enforcementRules"`
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package armotypes
|
||||
|
||||
const (
|
||||
CostumerGuidQuery = "costumerGUID"
|
||||
ClusterNameQuery = "cluster"
|
||||
DatacenterNameQuery = "datacenter"
|
||||
NamespaceQuery = "namespace"
|
||||
ProjectQuery = "project"
|
||||
WlidQuery = "wlid"
|
||||
SidQuery = "sid"
|
||||
)
|
||||
|
||||
// PortalBase holds basic items data from portal BE
|
||||
type PortalBase struct {
|
||||
GUID string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
Attributes map[string]interface{} `json:"attributes,omitempty"` // could be string
|
||||
}
|
||||
|
||||
type DesignatorType string
|
||||
|
||||
// Supported designators
|
||||
const (
|
||||
DesignatorAttributes DesignatorType = "Attributes"
|
||||
/*
|
||||
WorkloadID format.
|
||||
k8s format: wlid://cluster-<cluster>/namespace-<namespace>/<kind>-<name>
|
||||
native format: wlid://datacenter-<datacenter>/project-<project>/native-<name>
|
||||
*/
|
||||
DesignatorWlid DesignatorType = "Wlid"
|
||||
/*
|
||||
Wild card - subset of wlid. e.g.
|
||||
1. Include cluster:
|
||||
wlid://cluster-<cluster>/
|
||||
2. Include cluster and namespace (filter out all other namespaces):
|
||||
wlid://cluster-<cluster>/namespace-<namespace>/
|
||||
*/
|
||||
DesignatorWildWlid DesignatorType = "WildWlid"
|
||||
DesignatorWlidContainer DesignatorType = "WlidContainer"
|
||||
DesignatorWlidProcess DesignatorType = "WlidProcess"
|
||||
DesignatorSid DesignatorType = "Sid" // secret id
|
||||
)
|
||||
|
||||
// attributes
|
||||
const (
|
||||
AttributeCluster = "cluster"
|
||||
AttributeNamespace = "namespace"
|
||||
)
|
||||
|
||||
// PortalDesignator represented single designation options
|
||||
type PortalDesignator struct {
|
||||
DesignatorType DesignatorType `json:"designatorType"`
|
||||
WLID string `json:"wlid"`
|
||||
WildWLID string `json:"wildwlid"`
|
||||
SID string `json:"sid"`
|
||||
Attributes map[string]string `json:"attributes"`
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package armotypes
|
||||
|
||||
func MockPortalBase(customerGUID, name string, attributes map[string]interface{}) *PortalBase {
|
||||
if customerGUID == "" {
|
||||
customerGUID = "36b6f9e1-3b63-4628-994d-cbe16f81e9c7"
|
||||
}
|
||||
if name == "" {
|
||||
name = "portalbase-a"
|
||||
}
|
||||
if attributes == nil {
|
||||
attributes = make(map[string]interface{})
|
||||
}
|
||||
return &PortalBase{
|
||||
GUID: customerGUID,
|
||||
Name: name,
|
||||
Attributes: attributes,
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package armotypes
|
||||
|
||||
var IgnoreLabels = []string{AttributeCluster, AttributeNamespace}
|
||||
|
||||
// DigestPortalDesignator - get cluster namespace and labels from designator
|
||||
func DigestPortalDesignator(designator *PortalDesignator) (string, string, map[string]string) {
|
||||
switch designator.DesignatorType {
|
||||
case DesignatorAttributes:
|
||||
return DigestAttributesDesignator(designator.Attributes)
|
||||
// case DesignatorWlid: TODO
|
||||
// case DesignatorWildWlid: TODO
|
||||
default:
|
||||
}
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
func DigestAttributesDesignator(attributes map[string]string) (string, string, map[string]string) {
|
||||
cluster := ""
|
||||
namespace := ""
|
||||
labels := map[string]string{}
|
||||
if attributes == nil || len(attributes) == 0 {
|
||||
return cluster, namespace, labels
|
||||
}
|
||||
for k, v := range attributes {
|
||||
labels[k] = v
|
||||
}
|
||||
if v, ok := attributes[AttributeNamespace]; ok {
|
||||
namespace = v
|
||||
delete(labels, AttributeNamespace)
|
||||
}
|
||||
if v, ok := attributes[AttributeCluster]; ok {
|
||||
cluster = v
|
||||
delete(labels, AttributeCluster)
|
||||
}
|
||||
return cluster, namespace, labels
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// labels added to the workload
|
||||
const (
|
||||
ArmoPrefix string = "armo"
|
||||
ArmoAttach string = ArmoPrefix + ".attach"
|
||||
ArmoInitialSecret string = ArmoPrefix + ".initial"
|
||||
ArmoSecretStatus string = ArmoPrefix + ".secret"
|
||||
ArmoCompatibleLabel string = ArmoPrefix + ".compatible"
|
||||
|
||||
ArmoSecretProtectStatus string = "protect"
|
||||
ArmoSecretClearStatus string = "clear"
|
||||
)
|
||||
|
||||
// annotations added to the workload
|
||||
const (
|
||||
ArmoUpdate string = ArmoPrefix + ".last-update"
|
||||
ArmoWlid string = ArmoPrefix + ".wlid"
|
||||
ArmoSid string = ArmoPrefix + ".sid"
|
||||
ArmoJobID string = ArmoPrefix + ".job"
|
||||
ArmoJobIDPath string = ArmoJobID + "/id"
|
||||
ArmoJobParentPath string = ArmoJobID + "/parent"
|
||||
ArmoJobActionPath string = ArmoJobID + "/action"
|
||||
ArmoCompatibleAnnotation string = ArmoAttach + "/compatible"
|
||||
ArmoReplaceheaders string = ArmoAttach + "/replaceheaders"
|
||||
)
|
||||
|
||||
const ( // DEPRECATED
|
||||
|
||||
CAAttachLabel string = "cyberarmor"
|
||||
Patched string = "Patched"
|
||||
Done string = "Done"
|
||||
Encrypted string = "Protected"
|
||||
|
||||
CAInjectOld = "injectCyberArmor"
|
||||
|
||||
CAPrefix string = "cyberarmor"
|
||||
CAProtectedSecret string = CAPrefix + ".secret"
|
||||
CAInitialSecret string = CAPrefix + ".initial"
|
||||
CAInject string = CAPrefix + ".inject"
|
||||
CAIgnore string = CAPrefix + ".ignore"
|
||||
CAReplaceHeaders string = CAPrefix + ".removeSecurityHeaders"
|
||||
)
|
||||
|
||||
const ( // DEPRECATED
|
||||
CAUpdate string = CAPrefix + ".last-update"
|
||||
CAStatus string = CAPrefix + ".status"
|
||||
CAWlid string = CAPrefix + ".wlid"
|
||||
)
|
||||
|
||||
type ClusterConfig struct {
|
||||
EventReceiverREST string `json:"eventReceiverREST"`
|
||||
EventReceiverWS string `json:"eventReceiverWS"`
|
||||
MaserNotificationServer string `json:"maserNotificationServer"`
|
||||
Postman string `json:"postman"`
|
||||
Dashboard string `json:"dashboard"`
|
||||
Portal string `json:"portal"`
|
||||
CustomerGUID string `json:"customerGUID"`
|
||||
ClusterGUID string `json:"clusterGUID"`
|
||||
ClusterName string `json:"clusterName"`
|
||||
OciImageURL string `json:"ociImageURL"`
|
||||
NotificationWSURL string `json:"notificationWSURL"`
|
||||
NotificationRestURL string `json:"notificationRestURL"`
|
||||
VulnScanURL string `json:"vulnScanURL"`
|
||||
OracleURL string `json:"oracleURL"`
|
||||
ClairURL string `json:"clairURL"`
|
||||
}
|
||||
|
||||
// represents workload basic info
|
||||
type SpiffeBasicInfo struct {
|
||||
//cluster/datacenter
|
||||
Level0 string `json:"level0"`
|
||||
Level0Type string `json:"level0Type"`
|
||||
|
||||
//namespace/project
|
||||
Level1 string `json:"level0"`
|
||||
Level1Type string `json:"level0Type"`
|
||||
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ImageInfo struct {
|
||||
Registry string `json:"registry"`
|
||||
VersionImage string `json:"versionImage"`
|
||||
}
|
||||
|
||||
func IsAttached(labels map[string]string) *bool {
|
||||
attach := false
|
||||
if labels == nil {
|
||||
return nil
|
||||
}
|
||||
if attached, ok := labels[ArmoAttach]; ok {
|
||||
if strings.ToLower(attached) == "true" {
|
||||
attach = true
|
||||
return &attach
|
||||
} else {
|
||||
return &attach
|
||||
}
|
||||
}
|
||||
|
||||
// deprecated
|
||||
if _, ok := labels[CAAttachLabel]; ok {
|
||||
attach = true
|
||||
return &attach
|
||||
}
|
||||
|
||||
// deprecated
|
||||
if inject, ok := labels[CAInject]; ok {
|
||||
if strings.ToLower(inject) == "true" {
|
||||
attach = true
|
||||
return &attach
|
||||
}
|
||||
}
|
||||
|
||||
// deprecated
|
||||
if ignore, ok := labels[CAIgnore]; ok {
|
||||
if strings.ToLower(ignore) == "true" {
|
||||
return &attach
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsSecretProtected(labels map[string]string) *bool {
|
||||
protect := false
|
||||
if labels == nil {
|
||||
return nil
|
||||
}
|
||||
if protected, ok := labels[ArmoSecretStatus]; ok {
|
||||
if strings.ToLower(protected) == ArmoSecretProtectStatus {
|
||||
protect = true
|
||||
return &protect
|
||||
} else {
|
||||
return &protect
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadConfig(configPath string, loadToEnv bool) (*ClusterConfig, error) {
|
||||
if configPath == "" {
|
||||
configPath = "/etc/config/clusterData.json"
|
||||
}
|
||||
|
||||
dat, err := ioutil.ReadFile(configPath)
|
||||
if err != nil || len(dat) == 0 {
|
||||
return nil, fmt.Errorf("Config empty or not found. path: %s", configPath)
|
||||
}
|
||||
componentConfig := &ClusterConfig{}
|
||||
if err := json.Unmarshal(dat, componentConfig); err != nil {
|
||||
return componentConfig, fmt.Errorf("Failed to read component config, path: %s, reason: %s", configPath, err.Error())
|
||||
}
|
||||
if loadToEnv {
|
||||
componentConfig.LoadConfigToEnv()
|
||||
}
|
||||
return componentConfig, nil
|
||||
}
|
||||
|
||||
func (clusterConfig *ClusterConfig) LoadConfigToEnv() {
|
||||
|
||||
SetEnv("CA_CLUSTER_NAME", clusterConfig.ClusterName)
|
||||
SetEnv("CA_CLUSTER_GUID", clusterConfig.ClusterGUID)
|
||||
SetEnv("CA_ORACLE_SERVER", clusterConfig.OracleURL)
|
||||
SetEnv("CA_CUSTOMER_GUID", clusterConfig.CustomerGUID)
|
||||
SetEnv("CA_DASHBOARD_BACKEND", clusterConfig.Dashboard)
|
||||
SetEnv("CA_NOTIFICATION_SERVER_REST", clusterConfig.NotificationWSURL)
|
||||
SetEnv("CA_NOTIFICATION_SERVER_WS", clusterConfig.NotificationWSURL)
|
||||
SetEnv("CA_NOTIFICATION_SERVER_REST", clusterConfig.NotificationRestURL)
|
||||
SetEnv("CA_OCIMAGE_URL", clusterConfig.OciImageURL)
|
||||
SetEnv("CA_K8S_REPORT_URL", clusterConfig.EventReceiverWS)
|
||||
SetEnv("CA_EVENT_RECEIVER_HTTP", clusterConfig.EventReceiverREST)
|
||||
SetEnv("CA_VULNSCAN", clusterConfig.VulnScanURL)
|
||||
SetEnv("CA_POSTMAN", clusterConfig.Postman)
|
||||
SetEnv("MASTER_NOTIFICATION_SERVER_HOST", clusterConfig.MaserNotificationServer)
|
||||
SetEnv("CLAIR_URL", clusterConfig.ClairURL)
|
||||
|
||||
}
|
||||
|
||||
func SetEnv(key, value string) {
|
||||
if e := os.Getenv(key); e == "" {
|
||||
if err := os.Setenv(key, value); err != nil {
|
||||
glog.Warning("%s: %s", key, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// tests wlid parse
|
||||
|
||||
func TestSpiffeWLIDToInfoSuccess(t *testing.T) {
|
||||
|
||||
WLID := "wlid://cluster-HipsterShopCluster2/namespace-prod/deployment-cartservice"
|
||||
ms, er := SpiffeToSpiffeInfo(WLID)
|
||||
|
||||
if er != nil || ms.Level0 != "HipsterShopCluster2" || ms.Level0Type != "cluster" || ms.Level1 != "prod" || ms.Level1Type != "namespace" ||
|
||||
ms.Kind != "deployment" || ms.Name != "cartservice" {
|
||||
t.Errorf("TestSpiffeWLIDToInfoSuccess failed to parse %v", WLID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpiffeSIDInfoSuccess(t *testing.T) {
|
||||
|
||||
SID := "sid://cluster-HipsterShopCluster2/namespace-dev/secret-caregcred"
|
||||
ms, er := SpiffeToSpiffeInfo(SID)
|
||||
|
||||
if er != nil || ms.Level0 != "HipsterShopCluster2" || ms.Level0Type != "cluster" || ms.Level1 != "dev" || ms.Level1Type != "namespace" ||
|
||||
ms.Kind != "secret" || ms.Name != "caregcred" {
|
||||
t.Errorf("TestSpiffeSIDInfoSuccess failed to parse %v", SID)
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// wlid/ sid utils
|
||||
const (
|
||||
SpiffePrefix = "://"
|
||||
)
|
||||
|
||||
// wlid/ sid utils
|
||||
const (
|
||||
PackagePath = "vendor/github.com/armosec/capacketsgo"
|
||||
)
|
||||
|
||||
//AsSHA256 takes anything turns it into string :) https://blog.8bitzen.com/posts/22-08-2019-how-to-hash-a-struct-in-go
|
||||
func AsSHA256(v interface{}) string {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(fmt.Sprintf("%v", v)))
|
||||
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
func SpiffeToSpiffeInfo(spiffe string) (*SpiffeBasicInfo, error) {
|
||||
basicInfo := &SpiffeBasicInfo{}
|
||||
|
||||
pos := strings.Index(spiffe, SpiffePrefix)
|
||||
if pos < 0 {
|
||||
return nil, fmt.Errorf("invalid spiffe %s", spiffe)
|
||||
}
|
||||
|
||||
pos += len(SpiffePrefix)
|
||||
spiffeNoPrefix := spiffe[pos:]
|
||||
splits := strings.Split(spiffeNoPrefix, "/")
|
||||
if len(splits) < 3 {
|
||||
return nil, fmt.Errorf("invalid spiffe %s", spiffe)
|
||||
}
|
||||
|
||||
p0 := strings.Index(splits[0], "-")
|
||||
p1 := strings.Index(splits[1], "-")
|
||||
p2 := strings.Index(splits[2], "-")
|
||||
if p0 == -1 || p1 == -1 || p2 == -1 {
|
||||
return nil, fmt.Errorf("invalid spiffe %s", spiffe)
|
||||
}
|
||||
basicInfo.Level0Type = splits[0][:p0]
|
||||
basicInfo.Level0 = splits[0][p0+1:]
|
||||
basicInfo.Level1Type = splits[1][:p1]
|
||||
basicInfo.Level1 = splits[1][p1+1:]
|
||||
basicInfo.Kind = splits[2][:p2]
|
||||
basicInfo.Name = splits[2][p2+1:]
|
||||
|
||||
return basicInfo, nil
|
||||
}
|
||||
|
||||
func ImageTagToImageInfo(imageTag string) (*ImageInfo, error) {
|
||||
ImageInfo := &ImageInfo{}
|
||||
spDelimiter := "/"
|
||||
pos := strings.Index(imageTag, spDelimiter)
|
||||
if pos < 0 {
|
||||
ImageInfo.Registry = ""
|
||||
ImageInfo.VersionImage = imageTag
|
||||
return ImageInfo, nil
|
||||
}
|
||||
|
||||
splits := strings.Split(imageTag, spDelimiter)
|
||||
if len(splits) == 0 {
|
||||
|
||||
return nil, fmt.Errorf("Invalid image info %s", imageTag)
|
||||
}
|
||||
|
||||
ImageInfo.Registry = splits[0]
|
||||
if len(splits) > 1 {
|
||||
ImageInfo.VersionImage = splits[len(splits)-1]
|
||||
} else {
|
||||
ImageInfo.VersionImage = ""
|
||||
}
|
||||
|
||||
return ImageInfo, nil
|
||||
}
|
||||
|
||||
func BoolPointer(b bool) *bool { return &b }
|
||||
|
||||
func BoolToString(b bool) string {
|
||||
if b {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func BoolPointerToString(b *bool) string {
|
||||
if b == nil {
|
||||
return ""
|
||||
}
|
||||
if *b {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func StringToBool(s string) bool {
|
||||
if strings.ToLower(s) == "true" || strings.ToLower(s) == "1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func StringToBoolPointer(s string) *bool {
|
||||
if strings.ToLower(s) == "true" || strings.ToLower(s) == "1" {
|
||||
return BoolPointer(true)
|
||||
}
|
||||
if strings.ToLower(s) == "false" || strings.ToLower(s) == "0" {
|
||||
return BoolPointer(false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var NamespacesListToIgnore = make([]string, 0)
|
||||
var KubeNamespaces = []string{metav1.NamespaceSystem, metav1.NamespacePublic}
|
||||
|
||||
// NamespacesListToIgnore namespaces to ignore if a pod
|
||||
func InitNamespacesListToIgnore(caNamespace string) {
|
||||
if len(NamespacesListToIgnore) > 0 {
|
||||
return
|
||||
}
|
||||
NamespacesListToIgnore = append(NamespacesListToIgnore, KubeNamespaces...)
|
||||
NamespacesListToIgnore = append(NamespacesListToIgnore, caNamespace)
|
||||
}
|
||||
|
||||
func IfIgnoreNamespace(ns string) bool {
|
||||
for i := range NamespacesListToIgnore {
|
||||
if NamespacesListToIgnore[i] == ns {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IfKubeNamespace(ns string) bool {
|
||||
for i := range KubeNamespaces {
|
||||
if NamespacesListToIgnore[i] == ns {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hash(s string) string {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(s))
|
||||
return fmt.Sprintf("%d", h.Sum32())
|
||||
}
|
||||
func GenarateConfigMapName(wlid string) string {
|
||||
name := strings.ToLower(fmt.Sprintf("ca-%s-%s-%s", GetNamespaceFromWlid(wlid), GetKindFromWlid(wlid), GetNameFromWlid(wlid)))
|
||||
if len(name) >= 63 {
|
||||
name = hash(name)
|
||||
}
|
||||
return name
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// API fields
|
||||
var (
|
||||
WlidPrefix = "wlid://"
|
||||
SidPrefix = "sid://"
|
||||
ClusterWlidPrefix = "cluster-"
|
||||
NamespaceWlidPrefix = "namespace-"
|
||||
DataCenterWlidPrefix = "datacenter-"
|
||||
ProjectWlidPrefix = "project-"
|
||||
SecretSIDPrefix = "secret-"
|
||||
SubSecretSIDPrefix = "subsecret-"
|
||||
K8SKindsList = []string{"ComponentStatus", "ConfigMap", "ControllerRevision", "CronJob",
|
||||
"CustomResourceDefinition", "DaemonSet", "Deployment", "Endpoints", "Event", "HorizontalPodAutoscaler",
|
||||
"Ingress", "Job", "Lease", "LimitRange", "LocalSubjectAccessReview", "MutatingWebhookConfiguration",
|
||||
"Namespace", "NetworkPolicy", "Node", "PersistentVolume", "PersistentVolumeClaim", "Pod",
|
||||
"PodDisruptionBudget", "PodSecurityPolicy", "PodTemplate", "PriorityClass", "ReplicaSet",
|
||||
"ReplicationController", "ResourceQuota", "Role", "RoleBinding", "Secret", "SelfSubjectAccessReview",
|
||||
"SelfSubjectRulesReview", "Service", "ServiceAccount", "StatefulSet", "StorageClass",
|
||||
"SubjectAccessReview", "TokenReview", "ValidatingWebhookConfiguration", "VolumeAttachment"}
|
||||
NativeKindsList = []string{"Dockerized", "Native"}
|
||||
KindReverseMap = map[string]string{}
|
||||
dataImagesList = []string{}
|
||||
)
|
||||
|
||||
func IsWlid(id string) bool {
|
||||
return strings.HasPrefix(id, WlidPrefix)
|
||||
}
|
||||
|
||||
func IsSid(id string) bool {
|
||||
return strings.HasPrefix(id, SidPrefix)
|
||||
}
|
||||
|
||||
// GetK8SKindFronList get the calculated wlid
|
||||
func GetK8SKindFronList(kind string) string { // TODO GetK8SKindFromList
|
||||
for i := range K8SKindsList {
|
||||
if strings.ToLower(kind) == strings.ToLower(K8SKindsList[i]) {
|
||||
return K8SKindsList[i]
|
||||
}
|
||||
}
|
||||
return kind
|
||||
}
|
||||
|
||||
// IsK8SKindInList Check if the kind is a known kind
|
||||
func IsK8SKindInList(kind string) bool {
|
||||
for i := range K8SKindsList {
|
||||
if strings.ToLower(kind) == strings.ToLower(K8SKindsList[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// generateWLID
|
||||
func generateWLID(pLevel0, level0, pLevel1, level1, k, name string) string {
|
||||
kind := strings.ToLower(k)
|
||||
kind = strings.Replace(kind, "-", "", -1)
|
||||
|
||||
wlid := WlidPrefix
|
||||
wlid += fmt.Sprintf("%s%s", pLevel0, level0)
|
||||
if level1 == "" {
|
||||
return wlid
|
||||
}
|
||||
wlid += fmt.Sprintf("/%s%s", pLevel1, level1)
|
||||
|
||||
if kind == "" {
|
||||
return wlid
|
||||
}
|
||||
wlid += fmt.Sprintf("/%s", kind)
|
||||
|
||||
if name == "" {
|
||||
return wlid
|
||||
}
|
||||
wlid += fmt.Sprintf("-%s", name)
|
||||
|
||||
return wlid
|
||||
}
|
||||
|
||||
// GetWLID get the calculated wlid
|
||||
func GetWLID(level0, level1, k, name string) string {
|
||||
return generateWLID(ClusterWlidPrefix, level0, NamespaceWlidPrefix, level1, k, name)
|
||||
}
|
||||
|
||||
// GetK8sWLID get the k8s calculated wlid
|
||||
func GetK8sWLID(level0, level1, k, name string) string {
|
||||
return generateWLID(ClusterWlidPrefix, level0, NamespaceWlidPrefix, level1, k, name)
|
||||
}
|
||||
|
||||
// GetNativeWLID get the native calculated wlid
|
||||
func GetNativeWLID(level0, level1, k, name string) string {
|
||||
return generateWLID(DataCenterWlidPrefix, level0, ProjectWlidPrefix, level1, k, name)
|
||||
}
|
||||
|
||||
// WildWlidContainsWlid does WildWlid contains Wlid
|
||||
func WildWlidContainsWlid(wildWlid, wlid string) bool { // TODO- test
|
||||
if wildWlid == wlid {
|
||||
return true
|
||||
}
|
||||
wildWlidR, _ := RestoreMicroserviceIDsFromSpiffe(wildWlid)
|
||||
wlidR, _ := RestoreMicroserviceIDsFromSpiffe(wlid)
|
||||
if len(wildWlidR) > len(wildWlidR) {
|
||||
// invalid wlid
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range wildWlidR {
|
||||
if wildWlidR[i] != wlidR[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func restoreInnerIdentifiersFromID(spiffeSlices []string) []string {
|
||||
if len(spiffeSlices) >= 1 && strings.HasPrefix(spiffeSlices[0], ClusterWlidPrefix) {
|
||||
spiffeSlices[0] = spiffeSlices[0][len(ClusterWlidPrefix):]
|
||||
}
|
||||
if len(spiffeSlices) >= 2 && strings.HasPrefix(spiffeSlices[1], NamespaceWlidPrefix) {
|
||||
spiffeSlices[1] = spiffeSlices[1][len(NamespaceWlidPrefix):]
|
||||
}
|
||||
if len(spiffeSlices) >= 3 && strings.Contains(spiffeSlices[2], "-") {
|
||||
dashIdx := strings.Index(spiffeSlices[2], "-")
|
||||
spiffeSlices = append(spiffeSlices, spiffeSlices[2][dashIdx+1:])
|
||||
spiffeSlices[2] = spiffeSlices[2][:dashIdx]
|
||||
if val, ok := KindReverseMap[spiffeSlices[2]]; ok {
|
||||
spiffeSlices[2] = val
|
||||
}
|
||||
}
|
||||
return spiffeSlices
|
||||
}
|
||||
|
||||
// RestoreMicroserviceIDsFromSpiffe -
|
||||
func RestoreMicroserviceIDsFromSpiffe(spiffe string) ([]string, error) {
|
||||
if spiffe == "" {
|
||||
return nil, fmt.Errorf("in RestoreMicroserviceIDsFromSpiffe, expecting valid wlid recieved empty string")
|
||||
}
|
||||
|
||||
if StringHasWhitespace(spiffe) {
|
||||
return nil, fmt.Errorf("wlid %s invalid. whitespace found", spiffe)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(spiffe, WlidPrefix) {
|
||||
spiffe = spiffe[len(WlidPrefix):]
|
||||
} else if strings.HasPrefix(spiffe, SidPrefix) {
|
||||
spiffe = spiffe[len(SidPrefix):]
|
||||
}
|
||||
spiffeSlices := strings.Split(spiffe, "/")
|
||||
// The documented WLID format (https://cyberarmorio.sharepoint.com/sites/development2/Shared%20Documents/kubernetes_design1.docx?web=1)
|
||||
if len(spiffeSlices) <= 3 {
|
||||
spiffeSlices = restoreInnerIdentifiersFromID(spiffeSlices)
|
||||
}
|
||||
if len(spiffeSlices) != 4 { // first used WLID, deprecated since 24.10.2019
|
||||
return spiffeSlices, fmt.Errorf("invalid WLID format. format received: %v", spiffeSlices)
|
||||
}
|
||||
|
||||
for i := range spiffeSlices {
|
||||
if spiffeSlices[i] == "" {
|
||||
return spiffeSlices, fmt.Errorf("one or more entities are empty, spiffeSlices: %v", spiffeSlices)
|
||||
}
|
||||
}
|
||||
|
||||
return spiffeSlices, nil
|
||||
}
|
||||
|
||||
// RestoreMicroserviceIDsFromSpiffe -
|
||||
func RestoreMicroserviceIDs(spiffe string) []string {
|
||||
if spiffe == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if StringHasWhitespace(spiffe) {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(spiffe, WlidPrefix) {
|
||||
spiffe = spiffe[len(WlidPrefix):]
|
||||
} else if strings.HasPrefix(spiffe, SidPrefix) {
|
||||
spiffe = spiffe[len(SidPrefix):]
|
||||
}
|
||||
spiffeSlices := strings.Split(spiffe, "/")
|
||||
|
||||
return restoreInnerIdentifiersFromID(spiffeSlices)
|
||||
}
|
||||
|
||||
// GetClusterFromWlid parse wlid and get cluster
|
||||
func GetClusterFromWlid(wlid string) string {
|
||||
r := RestoreMicroserviceIDs(wlid)
|
||||
if len(r) >= 1 {
|
||||
return r[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetNamespaceFromWlid parse wlid and get Namespace
|
||||
func GetNamespaceFromWlid(wlid string) string {
|
||||
r := RestoreMicroserviceIDs(wlid)
|
||||
if len(r) >= 2 {
|
||||
return r[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetKindFromWlid parse wlid and get kind
|
||||
func GetKindFromWlid(wlid string) string {
|
||||
r := RestoreMicroserviceIDs(wlid)
|
||||
if len(r) >= 3 {
|
||||
return GetK8SKindFronList(r[2])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetNameFromWlid parse wlid and get name
|
||||
func GetNameFromWlid(wlid string) string {
|
||||
r := RestoreMicroserviceIDs(wlid)
|
||||
if len(r) >= 4 {
|
||||
return GetK8SKindFronList(r[3])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsWlidValid test if wlid is a valid wlid
|
||||
func IsWlidValid(wlid string) error {
|
||||
_, err := RestoreMicroserviceIDsFromSpiffe(wlid)
|
||||
return err
|
||||
}
|
||||
|
||||
// StringHasWhitespace check if a string has whitespace
|
||||
func StringHasWhitespace(str string) bool {
|
||||
if whitespace := strings.Index(str, " "); whitespace != -1 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
506
cautils/customerloader.go
Normal file
506
cautils/customerloader.go
Normal file
@@ -0,0 +1,506 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const configFileName = "config"
|
||||
|
||||
func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName + ".json") }
|
||||
|
||||
// ======================================================================================
|
||||
// =============================== Config structure =====================================
|
||||
// ======================================================================================
|
||||
|
||||
type ConfigObj struct {
|
||||
AccountID string `json:"accountID,omitempty"`
|
||||
ClientID string `json:"clientID,omitempty"`
|
||||
SecretKey string `json:"secretKey,omitempty"`
|
||||
CustomerGUID string `json:"customerGUID,omitempty"` // Deprecated
|
||||
Token string `json:"invitationParam,omitempty"`
|
||||
CustomerAdminEMail string `json:"adminMail,omitempty"`
|
||||
ClusterName string `json:"clusterName,omitempty"`
|
||||
}
|
||||
|
||||
// Config - convert ConfigObj to config file
|
||||
func (co *ConfigObj) Config() []byte {
|
||||
|
||||
// remove cluster name before saving to file
|
||||
clusterName := co.ClusterName
|
||||
customerAdminEMail := co.CustomerAdminEMail
|
||||
token := co.Token
|
||||
co.ClusterName = ""
|
||||
co.Token = ""
|
||||
co.CustomerAdminEMail = ""
|
||||
|
||||
b, err := json.MarshalIndent(co, "", " ")
|
||||
|
||||
co.ClusterName = clusterName
|
||||
co.CustomerAdminEMail = customerAdminEMail
|
||||
co.Token = token
|
||||
|
||||
if err == nil {
|
||||
return b
|
||||
}
|
||||
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// =============================== interface ============================================
|
||||
// ======================================================================================
|
||||
type ITenantConfig interface {
|
||||
// set
|
||||
SetTenant() error
|
||||
UpdateCachedConfig() error
|
||||
DeleteCachedConfig() error
|
||||
|
||||
// getters
|
||||
GetClusterName() string
|
||||
GetAccountID() string
|
||||
GetConfigObj() *ConfigObj
|
||||
// GetBackendAPI() getter.IBackend
|
||||
// GenerateURL()
|
||||
|
||||
IsConfigFound() bool
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// ============================ Local Config ============================================
|
||||
// ======================================================================================
|
||||
// Config when scanning YAML files or URL but not a Kubernetes cluster
|
||||
type LocalConfig struct {
|
||||
backendAPI getter.IBackend
|
||||
configObj *ConfigObj
|
||||
}
|
||||
|
||||
func NewLocalConfig(backendAPI getter.IBackend, customerGUID, clusterName string) *LocalConfig {
|
||||
var configObj *ConfigObj
|
||||
|
||||
lc := &LocalConfig{
|
||||
backendAPI: backendAPI,
|
||||
configObj: &ConfigObj{},
|
||||
}
|
||||
// get from configMap
|
||||
if existsConfigFile() { // get from file
|
||||
configObj, _ = loadConfigFromFile()
|
||||
} else {
|
||||
configObj = &ConfigObj{}
|
||||
}
|
||||
if configObj != nil {
|
||||
lc.configObj = configObj
|
||||
}
|
||||
if customerGUID != "" {
|
||||
lc.configObj.AccountID = customerGUID // override config customerGUID
|
||||
}
|
||||
if clusterName != "" {
|
||||
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
|
||||
}
|
||||
getAccountFromEnv(lc.configObj)
|
||||
|
||||
lc.backendAPI.SetAccountID(lc.configObj.AccountID)
|
||||
lc.backendAPI.SetClientID(lc.configObj.ClientID)
|
||||
lc.backendAPI.SetSecretKey(lc.configObj.SecretKey)
|
||||
|
||||
return lc
|
||||
}
|
||||
|
||||
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
|
||||
func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountID }
|
||||
func (lc *LocalConfig) GetClusterName() string { return lc.configObj.ClusterName }
|
||||
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
|
||||
func (lc *LocalConfig) SetTenant() error {
|
||||
|
||||
// ARMO tenant GUID
|
||||
if err := getTenantConfigFromBE(lc.backendAPI, lc.configObj); err != nil {
|
||||
return err
|
||||
}
|
||||
lc.UpdateCachedConfig()
|
||||
return nil
|
||||
|
||||
}
|
||||
func (lc *LocalConfig) UpdateCachedConfig() error {
|
||||
return updateConfigFile(lc.configObj)
|
||||
}
|
||||
|
||||
func (lc *LocalConfig) DeleteCachedConfig() error {
|
||||
return DeleteConfigFile()
|
||||
}
|
||||
|
||||
func getTenantConfigFromBE(backendAPI getter.IBackend, configObj *ConfigObj) error {
|
||||
|
||||
// get from armoBE
|
||||
tenantResponse, err := backendAPI.GetTenant()
|
||||
if err == nil && tenantResponse != nil {
|
||||
if tenantResponse.AdminMail != "" { // registered tenant
|
||||
configObj.CustomerAdminEMail = tenantResponse.AdminMail
|
||||
} else { // new tenant
|
||||
configObj.Token = tenantResponse.Token
|
||||
configObj.AccountID = tenantResponse.TenantID
|
||||
}
|
||||
} else {
|
||||
if err != nil && !strings.Contains(err.Error(), "already exists") {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// ========================== Cluster Config ============================================
|
||||
// ======================================================================================
|
||||
|
||||
// ClusterConfig configuration of specific cluster
|
||||
/*
|
||||
|
||||
Supported environments variables:
|
||||
KS_DEFAULT_CONFIGMAP_NAME // name of configmap, if not set default is 'kubescape'
|
||||
KS_DEFAULT_CONFIGMAP_NAMESPACE // configmap namespace, if not set default is 'default'
|
||||
|
||||
KS_ACCOUNT_ID
|
||||
KS_CLIENT_ID
|
||||
KS_SECRET_KEY
|
||||
|
||||
TODO - supprot:
|
||||
KS_CACHE // path to cached files
|
||||
*/
|
||||
type ClusterConfig struct {
|
||||
k8s *k8sinterface.KubernetesApi
|
||||
configMapName string
|
||||
configMapNamespace string
|
||||
backendAPI getter.IBackend
|
||||
configObj *ConfigObj
|
||||
}
|
||||
|
||||
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, customerGUID, clusterName string) *ClusterConfig {
|
||||
var configObj *ConfigObj
|
||||
c := &ClusterConfig{
|
||||
k8s: k8s,
|
||||
backendAPI: backendAPI,
|
||||
configObj: &ConfigObj{},
|
||||
configMapName: getConfigMapName(),
|
||||
configMapNamespace: getConfigMapNamespace(),
|
||||
}
|
||||
|
||||
// get from configMap
|
||||
if c.existsConfigMap() {
|
||||
configObj, _ = c.loadConfigFromConfigMap()
|
||||
}
|
||||
if configObj == nil && existsConfigFile() { // get from file
|
||||
configObj, _ = loadConfigFromFile()
|
||||
}
|
||||
if configObj != nil {
|
||||
c.configObj = configObj
|
||||
}
|
||||
if customerGUID != "" {
|
||||
c.configObj.AccountID = customerGUID // override config customerGUID
|
||||
}
|
||||
if clusterName != "" {
|
||||
c.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
|
||||
}
|
||||
getAccountFromEnv(c.configObj)
|
||||
|
||||
if c.configObj.ClusterName == "" {
|
||||
c.configObj.ClusterName = AdoptClusterName(k8sinterface.GetClusterName())
|
||||
} else { // override the cluster name if it has unwanted characters
|
||||
c.configObj.ClusterName = AdoptClusterName(c.configObj.ClusterName)
|
||||
}
|
||||
|
||||
c.backendAPI.SetAccountID(c.configObj.AccountID)
|
||||
c.backendAPI.SetClientID(c.configObj.ClientID)
|
||||
c.backendAPI.SetSecretKey(c.configObj.SecretKey)
|
||||
|
||||
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) IsConfigFound() bool { return existsConfigFile() || c.existsConfigMap() }
|
||||
|
||||
func (c *ClusterConfig) SetTenant() error {
|
||||
|
||||
// ARMO tenant GUID
|
||||
if err := getTenantConfigFromBE(c.backendAPI, c.configObj); err != nil {
|
||||
return err
|
||||
}
|
||||
c.UpdateCachedConfig()
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) UpdateCachedConfig() error {
|
||||
// update/create config
|
||||
if c.existsConfigMap() {
|
||||
if err := c.updateConfigMap(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := c.createConfigMap(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return updateConfigFile(c.configObj)
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) DeleteCachedConfig() error {
|
||||
if err := c.deleteConfigMap(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := DeleteConfigFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (c *ClusterConfig) GetClusterName() string {
|
||||
return c.configObj.ClusterName
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) ToMapString() map[string]interface{} {
|
||||
m := map[string]interface{}{}
|
||||
if bc, err := json.Marshal(c.configObj); err == nil {
|
||||
json.Unmarshal(bc, &m)
|
||||
}
|
||||
return m
|
||||
}
|
||||
func (c *ClusterConfig) loadConfigFromConfigMap() (*ConfigObj, error) {
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bData, err := json.Marshal(configMap.Data); err == nil {
|
||||
return readConfig(bData)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) existsConfigMap() bool {
|
||||
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
// TODO - check if has customerGUID
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) GetValueByKeyFromConfigMap(key string) (string, error) {
|
||||
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if val, ok := configMap.Data[key]; ok {
|
||||
return val, nil
|
||||
} else {
|
||||
return "", fmt.Errorf("value does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
func GetValueFromConfigJson(key string) (string, error) {
|
||||
data, err := os.ReadFile(ConfigFileFullPath())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var obj map[string]interface{}
|
||||
if err := json.Unmarshal(data, &obj); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if val, ok := obj[key]; ok {
|
||||
return fmt.Sprint(val), nil
|
||||
} else {
|
||||
return "", fmt.Errorf("value does not exist")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func SetKeyValueInConfigJson(key string, value string) error {
|
||||
data, err := os.ReadFile(ConfigFileFullPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var obj map[string]interface{}
|
||||
err = json.Unmarshal(data, &obj)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj[key] = value
|
||||
newData, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(ConfigFileFullPath(), newData, 0664)
|
||||
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) SetKeyValueInConfigmap(key string, value string) error {
|
||||
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
configMap = &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: c.configMapName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if len(configMap.Data) == 0 {
|
||||
configMap.Data = make(map[string]string)
|
||||
}
|
||||
|
||||
configMap.Data[key] = value
|
||||
|
||||
if err != nil {
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
|
||||
} else {
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func existsConfigFile() bool {
|
||||
_, err := os.ReadFile(ConfigFileFullPath())
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) createConfigMap() error {
|
||||
if c.k8s == nil {
|
||||
return nil
|
||||
}
|
||||
configMap := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: c.configMapName,
|
||||
},
|
||||
}
|
||||
c.updateConfigData(configMap)
|
||||
|
||||
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) updateConfigMap() error {
|
||||
if c.k8s == nil {
|
||||
return nil
|
||||
}
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.updateConfigData(configMap)
|
||||
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
func updateConfigFile(configObj *ConfigObj) error {
|
||||
if err := os.WriteFile(ConfigFileFullPath(), configObj.Config(), 0664); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {
|
||||
if len(configMap.Data) == 0 {
|
||||
configMap.Data = make(map[string]string)
|
||||
}
|
||||
m := c.ToMapString()
|
||||
for k, v := range m {
|
||||
if s, ok := v.(string); ok {
|
||||
configMap.Data[k] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
func loadConfigFromFile() (*ConfigObj, error) {
|
||||
dat, err := os.ReadFile(ConfigFileFullPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return readConfig(dat)
|
||||
}
|
||||
func readConfig(dat []byte) (*ConfigObj, error) {
|
||||
|
||||
if len(dat) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
configObj := &ConfigObj{}
|
||||
if err := json.Unmarshal(dat, configObj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if configObj.AccountID == "" {
|
||||
configObj.AccountID = configObj.CustomerGUID
|
||||
}
|
||||
configObj.CustomerGUID = ""
|
||||
return configObj, nil
|
||||
}
|
||||
|
||||
// Check if the customer is submitted
|
||||
func (clusterConfig *ClusterConfig) IsSubmitted() bool {
|
||||
return clusterConfig.existsConfigMap() || existsConfigFile()
|
||||
}
|
||||
|
||||
// Check if the customer is registered
|
||||
func (clusterConfig *ClusterConfig) IsRegistered() bool {
|
||||
|
||||
// get from armoBE
|
||||
tenantResponse, err := clusterConfig.backendAPI.GetTenant()
|
||||
if err == nil && tenantResponse != nil {
|
||||
if tenantResponse.AdminMail != "" { // this customer already belongs to some user
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (clusterConfig *ClusterConfig) deleteConfigMap() error {
|
||||
return clusterConfig.k8s.KubernetesClient.CoreV1().ConfigMaps(clusterConfig.configMapNamespace).Delete(context.Background(), clusterConfig.configMapName, metav1.DeleteOptions{})
|
||||
}
|
||||
|
||||
func DeleteConfigFile() error {
|
||||
return os.Remove(ConfigFileFullPath())
|
||||
}
|
||||
|
||||
func AdoptClusterName(clusterName string) string {
|
||||
return strings.ReplaceAll(clusterName, "/", "-")
|
||||
}
|
||||
|
||||
func getConfigMapName() string {
|
||||
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAME"); n != "" {
|
||||
return n
|
||||
}
|
||||
return "kubescape"
|
||||
}
|
||||
|
||||
func getConfigMapNamespace() string {
|
||||
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAMESPACE"); n != "" {
|
||||
return n
|
||||
}
|
||||
return "default"
|
||||
}
|
||||
|
||||
func getAccountFromEnv(configObj *ConfigObj) {
|
||||
// load from env
|
||||
if accountID := os.Getenv("KS_ACCOUNT_ID"); accountID != "" {
|
||||
configObj.AccountID = accountID
|
||||
}
|
||||
if clientID := os.Getenv("KS_CLIENT_ID"); clientID != "" {
|
||||
configObj.ClientID = clientID
|
||||
}
|
||||
if secretKey := os.Getenv("KS_SECRET_KEY"); secretKey != "" {
|
||||
configObj.SecretKey = secretKey
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,35 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/cautils/opapolicy"
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
|
||||
)
|
||||
|
||||
// K8SResources map[<api group>/<api version>/<resource>]<resource object>
|
||||
type K8SResources map[string]interface{}
|
||||
// K8SResources map[<api group>/<api version>/<resource>][]<resourceID>
|
||||
type K8SResources map[string][]string
|
||||
|
||||
type OPASessionObj struct {
|
||||
Frameworks []opapolicy.Framework
|
||||
K8SResources *K8SResources
|
||||
PostureReport *opapolicy.PostureReport
|
||||
K8SResources *K8SResources // input k8s objects
|
||||
Frameworks []reporthandling.Framework // list of frameworks to scan
|
||||
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<rtesource ID>]<resource>
|
||||
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<rtesource ID>]<resource result>
|
||||
PostureReport *reporthandling.PostureReport // scan results v1
|
||||
Report *reporthandlingv2.PostureReport // scan results v2
|
||||
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
|
||||
RegoInputData RegoInputData // input passed to rgo for scanning. map[<control name>][<input arguments>]
|
||||
}
|
||||
|
||||
func NewOPASessionObj(frameworks []opapolicy.Framework, k8sResources *K8SResources) *OPASessionObj {
|
||||
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources) *OPASessionObj {
|
||||
return &OPASessionObj{
|
||||
Frameworks: frameworks,
|
||||
K8SResources: k8sResources,
|
||||
PostureReport: &opapolicy.PostureReport{
|
||||
Report: &reporthandlingv2.PostureReport{},
|
||||
Frameworks: frameworks,
|
||||
K8SResources: k8sResources,
|
||||
AllResources: make(map[string]workloadinterface.IMetadata),
|
||||
ResourcesResult: make(map[string]resourcesresults.Result),
|
||||
PostureReport: &reporthandling.PostureReport{
|
||||
ClusterName: ClusterName,
|
||||
CustomerGUID: CustomerGUID,
|
||||
},
|
||||
@@ -26,9 +38,12 @@ func NewOPASessionObj(frameworks []opapolicy.Framework, k8sResources *K8SResourc
|
||||
|
||||
func NewOPASessionObjMock() *OPASessionObj {
|
||||
return &OPASessionObj{
|
||||
Frameworks: nil,
|
||||
K8SResources: nil,
|
||||
PostureReport: &opapolicy.PostureReport{
|
||||
Frameworks: nil,
|
||||
K8SResources: nil,
|
||||
AllResources: make(map[string]workloadinterface.IMetadata),
|
||||
ResourcesResult: make(map[string]resourcesresults.Result),
|
||||
Report: &reporthandlingv2.PostureReport{},
|
||||
PostureReport: &reporthandling.PostureReport{
|
||||
ClusterName: "",
|
||||
CustomerGUID: "",
|
||||
ReportID: "",
|
||||
@@ -42,8 +57,19 @@ type ComponentConfig struct {
|
||||
}
|
||||
|
||||
type Exception struct {
|
||||
Ignore *bool `json:"ignore"` // ignore test results
|
||||
MultipleScore *opapolicy.AlertScore `json:"multipleScore"` // MultipleScore number - float32
|
||||
Namespaces []string `json:"namespaces"`
|
||||
Regex string `json:"regex"` // not supported
|
||||
Ignore *bool `json:"ignore"` // ignore test results
|
||||
MultipleScore *reporthandling.AlertScore `json:"multipleScore"` // MultipleScore number - float32
|
||||
Namespaces []string `json:"namespaces"`
|
||||
Regex string `json:"regex"` // not supported
|
||||
}
|
||||
|
||||
type RegoInputData struct {
|
||||
PostureControlInputs map[string][]string `json:"postureControlInputs"`
|
||||
// ClusterName string `json:"clusterName"`
|
||||
// K8sConfig RegoK8sConfig `json:"k8sconfig"`
|
||||
}
|
||||
|
||||
type Policies struct {
|
||||
Frameworks []string
|
||||
Controls map[string]reporthandling.Control // map[<control ID>]<control>
|
||||
}
|
||||
|
||||
68
cautils/datastructuresmethods.go
Normal file
68
cautils/datastructuresmethods.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
pkgcautils "github.com/armosec/utils-go/utils"
|
||||
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
func NewPolicies() *Policies {
|
||||
return &Policies{
|
||||
Frameworks: make([]string, 0),
|
||||
Controls: make(map[string]reporthandling.Control),
|
||||
}
|
||||
}
|
||||
|
||||
func (policies *Policies) Set(frameworks []reporthandling.Framework, version string) {
|
||||
for i := range frameworks {
|
||||
if frameworks[i].Name != "" && len(frameworks[i].Controls) > 0 {
|
||||
policies.Frameworks = append(policies.Frameworks, frameworks[i].Name)
|
||||
}
|
||||
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) {
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func ruleWithArmoOpaDependency(attributes map[string]interface{}) bool {
|
||||
if attributes == nil {
|
||||
return false
|
||||
}
|
||||
if s, ok := attributes["armoOpa"]; ok { // TODO - make global
|
||||
return pkgcautils.StringToBool(s.(string))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Checks that kubescape version is in range of use for this rule
|
||||
// In local build (BuildNumber = ""):
|
||||
// returns true only if rule doesn't have the "until" attribute
|
||||
func isRuleKubescapeVersionCompatible(attributes map[string]interface{}, version string) bool {
|
||||
if from, ok := attributes["useFromKubescapeVersion"]; ok && from != nil {
|
||||
if version != "" {
|
||||
if from.(string) > BuildNumber {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if until, ok := attributes["useUntilKubescapeVersion"]; ok && until != nil {
|
||||
if version != "" {
|
||||
if until.(string) <= BuildNumber {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -21,48 +20,16 @@ func IsSilent() bool {
|
||||
}
|
||||
|
||||
var FailureDisplay = color.New(color.Bold, color.FgHiRed).FprintfFunc()
|
||||
var WarningDisplay = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
|
||||
var FailureTextDisplay = color.New(color.Faint, color.FgHiRed).FprintfFunc()
|
||||
var InfoDisplay = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
|
||||
var InfoTextDisplay = color.New(color.Faint, color.FgHiYellow).FprintfFunc()
|
||||
var SimpleDisplay = color.New(color.Bold, color.FgHiWhite).FprintfFunc()
|
||||
var InfoDisplay = color.New(color.Bold, color.FgCyan).FprintfFunc()
|
||||
var InfoTextDisplay = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
|
||||
var SimpleDisplay = color.New().FprintfFunc()
|
||||
var SuccessDisplay = color.New(color.Bold, color.FgHiGreen).FprintfFunc()
|
||||
var DescriptionDisplay = color.New(color.Faint, color.FgWhite).FprintfFunc()
|
||||
|
||||
var Spinner *spinner.Spinner
|
||||
|
||||
func ScanStartDisplay() {
|
||||
if IsSilent() {
|
||||
return
|
||||
}
|
||||
InfoDisplay(os.Stdout, "ARMO security scanner starting\n")
|
||||
}
|
||||
|
||||
func SuccessTextDisplay(str string) {
|
||||
if IsSilent() {
|
||||
return
|
||||
}
|
||||
SuccessDisplay(os.Stdout, "[success] ")
|
||||
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
|
||||
|
||||
}
|
||||
|
||||
func ErrorDisplay(str string) {
|
||||
if IsSilent() {
|
||||
return
|
||||
}
|
||||
SuccessDisplay(os.Stdout, "[Error] ")
|
||||
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
|
||||
|
||||
}
|
||||
|
||||
func ProgressTextDisplay(str string) {
|
||||
if IsSilent() {
|
||||
return
|
||||
}
|
||||
InfoDisplay(os.Stdout, "[progress] ")
|
||||
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
|
||||
|
||||
}
|
||||
func StartSpinner() {
|
||||
if !IsSilent() && isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
Spinner = spinner.New(spinner.CharSets[7], 100*time.Millisecond) // Build our new spinner
|
||||
|
||||
9
cautils/downloadinfo.go
Normal file
9
cautils/downloadinfo.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package cautils
|
||||
|
||||
type DownloadInfo struct {
|
||||
Path string // directory to save artifact. Default is "~/.kubescape/"
|
||||
FileName string // can be empty
|
||||
Target string // type of artifact to download
|
||||
Name string // name of artifact to download
|
||||
Account string // customerGUID
|
||||
}
|
||||
239
cautils/fileutils.go
Normal file
239
cautils/fileutils.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
YAML_PREFIX = []string{".yaml", ".yml"}
|
||||
JSON_PREFIX = []string{".json"}
|
||||
)
|
||||
|
||||
type FileFormat string
|
||||
|
||||
const (
|
||||
YAML_FILE_FORMAT FileFormat = "yaml"
|
||||
JSON_FILE_FORMAT FileFormat = "json"
|
||||
)
|
||||
|
||||
func LoadResourcesFromFiles(inputPatterns []string) ([]workloadinterface.IMetadata, error) {
|
||||
files, errs := listFiles(inputPatterns)
|
||||
if len(errs) > 0 {
|
||||
logger.L().Error(fmt.Sprintf("%v", errs))
|
||||
}
|
||||
if len(files) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
workloads, errs := loadFiles(files)
|
||||
if len(errs) > 0 {
|
||||
logger.L().Error(fmt.Sprintf("%v", errs))
|
||||
}
|
||||
return workloads, nil
|
||||
}
|
||||
|
||||
func loadFiles(filePaths []string) ([]workloadinterface.IMetadata, []error) {
|
||||
workloads := []workloadinterface.IMetadata{}
|
||||
errs := []error{}
|
||||
for i := range filePaths {
|
||||
f, err := loadFile(filePaths[i])
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
w, e := ReadFile(f, GetFileFormat(filePaths[i]))
|
||||
errs = append(errs, e...)
|
||||
if w != nil {
|
||||
workloads = append(workloads, w...)
|
||||
}
|
||||
}
|
||||
return workloads, errs
|
||||
}
|
||||
|
||||
func loadFile(filePath string) ([]byte, error) {
|
||||
return os.ReadFile(filePath)
|
||||
}
|
||||
func ReadFile(fileContent []byte, fileFromat FileFormat) ([]workloadinterface.IMetadata, []error) {
|
||||
|
||||
switch fileFromat {
|
||||
case YAML_FILE_FORMAT:
|
||||
return readYamlFile(fileContent)
|
||||
case JSON_FILE_FORMAT:
|
||||
return readJsonFile(fileContent)
|
||||
default:
|
||||
return nil, nil // []error{fmt.Errorf("file extension %s not supported", fileFromat)}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func listFiles(patterns []string) ([]string, []error) {
|
||||
files := []string{}
|
||||
errs := []error{}
|
||||
for i := range patterns {
|
||||
if strings.HasPrefix(patterns[i], "http") {
|
||||
continue
|
||||
}
|
||||
if !filepath.IsAbs(patterns[i]) {
|
||||
o, _ := os.Getwd()
|
||||
patterns[i] = filepath.Join(o, patterns[i])
|
||||
}
|
||||
if IsFile(patterns[i]) {
|
||||
files = append(files, patterns[i])
|
||||
} else {
|
||||
f, err := glob(filepath.Split(patterns[i])) //filepath.Glob(patterns[i])
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
files = append(files, f...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return files, errs
|
||||
}
|
||||
|
||||
func readYamlFile(yamlFile []byte) ([]workloadinterface.IMetadata, []error) {
|
||||
errs := []error{}
|
||||
|
||||
r := bytes.NewReader(yamlFile)
|
||||
dec := yaml.NewDecoder(r)
|
||||
yamlObjs := []workloadinterface.IMetadata{}
|
||||
|
||||
var t interface{}
|
||||
for dec.Decode(&t) == nil {
|
||||
j := convertYamlToJson(t)
|
||||
if j == nil {
|
||||
continue
|
||||
}
|
||||
if obj, ok := j.(map[string]interface{}); ok {
|
||||
if o := objectsenvelopes.NewObject(obj); o != nil {
|
||||
if o.GetKind() == "List" {
|
||||
yamlObjs = append(yamlObjs, handleListObject(o)...)
|
||||
} else {
|
||||
yamlObjs = append(yamlObjs, o)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("failed to convert yaml file to map[string]interface, file content: %v", j))
|
||||
}
|
||||
}
|
||||
|
||||
return yamlObjs, errs
|
||||
}
|
||||
|
||||
func readJsonFile(jsonFile []byte) ([]workloadinterface.IMetadata, []error) {
|
||||
workloads := []workloadinterface.IMetadata{}
|
||||
var jsonObj interface{}
|
||||
if err := json.Unmarshal(jsonFile, &jsonObj); err != nil {
|
||||
return workloads, []error{err}
|
||||
}
|
||||
|
||||
convertJsonToWorkload(jsonObj, &workloads)
|
||||
|
||||
return workloads, nil
|
||||
}
|
||||
func convertJsonToWorkload(jsonObj interface{}, workloads *[]workloadinterface.IMetadata) {
|
||||
|
||||
switch x := jsonObj.(type) {
|
||||
case map[string]interface{}:
|
||||
if o := objectsenvelopes.NewObject(x); o != nil {
|
||||
(*workloads) = append(*workloads, o)
|
||||
}
|
||||
case []interface{}:
|
||||
for i := range x {
|
||||
convertJsonToWorkload(x[i], workloads)
|
||||
}
|
||||
}
|
||||
}
|
||||
func convertYamlToJson(i interface{}) interface{} {
|
||||
switch x := i.(type) {
|
||||
case map[interface{}]interface{}:
|
||||
m2 := map[string]interface{}{}
|
||||
for k, v := range x {
|
||||
if s, ok := k.(string); ok {
|
||||
m2[s] = convertYamlToJson(v)
|
||||
}
|
||||
}
|
||||
return m2
|
||||
case []interface{}:
|
||||
for i, v := range x {
|
||||
x[i] = convertYamlToJson(v)
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func IsYaml(filePath string) bool {
|
||||
return StringInSlice(YAML_PREFIX, filepath.Ext(filePath)) != ValueNotFound
|
||||
}
|
||||
|
||||
func IsJson(filePath string) bool {
|
||||
return StringInSlice(JSON_PREFIX, filepath.Ext(filePath)) != ValueNotFound
|
||||
}
|
||||
|
||||
func glob(root, pattern string) ([]string, error) {
|
||||
var matches []string
|
||||
|
||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if matched, err := filepath.Match(pattern, filepath.Base(path)); err != nil {
|
||||
return err
|
||||
} else if matched {
|
||||
matches = append(matches, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
func IsFile(name string) bool {
|
||||
if fi, err := os.Stat(name); err == nil {
|
||||
if fi.Mode().IsRegular() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetFileFormat(filePath string) FileFormat {
|
||||
if IsYaml(filePath) {
|
||||
return YAML_FILE_FORMAT
|
||||
} else if IsJson(filePath) {
|
||||
return JSON_FILE_FORMAT
|
||||
} else {
|
||||
return FileFormat(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
// handleListObject handle a List manifest
|
||||
func handleListObject(obj workloadinterface.IMetadata) []workloadinterface.IMetadata {
|
||||
yamlObjs := []workloadinterface.IMetadata{}
|
||||
if i, ok := workloadinterface.InspectMap(obj.GetObject(), "items"); ok && i != nil {
|
||||
if items, ok := i.([]interface{}); ok && items != nil {
|
||||
for item := range items {
|
||||
if m, ok := items[item].(map[string]interface{}); ok && m != nil {
|
||||
if o := objectsenvelopes.NewObject(m); o != nil {
|
||||
yamlObjs = append(yamlObjs, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return yamlObjs
|
||||
}
|
||||
50
cautils/fileutils_test.go
Normal file
50
cautils/fileutils_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func onlineBoutiquePath() string {
|
||||
o, _ := os.Getwd()
|
||||
return filepath.Join(filepath.Dir(o), "examples/online-boutique/*")
|
||||
}
|
||||
|
||||
func TestListFiles(t *testing.T) {
|
||||
|
||||
filesPath := onlineBoutiquePath()
|
||||
|
||||
files, errs := listFiles([]string{filesPath})
|
||||
assert.Equal(t, 0, len(errs))
|
||||
assert.Equal(t, 12, len(files))
|
||||
}
|
||||
|
||||
func TestLoadFiles(t *testing.T) {
|
||||
files, _ := listFiles([]string{onlineBoutiquePath()})
|
||||
_, err := loadFiles(files)
|
||||
assert.Equal(t, 0, len(err))
|
||||
}
|
||||
|
||||
func TestLoadFile(t *testing.T) {
|
||||
files, _ := listFiles([]string{strings.Replace(onlineBoutiquePath(), "*", "adservice.yaml", 1)})
|
||||
assert.Equal(t, 1, len(files))
|
||||
|
||||
_, err := loadFile(files[0])
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
func TestMapResources(t *testing.T) {
|
||||
// policyHandler := &PolicyHandler{}
|
||||
// k8sResources, err := policyHandler.loadResources(opaSessionObj.Frameworks, scanInfo)
|
||||
// files, _ := listFiles([]string{onlineBoutiquePath()})
|
||||
// bb, err := loadFile(files[0])
|
||||
// if len(err) > 0 {
|
||||
// t.Errorf("%v", err)
|
||||
// }
|
||||
// for i := range bb {
|
||||
// t.Errorf("%s", bb[i].ToString())
|
||||
// }
|
||||
}
|
||||
363
cautils/getter/armoapi.go
Normal file
363
cautils/getter/armoapi.go
Normal file
@@ -0,0 +1,363 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
// =======================================================================================================================
|
||||
// =============================================== ArmoAPI ===============================================================
|
||||
// =======================================================================================================================
|
||||
|
||||
var (
|
||||
// ATTENTION!!!
|
||||
// Changes in this URLs variable names, or in the usage is affecting the build process! BE CAREFULL
|
||||
armoERURL = "report.armo.cloud"
|
||||
armoBEURL = "api.armo.cloud"
|
||||
armoFEURL = "portal.armo.cloud"
|
||||
armoAUTHURL = "auth.armo.cloud"
|
||||
|
||||
armoStageERURL = "report-ks.eustage2.cyberarmorsoft.com"
|
||||
armoStageBEURL = "api-stage.armo.cloud"
|
||||
armoStageFEURL = "armoui.eustage2.cyberarmorsoft.com"
|
||||
armoStageAUTHURL = "eggauth.eustage2.cyberarmorsoft.com"
|
||||
|
||||
armoDevERURL = "report.eudev3.cyberarmorsoft.com"
|
||||
armoDevBEURL = "api-dev.armo.cloud"
|
||||
armoDevFEURL = "armoui-dev.eudev3.cyberarmorsoft.com"
|
||||
armoDevAUTHURL = "eggauth.eudev3.cyberarmorsoft.com"
|
||||
)
|
||||
|
||||
// Armo API for downloading policies
|
||||
type ArmoAPI struct {
|
||||
httpClient *http.Client
|
||||
apiURL string
|
||||
authURL string
|
||||
erURL string
|
||||
feURL string
|
||||
accountID string
|
||||
clientID string
|
||||
secretKey string
|
||||
feToken FeLoginResponse
|
||||
authCookie string
|
||||
loggedIn bool
|
||||
}
|
||||
|
||||
var globalArmoAPIConnector *ArmoAPI
|
||||
|
||||
func SetARMOAPIConnector(armoAPI *ArmoAPI) {
|
||||
logger.L().Debug("Armo URLs", helpers.String("api", armoAPI.apiURL), helpers.String("auth", armoAPI.authURL), helpers.String("report", armoAPI.erURL), helpers.String("UI", armoAPI.feURL))
|
||||
globalArmoAPIConnector = armoAPI
|
||||
}
|
||||
|
||||
func GetArmoAPIConnector() *ArmoAPI {
|
||||
if globalArmoAPIConnector == nil {
|
||||
logger.L().Error("returning nil API connector")
|
||||
}
|
||||
return globalArmoAPIConnector
|
||||
}
|
||||
|
||||
func NewARMOAPIDev() *ArmoAPI {
|
||||
apiObj := newArmoAPI()
|
||||
|
||||
apiObj.apiURL = armoDevBEURL
|
||||
apiObj.authURL = armoDevAUTHURL
|
||||
apiObj.erURL = armoDevERURL
|
||||
apiObj.feURL = armoDevFEURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func NewARMOAPIProd() *ArmoAPI {
|
||||
apiObj := newArmoAPI()
|
||||
|
||||
apiObj.apiURL = armoBEURL
|
||||
apiObj.erURL = armoERURL
|
||||
apiObj.feURL = armoFEURL
|
||||
apiObj.authURL = armoAUTHURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func NewARMOAPIStaging() *ArmoAPI {
|
||||
apiObj := newArmoAPI()
|
||||
|
||||
apiObj.apiURL = armoStageBEURL
|
||||
apiObj.erURL = armoStageERURL
|
||||
apiObj.feURL = armoStageFEURL
|
||||
apiObj.authURL = armoStageAUTHURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func NewARMOAPICustomized(armoERURL, armoBEURL, armoFEURL, armoAUTHURL string) *ArmoAPI {
|
||||
apiObj := newArmoAPI()
|
||||
|
||||
apiObj.erURL = armoERURL
|
||||
apiObj.apiURL = armoBEURL
|
||||
apiObj.feURL = armoFEURL
|
||||
apiObj.authURL = armoAUTHURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func newArmoAPI() *ArmoAPI {
|
||||
return &ArmoAPI{
|
||||
httpClient: &http.Client{Timeout: time.Duration(61) * time.Second},
|
||||
loggedIn: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) Post(fullURL string, headers map[string]string, body []byte) (string, error) {
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
armoAPI.appendAuthHeaders(headers)
|
||||
return HttpPost(armoAPI.httpClient, fullURL, headers, body)
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) Delete(fullURL string, headers map[string]string) (string, error) {
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
armoAPI.appendAuthHeaders(headers)
|
||||
return HttpDelete(armoAPI.httpClient, fullURL, headers)
|
||||
}
|
||||
func (armoAPI *ArmoAPI) Get(fullURL string, headers map[string]string) (string, error) {
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
armoAPI.appendAuthHeaders(headers)
|
||||
return HttpGetter(armoAPI.httpClient, fullURL, headers)
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetAccountID() string { return armoAPI.accountID }
|
||||
func (armoAPI *ArmoAPI) IsLoggedIn() bool { return armoAPI.loggedIn }
|
||||
func (armoAPI *ArmoAPI) GetClientID() string { return armoAPI.clientID }
|
||||
func (armoAPI *ArmoAPI) GetSecretKey() string { return armoAPI.secretKey }
|
||||
func (armoAPI *ArmoAPI) GetFrontendURL() string { return armoAPI.feURL }
|
||||
func (armoAPI *ArmoAPI) GetAPIURL() string { return armoAPI.apiURL }
|
||||
func (armoAPI *ArmoAPI) GetReportReceiverURL() string { return armoAPI.erURL }
|
||||
func (armoAPI *ArmoAPI) SetAccountID(accountID string) { armoAPI.accountID = accountID }
|
||||
func (armoAPI *ArmoAPI) SetClientID(clientID string) { armoAPI.clientID = clientID }
|
||||
func (armoAPI *ArmoAPI) SetSecretKey(secretKey string) { armoAPI.secretKey = secretKey }
|
||||
|
||||
func (armoAPI *ArmoAPI) GetFramework(name string) (*reporthandling.Framework, error) {
|
||||
respStr, err := armoAPI.Get(armoAPI.getFrameworkURL(name), nil)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
framework := &reporthandling.Framework{}
|
||||
if err = JSONDecoder(respStr).Decode(framework); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
SaveInFile(framework, GetDefaultPath(name+".json"))
|
||||
|
||||
return framework, err
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetFrameworks() ([]reporthandling.Framework, error) {
|
||||
respStr, err := armoAPI.Get(armoAPI.getListFrameworkURL(), nil)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
frameworks := []reporthandling.Framework{}
|
||||
if err = JSONDecoder(respStr).Decode(&frameworks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// SaveInFile(framework, GetDefaultPath(name+".json"))
|
||||
|
||||
return frameworks, err
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetControl(policyName string) (*reporthandling.Control, error) {
|
||||
return nil, fmt.Errorf("control api is not public")
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||
exceptions := []armotypes.PostureExceptionPolicy{}
|
||||
|
||||
respStr, err := armoAPI.Get(armoAPI.getExceptionsURL(clusterName), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = JSONDecoder(respStr).Decode(&exceptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exceptions, nil
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetTenant() (*TenantResponse, error) {
|
||||
url := armoAPI.getAccountURL()
|
||||
if armoAPI.accountID != "" {
|
||||
url = fmt.Sprintf("%s?customerGUID=%s", url, armoAPI.accountID)
|
||||
}
|
||||
respStr, err := armoAPI.Get(url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant := &TenantResponse{}
|
||||
if err = JSONDecoder(respStr).Decode(tenant); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tenant.TenantID != "" {
|
||||
armoAPI.accountID = tenant.TenantID
|
||||
}
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
// ControlsInputs // map[<control name>][<input arguments>]
|
||||
func (armoAPI *ArmoAPI) GetAccountConfig(clusterName string) (*armotypes.CustomerConfig, error) {
|
||||
accountConfig := &armotypes.CustomerConfig{}
|
||||
if armoAPI.accountID == "" {
|
||||
return accountConfig, nil
|
||||
}
|
||||
respStr, err := armoAPI.Get(armoAPI.getAccountConfig(clusterName), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = JSONDecoder(respStr).Decode(&accountConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return accountConfig, nil
|
||||
}
|
||||
|
||||
// ControlsInputs // map[<control name>][<input arguments>]
|
||||
func (armoAPI *ArmoAPI) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
||||
accountConfig, err := armoAPI.GetAccountConfig(clusterName)
|
||||
if err == nil {
|
||||
return accountConfig.Settings.PostureControlInputs, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) ListCustomFrameworks() ([]string, error) {
|
||||
respStr, err := armoAPI.Get(armoAPI.getListFrameworkURL(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frs := []reporthandling.Framework{}
|
||||
if err = json.Unmarshal([]byte(respStr), &frs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
frameworkList := []string{}
|
||||
for _, fr := range frs {
|
||||
if !isNativeFramework(fr.Name) {
|
||||
frameworkList = append(frameworkList, fr.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return frameworkList, nil
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) ListFrameworks() ([]string, error) {
|
||||
respStr, err := armoAPI.Get(armoAPI.getListFrameworkURL(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frs := []reporthandling.Framework{}
|
||||
if err = json.Unmarshal([]byte(respStr), &frs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
frameworkList := []string{}
|
||||
for _, fr := range frs {
|
||||
if isNativeFramework(fr.Name) {
|
||||
frameworkList = append(frameworkList, strings.ToLower(fr.Name))
|
||||
} else {
|
||||
frameworkList = append(frameworkList, fr.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return frameworkList, nil
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) ListControls(l ListType) ([]string, error) {
|
||||
return nil, fmt.Errorf("control api is not public")
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) PostExceptions(exceptions []armotypes.PostureExceptionPolicy) error {
|
||||
|
||||
for i := range exceptions {
|
||||
ex, err := json.Marshal(exceptions[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = armoAPI.Post(armoAPI.exceptionsURL(""), map[string]string{"Content-Type": "application/json"}, ex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) DeleteException(exceptionName string) error {
|
||||
|
||||
_, err := armoAPI.Delete(armoAPI.exceptionsURL(exceptionName), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (armoAPI *ArmoAPI) Login() error {
|
||||
if armoAPI.accountID == "" {
|
||||
return fmt.Errorf("failed to login, missing accountID")
|
||||
}
|
||||
if armoAPI.clientID == "" {
|
||||
return fmt.Errorf("failed to login, missing clientID")
|
||||
}
|
||||
if armoAPI.secretKey == "" {
|
||||
return fmt.Errorf("failed to login, missing secretKey")
|
||||
}
|
||||
|
||||
// init URLs
|
||||
feLoginData := FeLoginData{ClientId: armoAPI.clientID, Secret: armoAPI.secretKey}
|
||||
body, _ := json.Marshal(feLoginData)
|
||||
|
||||
resp, err := http.Post(armoAPI.getApiToken(), "application/json", bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("error authenticating: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
responseBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var feLoginResponse FeLoginResponse
|
||||
|
||||
if err = json.Unmarshal(responseBody, &feLoginResponse); err != nil {
|
||||
return err
|
||||
}
|
||||
armoAPI.feToken = feLoginResponse
|
||||
|
||||
/* Now we have JWT */
|
||||
|
||||
armoAPI.authCookie, err = armoAPI.getAuthCookie()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
armoAPI.loggedIn = true
|
||||
return nil
|
||||
}
|
||||
169
cautils/getter/armoapiutils.go
Normal file
169
cautils/getter/armoapiutils.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var NativeFrameworks = []string{"nsa", "mitre", "armobest", "devopsbest"}
|
||||
|
||||
func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoFrameworks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
if isNativeFramework(frameworkName) {
|
||||
q.Add("frameworkName", strings.ToUpper(frameworkName))
|
||||
} else {
|
||||
// For customer framework has to be the way it was added
|
||||
q.Add("frameworkName", frameworkName)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getListFrameworkURL() string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoFrameworks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
func (armoAPI *ArmoAPI) getExceptionsURL(clusterName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoPostureExceptions"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
// if clusterName != "" { // TODO - fix customer name support in Armo BE
|
||||
// q.Add("clusterName", clusterName)
|
||||
// }
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) exceptionsURL(exceptionsPolicyName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/postureExceptionPolicy"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
if exceptionsPolicyName != "" { // for delete
|
||||
q.Add("policyName", exceptionsPolicyName)
|
||||
}
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getAccountConfig(clusterName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoCustomerConfiguration"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
if clusterName != "" { // TODO - fix customer name support in Armo BE
|
||||
q.Add("clusterName", clusterName)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getAccountURL() string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/createTenant"
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getApiToken() string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.authURL
|
||||
u.Path = "frontegg/identity/resources/auth/v1/api-token"
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getOpenidCustomers() string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/openid_customers"
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getAuthCookie() (string, error) {
|
||||
selectCustomer := ArmoSelectCustomer{SelectedCustomerGuid: armoAPI.accountID}
|
||||
requestBody, _ := json.Marshal(selectCustomer)
|
||||
client := &http.Client{}
|
||||
httpRequest, err := http.NewRequest(http.MethodPost, armoAPI.getOpenidCustomers(), bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
httpRequest.Header.Set("Content-Type", "application/json")
|
||||
httpRequest.Header.Set("Authorization", fmt.Sprintf("Bearer %s", armoAPI.feToken.Token))
|
||||
httpResponse, err := client.Do(httpRequest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer httpResponse.Body.Close()
|
||||
if httpResponse.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("failed to get cookie from %s: status %d", armoAPI.getOpenidCustomers(), httpResponse.StatusCode)
|
||||
}
|
||||
|
||||
cookies := httpResponse.Header.Get("set-cookie")
|
||||
if len(cookies) == 0 {
|
||||
return "", fmt.Errorf("no cookie field in response from %s", armoAPI.getOpenidCustomers())
|
||||
}
|
||||
|
||||
authCookie := ""
|
||||
for _, cookie := range strings.Split(cookies, ";") {
|
||||
kv := strings.Split(cookie, "=")
|
||||
if kv[0] == "auth" {
|
||||
authCookie = kv[1]
|
||||
}
|
||||
}
|
||||
|
||||
if len(authCookie) == 0 {
|
||||
return "", fmt.Errorf("no auth cookie field in response from %s", armoAPI.getOpenidCustomers())
|
||||
}
|
||||
|
||||
return authCookie, nil
|
||||
}
|
||||
func (armoAPI *ArmoAPI) appendAuthHeaders(headers map[string]string) {
|
||||
|
||||
if armoAPI.feToken.Token != "" {
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", armoAPI.feToken.Token)
|
||||
}
|
||||
if armoAPI.authCookie != "" {
|
||||
headers["Cookie"] = fmt.Sprintf("auth=%s", armoAPI.authCookie)
|
||||
}
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getCustomerGUIDFallBack() string {
|
||||
if armoAPI.accountID != "" {
|
||||
return armoAPI.accountID
|
||||
}
|
||||
return "11111111-1111-1111-1111-111111111111"
|
||||
}
|
||||
24
cautils/getter/datastructures.go
Normal file
24
cautils/getter/datastructures.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package getter
|
||||
|
||||
type FeLoginData struct {
|
||||
Secret string `json:"secret"`
|
||||
ClientId string `json:"clientId"`
|
||||
}
|
||||
|
||||
type FeLoginResponse struct {
|
||||
Token string `json:"accessToken"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
ExpiresIn int32 `json:"expiresIn"`
|
||||
Expires string `json:"expires"`
|
||||
}
|
||||
|
||||
type ArmoSelectCustomer struct {
|
||||
SelectedCustomerGuid string `json:"selectedCustomer"`
|
||||
}
|
||||
|
||||
type TenantResponse struct {
|
||||
TenantID string `json:"tenantId"`
|
||||
Token string `json:"token"`
|
||||
Expires string `json:"expires"`
|
||||
AdminMail string `json:"adminMail,omitempty"`
|
||||
}
|
||||
92
cautils/getter/downloadreleasedpolicy.go
Normal file
92
cautils/getter/downloadreleasedpolicy.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/opa-utils/gitregostore"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
// =======================================================================================================================
|
||||
// ======================================== DownloadReleasedPolicy =======================================================
|
||||
// =======================================================================================================================
|
||||
|
||||
// Use gitregostore to get policies from github release
|
||||
type DownloadReleasedPolicy struct {
|
||||
gs *gitregostore.GitRegoStore
|
||||
}
|
||||
|
||||
func NewDownloadReleasedPolicy() *DownloadReleasedPolicy {
|
||||
return &DownloadReleasedPolicy{
|
||||
gs: gitregostore.NewDefaultGitRegoStore(-1),
|
||||
}
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) GetControl(policyName string) (*reporthandling.Control, error) {
|
||||
var control *reporthandling.Control
|
||||
var err error
|
||||
|
||||
control, err = drp.gs.GetOPAControl(policyName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return control, nil
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) GetFramework(name string) (*reporthandling.Framework, error) {
|
||||
framework, err := drp.gs.GetOPAFrameworkByName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return framework, err
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
|
||||
frameworks, err := drp.gs.GetOPAFrameworks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return frameworks, err
|
||||
}
|
||||
|
||||
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) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
||||
defaultConfigInputs, err := drp.gs.GetDefaultConfigInputs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return defaultConfigInputs.Settings.PostureControlInputs, err
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) SetRegoObjects() error {
|
||||
fwNames, err := drp.gs.GetOPAFrameworksNamesList()
|
||||
if len(fwNames) != 0 && err == nil {
|
||||
return nil
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
40
cautils/getter/getpolicies.go
Normal file
40
cautils/getter/getpolicies.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
ListFrameworks() ([]string, error)
|
||||
ListControls(ListType) ([]string, error)
|
||||
}
|
||||
|
||||
type IExceptionsGetter interface {
|
||||
GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error)
|
||||
}
|
||||
type IBackend interface {
|
||||
GetAccountID() string
|
||||
GetClientID() string
|
||||
GetSecretKey() string
|
||||
|
||||
SetAccountID(accountID string)
|
||||
SetClientID(clientID string)
|
||||
SetSecretKey(secretKey string)
|
||||
|
||||
GetTenant() (*TenantResponse, error)
|
||||
}
|
||||
|
||||
type IControlsInputsGetter interface {
|
||||
GetControlsInputs(clusterName string) (map[string][]string, error)
|
||||
}
|
||||
142
cautils/getter/getpoliciesutils.go
Normal file
142
cautils/getter/getpoliciesutils.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetDefaultPath(name string) string {
|
||||
return filepath.Join(DefaultLocalStore, name)
|
||||
}
|
||||
|
||||
func SaveInFile(policy interface{}, pathStr string) error {
|
||||
encodedData, err := json.MarshalIndent(policy, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
pathDir := path.Dir(pathStr)
|
||||
if err := os.Mkdir(pathDir, 0744); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
|
||||
}
|
||||
err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
setHeaders(req, headers)
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
respStr, err := httpRespToString(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return respStr, nil
|
||||
}
|
||||
func HttpGetter(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) {
|
||||
|
||||
req, err := http.NewRequest("GET", fullURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
setHeaders(req, headers)
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
respStr, err := httpRespToString(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return respStr, nil
|
||||
}
|
||||
|
||||
func HttpPost(httpClient *http.Client, fullURL string, headers map[string]string, body []byte) (string, error) {
|
||||
|
||||
req, err := http.NewRequest("POST", fullURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
setHeaders(req, headers)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
respStr, err := httpRespToString(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return respStr, nil
|
||||
}
|
||||
|
||||
func setHeaders(req *http.Request, headers map[string]string) {
|
||||
if len(headers) >= 0 { // might be nil
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HTTPRespToString parses the body as string and checks the HTTP status code, it closes the body reader at the end
|
||||
func httpRespToString(resp *http.Response) (string, error) {
|
||||
if resp == nil || resp.Body == nil {
|
||||
return "", nil
|
||||
}
|
||||
strBuilder := strings.Builder{}
|
||||
defer resp.Body.Close()
|
||||
if resp.ContentLength > 0 {
|
||||
strBuilder.Grow(int(resp.ContentLength))
|
||||
}
|
||||
_, err := io.Copy(&strBuilder, resp.Body)
|
||||
respStr := strBuilder.String()
|
||||
if err != nil {
|
||||
respStrNewLen := len(respStr)
|
||||
if respStrNewLen > 1024 {
|
||||
respStrNewLen = 1024
|
||||
}
|
||||
return "", fmt.Errorf("http-error: '%s', reason: '%s'", resp.Status, respStr[:respStrNewLen])
|
||||
// return "", fmt.Errorf("HTTP request failed. URL: '%s', Read-ERROR: '%s', HTTP-CODE: '%s', BODY(top): '%s', HTTP-HEADERS: %v, HTTP-BODY-BUFFER-LENGTH: %v", resp.Request.URL.RequestURI(), err, resp.Status, respStr[:respStrNewLen], resp.Header, bytesNum)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
respStrNewLen := len(respStr)
|
||||
if respStrNewLen > 1024 {
|
||||
respStrNewLen = 1024
|
||||
}
|
||||
err = fmt.Errorf("http-error: '%s', reason: '%s'", resp.Status, respStr[:respStrNewLen])
|
||||
}
|
||||
|
||||
return respStr, err
|
||||
}
|
||||
149
cautils/getter/loadpolicy.go
Normal file
149
cautils/getter/loadpolicy.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
// =======================================================================================================================
|
||||
// ============================================== LoadPolicy =============================================================
|
||||
// =======================================================================================================================
|
||||
var DefaultLocalStore = getCacheDir()
|
||||
|
||||
func getCacheDir() string {
|
||||
defaultDirPath := ".kubescape"
|
||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
||||
defaultDirPath = filepath.Join(homeDir, defaultDirPath)
|
||||
}
|
||||
return defaultDirPath
|
||||
}
|
||||
|
||||
// Load policies from a local repository
|
||||
type LoadPolicy struct {
|
||||
filePaths []string
|
||||
}
|
||||
|
||||
func NewLoadPolicy(filePaths []string) *LoadPolicy {
|
||||
return &LoadPolicy{
|
||||
filePaths: filePaths,
|
||||
}
|
||||
}
|
||||
|
||||
// Return control from file
|
||||
func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, error) {
|
||||
|
||||
control := &reporthandling.Control{}
|
||||
filePath := lp.filePath()
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(f, control); err != nil {
|
||||
return control, err
|
||||
}
|
||||
if controlName != "" && !strings.EqualFold(controlName, control.Name) && !strings.EqualFold(controlName, control.ControlID) {
|
||||
framework, err := lp.GetFramework(control.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("control from file not matching")
|
||||
} else {
|
||||
for _, ctrl := range framework.Controls {
|
||||
if strings.EqualFold(ctrl.Name, controlName) || strings.EqualFold(ctrl.ControlID, controlName) {
|
||||
control = &ctrl
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return control, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) {
|
||||
framework := &reporthandling.Framework{}
|
||||
var err error
|
||||
for _, filePath := range lp.filePaths {
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(f, framework); err != nil {
|
||||
return framework, err
|
||||
}
|
||||
if strings.EqualFold(frameworkName, framework.Name) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if frameworkName != "" && !strings.EqualFold(frameworkName, framework.Name) {
|
||||
|
||||
return nil, fmt.Errorf("framework from file not matching")
|
||||
}
|
||||
return framework, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
|
||||
frameworks := []reporthandling.Framework{}
|
||||
var err error
|
||||
return frameworks, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) ListFrameworks() ([]string, error) {
|
||||
fwNames := []string{}
|
||||
framework := &reporthandling.Framework{}
|
||||
for _, f := range lp.filePaths {
|
||||
file, err := os.ReadFile(f)
|
||||
if err == nil {
|
||||
if err := json.Unmarshal(file, framework); err == nil {
|
||||
if !contains(fwNames, framework.Name) {
|
||||
fwNames = append(fwNames, framework.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fwNames, nil
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) ListControls(listType ListType) ([]string, error) {
|
||||
// TODO - Support
|
||||
return []string{}, fmt.Errorf("loading controls list from file is not supported")
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||
filePath := lp.filePath()
|
||||
exception := []armotypes.PostureExceptionPolicy{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(f, &exception)
|
||||
return exception, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
||||
filePath := lp.filePath()
|
||||
accountConfig := &armotypes.CustomerConfig{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(f, &accountConfig.Settings.PostureControlInputs); err == nil {
|
||||
return accountConfig.Settings.PostureControlInputs, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// temporary support for a list of files
|
||||
func (lp *LoadPolicy) filePath() string {
|
||||
if len(lp.filePaths) > 0 {
|
||||
return lp.filePaths[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
13
cautils/getter/loadpolicy_test.go
Normal file
13
cautils/getter/loadpolicy_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var mockFrameworkBasePath = filepath.Join("examples", "mocks", "frameworks")
|
||||
|
||||
func MockNewLoadPolicy() *LoadPolicy {
|
||||
return &LoadPolicy{
|
||||
filePaths: []string{""},
|
||||
}
|
||||
}
|
||||
23
cautils/jsonutils.go
Normal file
23
cautils/jsonutils.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
const (
|
||||
empty = ""
|
||||
tab = " "
|
||||
)
|
||||
|
||||
func PrettyJson(data interface{}) ([]byte, error) {
|
||||
buffer := new(bytes.Buffer)
|
||||
encoder := json.NewEncoder(buffer)
|
||||
encoder.SetIndent(empty, tab)
|
||||
|
||||
err := encoder.Encode(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ecr"
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
// For GCR there are some permissions one need to assign in order to allow ARMO to pull images:
|
||||
// https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity
|
||||
// gcloud iam service-accounts create armo-controller-sa
|
||||
// gcloud projects add-iam-policy-binding <PROJECT_NAME> --role roles/storage.objectViewer --member "serviceAccount:armo-controller-sa@<PROJECT_NAME>.iam.gserviceaccount.com"
|
||||
// gcloud iam service-accounts add-iam-policy-binding --role roles/iam.workloadIdentityUser --member "serviceAccount:<PROJECT_NAME>.svc.id.goog[cyberarmor-system/ca-controller-service-account]" armo-controller-sa@<PROJECT_NAME>.iam.gserviceaccount.com
|
||||
// kubectl annotate serviceaccount --overwrite --namespace cyberarmor-system ca-controller-service-account iam.gke.io/gcp-service-account=armo-controller-sa@<PROJECT_NAME>.iam.gserviceaccount.com
|
||||
|
||||
const (
|
||||
gcrDefaultServiceAccountName = "default"
|
||||
// armoServiceAccountName = "ca-controller-service-account"
|
||||
)
|
||||
|
||||
var (
|
||||
httpClient = http.Client{Timeout: 5 * time.Second}
|
||||
)
|
||||
|
||||
// CheckIsECRImage check if this image is suspected as ECR hosted image
|
||||
func CheckIsECRImage(imageTag string) bool {
|
||||
return strings.Contains(imageTag, "dkr.ecr")
|
||||
}
|
||||
|
||||
// GetLoginDetailsForECR return user name + password using the default iam-role OR ~/.aws/config of the machine
|
||||
func GetLoginDetailsForECR(imageTag string) (string, string, error) {
|
||||
// imageTag := "015253967648.dkr.ecr.eu-central-1.amazonaws.com/armo:1"
|
||||
imageTagSlices := strings.Split(imageTag, ".")
|
||||
repo := imageTagSlices[0]
|
||||
region := imageTagSlices[3]
|
||||
mySession := session.Must(session.NewSession())
|
||||
ecrClient := ecr.New(mySession, aws.NewConfig().WithRegion(region))
|
||||
input := &ecr.GetAuthorizationTokenInput{
|
||||
RegistryIds: []*string{&repo},
|
||||
}
|
||||
res, err := ecrClient.GetAuthorizationToken(input)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("in PullFromECR, failed to GetAuthorizationToken: %v", err)
|
||||
}
|
||||
res64 := (*res.AuthorizationData[0].AuthorizationToken)
|
||||
resB, err := base64.StdEncoding.DecodeString(res64)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("in PullFromECR, failed to DecodeString: %v", err)
|
||||
}
|
||||
delimiterIdx := bytes.IndexByte(resB, ':')
|
||||
// userName := resB[:delimiterIdx]
|
||||
// resB = resB[delimiterIdx+1:]
|
||||
// resB, err = base64.StdEncoding.DecodeString(string(resB))
|
||||
// if err != nil {
|
||||
// t.Errorf("failed to DecodeString #2: %v\n\n", err)
|
||||
// }
|
||||
return string(resB[:delimiterIdx]), string(resB[delimiterIdx+1:]), nil
|
||||
}
|
||||
|
||||
func CheckIsACRImage(imageTag string) bool {
|
||||
// atest1.azurecr.io/go-inf:1
|
||||
return strings.Contains(imageTag, ".azurecr.io/")
|
||||
}
|
||||
|
||||
type azureADDResponseJson struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresIn string `json:"expires_in"`
|
||||
ExpiresOn string `json:"expires_on"`
|
||||
NotBefore string `json:"not_before"`
|
||||
Resource string `json:"resource"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
|
||||
func getAzureAADAccessToken() (string, error) {
|
||||
msi_endpoint, err := url.Parse("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("creating URL : %v", err)
|
||||
}
|
||||
msi_parameters := url.Values{}
|
||||
msi_parameters.Add("resource", "https://management.azure.com/")
|
||||
msi_parameters.Add("api-version", "2018-02-01")
|
||||
msi_endpoint.RawQuery = msi_parameters.Encode()
|
||||
req, err := http.NewRequest("GET", msi_endpoint.String(), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("creating HTTP request : %v", err)
|
||||
}
|
||||
req.Header.Add("Metadata", "true")
|
||||
|
||||
// Call managed services for Azure resources token endpoint
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("calling token endpoint : %v", err)
|
||||
}
|
||||
|
||||
// Pull out response body
|
||||
responseBytes, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("reading response body : %v", err)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return "", fmt.Errorf("azure ActiveDirectory AT resp: %v, %v", resp.Status, string(responseBytes))
|
||||
}
|
||||
|
||||
// Unmarshall response body into struct
|
||||
var r azureADDResponseJson
|
||||
err = json.Unmarshal(responseBytes, &r)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unmarshalling the response: %v", err)
|
||||
}
|
||||
return r.AccessToken, nil
|
||||
}
|
||||
|
||||
// GetLoginDetailsForAzurCR return user name + password to use
|
||||
func GetLoginDetailsForAzurCR(imageTag string) (string, string, error) {
|
||||
// imageTag := "atest1.azurecr.io/go-inf:1"
|
||||
imageTagSlices := strings.Split(imageTag, "/")
|
||||
azureIdensAT, err := getAzureAADAccessToken()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
atMap := make(map[string]interface{})
|
||||
azureIdensATSlices := strings.Split(azureIdensAT, ".")
|
||||
if len(azureIdensATSlices) < 2 {
|
||||
return "", "", fmt.Errorf("len(azureIdensATSlices) < 2")
|
||||
}
|
||||
resB, err := base64.RawStdEncoding.DecodeString(azureIdensATSlices[1])
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("in GetLoginDetailsForAzurCR, failed to DecodeString: %v, %s", err, azureIdensATSlices[1])
|
||||
}
|
||||
if err := json.Unmarshal(resB, &atMap); err != nil {
|
||||
return "", "", fmt.Errorf("failed to unmarshal azureIdensAT: %v, %s", err, string(resB))
|
||||
}
|
||||
// excahnging AAD for ACR refresh token
|
||||
refreshToken, err := excahngeAzureAADAccessTokenForACRRefreshToken(imageTagSlices[0], fmt.Sprintf("%v", atMap["tid"]), azureIdensAT)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to excahngeAzureAADAccessTokenForACRRefreshToken: %v, registry: %s, tenantID: %s, azureAADAT: %s", err, imageTagSlices[0], fmt.Sprintf("%v", atMap["tid"]), azureIdensAT)
|
||||
}
|
||||
|
||||
return "00000000-0000-0000-0000-000000000000", refreshToken, nil
|
||||
}
|
||||
|
||||
func excahngeAzureAADAccessTokenForACRRefreshToken(registry, tenantID, azureAADAT string) (string, error) {
|
||||
msi_parameters := url.Values{}
|
||||
msi_parameters.Add("service", registry)
|
||||
msi_parameters.Add("grant_type", "access_token")
|
||||
msi_parameters.Add("tenant", tenantID)
|
||||
msi_parameters.Add("access_token", azureAADAT)
|
||||
postBodyStr := msi_parameters.Encode()
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("https://%v/oauth2/exchange", registry), strings.NewReader(postBodyStr))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("creating HTTP request : %v", err)
|
||||
}
|
||||
req.Header.Add("Metadata", "true")
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
// Call managed services for Azure resources token endpoint
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("calling token endpoint : %v", err)
|
||||
}
|
||||
|
||||
// Pull out response body
|
||||
responseBytes, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("reading response body : %v", err)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return "", fmt.Errorf("azure exchange AT resp: %v, %v", resp.Status, string(responseBytes))
|
||||
}
|
||||
resultMap := make(map[string]string)
|
||||
err = json.Unmarshal(responseBytes, &resultMap)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unmarshalling the response: %v", err)
|
||||
}
|
||||
return resultMap["refresh_token"], nil
|
||||
}
|
||||
|
||||
func CheckIsGCRImage(imageTag string) bool {
|
||||
// gcr.io/elated-pottery-310110/golang-inf:2
|
||||
return strings.Contains(imageTag, "gcr.io/")
|
||||
}
|
||||
|
||||
// GetLoginDetailsForGCR return user name + password to use
|
||||
func GetLoginDetailsForGCR(imageTag string) (string, string, error) {
|
||||
msi_endpoint, err := url.Parse(fmt.Sprintf("http://169.254.169.254/computeMetadata/v1/instance/service-accounts/%s/token", gcrDefaultServiceAccountName))
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("creating URL : %v", err)
|
||||
}
|
||||
req, err := http.NewRequest("GET", msi_endpoint.String(), nil)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("creating HTTP request : %v", err)
|
||||
}
|
||||
req.Header.Add("Metadata-Flavor", "Google")
|
||||
|
||||
// Call managed services for Azure resources token endpoint
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("calling token endpoint : %v", err)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return "", "", fmt.Errorf("HTTP Status : %v, make sure the '%s' service account is configured for ARMO pod", resp.Status, gcrDefaultServiceAccountName)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
respMap := make(map[string]interface{})
|
||||
if err := json.NewDecoder(resp.Body).Decode(&respMap); err != nil {
|
||||
return "", "", fmt.Errorf("json Decode : %v", err)
|
||||
}
|
||||
return "oauth2accesstoken", fmt.Sprintf("%v", respMap["access_token"]), nil
|
||||
}
|
||||
|
||||
func GetCloudVendorRegistryCredentials(imageTag string) (map[string]types.AuthConfig, error) {
|
||||
secrets := map[string]types.AuthConfig{}
|
||||
var errRes error
|
||||
if CheckIsACRImage(imageTag) {
|
||||
userName, password, err := GetLoginDetailsForAzurCR(imageTag)
|
||||
if err != nil {
|
||||
errRes = fmt.Errorf("failed to GetLoginDetailsForACR(%s): %v", imageTag, err)
|
||||
} else {
|
||||
secrets[imageTag] = types.AuthConfig{
|
||||
Username: userName,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if CheckIsECRImage(imageTag) {
|
||||
userName, password, err := GetLoginDetailsForECR(imageTag)
|
||||
if err != nil {
|
||||
errRes = fmt.Errorf("failed to GetLoginDetailsForECR(%s): %v", imageTag, err)
|
||||
} else {
|
||||
secrets[imageTag] = types.AuthConfig{
|
||||
Username: userName,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if CheckIsGCRImage(imageTag) {
|
||||
userName, password, err := GetLoginDetailsForGCR(imageTag)
|
||||
if err != nil {
|
||||
errRes = fmt.Errorf("failed to GetLoginDetailsForGCR(%s): %v", imageTag, err)
|
||||
} else {
|
||||
secrets[imageTag] = types.AuthConfig{
|
||||
Username: userName,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return secrets, errRes
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
|
||||
// DO NOT REMOVE - load cloud providers auth
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
)
|
||||
|
||||
// K8SConfig pointer to k8s config
|
||||
var K8SConfig *restclient.Config
|
||||
|
||||
// KubernetesApi -
|
||||
type KubernetesApi struct {
|
||||
KubernetesClient kubernetes.Interface
|
||||
DynamicClient dynamic.Interface
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// NewKubernetesApi -
|
||||
func NewKubernetesApi() *KubernetesApi {
|
||||
|
||||
kubernetesClient, err := kubernetes.NewForConfig(GetK8sConfig())
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to load config file, reason: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
dynamicClient, err := dynamic.NewForConfig(K8SConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to load config file, reason: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return &KubernetesApi{
|
||||
KubernetesClient: kubernetesClient,
|
||||
DynamicClient: dynamicClient,
|
||||
Context: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
// RunningIncluster whether running in cluster
|
||||
var RunningIncluster bool
|
||||
|
||||
// LoadK8sConfig load config from local file or from cluster
|
||||
func LoadK8sConfig() error {
|
||||
kubeconfig, err := config.GetConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load kubernetes config: %s\n", err)
|
||||
}
|
||||
if _, err := restclient.InClusterConfig(); err == nil {
|
||||
RunningIncluster = true
|
||||
}
|
||||
K8SConfig = kubeconfig
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetK8sConfig get config. load if not loaded yer
|
||||
func GetK8sConfig() *restclient.Config {
|
||||
if K8SConfig == nil {
|
||||
if err := LoadK8sConfig(); err != nil {
|
||||
// print error
|
||||
fmt.Printf("%s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
return K8SConfig
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/cautils"
|
||||
)
|
||||
|
||||
func TestGetGroupVersionResource(t *testing.T) {
|
||||
wlid := "wlid://cluster-david-v1/namespace-default/deployment-nginx-deployment"
|
||||
r, err := GetGroupVersionResource(cautils.GetKindFromWlid(wlid))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if r.Group != "apps" {
|
||||
t.Errorf("wrong group")
|
||||
}
|
||||
if r.Version != "v1" {
|
||||
t.Errorf("wrong Version")
|
||||
}
|
||||
if r.Resource != "deployments" {
|
||||
t.Errorf("wrong Resource")
|
||||
}
|
||||
|
||||
r2, err := GetGroupVersionResource("NetworkPolicy")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if r2.Resource != "networkpolicies" {
|
||||
t.Errorf("wrong Resource")
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/cautils"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
//
|
||||
// Uncomment to load all auth plugins
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth
|
||||
//
|
||||
// Or uncomment to load specific auth plugins
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||
)
|
||||
|
||||
func (k8sAPI *KubernetesApi) GetWorkloadByWlid(wlid string) (*Workload, error) {
|
||||
return k8sAPI.GetWorkload(cautils.GetNamespaceFromWlid(wlid), cautils.GetKindFromWlid(wlid), cautils.GetNameFromWlid(wlid))
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) GetWorkload(namespace, kind, name string) (*Workload, error) {
|
||||
groupVersionResource, err := GetGroupVersionResource(kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err := k8sAPI.ResourceInterface(&groupVersionResource, namespace).Get(k8sAPI.Context, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to GET resource, kind: '%s', namespace: '%s', name: '%s', reason: %s", kind, namespace, name, err.Error())
|
||||
}
|
||||
return NewWorkloadObj(w.Object), nil
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) ListWorkloads(groupVersionResource *schema.GroupVersionResource, namespace string, podLabels, fieldSelector map[string]string) ([]Workload, error) {
|
||||
listOptions := metav1.ListOptions{}
|
||||
if podLabels != nil && len(podLabels) > 0 {
|
||||
set := labels.Set(podLabels)
|
||||
listOptions.LabelSelector = SelectorToString(set)
|
||||
}
|
||||
if fieldSelector != nil && len(fieldSelector) > 0 {
|
||||
set := labels.Set(fieldSelector)
|
||||
listOptions.FieldSelector = SelectorToString(set)
|
||||
}
|
||||
uList, err := k8sAPI.ResourceInterface(groupVersionResource, namespace).List(k8sAPI.Context, listOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to LIST resources, reason: %s", err.Error())
|
||||
}
|
||||
workloads := make([]Workload, len(uList.Items))
|
||||
for i := range uList.Items {
|
||||
workloads[i] = *NewWorkloadObj(uList.Items[i].Object)
|
||||
}
|
||||
return workloads, nil
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) DeleteWorkloadByWlid(wlid string) error {
|
||||
groupVersionResource, err := GetGroupVersionResource(cautils.GetKindFromWlid(wlid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = k8sAPI.ResourceInterface(&groupVersionResource, cautils.GetNamespaceFromWlid(wlid)).Delete(k8sAPI.Context, cautils.GetNameFromWlid(wlid), metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to DELETE resource, workloadID: '%s', reason: %s", wlid, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) CreateWorkload(workload *Workload) (*Workload, error) {
|
||||
groupVersionResource, err := GetGroupVersionResource(workload.GetKind())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj, err := workload.ToUnstructured()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w, err := k8sAPI.ResourceInterface(&groupVersionResource, workload.GetNamespace()).Create(k8sAPI.Context, obj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to CREATE resource, workload: '%s', reason: %s", workload.Json(), err.Error())
|
||||
}
|
||||
return NewWorkloadObj(w.Object), nil
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) UpdateWorkload(workload *Workload) (*Workload, error) {
|
||||
groupVersionResource, err := GetGroupVersionResource(workload.GetKind())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj, err := workload.ToUnstructured()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err := k8sAPI.ResourceInterface(&groupVersionResource, workload.GetNamespace()).Update(k8sAPI.Context, obj, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to UPDATE resource, workload: '%s', reason: %s", workload.Json(), err.Error())
|
||||
}
|
||||
return NewWorkloadObj(w.Object), nil
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) GetNamespace(ns string) (*Workload, error) {
|
||||
groupVersionResource, err := GetGroupVersionResource("namespace")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w, err := k8sAPI.DynamicClient.Resource(groupVersionResource).Get(k8sAPI.Context, ns, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get namespace: '%s', reason: %s", ns, err.Error())
|
||||
}
|
||||
return NewWorkloadObj(w.Object), nil
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) ResourceInterface(resource *schema.GroupVersionResource, namespace string) dynamic.ResourceInterface {
|
||||
if IsNamespaceScope(resource.Group, resource.Resource) {
|
||||
return k8sAPI.DynamicClient.Resource(*resource).Namespace(namespace)
|
||||
}
|
||||
return k8sAPI.DynamicClient.Resource(*resource)
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) CalculateWorkloadParentRecursive(workload *Workload) (string, string, error) {
|
||||
ownerReferences, err := workload.GetOwnerReferences() // OwnerReferences in workload
|
||||
if err != nil {
|
||||
return workload.GetKind(), workload.GetName(), err
|
||||
}
|
||||
if len(ownerReferences) == 0 {
|
||||
return workload.GetKind(), workload.GetName(), nil // parent found
|
||||
}
|
||||
ownerReference := ownerReferences[0]
|
||||
|
||||
parentWorkload, err := k8sAPI.GetWorkload(workload.GetNamespace(), ownerReference.Kind, ownerReference.Name)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "not found in resourceMap") { // if parent is RCD
|
||||
return workload.GetKind(), workload.GetName(), nil // parent found
|
||||
}
|
||||
return workload.GetKind(), workload.GetName(), err
|
||||
}
|
||||
return k8sAPI.CalculateWorkloadParentRecursive(parentWorkload)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
dynamicfake "k8s.io/client-go/dynamic/fake"
|
||||
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
||||
//
|
||||
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
// Uncomment to load all auth plugins
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth
|
||||
//
|
||||
// Or uncomment to load specific auth plugins
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||
)
|
||||
|
||||
// NewKubernetesApi -
|
||||
func NewKubernetesApiMock() *KubernetesApi {
|
||||
|
||||
return &KubernetesApi{
|
||||
KubernetesClient: kubernetesfake.NewSimpleClientset(),
|
||||
DynamicClient: dynamicfake.NewSimpleDynamicClient(&runtime.Scheme{}),
|
||||
Context: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
// func TestListDynamic(t *testing.T) {
|
||||
// k8s := NewKubernetesApi()
|
||||
// resource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
||||
// clientResource, err := k8s.DynamicClient.Resource(resource).Namespace("default").List(k8s.Context, metav1.ListOptions{})
|
||||
// if err != nil {
|
||||
// t.Errorf("err: %v", err)
|
||||
// } else {
|
||||
// bla, _ := json.Marshal(clientResource)
|
||||
// // t.Errorf("BearerToken: %v", *K8SConfig)
|
||||
// // ioutil.WriteFile("bla.json", bla, 777)
|
||||
// t.Errorf("clientResource: %s", string(bla))
|
||||
// }
|
||||
// }
|
||||
@@ -1,66 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
//
|
||||
// Uncomment to load all auth plugins
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth
|
||||
//
|
||||
// Or uncomment to load specific auth plugins
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||
|
||||
func ConvertUnstructuredSliceToMap(unstructuredSlice []unstructured.Unstructured) []map[string]interface{} {
|
||||
converted := make([]map[string]interface{}, len(unstructuredSlice))
|
||||
for i := range unstructuredSlice {
|
||||
converted[i] = unstructuredSlice[i].Object
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func FilterOutOwneredResources(result []unstructured.Unstructured) []unstructured.Unstructured {
|
||||
response := []unstructured.Unstructured{}
|
||||
recognizedOwners := []string{"Deployment", "ReplicaSet", "DaemonSet", "StatefulSet", "Job", "CronJob"}
|
||||
for i := range result {
|
||||
ownerReferences := result[i].GetOwnerReferences()
|
||||
if len(ownerReferences) == 0 {
|
||||
response = append(response, result[i])
|
||||
} else if !IsStringInSlice(recognizedOwners, ownerReferences[0].Kind) {
|
||||
response = append(response, result[i])
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func IsStringInSlice(slice []string, val string) bool {
|
||||
for _, item := range slice {
|
||||
if item == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// String returns all labels listed as a human readable string.
|
||||
// Conveniently, exactly the format that ParseSelector takes.
|
||||
func SelectorToString(ls labels.Set) string {
|
||||
selector := make([]string, 0, len(ls))
|
||||
for key, value := range ls {
|
||||
if value != "" {
|
||||
selector = append(selector, key+"="+value)
|
||||
} else {
|
||||
selector = append(selector, key)
|
||||
}
|
||||
}
|
||||
// Sort for determinism.
|
||||
sort.StringSlice(selector).Sort()
|
||||
return strings.Join(selector, ",")
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestConvertUnstructuredSliceToMap(t *testing.T) {
|
||||
converted := ConvertUnstructuredSliceToMap(V1KubeSystemNamespaceMock().Items)
|
||||
if len(converted) == 0 { // != 7
|
||||
t.Errorf("len(converted) == 0")
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/cautils"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
func IsAttached(labels map[string]string) *bool {
|
||||
return IsLabel(labels, cautils.ArmoAttach)
|
||||
}
|
||||
|
||||
func IsAgentCompatibleLabel(labels map[string]string) *bool {
|
||||
return IsLabel(labels, cautils.ArmoCompatibleLabel)
|
||||
}
|
||||
func IsAgentCompatibleAnnotation(annotations map[string]string) *bool {
|
||||
return IsLabel(annotations, cautils.ArmoCompatibleAnnotation)
|
||||
}
|
||||
func SetAgentCompatibleLabel(labels map[string]string, val bool) {
|
||||
SetLabel(labels, cautils.ArmoCompatibleLabel, val)
|
||||
}
|
||||
func SetAgentCompatibleAnnotation(annotations map[string]string, val bool) {
|
||||
SetLabel(annotations, cautils.ArmoCompatibleAnnotation, val)
|
||||
}
|
||||
func IsLabel(labels map[string]string, key string) *bool {
|
||||
if labels == nil || len(labels) == 0 {
|
||||
return nil
|
||||
}
|
||||
var k bool
|
||||
if l, ok := labels[key]; ok {
|
||||
if l == "true" {
|
||||
k = true
|
||||
} else if l == "false" {
|
||||
k = false
|
||||
}
|
||||
return &k
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func SetLabel(labels map[string]string, key string, val bool) {
|
||||
if labels == nil {
|
||||
return
|
||||
}
|
||||
v := ""
|
||||
if val {
|
||||
v = "true"
|
||||
} else {
|
||||
v = "false"
|
||||
}
|
||||
labels[key] = v
|
||||
}
|
||||
func (k8sAPI *KubernetesApi) ListAttachedPods(namespace string) ([]corev1.Pod, error) {
|
||||
return k8sAPI.ListPods(namespace, map[string]string{cautils.ArmoAttach: cautils.BoolToString(true)})
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) ListPods(namespace string, podLabels map[string]string) ([]corev1.Pod, error) {
|
||||
listOptions := metav1.ListOptions{}
|
||||
if podLabels != nil && len(podLabels) > 0 {
|
||||
set := labels.Set(podLabels)
|
||||
listOptions.LabelSelector = set.AsSelector().String()
|
||||
}
|
||||
pods, err := k8sAPI.KubernetesClient.CoreV1().Pods(namespace).List(context.Background(), listOptions)
|
||||
if err != nil {
|
||||
return []corev1.Pod{}, err
|
||||
}
|
||||
return pods.Items, nil
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,142 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
const ValueNotFound = -1
|
||||
|
||||
// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#-strong-api-groups-strong-
|
||||
var ResourceGroupMapping = map[string]string{
|
||||
"services": "/v1",
|
||||
"pods": "/v1",
|
||||
"replicationcontrollers": "/v1",
|
||||
"podtemplates": "/v1",
|
||||
"namespaces": "/v1",
|
||||
"nodes": "/v1",
|
||||
"configmaps": "/v1",
|
||||
"secrets": "/v1",
|
||||
"serviceaccounts": "/v1",
|
||||
"persistentvolumeclaims": "/v1",
|
||||
"limitranges": "/v1",
|
||||
"resourcequotas": "/v1",
|
||||
"daemonsets": "apps/v1",
|
||||
"deployments": "apps/v1",
|
||||
"replicasets": "apps/v1",
|
||||
"statefulsets": "apps/v1",
|
||||
"controllerrevisions": "apps/v1",
|
||||
"jobs": "batch/v1",
|
||||
"cronjobs": "batch/v1beta1",
|
||||
"horizontalpodautoscalers": "autoscaling/v1",
|
||||
"ingresses": "extensions/v1beta1",
|
||||
"networkpolicies": "networking.k8s.io/v1",
|
||||
"clusterroles": "rbac.authorization.k8s.io/v1",
|
||||
"clusterrolebindings": "rbac.authorization.k8s.io/v1",
|
||||
"roles": "rbac.authorization.k8s.io/v1",
|
||||
"rolebindings": "rbac.authorization.k8s.io/v1",
|
||||
"mutatingwebhookconfigurations": "admissionregistration.k8s.io/v1",
|
||||
"validatingwebhookconfigurations": "admissionregistration.k8s.io/v1",
|
||||
}
|
||||
|
||||
var GroupsClusterScope = []string{}
|
||||
var ResourceClusterScope = []string{"nodes", "namespaces", "clusterroles", "clusterrolebindings"}
|
||||
|
||||
func GetGroupVersionResource(resource string) (schema.GroupVersionResource, error) {
|
||||
resource = updateResourceKind(resource)
|
||||
if r, ok := ResourceGroupMapping[resource]; ok {
|
||||
gv := strings.Split(r, "/")
|
||||
return schema.GroupVersionResource{Group: gv[0], Version: gv[1], Resource: resource}, nil
|
||||
}
|
||||
return schema.GroupVersionResource{}, fmt.Errorf("resource '%s' not found in resourceMap", resource)
|
||||
}
|
||||
|
||||
func IsNamespaceScope(apiGroup, resource string) bool {
|
||||
return StringInSlice(GroupsClusterScope, apiGroup) == ValueNotFound &&
|
||||
StringInSlice(ResourceClusterScope, resource) == ValueNotFound
|
||||
}
|
||||
|
||||
func StringInSlice(strSlice []string, str string) int {
|
||||
for i := range strSlice {
|
||||
if strSlice[i] == str {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return ValueNotFound
|
||||
}
|
||||
|
||||
func JoinResourceTriplets(group, version, resource string) string {
|
||||
return fmt.Sprintf("%s/%s/%s", group, version, resource)
|
||||
}
|
||||
func GetResourceTriplets(group, version, resource string) []string {
|
||||
resourceTriplets := []string{}
|
||||
if resource == "" {
|
||||
// load full map
|
||||
for k, v := range ResourceGroupMapping {
|
||||
g := strings.Split(v, "/")
|
||||
resourceTriplets = append(resourceTriplets, JoinResourceTriplets(g[0], g[1], k))
|
||||
}
|
||||
} else if version == "" {
|
||||
// load by resource
|
||||
if v, ok := ResourceGroupMapping[resource]; ok {
|
||||
g := strings.Split(v, "/")
|
||||
if group == "" {
|
||||
group = g[0]
|
||||
}
|
||||
resourceTriplets = append(resourceTriplets, JoinResourceTriplets(group, g[1], resource))
|
||||
} else {
|
||||
glog.Errorf("Resource '%s' unknown", resource)
|
||||
}
|
||||
} else if group == "" {
|
||||
// load by resource and version
|
||||
if v, ok := ResourceGroupMapping[resource]; ok {
|
||||
g := strings.Split(v, "/")
|
||||
resourceTriplets = append(resourceTriplets, JoinResourceTriplets(g[0], version, resource))
|
||||
} else {
|
||||
glog.Errorf("Resource '%s' unknown", resource)
|
||||
}
|
||||
} else {
|
||||
resourceTriplets = append(resourceTriplets, JoinResourceTriplets(group, version, resource))
|
||||
}
|
||||
return resourceTriplets
|
||||
}
|
||||
func ResourceGroupToString(group, version, resource string) []string {
|
||||
if group == "*" {
|
||||
group = ""
|
||||
}
|
||||
if version == "*" {
|
||||
version = ""
|
||||
}
|
||||
if resource == "*" {
|
||||
resource = ""
|
||||
}
|
||||
resource = updateResourceKind(resource)
|
||||
return GetResourceTriplets(group, version, resource)
|
||||
}
|
||||
|
||||
func StringToResourceGroup(str string) (string, string, string) {
|
||||
splitted := strings.Split(str, "/")
|
||||
for i := range splitted {
|
||||
if splitted[i] == "*" {
|
||||
splitted[i] = ""
|
||||
}
|
||||
}
|
||||
return splitted[0], splitted[1], splitted[2]
|
||||
}
|
||||
|
||||
func updateResourceKind(resource string) string {
|
||||
resource = strings.ToLower(resource)
|
||||
|
||||
if resource != "" && !strings.HasSuffix(resource, "s") {
|
||||
if strings.HasSuffix(resource, "y") {
|
||||
return fmt.Sprintf("%sies", strings.TrimSuffix(resource, "y")) // e.g. NetworkPolicy -> networkpolicies
|
||||
} else {
|
||||
return fmt.Sprintf("%ss", resource) // add 's' at the end of a resource
|
||||
}
|
||||
}
|
||||
return resource
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestResourceGroupToString(t *testing.T) {
|
||||
allResources := ResourceGroupToString("*", "*", "*")
|
||||
if len(allResources) != len(ResourceGroupMapping) {
|
||||
t.Errorf("Expected len: %d, received: %d", len(ResourceGroupMapping), len(allResources))
|
||||
}
|
||||
pod := ResourceGroupToString("*", "*", "Pod")
|
||||
if len(pod) == 0 || pod[0] != "/v1/pods" {
|
||||
t.Errorf("pod: %v", pod)
|
||||
}
|
||||
deployments := ResourceGroupToString("*", "*", "Deployment")
|
||||
if len(deployments) == 0 || deployments[0] != "apps/v1/deployments" {
|
||||
t.Errorf("deployments: %v", deployments)
|
||||
}
|
||||
cronjobs := ResourceGroupToString("*", "*", "cronjobs")
|
||||
if len(cronjobs) == 0 || cronjobs[0] != "batch/v1beta1/cronjobs" {
|
||||
t.Errorf("cronjobs: %v", cronjobs)
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/apis"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
type IWorkload interface {
|
||||
IBasicWorkload
|
||||
|
||||
// Convert
|
||||
ToUnstructured() (*unstructured.Unstructured, error)
|
||||
ToString() string
|
||||
Json() string // DEPRECATED
|
||||
|
||||
// GET
|
||||
GetWlid() string
|
||||
GetJobID() *apis.JobTracking
|
||||
GetVersion() string
|
||||
GetGroup() string
|
||||
|
||||
// SET
|
||||
SetWlid(string)
|
||||
SetInject()
|
||||
SetIgnore()
|
||||
SetUpdateTime()
|
||||
SetJobID(apis.JobTracking)
|
||||
SetCompatible()
|
||||
SetIncompatible()
|
||||
SetReplaceheaders()
|
||||
|
||||
// EXIST
|
||||
IsIgnore() bool
|
||||
IsInject() bool
|
||||
IsAttached() bool
|
||||
IsCompatible() bool
|
||||
IsIncompatible() bool
|
||||
|
||||
// REMOVE
|
||||
RemoveWlid()
|
||||
RemoveSecretData()
|
||||
RemoveInject()
|
||||
RemoveIgnore()
|
||||
RemoveUpdateTime()
|
||||
RemoveJobID()
|
||||
RemoveCompatible()
|
||||
RemoveArmoMetadata()
|
||||
RemoveArmoLabels()
|
||||
RemoveArmoAnnotations()
|
||||
}
|
||||
type IBasicWorkload interface {
|
||||
|
||||
// Set
|
||||
SetKind(string)
|
||||
SetWorkload(map[string]interface{})
|
||||
SetLabel(key, value string)
|
||||
SetAnnotation(key, value string)
|
||||
SetNamespace(string)
|
||||
SetName(string)
|
||||
|
||||
// Get
|
||||
GetNamespace() string
|
||||
GetName() string
|
||||
GetGenerateName() string
|
||||
GetApiVersion() string
|
||||
GetKind() string
|
||||
GetInnerAnnotation(string) (string, bool)
|
||||
GetPodAnnotation(string) (string, bool)
|
||||
GetAnnotation(string) (string, bool)
|
||||
GetLabel(string) (string, bool)
|
||||
GetAnnotations() map[string]string
|
||||
GetInnerAnnotations() map[string]string
|
||||
GetPodAnnotations() map[string]string
|
||||
GetLabels() map[string]string
|
||||
GetInnerLabels() map[string]string
|
||||
GetPodLabels() map[string]string
|
||||
GetVolumes() ([]corev1.Volume, error)
|
||||
GetReplicas() int
|
||||
GetContainers() ([]corev1.Container, error)
|
||||
GetInitContainers() ([]corev1.Container, error)
|
||||
GetOwnerReferences() ([]metav1.OwnerReference, error)
|
||||
GetImagePullSecret() ([]corev1.LocalObjectReference, error)
|
||||
GetServiceAccountName() string
|
||||
GetSelector() (*metav1.LabelSelector, error)
|
||||
GetResourceVersion() string
|
||||
GetUID() string
|
||||
GetPodSpec() (*corev1.PodSpec, error)
|
||||
|
||||
GetWorkload() map[string]interface{}
|
||||
|
||||
// REMOVE
|
||||
RemoveLabel(string)
|
||||
RemoveAnnotation(string)
|
||||
RemovePodStatus()
|
||||
RemoveResourceVersion()
|
||||
}
|
||||
|
||||
type Workload struct {
|
||||
workload map[string]interface{}
|
||||
}
|
||||
|
||||
func NewWorkload(bWorkload []byte) (*Workload, error) {
|
||||
workload := make(map[string]interface{})
|
||||
if bWorkload != nil {
|
||||
if err := json.Unmarshal(bWorkload, &workload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &Workload{
|
||||
workload: workload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewWorkloadObj(workload map[string]interface{}) *Workload {
|
||||
return &Workload{
|
||||
workload: workload,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Workload) Json() string {
|
||||
return w.ToString()
|
||||
}
|
||||
func (w *Workload) ToString() string {
|
||||
if w.GetWorkload() == nil {
|
||||
return ""
|
||||
}
|
||||
bWorkload, err := json.Marshal(w.GetWorkload())
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return string(bWorkload)
|
||||
}
|
||||
|
||||
func (workload *Workload) DeepCopy(w map[string]interface{}) {
|
||||
workload.workload = make(map[string]interface{})
|
||||
byt, _ := json.Marshal(w)
|
||||
json.Unmarshal(byt, &workload.workload)
|
||||
}
|
||||
|
||||
func (w *Workload) ToUnstructured() (*unstructured.Unstructured, error) {
|
||||
obj := &unstructured.Unstructured{}
|
||||
if w.workload == nil {
|
||||
return obj, nil
|
||||
}
|
||||
bWorkload, err := json.Marshal(w.workload)
|
||||
if err != nil {
|
||||
return obj, err
|
||||
}
|
||||
if err := json.Unmarshal(bWorkload, obj); err != nil {
|
||||
return obj, err
|
||||
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
@@ -1,642 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/apis"
|
||||
"github.com/armosec/kubescape/cautils/cautils"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ======================================= DELETE ========================================
|
||||
|
||||
func (w *Workload) RemoveInject() {
|
||||
w.RemovePodLabel(cautils.CAInject) // DEPRECATED
|
||||
w.RemovePodLabel(cautils.CAAttachLabel) // DEPRECATED
|
||||
w.RemovePodLabel(cautils.ArmoAttach)
|
||||
|
||||
w.RemoveLabel(cautils.CAInject) // DEPRECATED
|
||||
w.RemoveLabel(cautils.CAAttachLabel) // DEPRECATED
|
||||
w.RemoveLabel(cautils.ArmoAttach)
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveIgnore() {
|
||||
w.RemovePodLabel(cautils.CAIgnore) // DEPRECATED
|
||||
w.RemovePodLabel(cautils.ArmoAttach)
|
||||
|
||||
w.RemoveLabel(cautils.CAIgnore) // DEPRECATED
|
||||
w.RemoveLabel(cautils.ArmoAttach)
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveWlid() {
|
||||
w.RemovePodAnnotation(cautils.CAWlid) // DEPRECATED
|
||||
w.RemovePodAnnotation(cautils.ArmoWlid)
|
||||
|
||||
w.RemoveAnnotation(cautils.CAWlid) // DEPRECATED
|
||||
w.RemoveAnnotation(cautils.ArmoWlid)
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveCompatible() {
|
||||
w.RemovePodAnnotation(cautils.ArmoCompatibleAnnotation)
|
||||
}
|
||||
func (w *Workload) RemoveJobID() {
|
||||
w.RemovePodAnnotation(cautils.ArmoJobIDPath)
|
||||
w.RemovePodAnnotation(cautils.ArmoJobParentPath)
|
||||
w.RemovePodAnnotation(cautils.ArmoJobActionPath)
|
||||
|
||||
w.RemoveAnnotation(cautils.ArmoJobIDPath)
|
||||
w.RemoveAnnotation(cautils.ArmoJobParentPath)
|
||||
w.RemoveAnnotation(cautils.ArmoJobActionPath)
|
||||
}
|
||||
func (w *Workload) RemoveArmoMetadata() {
|
||||
w.RemoveArmoLabels()
|
||||
w.RemoveArmoAnnotations()
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveArmoAnnotations() {
|
||||
l := w.GetAnnotations()
|
||||
if l != nil {
|
||||
for k := range l {
|
||||
if strings.HasPrefix(k, cautils.ArmoPrefix) {
|
||||
w.RemoveAnnotation(k)
|
||||
}
|
||||
if strings.HasPrefix(k, cautils.CAPrefix) { // DEPRECATED
|
||||
w.RemoveAnnotation(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
lp := w.GetPodAnnotations()
|
||||
if lp != nil {
|
||||
for k := range lp {
|
||||
if strings.HasPrefix(k, cautils.ArmoPrefix) {
|
||||
w.RemovePodAnnotation(k)
|
||||
}
|
||||
if strings.HasPrefix(k, cautils.CAPrefix) { // DEPRECATED
|
||||
w.RemovePodAnnotation(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func (w *Workload) RemoveArmoLabels() {
|
||||
l := w.GetLabels()
|
||||
if l != nil {
|
||||
for k := range l {
|
||||
if strings.HasPrefix(k, cautils.ArmoPrefix) {
|
||||
w.RemoveLabel(k)
|
||||
}
|
||||
if strings.HasPrefix(k, cautils.CAPrefix) { // DEPRECATED
|
||||
w.RemoveLabel(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
lp := w.GetPodLabels()
|
||||
if lp != nil {
|
||||
for k := range lp {
|
||||
if strings.HasPrefix(k, cautils.ArmoPrefix) {
|
||||
w.RemovePodLabel(k)
|
||||
}
|
||||
if strings.HasPrefix(k, cautils.CAPrefix) { // DEPRECATED
|
||||
w.RemovePodLabel(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func (w *Workload) RemoveUpdateTime() {
|
||||
|
||||
// remove from pod
|
||||
w.RemovePodAnnotation(cautils.CAUpdate) // DEPRECATED
|
||||
w.RemovePodAnnotation(cautils.ArmoUpdate)
|
||||
|
||||
// remove from workload
|
||||
w.RemoveAnnotation(cautils.CAUpdate) // DEPRECATED
|
||||
w.RemoveAnnotation(cautils.ArmoUpdate)
|
||||
}
|
||||
func (w *Workload) RemoveSecretData() {
|
||||
w.RemoveAnnotation("kubectl.kubernetes.io/last-applied-configuration")
|
||||
delete(w.workload, "data")
|
||||
}
|
||||
|
||||
func (w *Workload) RemovePodStatus() {
|
||||
delete(w.workload, "status")
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveResourceVersion() {
|
||||
if _, ok := w.workload["metadata"]; !ok {
|
||||
return
|
||||
}
|
||||
meta, _ := w.workload["metadata"].(map[string]interface{})
|
||||
delete(meta, "resourceVersion")
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveLabel(key string) {
|
||||
w.RemoveMetadata([]string{"metadata"}, "labels", key)
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveAnnotation(key string) {
|
||||
w.RemoveMetadata([]string{"metadata"}, "annotations", key)
|
||||
}
|
||||
|
||||
func (w *Workload) RemovePodAnnotation(key string) {
|
||||
w.RemoveMetadata(PodMetadata(w.GetKind()), "annotations", key)
|
||||
}
|
||||
|
||||
func (w *Workload) RemovePodLabel(key string) {
|
||||
w.RemoveMetadata(PodMetadata(w.GetKind()), "labels", key)
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveMetadata(scope []string, metadata, key string) {
|
||||
|
||||
workload := w.workload
|
||||
for i := range scope {
|
||||
if _, ok := workload[scope[i]]; !ok {
|
||||
return
|
||||
}
|
||||
workload, _ = workload[scope[i]].(map[string]interface{})
|
||||
}
|
||||
|
||||
if _, ok := workload[metadata]; !ok {
|
||||
return
|
||||
}
|
||||
|
||||
labels, _ := workload[metadata].(map[string]interface{})
|
||||
delete(labels, key)
|
||||
|
||||
}
|
||||
|
||||
// ========================================= SET =========================================
|
||||
|
||||
func (w *Workload) SetWorkload(workload map[string]interface{}) {
|
||||
w.workload = workload
|
||||
}
|
||||
|
||||
func (w *Workload) SetKind(kind string) {
|
||||
w.workload["kind"] = kind
|
||||
}
|
||||
|
||||
func (w *Workload) SetInject() {
|
||||
w.SetPodLabel(cautils.ArmoAttach, cautils.BoolToString(true))
|
||||
}
|
||||
|
||||
func (w *Workload) SetJobID(jobTracking apis.JobTracking) {
|
||||
w.SetPodAnnotation(cautils.ArmoJobIDPath, jobTracking.JobID)
|
||||
w.SetPodAnnotation(cautils.ArmoJobParentPath, jobTracking.ParentID)
|
||||
w.SetPodAnnotation(cautils.ArmoJobActionPath, fmt.Sprintf("%d", jobTracking.LastActionNumber))
|
||||
}
|
||||
|
||||
func (w *Workload) SetIgnore() {
|
||||
w.SetPodLabel(cautils.ArmoAttach, cautils.BoolToString(false))
|
||||
}
|
||||
|
||||
func (w *Workload) SetCompatible() {
|
||||
w.SetPodAnnotation(cautils.ArmoCompatibleAnnotation, cautils.BoolToString(true))
|
||||
}
|
||||
|
||||
func (w *Workload) SetIncompatible() {
|
||||
w.SetPodAnnotation(cautils.ArmoCompatibleAnnotation, cautils.BoolToString(false))
|
||||
}
|
||||
|
||||
func (w *Workload) SetReplaceheaders() {
|
||||
w.SetPodAnnotation(cautils.ArmoReplaceheaders, cautils.BoolToString(true))
|
||||
}
|
||||
|
||||
func (w *Workload) SetWlid(wlid string) {
|
||||
w.SetPodAnnotation(cautils.ArmoWlid, wlid)
|
||||
}
|
||||
|
||||
func (w *Workload) SetUpdateTime() {
|
||||
w.SetPodAnnotation(cautils.ArmoUpdate, string(time.Now().UTC().Format("02-01-2006 15:04:05")))
|
||||
}
|
||||
|
||||
func (w *Workload) SetNamespace(namespace string) {
|
||||
w.SetMetadata([]string{"metadata"}, "namespace", namespace)
|
||||
}
|
||||
|
||||
func (w *Workload) SetName(name string) {
|
||||
w.SetMetadata([]string{"metadata"}, "name", name)
|
||||
}
|
||||
|
||||
func (w *Workload) SetLabel(key, value string) {
|
||||
w.SetMetadata([]string{"metadata", "labels"}, key, value)
|
||||
}
|
||||
|
||||
func (w *Workload) SetPodLabel(key, value string) {
|
||||
w.SetMetadata(append(PodMetadata(w.GetKind()), "labels"), key, value)
|
||||
}
|
||||
func (w *Workload) SetAnnotation(key, value string) {
|
||||
w.SetMetadata([]string{"metadata", "annotations"}, key, value)
|
||||
}
|
||||
func (w *Workload) SetPodAnnotation(key, value string) {
|
||||
w.SetMetadata(append(PodMetadata(w.GetKind()), "annotations"), key, value)
|
||||
}
|
||||
|
||||
func (w *Workload) SetMetadata(scope []string, key string, val interface{}) {
|
||||
workload := w.workload
|
||||
for i := range scope {
|
||||
if _, ok := workload[scope[i]]; !ok {
|
||||
workload[scope[i]] = make(map[string]interface{})
|
||||
}
|
||||
workload, _ = workload[scope[i]].(map[string]interface{})
|
||||
}
|
||||
|
||||
workload[key] = val
|
||||
}
|
||||
|
||||
// ========================================= GET =========================================
|
||||
func (w *Workload) GetWorkload() map[string]interface{} {
|
||||
return w.workload
|
||||
}
|
||||
func (w *Workload) GetNamespace() string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "namespace"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetName() string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "name"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetApiVersion() string {
|
||||
if v, ok := InspectWorkload(w.workload, "apiVersion"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetVersion() string {
|
||||
apiVersion := w.GetApiVersion()
|
||||
splitted := strings.Split(apiVersion, "/")
|
||||
if len(splitted) == 1 {
|
||||
return splitted[0]
|
||||
} else if len(splitted) == 2 {
|
||||
return splitted[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetGroup() string {
|
||||
apiVersion := w.GetApiVersion()
|
||||
splitted := strings.Split(apiVersion, "/")
|
||||
if len(splitted) == 2 {
|
||||
return splitted[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetGenerateName() string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "generateName"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetReplicas() int {
|
||||
if v, ok := InspectWorkload(w.workload, "spec", "replicas"); ok {
|
||||
replicas, isok := v.(float64)
|
||||
if isok {
|
||||
return int(replicas)
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (w *Workload) GetKind() string {
|
||||
if v, ok := InspectWorkload(w.workload, "kind"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (w *Workload) GetSelector() (*metav1.LabelSelector, error) {
|
||||
selector := &metav1.LabelSelector{}
|
||||
if v, ok := InspectWorkload(w.workload, "spec", "selector", "matchLabels"); ok && v != nil {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return selector, err
|
||||
}
|
||||
if err := json.Unmarshal(b, selector); err != nil {
|
||||
return selector, err
|
||||
}
|
||||
return selector, nil
|
||||
}
|
||||
return selector, nil
|
||||
}
|
||||
|
||||
func (w *Workload) GetAnnotation(annotation string) (string, bool) {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "annotations", annotation); ok {
|
||||
return v.(string), ok
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
func (w *Workload) GetLabel(label string) (string, bool) {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "labels", label); ok {
|
||||
return v.(string), ok
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (w *Workload) GetPodLabel(label string) (string, bool) {
|
||||
if v, ok := InspectWorkload(w.workload, append(PodMetadata(w.GetKind()), "labels", label)...); ok && v != nil {
|
||||
return v.(string), ok
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (w *Workload) GetLabels() map[string]string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "labels"); ok && v != nil {
|
||||
labels := make(map[string]string)
|
||||
for k, i := range v.(map[string]interface{}) {
|
||||
labels[k] = i.(string)
|
||||
}
|
||||
return labels
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInnerLabels - DEPRECATED
|
||||
func (w *Workload) GetInnerLabels() map[string]string {
|
||||
return w.GetPodLabels()
|
||||
}
|
||||
|
||||
func (w *Workload) GetPodLabels() map[string]string {
|
||||
if v, ok := InspectWorkload(w.workload, append(PodMetadata(w.GetKind()), "labels")...); ok && v != nil {
|
||||
labels := make(map[string]string)
|
||||
for k, i := range v.(map[string]interface{}) {
|
||||
labels[k] = i.(string)
|
||||
}
|
||||
return labels
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInnerAnnotations - DEPRECATED
|
||||
func (w *Workload) GetInnerAnnotations() map[string]string {
|
||||
return w.GetPodAnnotations()
|
||||
}
|
||||
|
||||
// GetPodAnnotations
|
||||
func (w *Workload) GetPodAnnotations() map[string]string {
|
||||
if v, ok := InspectWorkload(w.workload, append(PodMetadata(w.GetKind()), "annotations")...); ok && v != nil {
|
||||
annotations := make(map[string]string)
|
||||
for k, i := range v.(map[string]interface{}) {
|
||||
annotations[k] = fmt.Sprintf("%v", i)
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInnerAnnotation DEPRECATED
|
||||
func (w *Workload) GetInnerAnnotation(annotation string) (string, bool) {
|
||||
return w.GetPodAnnotation(annotation)
|
||||
}
|
||||
|
||||
func (w *Workload) GetPodAnnotation(annotation string) (string, bool) {
|
||||
if v, ok := InspectWorkload(w.workload, append(PodMetadata(w.GetKind()), "annotations", annotation)...); ok && v != nil {
|
||||
return v.(string), ok
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (w *Workload) GetAnnotations() map[string]string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "annotations"); ok && v != nil {
|
||||
annotations := make(map[string]string)
|
||||
for k, i := range v.(map[string]interface{}) {
|
||||
annotations[k] = fmt.Sprintf("%v", i)
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVolumes -
|
||||
func (w *Workload) GetVolumes() ([]corev1.Volume, error) {
|
||||
volumes := []corev1.Volume{}
|
||||
|
||||
interVolumes, _ := InspectWorkload(w.workload, append(PodSpec(w.GetKind()), "volumes")...)
|
||||
if interVolumes == nil {
|
||||
return volumes, nil
|
||||
}
|
||||
volumesBytes, err := json.Marshal(interVolumes)
|
||||
if err != nil {
|
||||
return volumes, err
|
||||
}
|
||||
err = json.Unmarshal(volumesBytes, &volumes)
|
||||
|
||||
return volumes, err
|
||||
}
|
||||
|
||||
func (w *Workload) GetServiceAccountName() string {
|
||||
|
||||
if v, ok := InspectWorkload(w.workload, append(PodSpec(w.GetKind()), "serviceAccountName")...); ok && v != nil {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetPodSpec() (*corev1.PodSpec, error) {
|
||||
podSpec := &corev1.PodSpec{}
|
||||
podSepcRaw, _ := InspectWorkload(w.workload, PodSpec(w.GetKind())...)
|
||||
if podSepcRaw == nil {
|
||||
return podSpec, fmt.Errorf("no PodSpec for workload: %v", w)
|
||||
}
|
||||
b, err := json.Marshal(podSepcRaw)
|
||||
if err != nil {
|
||||
return podSpec, err
|
||||
}
|
||||
err = json.Unmarshal(b, podSpec)
|
||||
|
||||
return podSpec, err
|
||||
}
|
||||
|
||||
func (w *Workload) GetImagePullSecret() ([]corev1.LocalObjectReference, error) {
|
||||
imgPullSecrets := []corev1.LocalObjectReference{}
|
||||
|
||||
iImgPullSecrets, _ := InspectWorkload(w.workload, append(PodSpec(w.GetKind()), "imagePullSecrets")...)
|
||||
b, err := json.Marshal(iImgPullSecrets)
|
||||
if err != nil {
|
||||
return imgPullSecrets, err
|
||||
}
|
||||
err = json.Unmarshal(b, &imgPullSecrets)
|
||||
|
||||
return imgPullSecrets, err
|
||||
}
|
||||
|
||||
// GetContainers -
|
||||
func (w *Workload) GetContainers() ([]corev1.Container, error) {
|
||||
containers := []corev1.Container{}
|
||||
|
||||
interContainers, _ := InspectWorkload(w.workload, append(PodSpec(w.GetKind()), "containers")...)
|
||||
if interContainers == nil {
|
||||
return containers, nil
|
||||
}
|
||||
containersBytes, err := json.Marshal(interContainers)
|
||||
if err != nil {
|
||||
return containers, err
|
||||
}
|
||||
err = json.Unmarshal(containersBytes, &containers)
|
||||
|
||||
return containers, err
|
||||
}
|
||||
|
||||
// GetInitContainers -
|
||||
func (w *Workload) GetInitContainers() ([]corev1.Container, error) {
|
||||
containers := []corev1.Container{}
|
||||
|
||||
interContainers, _ := InspectWorkload(w.workload, append(PodSpec(w.GetKind()), "initContainers")...)
|
||||
if interContainers == nil {
|
||||
return containers, nil
|
||||
}
|
||||
containersBytes, err := json.Marshal(interContainers)
|
||||
if err != nil {
|
||||
return containers, err
|
||||
}
|
||||
err = json.Unmarshal(containersBytes, &containers)
|
||||
|
||||
return containers, err
|
||||
}
|
||||
|
||||
// GetOwnerReferences -
|
||||
func (w *Workload) GetOwnerReferences() ([]metav1.OwnerReference, error) {
|
||||
ownerReferences := []metav1.OwnerReference{}
|
||||
interOwnerReferences, ok := InspectWorkload(w.workload, "metadata", "ownerReferences")
|
||||
if !ok {
|
||||
return ownerReferences, nil
|
||||
}
|
||||
|
||||
ownerReferencesBytes, err := json.Marshal(interOwnerReferences)
|
||||
if err != nil {
|
||||
return ownerReferences, err
|
||||
}
|
||||
err = json.Unmarshal(ownerReferencesBytes, &ownerReferences)
|
||||
if err != nil {
|
||||
return ownerReferences, err
|
||||
|
||||
}
|
||||
return ownerReferences, nil
|
||||
}
|
||||
func (w *Workload) GetResourceVersion() string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "resourceVersion"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (w *Workload) GetUID() string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "uid"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (w *Workload) GetWlid() string {
|
||||
if wlid, ok := w.GetAnnotation(cautils.ArmoWlid); ok {
|
||||
return wlid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetJobID() *apis.JobTracking {
|
||||
jobTracking := apis.JobTracking{}
|
||||
if job, ok := w.GetPodAnnotation(cautils.ArmoJobIDPath); ok {
|
||||
jobTracking.JobID = job
|
||||
}
|
||||
if parent, ok := w.GetPodAnnotation(cautils.ArmoJobParentPath); ok {
|
||||
jobTracking.ParentID = parent
|
||||
}
|
||||
if action, ok := w.GetPodAnnotation(cautils.ArmoJobActionPath); ok {
|
||||
if i, err := strconv.Atoi(action); err == nil {
|
||||
jobTracking.LastActionNumber = i
|
||||
}
|
||||
}
|
||||
if jobTracking.LastActionNumber == 0 { // start the counter at 1
|
||||
jobTracking.LastActionNumber = 1
|
||||
}
|
||||
return &jobTracking
|
||||
}
|
||||
|
||||
// func (w *Workload) GetJobID() string {
|
||||
// if status, ok := w.GetAnnotation(cautils.ArmoJobID); ok {
|
||||
// return status
|
||||
// }
|
||||
// return ""
|
||||
// }
|
||||
|
||||
// ========================================= IS =========================================
|
||||
|
||||
func (w *Workload) IsInject() bool {
|
||||
return w.IsAttached()
|
||||
}
|
||||
|
||||
func (w *Workload) IsIgnore() bool {
|
||||
if attach := cautils.IsAttached(w.GetPodLabels()); attach != nil {
|
||||
return !(*attach)
|
||||
}
|
||||
if attach := cautils.IsAttached(w.GetLabels()); attach != nil {
|
||||
return !(*attach)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Workload) IsCompatible() bool {
|
||||
if c, ok := w.GetPodAnnotation(cautils.ArmoCompatibleAnnotation); ok {
|
||||
return cautils.StringToBool(c)
|
||||
|
||||
}
|
||||
if c, ok := w.GetAnnotation(cautils.ArmoCompatibleAnnotation); ok {
|
||||
return cautils.StringToBool(c)
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Workload) IsIncompatible() bool {
|
||||
if c, ok := w.GetPodAnnotation(cautils.ArmoCompatibleAnnotation); ok {
|
||||
return !cautils.StringToBool(c)
|
||||
}
|
||||
if c, ok := w.GetAnnotation(cautils.ArmoCompatibleAnnotation); ok {
|
||||
return !cautils.StringToBool(c)
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (w *Workload) IsAttached() bool {
|
||||
if attach := cautils.IsAttached(w.GetPodLabels()); attach != nil {
|
||||
return *attach
|
||||
}
|
||||
if attach := cautils.IsAttached(w.GetLabels()); attach != nil {
|
||||
return *attach
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Workload) IsReplaceheaders() bool {
|
||||
if c, ok := w.GetPodAnnotation(cautils.ArmoReplaceheaders); ok {
|
||||
return cautils.StringToBool(c)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ======================================= UTILS =========================================
|
||||
|
||||
// InspectWorkload -
|
||||
func InspectWorkload(workload interface{}, scopes ...string) (val interface{}, k bool) {
|
||||
|
||||
val, k = nil, false
|
||||
if len(scopes) == 0 {
|
||||
if workload != nil {
|
||||
return workload, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
if data, ok := workload.(map[string]interface{}); ok {
|
||||
val, k = InspectWorkload(data[scopes[0]], scopes[1:]...)
|
||||
}
|
||||
return val, k
|
||||
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ========================================= IS =========================================
|
||||
|
||||
func TestLabels(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"labels":{"app":"demoservice-server","cyberarmor.inject":"true"},"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if workload.GetKind() != "Deployment" {
|
||||
t.Errorf("wrong kind")
|
||||
}
|
||||
if workload.GetNamespace() != "default" {
|
||||
t.Errorf("wrong namespace")
|
||||
}
|
||||
if workload.GetName() != "demoservice-server" {
|
||||
t.Errorf("wrong name")
|
||||
}
|
||||
if !workload.IsInject() {
|
||||
t.Errorf("expect to find inject label")
|
||||
}
|
||||
if workload.IsIgnore() {
|
||||
t.Errorf("expect to find ignore label")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetNamespace(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"demoservice-server"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"demoservice-server"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
workload.SetNamespace("default")
|
||||
if workload.GetNamespace() != "default" {
|
||||
t.Errorf("wrong namespace")
|
||||
}
|
||||
}
|
||||
func TestSetLabels(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
workload.SetLabel("bla", "daa")
|
||||
v, ok := workload.GetLabel("bla")
|
||||
if !ok || v != "daa" {
|
||||
t.Errorf("expect to find label")
|
||||
}
|
||||
workload.RemoveLabel("bla")
|
||||
v2, ok2 := workload.GetLabel("bla")
|
||||
if ok2 || v2 == "daa" {
|
||||
t.Errorf("label not deleted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetAnnotations(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
workload.SetAnnotation("bla", "daa")
|
||||
v, ok := workload.GetAnnotation("bla")
|
||||
if !ok || v != "daa" {
|
||||
t.Errorf("expect to find annotation")
|
||||
}
|
||||
workload.RemoveAnnotation("bla")
|
||||
v2, ok2 := workload.GetAnnotation("bla")
|
||||
if ok2 || v2 == "daa" {
|
||||
t.Errorf("annotation not deleted")
|
||||
}
|
||||
}
|
||||
func TestSetPodLabels(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
workload.SetPodLabel("bla", "daa")
|
||||
v, ok := workload.GetPodLabel("bla")
|
||||
if !ok || v != "daa" {
|
||||
t.Errorf("expect to find label")
|
||||
}
|
||||
workload.RemovePodLabel("bla")
|
||||
v2, ok2 := workload.GetPodLabel("bla")
|
||||
if ok2 || v2 == "daa" {
|
||||
t.Errorf("label not deleted")
|
||||
}
|
||||
}
|
||||
func TestRemoveArmo(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server", "armo.attach": "true"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if !workload.IsAttached() {
|
||||
t.Errorf("expect to be attached")
|
||||
}
|
||||
workload.RemoveArmoMetadata()
|
||||
if workload.IsAttached() {
|
||||
t.Errorf("expect to be clear")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSetWlid(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
workload.SetWlid("wlid://bla")
|
||||
// t.Errorf(workload.Json())
|
||||
|
||||
}
|
||||
|
||||
func TestGetResourceVersion(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if workload.GetResourceVersion() != "1016043" {
|
||||
t.Errorf("wrong resourceVersion")
|
||||
}
|
||||
|
||||
}
|
||||
func TestGetUID(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if workload.GetUID() != "e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e" {
|
||||
t.Errorf("wrong UID")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIsAttached(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"3"},"creationTimestamp":"2021-06-21T04:52:05Z","generation":3,"name":"emailservice","namespace":"default"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"emailservice"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"annotations":{"armo.last-update":"21-06-2021 06:40:42","armo.wlid":"wlid://cluster-david-demo/namespace-default/deployment-emailservice"},"creationTimestamp":null,"labels":{"app":"emailservice","armo.attach":"true"}},"spec":{"containers":[{"env":[{"name":"PORT","value":"8080"},{"name":"DISABLE_PROFILER","value":"1"}],"image":"gcr.io/google-samples/microservices-demo/emailservice:v0.2.3","imagePullPolicy":"IfNotPresent","livenessProbe":{"exec":{"command":["/bin/grpc_health_probe","-addr=:8080"]},"failureThreshold":3,"periodSeconds":5,"successThreshold":1,"timeoutSeconds":1},"name":"server","ports":[{"containerPort":8080,"protocol":"TCP"}],"readinessProbe":{"exec":{"command":["/bin/grpc_health_probe","-addr=:8080"]},"failureThreshold":3,"periodSeconds":5,"successThreshold":1,"timeoutSeconds":1},"resources":{"limits":{"cpu":"200m","memory":"128Mi"},"requests":{"cpu":"100m","memory":"64Mi"}},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"serviceAccount":"default","serviceAccountName":"default","terminationGracePeriodSeconds":5}}}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if !workload.IsAttached() {
|
||||
t.Errorf("expected attached")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package k8sinterface
|
||||
|
||||
func PodSpec(kind string) []string {
|
||||
switch kind {
|
||||
case "Pod", "Namespace":
|
||||
return []string{"spec"}
|
||||
case "CronJob":
|
||||
return []string{"spec", "jobTemplate", "spec", "template", "spec"}
|
||||
default:
|
||||
return []string{"spec", "template", "spec"}
|
||||
}
|
||||
}
|
||||
|
||||
func PodMetadata(kind string) []string {
|
||||
switch kind {
|
||||
case "Pod", "Namespace", "Secret":
|
||||
return []string{"metadata"}
|
||||
case "CronJob":
|
||||
return []string{"spec", "jobTemplate", "spec", "template", "metadata"}
|
||||
default:
|
||||
return []string{"spec", "template", "metadata"}
|
||||
}
|
||||
}
|
||||
26
cautils/logger/helpers/datastructures.go
Normal file
26
cautils/logger/helpers/datastructures.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package helpers
|
||||
|
||||
type StringObj struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
|
||||
type ErrorObj struct {
|
||||
key string
|
||||
value error
|
||||
}
|
||||
|
||||
type IntObj struct {
|
||||
key string
|
||||
value int
|
||||
}
|
||||
|
||||
type InterfaceObj struct {
|
||||
key string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func Error(e error) *ErrorObj { return &ErrorObj{key: "error", value: e} }
|
||||
func Int(k string, v int) *IntObj { return &IntObj{key: k, value: v} }
|
||||
func String(k, v string) *StringObj { return &StringObj{key: k, value: v} }
|
||||
func Interface(k string, v interface{}) *InterfaceObj { return &InterfaceObj{key: k, value: v} }
|
||||
69
cautils/logger/helpers/level.go
Normal file
69
cautils/logger/helpers/level.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Level int8
|
||||
|
||||
const (
|
||||
UnknownLevel Level = iota - -1
|
||||
DebugLevel
|
||||
InfoLevel //default
|
||||
SuccessLevel
|
||||
WarningLevel
|
||||
ErrorLevel
|
||||
FatalLevel
|
||||
|
||||
_defaultLevel = InfoLevel
|
||||
_minLevel = DebugLevel
|
||||
_maxLevel = FatalLevel
|
||||
)
|
||||
|
||||
func ToLevel(level string) Level {
|
||||
switch strings.ToLower(level) {
|
||||
case "debug":
|
||||
return DebugLevel
|
||||
case "info":
|
||||
return InfoLevel
|
||||
case "success":
|
||||
return SuccessLevel
|
||||
case "warning", "warn":
|
||||
return WarningLevel
|
||||
case "error":
|
||||
return ErrorLevel
|
||||
case "fatal":
|
||||
return FatalLevel
|
||||
default:
|
||||
return UnknownLevel
|
||||
}
|
||||
}
|
||||
func (l Level) String() string {
|
||||
switch l {
|
||||
case DebugLevel:
|
||||
return "debug"
|
||||
case InfoLevel:
|
||||
return "info"
|
||||
case SuccessLevel:
|
||||
return "success"
|
||||
case WarningLevel:
|
||||
return "warning"
|
||||
case ErrorLevel:
|
||||
return "error"
|
||||
case FatalLevel:
|
||||
return "fatal"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (l Level) Skip(l2 Level) bool {
|
||||
return l < l2
|
||||
}
|
||||
|
||||
func SupportedLevels() []string {
|
||||
levels := []string{}
|
||||
for i := _minLevel; i <= _maxLevel; i++ {
|
||||
levels = append(levels, i.String())
|
||||
}
|
||||
return levels
|
||||
}
|
||||
62
cautils/logger/helpers/methods.go
Normal file
62
cautils/logger/helpers/methods.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package helpers
|
||||
|
||||
type IDetails interface {
|
||||
Key() string
|
||||
Value() interface{}
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// ============================== String ================================================
|
||||
// ======================================================================================
|
||||
|
||||
// Key
|
||||
func (s *StringObj) Key() string {
|
||||
return s.key
|
||||
}
|
||||
|
||||
// Value
|
||||
func (s *StringObj) Value() interface{} {
|
||||
return s.value
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// =============================== Error ================================================
|
||||
// ======================================================================================
|
||||
|
||||
// Key
|
||||
func (s *ErrorObj) Key() string {
|
||||
return s.key
|
||||
}
|
||||
|
||||
// Value
|
||||
func (s *ErrorObj) Value() interface{} {
|
||||
return s.value
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// ================================= Int ================================================
|
||||
// ======================================================================================
|
||||
|
||||
// Key
|
||||
func (s *IntObj) Key() string {
|
||||
return s.key
|
||||
}
|
||||
|
||||
// Value
|
||||
func (s *IntObj) Value() interface{} {
|
||||
return s.value
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// =========================== Interface ================================================
|
||||
// ======================================================================================
|
||||
|
||||
// Key
|
||||
func (s *InterfaceObj) Key() string {
|
||||
return s.key
|
||||
}
|
||||
|
||||
// Value
|
||||
func (s *InterfaceObj) Value() interface{} {
|
||||
return s.value
|
||||
}
|
||||
51
cautils/logger/methods.go
Normal file
51
cautils/logger/methods.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/cautils/logger/prettylogger"
|
||||
"github.com/armosec/kubescape/cautils/logger/zaplogger"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
type ILogger interface {
|
||||
Fatal(msg string, details ...helpers.IDetails) // print log and exit 1
|
||||
Error(msg string, details ...helpers.IDetails)
|
||||
Success(msg string, details ...helpers.IDetails)
|
||||
Warning(msg string, details ...helpers.IDetails)
|
||||
Info(msg string, details ...helpers.IDetails)
|
||||
Debug(msg string, details ...helpers.IDetails)
|
||||
|
||||
SetLevel(level string) error
|
||||
GetLevel() string
|
||||
|
||||
SetWriter(w *os.File)
|
||||
GetWriter() *os.File
|
||||
}
|
||||
|
||||
var l ILogger
|
||||
|
||||
func L() ILogger {
|
||||
if l == nil {
|
||||
InitializeLogger("")
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func InitializeLogger(loggerName string) {
|
||||
|
||||
switch strings.ToLower(loggerName) {
|
||||
case "zap":
|
||||
l = zaplogger.NewZapLogger()
|
||||
case "pretty":
|
||||
l = prettylogger.NewPrettyLogger()
|
||||
default:
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
l = prettylogger.NewPrettyLogger()
|
||||
} else {
|
||||
l = zaplogger.NewZapLogger()
|
||||
}
|
||||
}
|
||||
}
|
||||
31
cautils/logger/prettylogger/colors.go
Normal file
31
cautils/logger/prettylogger/colors.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package prettylogger
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
var prefixError = color.New(color.Bold, color.FgHiRed).FprintfFunc()
|
||||
var prefixWarning = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
|
||||
var prefixInfo = color.New(color.Bold, color.FgCyan).FprintfFunc()
|
||||
var prefixSuccess = color.New(color.Bold, color.FgHiGreen).FprintfFunc()
|
||||
var prefixDebug = color.New(color.Bold, color.FgWhite).FprintfFunc()
|
||||
var message = color.New().FprintfFunc()
|
||||
|
||||
func prefix(l helpers.Level) func(w io.Writer, format string, a ...interface{}) {
|
||||
switch l {
|
||||
case helpers.DebugLevel:
|
||||
return prefixDebug
|
||||
case helpers.InfoLevel:
|
||||
return prefixInfo
|
||||
case helpers.SuccessLevel:
|
||||
return prefixSuccess
|
||||
case helpers.WarningLevel:
|
||||
return prefixWarning
|
||||
case helpers.ErrorLevel, helpers.FatalLevel:
|
||||
return prefixError
|
||||
}
|
||||
return message
|
||||
}
|
||||
78
cautils/logger/prettylogger/logger.go
Normal file
78
cautils/logger/prettylogger/logger.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package prettylogger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
)
|
||||
|
||||
type PrettyLogger struct {
|
||||
writer *os.File
|
||||
level helpers.Level
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewPrettyLogger() *PrettyLogger {
|
||||
return &PrettyLogger{
|
||||
writer: os.Stderr, // default to stderr
|
||||
level: helpers.InfoLevel,
|
||||
mutex: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (pl *PrettyLogger) GetLevel() string { return pl.level.String() }
|
||||
func (pl *PrettyLogger) SetWriter(w *os.File) { pl.writer = w }
|
||||
func (pl *PrettyLogger) GetWriter() *os.File { return pl.writer }
|
||||
|
||||
func (pl *PrettyLogger) SetLevel(level string) error {
|
||||
pl.level = helpers.ToLevel(level)
|
||||
if pl.level == helpers.UnknownLevel {
|
||||
return fmt.Errorf("level '%s' unknown", level)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (pl *PrettyLogger) Fatal(msg string, details ...helpers.IDetails) {
|
||||
pl.print(helpers.FatalLevel, msg, details...)
|
||||
os.Exit(1)
|
||||
}
|
||||
func (pl *PrettyLogger) Error(msg string, details ...helpers.IDetails) {
|
||||
pl.print(helpers.ErrorLevel, msg, details...)
|
||||
}
|
||||
func (pl *PrettyLogger) Warning(msg string, details ...helpers.IDetails) {
|
||||
pl.print(helpers.WarningLevel, msg, details...)
|
||||
}
|
||||
func (pl *PrettyLogger) Info(msg string, details ...helpers.IDetails) {
|
||||
pl.print(helpers.InfoLevel, msg, details...)
|
||||
}
|
||||
func (pl *PrettyLogger) Debug(msg string, details ...helpers.IDetails) {
|
||||
pl.print(helpers.DebugLevel, msg, details...)
|
||||
}
|
||||
func (pl *PrettyLogger) Success(msg string, details ...helpers.IDetails) {
|
||||
pl.print(helpers.SuccessLevel, msg, details...)
|
||||
}
|
||||
|
||||
func (pl *PrettyLogger) print(level helpers.Level, msg string, details ...helpers.IDetails) {
|
||||
if !level.Skip(pl.level) {
|
||||
pl.mutex.Lock()
|
||||
prefix(level)(pl.writer, "[%s] ", level.String())
|
||||
if d := detailsToString(details); d != "" {
|
||||
msg = fmt.Sprintf("%s. %s", msg, d)
|
||||
}
|
||||
message(pl.writer, fmt.Sprintf("%s\n", msg))
|
||||
pl.mutex.Unlock()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func detailsToString(details []helpers.IDetails) string {
|
||||
s := ""
|
||||
for i := range details {
|
||||
s += fmt.Sprintf("%s: %s", details[i].Key(), details[i].Value())
|
||||
if i < len(details)-1 {
|
||||
s += "; "
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
78
cautils/logger/zaplogger/logger.go
Normal file
78
cautils/logger/zaplogger/logger.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package zaplogger
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type ZapLogger struct {
|
||||
zapL *zap.Logger
|
||||
cfg zap.Config
|
||||
}
|
||||
|
||||
func NewZapLogger() *ZapLogger {
|
||||
ec := zap.NewProductionEncoderConfig()
|
||||
ec.EncodeTime = zapcore.RFC3339TimeEncoder
|
||||
cfg := zap.NewProductionConfig()
|
||||
cfg.DisableCaller = true
|
||||
cfg.DisableStacktrace = true
|
||||
cfg.Encoding = "json"
|
||||
cfg.EncoderConfig = ec
|
||||
|
||||
zapLogger, err := cfg.Build()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &ZapLogger{
|
||||
zapL: zapLogger,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (zl *ZapLogger) GetLevel() string { return zl.cfg.Level.Level().String() }
|
||||
func (zl *ZapLogger) SetWriter(w *os.File) {}
|
||||
func (zl *ZapLogger) GetWriter() *os.File { return nil }
|
||||
func GetWriter() *os.File { return nil }
|
||||
|
||||
func (zl *ZapLogger) SetLevel(level string) error {
|
||||
l := zapcore.Level(1)
|
||||
err := l.Set(level)
|
||||
if err == nil {
|
||||
zl.cfg.Level.SetLevel(l)
|
||||
}
|
||||
return err
|
||||
}
|
||||
func (zl *ZapLogger) Fatal(msg string, details ...helpers.IDetails) {
|
||||
zl.zapL.Fatal(msg, detailsToZapFields(details)...)
|
||||
}
|
||||
|
||||
func (zl *ZapLogger) Error(msg string, details ...helpers.IDetails) {
|
||||
zl.zapL.Error(msg, detailsToZapFields(details)...)
|
||||
}
|
||||
|
||||
func (zl *ZapLogger) Warning(msg string, details ...helpers.IDetails) {
|
||||
zl.zapL.Warn(msg, detailsToZapFields(details)...)
|
||||
}
|
||||
|
||||
func (zl *ZapLogger) Success(msg string, details ...helpers.IDetails) {
|
||||
zl.zapL.Info(msg, detailsToZapFields(details)...)
|
||||
}
|
||||
|
||||
func (zl *ZapLogger) Info(msg string, details ...helpers.IDetails) {
|
||||
zl.zapL.Info(msg, detailsToZapFields(details)...)
|
||||
}
|
||||
|
||||
func (zl *ZapLogger) Debug(msg string, details ...helpers.IDetails) {
|
||||
zl.zapL.Debug(msg, detailsToZapFields(details)...)
|
||||
}
|
||||
|
||||
func detailsToZapFields(details []helpers.IDetails) []zapcore.Field {
|
||||
zapFields := []zapcore.Field{}
|
||||
for i := range details {
|
||||
zapFields = append(zapFields, zap.Any(details[i].Key(), details[i].Value()))
|
||||
}
|
||||
return zapFields
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package opapolicy
|
||||
|
||||
const (
|
||||
PostureRestAPIPathV1 = "/v1/posture"
|
||||
PostureRedisPrefix = "_postureReportv1"
|
||||
K8sPostureNotification = "/k8srestapi/v1/newPostureReport"
|
||||
)
|
||||
@@ -1,192 +0,0 @@
|
||||
package opapolicy
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
armotypes "github.com/armosec/kubescape/cautils/armotypes"
|
||||
)
|
||||
|
||||
type AlertScore float32
|
||||
type RuleLanguages string
|
||||
|
||||
const (
|
||||
RegoLanguage RuleLanguages = "Rego"
|
||||
RegoLanguage2 RuleLanguages = "rego"
|
||||
)
|
||||
|
||||
// RegoResponse the expected response of single run of rego policy
|
||||
type RuleResponse struct {
|
||||
AlertMessage string `json:"alertMessage"`
|
||||
PackageName string `json:"packagename"`
|
||||
AlertScore AlertScore `json:"alertScore"`
|
||||
// AlertObject AlertObject `json:"alertObject"`
|
||||
AlertObject AlertObject `json:"alertObject"` // TODO - replace interface to AlertObject
|
||||
Context []string `json:"context"` // TODO - Remove
|
||||
Rulename string `json:"rulename"` // TODO - Remove
|
||||
ExceptionName string `json:"exceptionName"`
|
||||
}
|
||||
|
||||
type AlertObject struct {
|
||||
K8SApiObjects []map[string]interface{} `json:"k8sApiObjects,omitempty"`
|
||||
ExternalObjects map[string]interface{} `json:"externalObjects,omitempty"`
|
||||
}
|
||||
|
||||
type FrameworkReport struct {
|
||||
Name string `json:"name"`
|
||||
ControlReports []ControlReport `json:"controlReports"`
|
||||
}
|
||||
type ControlReport struct {
|
||||
Name string `json:"name"`
|
||||
RuleReports []RuleReport `json:"ruleReports"`
|
||||
Remediation string `json:"remediation"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
type RuleReport struct {
|
||||
Name string `json:"name"`
|
||||
Remediation string `json:"remediation"`
|
||||
RuleStatus RuleStatus `json:"ruleStatus"`
|
||||
RuleResponses []RuleResponse `json:"ruleResponses"`
|
||||
ListInputResources []map[string]interface{} `json:"-"`
|
||||
ListInputKinds []string `json:"-"`
|
||||
}
|
||||
type RuleStatus struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// PostureReport
|
||||
type PostureReport struct {
|
||||
CustomerGUID string `json:"customerGUID"`
|
||||
ClusterName string `json:"clusterName"`
|
||||
ReportID string `json:"reportID"`
|
||||
JobID string `json:"jobID"`
|
||||
ReportGenerationTime time.Time `json:"generationTime"`
|
||||
FrameworkReports []FrameworkReport `json:"frameworks"`
|
||||
}
|
||||
|
||||
// RuleMatchObjects defines which objects this rule applied on
|
||||
type RuleMatchObjects struct {
|
||||
APIGroups []string `json:"apiGroups"` // apps
|
||||
APIVersions []string `json:"apiVersions"` // v1/ v1beta1 / *
|
||||
Resources []string `json:"resources"` // dep.., pods,
|
||||
}
|
||||
|
||||
// RuleMatchObjects defines which objects this rule applied on
|
||||
type RuleDependency struct {
|
||||
PackageName string `json:"packageName"` // package name
|
||||
}
|
||||
|
||||
// PolicyRule represents single rule, the fundamental executable block of policy
|
||||
type PolicyRule struct {
|
||||
armotypes.PortalBase `json:",inline"`
|
||||
CreationTime string `json:"creationTime"`
|
||||
Rule string `json:"rule"` // multiline string!
|
||||
RuleLanguage RuleLanguages `json:"ruleLanguage"`
|
||||
Match []RuleMatchObjects `json:"match"`
|
||||
RuleDependencies []RuleDependency `json:"ruleDependencies"`
|
||||
Description string `json:"description"`
|
||||
Remediation string `json:"remediation"`
|
||||
RuleQuery string `json:"ruleQuery"` // default "armo_builtins" - DEPRECATED
|
||||
}
|
||||
|
||||
// Control represents a collection of rules which are combined together to single purpose
|
||||
type Control struct {
|
||||
armotypes.PortalBase `json:",inline"`
|
||||
CreationTime string `json:"creationTime"`
|
||||
Description string `json:"description"`
|
||||
Remediation string `json:"remediation"`
|
||||
Rules []PolicyRule `json:"rules"`
|
||||
// for new list of rules in POST/UPADTE requests
|
||||
RulesIDs *[]string `json:"rulesIDs,omitempty"`
|
||||
}
|
||||
|
||||
type UpdatedControl struct {
|
||||
Control `json:",inline"`
|
||||
Rules []interface{} `json:"rules"`
|
||||
}
|
||||
|
||||
// Framework represents a collection of controls which are combined together to expose comprehensive behavior
|
||||
type Framework struct {
|
||||
armotypes.PortalBase `json:",inline"`
|
||||
CreationTime string `json:"creationTime"`
|
||||
Description string `json:"description"`
|
||||
Controls []Control `json:"controls"`
|
||||
// for new list of controls in POST/UPADTE requests
|
||||
ControlsIDs *[]string `json:"controlsIDs,omitempty"`
|
||||
}
|
||||
|
||||
type UpdatedFramework struct {
|
||||
Framework `json:",inline"`
|
||||
Controls []interface{} `json:"controls"`
|
||||
}
|
||||
|
||||
type NotificationPolicyType string
|
||||
type NotificationPolicyKind string
|
||||
|
||||
// Supported NotificationTypes
|
||||
const (
|
||||
TypeValidateRules NotificationPolicyType = "validateRules"
|
||||
TypeExecPostureScan NotificationPolicyType = "execPostureScan"
|
||||
TypeUpdateRules NotificationPolicyType = "updateRules"
|
||||
)
|
||||
|
||||
// Supported NotificationKinds
|
||||
const (
|
||||
KindFramework NotificationPolicyKind = "Framework"
|
||||
KindControl NotificationPolicyKind = "Control"
|
||||
KindRule NotificationPolicyKind = "Rule"
|
||||
)
|
||||
|
||||
type PolicyNotification struct {
|
||||
NotificationType NotificationPolicyType `json:"notificationType"`
|
||||
Rules []PolicyIdentifier `json:"rules"`
|
||||
ReportID string `json:"reportID"`
|
||||
JobID string `json:"jobID"`
|
||||
Designators armotypes.PortalDesignator `json:"designators"`
|
||||
}
|
||||
|
||||
type PolicyIdentifier struct {
|
||||
Kind NotificationPolicyKind `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ScanInfo struct {
|
||||
PolicyIdentifier PolicyIdentifier
|
||||
Format string
|
||||
Output string
|
||||
ExcludedNamespaces string
|
||||
InputPatterns []string
|
||||
Silent bool
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) Init() {
|
||||
// scanInfo.setSilentMode()
|
||||
scanInfo.setOutputFile()
|
||||
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setSilentMode() {
|
||||
if scanInfo.Format == "json" || scanInfo.Format == "junit" {
|
||||
scanInfo.Silent = true
|
||||
}
|
||||
if scanInfo.Output != "" {
|
||||
scanInfo.Silent = true
|
||||
}
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setOutputFile() {
|
||||
if scanInfo.Output == "" {
|
||||
return
|
||||
}
|
||||
if scanInfo.Format == "json" {
|
||||
if filepath.Ext(scanInfo.Output) != "json" {
|
||||
scanInfo.Output += ".json"
|
||||
}
|
||||
}
|
||||
if scanInfo.Format == "junit" {
|
||||
if filepath.Ext(scanInfo.Output) != "xml" {
|
||||
scanInfo.Output += ".xml"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
package opapolicy
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
armotypes "github.com/armosec/kubescape/cautils/armotypes"
|
||||
)
|
||||
|
||||
// Mock A
|
||||
var (
|
||||
AMockCustomerGUID = "5d817063-096f-4d91-b39b-8665240080af"
|
||||
AMockJobID = "36b6f9e1-3b63-4628-994d-cbe16f81e9c7"
|
||||
AMockReportID = "2c31e4da-c6fe-440d-9b8a-785b80c8576a"
|
||||
AMockClusterName = "clusterA"
|
||||
AMockFrameworkName = "testFrameworkA"
|
||||
AMockControlName = "testControlA"
|
||||
AMockRuleName = "testRuleA"
|
||||
AMockPortalBase = *armotypes.MockPortalBase(AMockCustomerGUID, "", nil)
|
||||
)
|
||||
|
||||
func MockRuleResponseA() *RuleResponse {
|
||||
return &RuleResponse{
|
||||
AlertMessage: "test alert message A",
|
||||
AlertScore: 0,
|
||||
Rulename: AMockRuleName,
|
||||
PackageName: "test.package.name.A",
|
||||
Context: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
func MockFrameworkReportA() *FrameworkReport {
|
||||
return &FrameworkReport{
|
||||
Name: AMockFrameworkName,
|
||||
ControlReports: []ControlReport{
|
||||
{
|
||||
Name: AMockControlName,
|
||||
RuleReports: []RuleReport{
|
||||
{
|
||||
Name: AMockRuleName,
|
||||
Remediation: "remove privilegedContainer: True flag from your pod spec",
|
||||
RuleResponses: []RuleResponse{
|
||||
*MockRuleResponseA(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func MockPostureReportA() *PostureReport {
|
||||
return &PostureReport{
|
||||
CustomerGUID: AMockCustomerGUID,
|
||||
ClusterName: AMockClusterName,
|
||||
ReportID: AMockReportID,
|
||||
JobID: AMockJobID,
|
||||
ReportGenerationTime: time.Now().UTC(),
|
||||
FrameworkReports: []FrameworkReport{*MockFrameworkReportA()},
|
||||
}
|
||||
}
|
||||
|
||||
func MockFrameworkA() *Framework {
|
||||
return &Framework{
|
||||
PortalBase: *armotypes.MockPortalBase("aaaaaaaa-096f-4d91-b39b-8665240080af", AMockFrameworkName, nil),
|
||||
CreationTime: "",
|
||||
Description: "mock framework descryption",
|
||||
Controls: []Control{
|
||||
{
|
||||
PortalBase: *armotypes.MockPortalBase("aaaaaaaa-aaaa-4d91-b39b-8665240080af", AMockControlName, nil),
|
||||
Rules: []PolicyRule{
|
||||
*MockRuleA(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func MockRuleUntrustedRegistries() *PolicyRule {
|
||||
return &PolicyRule{
|
||||
PortalBase: *armotypes.MockPortalBase("aaaaaaaa-aaaa-aaaa-b39b-8665240080af", AMockControlName, nil),
|
||||
Rule: `
|
||||
package armo_builtins
|
||||
# Check for images from blacklisted repos
|
||||
|
||||
untrusted_registries(z) = x {
|
||||
x := ["015253967648.dkr.ecr.eu-central-1.amazonaws.com/"]
|
||||
}
|
||||
|
||||
public_registries(z) = y{
|
||||
y := ["quay.io/kiali/","quay.io/datawire/","quay.io/keycloak/","quay.io/bitnami/"]
|
||||
}
|
||||
|
||||
untrustedImageRepo[msga] {
|
||||
pod := input[_]
|
||||
k := pod.kind
|
||||
k == "Pod"
|
||||
container := pod.spec.containers[_]
|
||||
image := container.image
|
||||
repo_prefix := untrusted_registries(image)[_]
|
||||
startswith(image, repo_prefix)
|
||||
selfLink := pod.metadata.selfLink
|
||||
containerName := container.name
|
||||
|
||||
msga := {
|
||||
"alertMessage": sprintf("image '%v' in container '%s' in [%s] comes from untrusted registry", [image, containerName, selfLink]),
|
||||
"alert": true,
|
||||
"prevent": false,
|
||||
"alertScore": 2,
|
||||
"alertObject": [{"pod":pod}]
|
||||
}
|
||||
}
|
||||
|
||||
untrustedImageRepo[msga] {
|
||||
pod := input[_]
|
||||
k := pod.kind
|
||||
k == "Pod"
|
||||
container := pod.spec.containers[_]
|
||||
image := container.image
|
||||
repo_prefix := public_registries(image)[_]
|
||||
startswith(pod, repo_prefix)
|
||||
selfLink := input.metadata.selfLink
|
||||
containerName := container.name
|
||||
|
||||
msga := {
|
||||
"alertMessage": sprintf("image '%v' in container '%s' in [%s] comes from public registry", [image, containerName, selfLink]),
|
||||
"alert": true,
|
||||
"prevent": false,
|
||||
"alertScore": 1,
|
||||
"alertObject": [{"pod":pod}]
|
||||
}
|
||||
}
|
||||
`,
|
||||
RuleLanguage: RegoLanguage,
|
||||
Match: []RuleMatchObjects{
|
||||
{
|
||||
APIVersions: []string{"v1"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"pods"},
|
||||
},
|
||||
},
|
||||
RuleDependencies: []RuleDependency{
|
||||
{
|
||||
PackageName: "kubernetes.api.client",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func MockRuleA() *PolicyRule {
|
||||
return &PolicyRule{
|
||||
PortalBase: *armotypes.MockPortalBase("aaaaaaaa-aaaa-aaaa-b39b-8665240080af", AMockControlName, nil),
|
||||
Rule: MockRegoPrivilegedPods(), //
|
||||
RuleLanguage: RegoLanguage,
|
||||
Match: []RuleMatchObjects{
|
||||
{
|
||||
APIVersions: []string{"v1"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"pods"},
|
||||
},
|
||||
},
|
||||
RuleDependencies: []RuleDependency{
|
||||
{
|
||||
PackageName: "kubernetes.api.client",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func MockRuleB() *PolicyRule {
|
||||
return &PolicyRule{
|
||||
PortalBase: *armotypes.MockPortalBase("bbbbbbbb-aaaa-aaaa-b39b-8665240080af", AMockControlName, nil),
|
||||
Rule: MockExternalFacingService(), //
|
||||
RuleLanguage: RegoLanguage,
|
||||
Match: []RuleMatchObjects{
|
||||
{
|
||||
APIVersions: []string{"v1"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"pods"},
|
||||
},
|
||||
},
|
||||
RuleDependencies: []RuleDependency{
|
||||
{
|
||||
PackageName: "kubernetes.api.client",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func MockPolicyNotificationA() *PolicyNotification {
|
||||
return &PolicyNotification{
|
||||
NotificationType: TypeExecPostureScan,
|
||||
ReportID: AMockReportID,
|
||||
JobID: AMockJobID,
|
||||
Designators: armotypes.PortalDesignator{},
|
||||
Rules: []PolicyIdentifier{
|
||||
{
|
||||
Kind: KindFramework,
|
||||
Name: AMockFrameworkName,
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func MockTemp() string {
|
||||
return `
|
||||
package armo_builtins
|
||||
import data.kubernetes.api.client as client
|
||||
deny[msga] {
|
||||
#object := input[_]
|
||||
object := client.query_all("pods")
|
||||
obj := object.body.items[_]
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": "found object",
|
||||
"alertScore": 3,
|
||||
"alertObject": {"object": obj},
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
func MockRegoPrivilegedPods() string {
|
||||
return `package armo_builtins
|
||||
|
||||
import data.kubernetes.api.client as client
|
||||
|
||||
# Deny mutating action unless user is in group owning the resource
|
||||
|
||||
#privileged pods
|
||||
deny[msga] {
|
||||
|
||||
pod := input[_]
|
||||
containers := pod.spec.containers[_]
|
||||
containers.securityContext.privileged == true
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("the following pods are defined as privileged: %v", [pod]),
|
||||
"alertScore": 3,
|
||||
"alertObject": pod,
|
||||
}
|
||||
}
|
||||
|
||||
#handles majority of workload resources
|
||||
deny[msga] {
|
||||
wl := input[_]
|
||||
spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"}
|
||||
spec_template_spec_patterns[wl.kind]
|
||||
containers := wl.spec.template.spec.containers[_]
|
||||
containers.securityContext.privileged == true
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("the following workloads are defined as privileged: %v", [wl]),
|
||||
"alertScore": 3,
|
||||
"alertObject": wl,
|
||||
}
|
||||
}
|
||||
|
||||
#handles cronjob
|
||||
deny[msga] {
|
||||
wl := input[_]
|
||||
wl.kind == "CronJob"
|
||||
containers := wl.spec.jobTemplate.spec.template.spec.containers[_]
|
||||
containers.securityContext.privileged == true
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("the following cronjobs are defined as privileged: %v", [wl]),
|
||||
"alertScore": 3,
|
||||
"alertObject": wl,
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
func MockExternalFacingService() string {
|
||||
return "\n\tpackage armo_builtins\n\n\timport data.kubernetes.api.client as client\n\timport data.cautils as cautils\n\ndeny[msga] {\n\n\twl := input[_]\n\tcluster_resource := client.query_all(\n\t\t\"services\"\n\t)\n\n\tlabels := wl.metadata.labels\n\tfiltered_labels := json.remove(labels, [\"pod-template-hash\"])\n \n#service := cluster_resource.body.items[i]\nservices := [svc | cluster_resource.body.items[i].metadata.namespace == wl.metadata.namespace; svc := cluster_resource.body.items[i]]\nservice := services[_]\nnp_or_lb := {\"NodePort\", \"LoadBalancer\"}\nnp_or_lb[service.spec.type]\ncautils.is_subobject(service.spec.selector,filtered_labels)\n\n msga := {\n\t\t\"alertMessage\": sprintf(\"%v pod %v expose external facing service: %v\",[wl.metadata.namespace, wl.metadata.name, service.metadata.name]),\n\t\t\"alertScore\": 2,\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertObject\": {\"srvc\":service}\n\t}\n}\n\t"
|
||||
}
|
||||
func GetRuntimePods() string {
|
||||
return `
|
||||
package armo_builtins
|
||||
|
||||
import data.kubernetes.api.client as client
|
||||
|
||||
|
||||
deny[msga] {
|
||||
|
||||
|
||||
cluster_resource := client.query_all(
|
||||
"pods"
|
||||
)
|
||||
|
||||
pod := cluster_resource.body.items[i]
|
||||
msga := {
|
||||
"alertMessage": "got something",
|
||||
"alertScore": 2,
|
||||
"packagename": "armo_builtins",
|
||||
"alertObject": {"pod": pod}
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package opapolicy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMockPolicyNotificationA(t *testing.T) {
|
||||
policy := MockPolicyNotificationA()
|
||||
bp, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
t.Logf("%s\n", string(bp))
|
||||
// t.Errorf("%s\n", string(bp))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMockFrameworkA(t *testing.T) {
|
||||
policy := MockFrameworkA()
|
||||
bp, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
t.Logf("%s\n", string(bp))
|
||||
// t.Errorf("%s\n", string(bp))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMockPostureReportA(t *testing.T) {
|
||||
policy := MockPostureReportA()
|
||||
bp, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
// t.Errorf("%s\n", string(bp))
|
||||
t.Logf("%s\n", string(bp))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package opapolicy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/open-policy-agent/opa/rego"
|
||||
)
|
||||
|
||||
func (pn *PolicyNotification) ToJSONBytesBuffer() (*bytes.Buffer, error) {
|
||||
res, err := json.Marshal(pn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewBuffer(res), err
|
||||
}
|
||||
|
||||
func (ruleReport *RuleReport) GetRuleStatus() (string, []RuleResponse, []RuleResponse) {
|
||||
if len(ruleReport.RuleResponses) == 0 {
|
||||
return "success", nil, nil
|
||||
}
|
||||
exceptions := make([]RuleResponse, 0)
|
||||
failed := make([]RuleResponse, 0)
|
||||
|
||||
for _, rule := range ruleReport.RuleResponses {
|
||||
if rule.ExceptionName != "" {
|
||||
failed = append(failed, rule)
|
||||
} else {
|
||||
exceptions = append(exceptions, rule)
|
||||
}
|
||||
}
|
||||
|
||||
status := "failed"
|
||||
if len(failed) == 0 && len(exceptions) > 0 {
|
||||
status = "warning"
|
||||
}
|
||||
return status, failed, exceptions
|
||||
}
|
||||
func ParseRegoResult(regoResult *rego.ResultSet) ([]RuleResponse, error) {
|
||||
var errs error
|
||||
ruleResponses := []RuleResponse{}
|
||||
for _, result := range *regoResult {
|
||||
for desicionIdx := range result.Expressions {
|
||||
if resMap, ok := result.Expressions[desicionIdx].Value.(map[string]interface{}); ok {
|
||||
for objName := range resMap {
|
||||
jsonBytes, err := json.Marshal(resMap[objName])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("in parseRegoResult, json.Marshal failed. name: %s, obj: %v, reason: %s", objName, resMap[objName], err)
|
||||
glog.Error(err)
|
||||
errs = fmt.Errorf("%s\n%s", errs, err)
|
||||
continue
|
||||
}
|
||||
desObj := make([]RuleResponse, 0)
|
||||
if err := json.Unmarshal(jsonBytes, &desObj); err != nil {
|
||||
err = fmt.Errorf("in parseRegoResult, json.Unmarshal failed. name: %s, obj: %v, reason: %s", objName, resMap[objName], err)
|
||||
glog.Error(err)
|
||||
errs = fmt.Errorf("%s\n%s", errs, err)
|
||||
continue
|
||||
}
|
||||
ruleResponses = append(ruleResponses, desObj...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ruleResponses, errs
|
||||
}
|
||||
|
||||
func (controlReport *ControlReport) GetNumberOfResources() int {
|
||||
sum := 0
|
||||
for i := range controlReport.RuleReports {
|
||||
if controlReport.RuleReports[i].ListInputResources == nil {
|
||||
continue
|
||||
}
|
||||
sum += len(controlReport.RuleReports[i].ListInputResources)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (controlReport *ControlReport) ListControlsInputKinds() []string {
|
||||
listControlsInputKinds := []string{}
|
||||
for i := range controlReport.RuleReports {
|
||||
listControlsInputKinds = append(listControlsInputKinds, controlReport.RuleReports[i].ListInputKinds...)
|
||||
}
|
||||
return listControlsInputKinds
|
||||
}
|
||||
func (controlReport *ControlReport) Passed() bool {
|
||||
for i := range controlReport.RuleReports {
|
||||
if len(controlReport.RuleReports[i].RuleResponses) > 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (controlReport *ControlReport) Failed() bool {
|
||||
return !controlReport.Passed()
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package opapolicy
|
||||
|
||||
import (
|
||||
"github.com/francoispqt/gojay"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
responsible on fast unmarshaling of various COMMON containerscan structures and substructures
|
||||
|
||||
*/
|
||||
// UnmarshalJSONObject - File inside a pkg
|
||||
func (r *PostureReport) UnmarshalJSONObject(dec *gojay.Decoder, key string) (err error) {
|
||||
|
||||
switch key {
|
||||
case "customerGUID":
|
||||
err = dec.String(&(r.CustomerGUID))
|
||||
|
||||
case "clusterName":
|
||||
err = dec.String(&(r.ClusterName))
|
||||
|
||||
case "reportID":
|
||||
err = dec.String(&(r.ReportID))
|
||||
case "jobID":
|
||||
err = dec.String(&(r.JobID))
|
||||
case "generationTime":
|
||||
err = dec.Time(&(r.ReportGenerationTime), time.RFC3339)
|
||||
r.ReportGenerationTime = r.ReportGenerationTime.Local()
|
||||
}
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
// func (files *PkgFiles) UnmarshalJSONArray(dec *gojay.Decoder) error {
|
||||
// lae := PackageFile{}
|
||||
// if err := dec.Object(&lae); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// *files = append(*files, lae)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (file *PostureReport) NKeys() int {
|
||||
return 0
|
||||
}
|
||||
//------------------------
|
||||
@@ -1,219 +0,0 @@
|
||||
package resources
|
||||
|
||||
var RegoCAUtils = `
|
||||
package cautils
|
||||
|
||||
list_contains(lista,element) {
|
||||
some i
|
||||
lista[i] == element
|
||||
}
|
||||
|
||||
# getPodName(metadata) = name {
|
||||
# name := metadata.generateName
|
||||
#}
|
||||
getPodName(metadata) = name {
|
||||
name := metadata.name
|
||||
}
|
||||
|
||||
#returns subobject ,sub1 is partial to parent, e.g parent = {a:a,b:b,c:c,d:d}
|
||||
# sub1 = {b:b,c:c} - result is {b:b,c:c}, if sub1={b:b,e:f} returns {b:b}
|
||||
object_intersection(parent,sub1) = r{
|
||||
|
||||
r := {k:p | p := sub1[k]
|
||||
parent[k]== p
|
||||
}
|
||||
}
|
||||
|
||||
#returns if parent contains sub(both are objects not sets!!)
|
||||
is_subobject(sub,parent) {
|
||||
object_intersection(sub,parent) == sub
|
||||
}
|
||||
`
|
||||
|
||||
var RegoDesignators = `
|
||||
package designators
|
||||
|
||||
import data.cautils
|
||||
#functions that related to designators
|
||||
|
||||
#allowed_namespace
|
||||
#@input@: receive as part of the input object "included_namespaces" list
|
||||
#@input@: item's namespace as "namespace"
|
||||
#returns true if namespace exists in that list
|
||||
included_namespaces(namespace){
|
||||
cautils.list_contains(["default"],namespace)
|
||||
}
|
||||
|
||||
#forbidden_namespaces
|
||||
#@input@: receive as part of the input object "forbidden_namespaces" list
|
||||
#@input@: item's namespace as "namespace"
|
||||
#returns true if namespace exists in that list
|
||||
excluded_namespaces(namespace){
|
||||
not cautils.list_contains(["excluded"],namespace)
|
||||
}
|
||||
|
||||
forbidden_wlids(wlid){
|
||||
input.forbidden_wlids[_] == wlid
|
||||
}
|
||||
|
||||
filter_k8s_object(obj) = filtered {
|
||||
#put
|
||||
filtered := obj
|
||||
#filtered := [ x | cautils.list_contains(["default"],obj[i].metadata.namespace) ; x := obj[i] ]
|
||||
# filtered := [ x | not cautils.list_contains([],filter1Set[i].metadata.namespace); x := filter1Set[i]]
|
||||
|
||||
}
|
||||
`
|
||||
var RegoKubernetesApiClient = `
|
||||
package kubernetes.api.client
|
||||
|
||||
# service account token
|
||||
token := data.k8sconfig.token
|
||||
|
||||
# Cluster host
|
||||
host := data.k8sconfig.host
|
||||
|
||||
# default certificate path
|
||||
# crt_file := "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||
crt_file := data.k8sconfig.crtfile
|
||||
|
||||
client_crt_file := data.k8sconfig.clientcrtfile
|
||||
client_key_file := data.k8sconfig.clientkeyfile
|
||||
|
||||
|
||||
# This information could be retrieved from the kubernetes API
|
||||
# too, but would essentially require a request per API group,
|
||||
# so for now use a lookup table for the most common resources.
|
||||
resource_group_mapping := {
|
||||
"services": "api/v1",
|
||||
"pods": "api/v1",
|
||||
"configmaps": "api/v1",
|
||||
"secrets": "api/v1",
|
||||
"persistentvolumeclaims": "api/v1",
|
||||
"daemonsets": "apis/apps/v1",
|
||||
"deployments": "apis/apps/v1",
|
||||
"statefulsets": "apis/apps/v1",
|
||||
"horizontalpodautoscalers": "api/autoscaling/v1",
|
||||
"jobs": "apis/batch/v1",
|
||||
"cronjobs": "apis/batch/v1beta1",
|
||||
"ingresses": "api/extensions/v1beta1",
|
||||
"replicasets": "apis/apps/v1",
|
||||
"networkpolicies": "apis/networking.k8s.io/v1",
|
||||
"clusterroles": "apis/rbac.authorization.k8s.io/v1",
|
||||
"clusterrolebindings": "apis/rbac.authorization.k8s.io/v1",
|
||||
"roles": "apis/rbac.authorization.k8s.io/v1",
|
||||
"rolebindings": "apis/rbac.authorization.k8s.io/v1",
|
||||
"serviceaccounts": "api/v1"
|
||||
}
|
||||
|
||||
# Query for given resource/name in provided namespace
|
||||
# Example: query_ns("deployments", "my-app", "default")
|
||||
query_name_ns(resource, name, namespace) = http.send({
|
||||
"url": sprintf("%v/%v/namespaces/%v/%v/%v", [
|
||||
host,
|
||||
resource_group_mapping[resource],
|
||||
namespace,
|
||||
resource,
|
||||
name,
|
||||
]),
|
||||
"method": "get",
|
||||
"headers": {"authorization": token},
|
||||
"tls_client_cert_file": client_crt_file,
|
||||
"tls_client_key_file": client_key_file,
|
||||
"tls_ca_cert_file": crt_file,
|
||||
"raise_error": true,
|
||||
})
|
||||
|
||||
# Query for given resource type using label selectors
|
||||
# https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api
|
||||
# Example: query_label_selector_ns("deployments", {"app": "opa-kubernetes-api-client"}, "default")
|
||||
query_label_selector_ns(resource, selector, namespace) = http.send({
|
||||
"url": sprintf("%v/%v/namespaces/%v/%v?labelSelector=%v", [
|
||||
host,
|
||||
resource_group_mapping[resource],
|
||||
namespace,
|
||||
resource,
|
||||
label_map_to_query_string(selector),
|
||||
]),
|
||||
"method": "get",
|
||||
"headers": {"authorization": token},
|
||||
"tls_client_cert_file": client_crt_file,
|
||||
"tls_client_key_file": client_key_file,
|
||||
"tls_ca_cert_file": crt_file,
|
||||
"raise_error": true,
|
||||
})
|
||||
|
||||
# x := field_transform_to_qry_param("spec.selector",input)
|
||||
# input = {"app": "acmefit", "service": "catalog-db"}
|
||||
# result: "spec.selector.app%3Dacmefit,spec.selector.service%3Dcatalog-db"
|
||||
|
||||
|
||||
query_field_selector_ns(resource, field, selector, namespace) = http.send({
|
||||
"url": sprintf("%v/%v/namespaces/%v/%v?fieldSelector=%v", [
|
||||
host,
|
||||
resource_group_mapping[resource],
|
||||
namespace,
|
||||
resource,
|
||||
field_transform_to_qry_param(field,selector),
|
||||
]),
|
||||
"method": "get",
|
||||
"headers": {"authorization": token},
|
||||
"tls_client_cert_file": client_crt_file,
|
||||
"tls_client_key_file": client_key_file,
|
||||
"tls_ca_cert_file": crt_file,
|
||||
"raise_error": true,
|
||||
|
||||
})
|
||||
|
||||
# # Query for all resources of type resource in all namespaces
|
||||
# # Example: query_all("deployments")
|
||||
# query_all(resource) = http.send({
|
||||
# "url": sprintf("https://%v:%v/%v/%v", [
|
||||
# ip,
|
||||
# port,
|
||||
# resource_group_mapping[resource],
|
||||
# resource,
|
||||
# ]),
|
||||
# "method": "get",
|
||||
# "headers": {"authorization": sprintf("Bearer %v", [token])},
|
||||
# "tls_client_cert_file": crt_file,
|
||||
# "raise_error": true,
|
||||
# })
|
||||
|
||||
# Query for all resources of type resource in all namespaces
|
||||
# Example: query_all("deployments")
|
||||
query_all(resource) = http.send({
|
||||
"url": sprintf("%v/%v/%v", [
|
||||
host,
|
||||
resource_group_mapping[resource],
|
||||
resource,
|
||||
]),
|
||||
"method": "get",
|
||||
"headers": {"authorization": token},
|
||||
"tls_client_cert_file": client_crt_file,
|
||||
"tls_client_key_file": client_key_file,
|
||||
"tls_ca_cert_file": crt_file,
|
||||
"raise_error": true,
|
||||
})
|
||||
|
||||
|
||||
|
||||
# Query for all resources of type resource in all namespaces - without authentication
|
||||
# Example: query_all("deployments")
|
||||
query_all_no_auth(resource) = http.send({
|
||||
"url": sprintf("%v/%v/namespaces/default/%v", [
|
||||
host,
|
||||
resource_group_mapping[resource],
|
||||
resource,
|
||||
]),
|
||||
"method": "get",
|
||||
"raise_error": true,
|
||||
"tls_insecure_skip_verify" : true,
|
||||
})
|
||||
|
||||
field_transform_to_qry_param(field,map) = finala {
|
||||
mid := {concat(".",[field,key]): val | val := map[key]}
|
||||
finala := label_map_to_query_string(mid)
|
||||
}
|
||||
label_map_to_query_string(map) = concat(",", [str | val := map[key]; str := concat("%3D", [key, val])])
|
||||
`
|
||||
@@ -1,20 +0,0 @@
|
||||
package armo_builtins
|
||||
|
||||
# import data.kubernetes.api.client as client
|
||||
import data.cautils as cautils
|
||||
|
||||
|
||||
# alert cronjobs
|
||||
|
||||
#handles cronjob
|
||||
deny[msga] {
|
||||
|
||||
wl := input[_]
|
||||
wl.kind == "CronJob"
|
||||
msga := {
|
||||
"alertMessage": sprintf("the following cronjobs are defined: %v", [wl]),
|
||||
"alertScore": 2,
|
||||
"packagename": "armo_builtins",
|
||||
"alertObject": wl
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package armo_builtins
|
||||
|
||||
import data.kubernetes.api.client as client
|
||||
|
||||
|
||||
# input: pod
|
||||
# apiversion: v1
|
||||
# does:
|
||||
# returns the external facing services of that pod
|
||||
#
|
||||
#
|
||||
deny[msga] {
|
||||
pod := input[_]
|
||||
podns := pod.metadata.namespace
|
||||
podname := getName(pod.metadata)
|
||||
# pod := client.query_name_ns("pods","frontend-86c5ffb485-kfp9d", "default")
|
||||
labels := pod.body.metadata.labels
|
||||
filtered_labels := json.remove(labels, ["pod-template-hash"])
|
||||
|
||||
cluster_resource := client.query_all(
|
||||
"services"
|
||||
)
|
||||
|
||||
|
||||
services := [svc | cluster_resource.body.items[i].metadata.namespace == podns; svc := cluster_resource.body.items[i]]
|
||||
service := services[_]
|
||||
np_or_lb := {"NodePort", "LoadBalancer"}
|
||||
np_or_lb[service.spec.type]
|
||||
service.spec.selector == filtered_labels
|
||||
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("pod %v/%v exposed services: %v\n", [podns,podname,service]),
|
||||
"alertScore": 7,
|
||||
"alertObject": {"service":service,"labels":filtered_labels, "podname":podname,"namespace":podns}
|
||||
}
|
||||
}
|
||||
|
||||
getName(metadata) = name {
|
||||
name := metadata.generateName
|
||||
}
|
||||
getName(metadata) = name {
|
||||
name := metadata.name
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package armo_builtins
|
||||
#import data.kubernetes.api.client as client
|
||||
import data.cautils as cautils
|
||||
|
||||
# input: pod
|
||||
# apiversion: v1
|
||||
# does:
|
||||
# returns hostPath volumes
|
||||
#
|
||||
#
|
||||
deny[msga] {
|
||||
pod := input[_]
|
||||
pod.kind == "Pod"
|
||||
volumes := pod.spec.volumes
|
||||
volume := volumes[_]
|
||||
# crsrcs.body.spec.containers[_].volumeMounts[_].name = volume.name
|
||||
volume.hostPath
|
||||
podname := cautils.getPodName(pod.metadata)
|
||||
obj := {"volume":volume,"podname": podname}
|
||||
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("pod: %v has {%v,%v} ashostPath volume \n\n\n", [podname, volume]),
|
||||
"alertScore": 7,
|
||||
"alertObject": [obj]
|
||||
}
|
||||
}
|
||||
|
||||
isRWMount(mount) {
|
||||
not mount.readOnly
|
||||
}
|
||||
isRWMount(mount) {
|
||||
mount.readOnly == false
|
||||
}
|
||||
|
||||
|
||||
#handles majority of workload resources
|
||||
deny[msga] {
|
||||
|
||||
wl := input[_]
|
||||
spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"}
|
||||
spec_template_spec_patterns[wl.kind]
|
||||
volumes := wl.spec.template.spec.volumes
|
||||
volume := volumes[_]
|
||||
volume.hostPath
|
||||
wlname := cautils.getPodName(wl.metadata)
|
||||
obj := {"volume":volume,"podname": wlname}
|
||||
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("%v: %v has {%v,%v} as hostPath volume\n\n\n", [wl.kind,wlname, volume]),
|
||||
"alertScore": 7,
|
||||
"alertObject": [obj]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package armo_builtins
|
||||
|
||||
#import data.kubernetes.api.client as client
|
||||
|
||||
|
||||
# Deny mutating action unless user is in group owning the resource
|
||||
|
||||
|
||||
#privileged pods
|
||||
deny[msga] {
|
||||
|
||||
|
||||
pod := input[_]
|
||||
containers := pod.spec.containers[_]
|
||||
containers.securityContext.privileged == true
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("the following pods are defined as privileged: %v", [pod]),
|
||||
"alertScore": 3,
|
||||
"alertObject": pod,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#handles majority of workload resources
|
||||
deny[msga] {
|
||||
|
||||
wl := input[_]
|
||||
spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"}
|
||||
spec_template_spec_patterns[wl.kind]
|
||||
containers := wl.spec.template.spec.containers[_]
|
||||
containers.securityContext.privileged == true
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("the following workloads are defined as privileged: %v", [wl]),
|
||||
"alertScore": 3,
|
||||
"alertObject": wl,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#handles cronjob
|
||||
deny[msga] {
|
||||
|
||||
wl := input[_]
|
||||
wl.kind == "CronJob"
|
||||
containers := wl.spec.jobTemplate.spec.template.spec.containers[_]
|
||||
containers.securityContext.privileged == true
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("the following cronjobs are defined as privileged: %v", [wl]),
|
||||
"alertScore": 3,
|
||||
"alertObject": wl,
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package armo_builtins
|
||||
import data.kubernetes.api.client as client
|
||||
import data.cautils as cautils
|
||||
|
||||
|
||||
# input: None
|
||||
# apiversion: v1
|
||||
# does:
|
||||
# returns roles+ related subjects in rolebinding
|
||||
|
||||
|
||||
deny[msga] {
|
||||
# rsrc := client.query_all("roles")
|
||||
# role := rsrc.body.items[_]
|
||||
role := input[_]
|
||||
role.kind == "Role"
|
||||
rule := role.rules[_]
|
||||
cautils.list_contains(rule.resources,"secrets")
|
||||
canViewSecrets(rule)
|
||||
rbsrc := client.query_all("rolebindings")
|
||||
rolebinding := rbsrc.body.items[_]
|
||||
rolebinding.roleRef.kind == "Role"
|
||||
rolebinding.roleRef.name == role.metadata.name
|
||||
|
||||
|
||||
msga := {
|
||||
"alertMessage": sprintf("the following users: %v , got read secret access roles", [rolebinding.subjects]),
|
||||
"alertScore": 9,
|
||||
"packagename": "armo_builtins",
|
||||
"alertObject": {"role":role,"users":rolebinding.subjects}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# input: None
|
||||
# apiversion: v1
|
||||
# does:
|
||||
# returns clusterroles+ related subjects in rolebinding
|
||||
|
||||
|
||||
deny[msga] {
|
||||
# rsrc := client.query_all("clusterroles")
|
||||
# role := rsrc.body.items[_]
|
||||
role := input[_]
|
||||
role.kind == "ClusterRole"
|
||||
rule := role.rules[_]
|
||||
cautils.list_contains(rule.resources,"secrets")
|
||||
canViewSecrets(rule)
|
||||
rbsrc := client.query_all("rolebindings")
|
||||
rolebinding := rbsrc.body.items[_]
|
||||
rolebinding.roleRef.kind == "ClusterRole"
|
||||
rolebinding.roleRef.name == role.metadata.name
|
||||
|
||||
|
||||
msga := {
|
||||
"alertMessage": sprintf("the following users: %v , got read secret access roles", [rolebinding.subjects]),
|
||||
"alertScore": 9,
|
||||
"packagename": "armo_builtins",
|
||||
"alertObject": {"clusterrole":role,"users":rolebinding.subjects}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# input: None
|
||||
# apiversion: v1
|
||||
# does:
|
||||
# returns clusterroles+ related subjects in clusterrolebinding
|
||||
#
|
||||
#
|
||||
deny[msga] {
|
||||
# rsrc := client.query_all("clusterroles")
|
||||
# role := rsrc.body.items[_]
|
||||
role := input[_]
|
||||
role.kind == "ClusterRole"
|
||||
rule := role.rules[_]
|
||||
cautils.list_contains(rule.resources,"secrets")
|
||||
canViewSecrets(rule)
|
||||
rbsrc := client.query_all("clusterrolebindings")
|
||||
rolebinding := rbsrc.body.items[_]
|
||||
rolebinding.roleRef.kind == "ClusterRole"
|
||||
rolebinding.roleRef.name == role.metadata.name
|
||||
|
||||
|
||||
msga := {
|
||||
"alertMessage": sprintf("the following users: %v , got read secret access roles", [rolebinding.subjects]),
|
||||
"alertScore": 9,
|
||||
"packagename": "armo_builtins",
|
||||
"alertObject": {"clusterrole":role,"users":rolebinding.subjects}
|
||||
}
|
||||
}
|
||||
|
||||
canViewSecrets(rule) {
|
||||
cautils.list_contains(rule.verbs,"get")
|
||||
}
|
||||
canViewSecrets(rule) {
|
||||
cautils.list_contains(rule.verbs,"watch")
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package armo_builtins
|
||||
#import data.kubernetes.api.client as client
|
||||
import data.cautils as cautils
|
||||
|
||||
# input: pod
|
||||
# apiversion: v1
|
||||
# does:
|
||||
# returns rw hostpath volumes of that pod
|
||||
#
|
||||
#
|
||||
deny[msga] {
|
||||
pod := input[_]
|
||||
pod.kind == "Pod"
|
||||
volumes := pod.spec.volumes
|
||||
volume := volumes[_]
|
||||
# crsrcs.body.spec.containers[_].volumeMounts[_].name = volume.name
|
||||
mount := pod.spec.containers[_].volumeMounts[_]
|
||||
mount.name == volume.name
|
||||
volume.hostPath
|
||||
isRWMount(mount)
|
||||
podname := cautils.getPodName(pod.metadata)
|
||||
obj := {"volume":volume,"mount":mount,"podname": podname}
|
||||
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("pod: %v has {%v,%v} as rw hostPath volume and volumemount pair\n\n\n", [podname, volume,mount]),
|
||||
"alertScore": 7,
|
||||
"alertObject": [obj],
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
isRWMount(mount) {
|
||||
not mount.readOnly
|
||||
}
|
||||
isRWMount(mount) {
|
||||
mount.readOnly == false
|
||||
}
|
||||
|
||||
|
||||
#handles majority of workload resources
|
||||
deny[msga] {
|
||||
|
||||
wl := input[_]
|
||||
spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"}
|
||||
spec_template_spec_patterns[wl.kind]
|
||||
volumes := wl.spec.template.spec.volumes
|
||||
volume := volumes[_]
|
||||
mount := wl.spec.template.spec.containers[_].volumeMounts[_]
|
||||
mount.name == volume.name
|
||||
volume.hostPath
|
||||
isRWMount(mount)
|
||||
wlname := cautils.getPodName(wl.metadata)
|
||||
obj := {"volume":volume,"mount":mount,"podname": wlname}
|
||||
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("%v: %v has {%v,%v} as rw hostPath volume and volumemount pair\n\n\n", [wl.kind,wlname, volume,mount]),
|
||||
"alertScore": 7,
|
||||
"alertObject": [obj],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package armo_builtins
|
||||
import data.kubernetes.api.client as client
|
||||
import data.cautils as cautils
|
||||
|
||||
# input: pod
|
||||
# apiversion: v1
|
||||
# does:
|
||||
# returns the external facing services of that pod
|
||||
#
|
||||
#
|
||||
deny[msga] {
|
||||
pod := input[_]
|
||||
podns := pod.metadata.namespace
|
||||
podname := cautils.getPodName(pod.metadata)
|
||||
# pod := client.query_name_ns("pods", "catalog-mongo-6f468d99b4-pn242", "default")
|
||||
labels := pod.body.metadata.labels
|
||||
filtered_labels := json.remove(labels, ["pod-template-hash"])
|
||||
|
||||
cluster_resource := client.query_all(
|
||||
"services"
|
||||
)
|
||||
|
||||
services := [svc | cluster_resource.body.items[i].metadata.namespace == podns; svc := cluster_resource.body.items[i]]
|
||||
service := services[_]
|
||||
service.spec.selector == filtered_labels
|
||||
|
||||
hasSSHPorts(service)
|
||||
|
||||
msga := {
|
||||
"alertMessage": sprintf("pod %v/%v exposed by SSH services: %v\n", [podns,podname,service]),
|
||||
"packagename": "armo_builtins",
|
||||
"alertScore": 7,
|
||||
"alertObject": [{"pod":pod,"service":{service}}]
|
||||
}
|
||||
}
|
||||
|
||||
hasSSHPorts(service) {
|
||||
port := service.spec.ports[_]
|
||||
port.port == 22
|
||||
}
|
||||
|
||||
|
||||
hasSSHPorts(service) {
|
||||
port := service.spec.ports[_]
|
||||
port.port == 2222
|
||||
}
|
||||
|
||||
hasSSHPorts(service) {
|
||||
port := service.spec.ports[_]
|
||||
port.targetPort == 22
|
||||
}
|
||||
|
||||
|
||||
hasSSHPorts(service) {
|
||||
port := service.spec.ports[_]
|
||||
port.targetPort == 2222
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"guid": "3b0467c9-488d-c244-99d0-90fbf600aaff",
|
||||
"name": "[Builtin] rule-deny-access-to-secrets",
|
||||
"creationTime": "2019-09-04T12:04:58.461455",
|
||||
"description": "determines which users can get/list/watch secrets",
|
||||
"attributes": {
|
||||
"m$K8sThreatMatrix": "Credential Access::List k8s Secrets"
|
||||
},
|
||||
"ruleDependencies": [
|
||||
{
|
||||
"packageName":"cautils"
|
||||
},
|
||||
{
|
||||
"packageName":"kubernetes.api.client"
|
||||
}
|
||||
],
|
||||
"remediation": "",
|
||||
"match": [
|
||||
{
|
||||
"resources": [
|
||||
"Role","ClusterRole"
|
||||
],
|
||||
"apiVersions": [
|
||||
"v1"
|
||||
],
|
||||
"apiGroups": [
|
||||
"rbac.authorization.k8s.io"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ruleLanguage": "Rego",
|
||||
"rule": "\npackage armo_builtins\nimport data.kubernetes.api.client as client\nimport data.cautils as cautils\n\n\n# input: None\n# apiversion: v1\n# does: \n#\treturns roles+ related subjects in rolebinding\n\n\ndeny[msga] {\n\t# rsrc := client.query_all(\"roles\")\n\t# role := rsrc.body.items[_]\n\trole := input[_]\n\trole.kind == \"Role\"\n\trule := role.rules[_]\n\tcautils.list_contains(rule.resources,\"secrets\")\n\tcanViewSecrets(rule)\n\trbsrc := client.query_all(\"rolebindings\")\n\trolebinding := rbsrc.body.items[_]\n\trolebinding.roleRef.kind == \"Role\"\n\trolebinding.roleRef.name == role.metadata.name\n\t\n \n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"the following users: %v , got read secret access roles\", [rolebinding.subjects]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 9,\n\t\t\"alertObject\": {\"role\":role,\"users\":rolebinding.subjects}\n\t\n\t}\n}\n\n\n\n# input: None\n# apiversion: v1\n# does: \n#\treturns clusterroles+ related subjects in rolebinding\n\n\ndeny[msga] {\n\t# rsrc := client.query_all(\"clusterroles\")\n\t# role := rsrc.body.items[_]\n\trole := input[_]\n\trole.kind == \"ClusterRole\"\n\trule := role.rules[_]\n\tcautils.list_contains(rule.resources,\"secrets\")\n\tcanViewSecrets(rule)\n\trbsrc := client.query_all(\"rolebindings\")\n\trolebinding := rbsrc.body.items[_]\n\trolebinding.roleRef.kind == \"ClusterRole\"\n\trolebinding.roleRef.name == role.metadata.name\n\t\n \n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"the following users: %v , got read secret access roles\", [rolebinding.subjects]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 9,\n\t\t\"alertObject\": {\"clusterrole\":role,\"users\":rolebinding.subjects}\n\t\n\t}\n}\n\n\n# input: None\n# apiversion: v1\n# does: \n#\treturns clusterroles+ related subjects in clusterrolebinding\n#\n#\ndeny[msga] {\n\t# rsrc := client.query_all(\"clusterroles\")\n\t# role := rsrc.body.items[_]\n\trole := input[_]\n\trole.kind == \"ClusterRole\"\n\trule := role.rules[_]\n\tcautils.list_contains(rule.resources,\"secrets\")\n\tcanViewSecrets(rule)\n\trbsrc := client.query_all(\"clusterrolebindings\")\n\trolebinding := rbsrc.body.items[_]\n\trolebinding.roleRef.kind == \"ClusterRole\"\n\trolebinding.roleRef.name == role.metadata.name\n\t\n \n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"the following users: %v , got read secret access roles\", [rolebinding.subjects]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 9,\n\t\t\"alertObject\": {\"clusterrole\":role,\"users\":rolebinding.subjects}\n\t\n\t}\n}\n\ncanViewSecrets(rule) {\n\tcautils.list_contains(rule.verbs,\"get\")\n}\ncanViewSecrets(rule) {\n\tcautils.list_contains(rule.verbs,\"watch\")\n}\n"
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"guid": "3b0467c9-488d-c244-99d0-90fbf600aaff",
|
||||
"name": "[Builtin] rule-can-ssh-to-pod",
|
||||
"creationTime": "2019-09-04T12:04:58.461455",
|
||||
"description": "denies pods with SSH ports opened(22/222)",
|
||||
"attributes": {
|
||||
"microsoftK8sThreatMatrix": "val1"
|
||||
},
|
||||
"ruleDependencies": [
|
||||
{
|
||||
"packageName":"cautils"
|
||||
},
|
||||
{
|
||||
"packageName":"kubernetes.api.client"
|
||||
}
|
||||
],
|
||||
"remediation": "create a network policy that protects SSH ports",
|
||||
"match": [
|
||||
{
|
||||
"resources": [
|
||||
"Pods"
|
||||
],
|
||||
"apiVersions": [
|
||||
"v1"
|
||||
],
|
||||
"apiGroups": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ruleLanguage": "Rego",
|
||||
"rule": "\npackage armo_builtins\nimport data.kubernetes.api.client as client\nimport data.cautils as cautils\n\n# input: pod\n# apiversion: v1\n# does: \n#\treturns the external facing services of that pod\n#\n#\ndeny[msga] {\n\tpod := input[_]\n\tpodns := pod.metadata.namespace\n\tpodname := cautils.getPodName(pod.metadata)\n\t# pod := client.query_name_ns(\"pods\", \"catalog-mongo-6f468d99b4-pn242\", \"default\")\n\tlabels := pod.body.metadata.labels\n\tfiltered_labels := json.remove(labels, [\"pod-template-hash\"])\n \n\t cluster_resource := client.query_all(\n\t \t\"services\"\n\t )\n\n\tservices := [svc | cluster_resource.body.items[i].metadata.namespace == podns; svc := cluster_resource.body.items[i]]\n\tservice := \tservices[_]\n\tservice.spec.selector == filtered_labels\n \n\thasSSHPorts(service)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"pod %v/%v exposed by SSH services: %v\n\", [podns,podname,service]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 7,\n\t\t\"alertObject\": [{\"pod\":pod,\"service\":{service}}]\n\t\n\t}\n}\n\nhasSSHPorts(service) {\n\tport := service.spec.ports[_]\n\tport.port == 22\n}\n\n\nhasSSHPorts(service) {\n\tport := service.spec.ports[_]\n\tport.port == 2222\n}\n\nhasSSHPorts(service) {\n\tport := service.spec.ports[_]\n\tport.targetPort == 22\n}\n\n\nhasSSHPorts(service) {\n\tport := service.spec.ports[_]\n\tport.targetPort == 2222\n}\n"
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"guid": "",
|
||||
"name": "[Builtin] rule-identify-blacklisted-image-registries",
|
||||
"creationTime": "",
|
||||
"description": "Identifying if pod container images are from unallowed registries",
|
||||
"attributes": {
|
||||
"m$K8sThreatMatrix": "Initial Access::Compromised images in registry"
|
||||
},
|
||||
"ruleDependencies": [
|
||||
{
|
||||
"packageName": "cautils"
|
||||
},
|
||||
{
|
||||
"packageName": "kubernetes.api.client"
|
||||
}
|
||||
],
|
||||
"remediation": "Use images from safe registry",
|
||||
"match": [
|
||||
{
|
||||
"resources": [
|
||||
"Pods"
|
||||
],
|
||||
"apiVersions": [
|
||||
"v1"
|
||||
],
|
||||
"apiGroups": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ruleLanguage": "Rego",
|
||||
"rule": "\npackage armo_builtins\n# Check for images from blacklisted repos\n\nuntrusted_registries(z) = x {\n\tx := [\"015253967648.dkr.ecr.eu-central-1.amazonaws.com/\"]\t\n}\n\npublic_registries(z) = y{\n\ty := [\"quay.io/kiali/\",\"quay.io/datawire/\",\"quay.io/keycloak/\",\"quay.io/bitnami/\"]\n}\n\nuntrustedImageRepo[msga] {\n\tpod := input[_]\n\tk := pod.kind\n\tk == \"Pod\"\n\tcontainer := pod.spec.containers[_]\n\timage := container.image\n repo_prefix := untrusted_registries(image)[_]\n\tstartswith(image, repo_prefix)\n\tselfLink := pod.metadata.selfLink\n\tcontainerName := container.name\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"image '%v' in container '%s' in [%s] comes from untrusted registry\", [image, containerName, selfLink]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 2,\n\t\t\"alertObject\": [{\"pod\":pod}]\n\t}\n}\n\nuntrustedImageRepo[msga] {\n pod := input[_]\n\tk := pod.kind\n\tk == \"Pod\"\n\tcontainer := pod.spec.containers[_]\n\timage := container.image\n repo_prefix := public_registries(image)[_]\n\tstartswith(pod, repo_prefix)\n\tselfLink := input.metadata.selfLink\n\tcontainerName := container.name\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"image '%v' in container '%s' in [%s] comes from public registry\", [image, containerName, selfLink]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 1,\n\t\t\"alertObject\": [{\"pod\":pod}]\n\t}\n}"
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"guid": "3b0467c9-488d-c244-99d0-90fbf600aaff",
|
||||
"name": "[Builtin] rule-pod-external-facing",
|
||||
"creationTime": "2019-09-04T12:04:58.461455",
|
||||
"description": "denies pods with external facing services, grabs related services",
|
||||
"attributes": {
|
||||
"microsoftK8sThreatMatrix": "val1"
|
||||
},
|
||||
"ruleDependencies": [
|
||||
{
|
||||
"packageName":"kubernetes.api.client"
|
||||
}
|
||||
],
|
||||
"remediation": "create a network policy that controls which protect your cluster from unwanted connections and the outside world",
|
||||
"match": [
|
||||
{
|
||||
"resources": [
|
||||
"Pods"
|
||||
],
|
||||
"apiVersions": [
|
||||
"v1"
|
||||
],
|
||||
"apiGroups": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ruleLanguage": "Rego",
|
||||
"rule": "\npackage armo_builtins\n\nimport data.kubernetes.api.client as client\n\n\n# input: pod\n# apiversion: v1\n# does: \n#\treturns the external facing services of that pod\n#\n#\ndeny[msga] {\n\tpod := input[_]\n\tpodns := pod.metadata.namespace\n\tpodname := getName(pod.metadata)\n\t# pod := client.query_name_ns(\"pods\",\"frontend-86c5ffb485-kfp9d\", \"default\")\n\tlabels := pod.body.metadata.labels\n\tfiltered_labels := json.remove(labels, [\"pod-template-hash\"])\n \n\t cluster_resource := client.query_all(\n\t \t\"services\"\n\t )\n\n\n\tservices := [svc | cluster_resource.body.items[i].metadata.namespace == podns; svc := cluster_resource.body.items[i]]\n\tservice := \tservices[_]\n\tnp_or_lb := {\"NodePort\", \"LoadBalancer\"}\n\tnp_or_lb[service.spec.type]\n\tservice.spec.selector == filtered_labels\n \n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"pod %v/%v exposed services: %v\n\", [podns,podname,service]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 7,\n\t\t\"alertObject\": {\"service\":service,\"labels\":filtered_labels, \"podname\":podname,\"namespace\":podns}\n\t\n\t}\n}\n\ngetName(metadata) = name {\n\tname := metadata.generateName\n}\ngetName(metadata) = name {\n\tname := metadata.name\n}\n"
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"guid": "3b0467c9-488d-c244-99d0-90fbf600aaff",
|
||||
"name": "[Builtin] alert-any-hostpath",
|
||||
"creationTime": "2019-09-04T12:04:58.461455",
|
||||
"description": "determines if any workload contains a hostPath volume",
|
||||
"attributes": {
|
||||
"m$K8sThreatMatrix": "Privilege Escalation::hostPath mount"
|
||||
},
|
||||
"ruleDependencies": [
|
||||
{
|
||||
"packageName":"cautils"
|
||||
}
|
||||
],
|
||||
"remediation": "consider if hostPath is really necessary - reading sensitive data like hostPath credentials might endanger cluster, if so consider encrypting the data",
|
||||
|
||||
"match": [
|
||||
{
|
||||
"resources": [
|
||||
"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job","Pod"
|
||||
],
|
||||
"apiVersions": [
|
||||
"v1"
|
||||
],
|
||||
"apiGroups": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ruleLanguage": "Rego",
|
||||
"rule": "\npackage armo_builtins\nimport data.kubernetes.api.client as client\nimport data.cautils as cautils\n\n# input: pod\n# apiversion: v1\n# does: \n#\treturns hostPath volumes\n#\n#\ndeny[msga] {\n pod := input[_]\n pod.kind == \"Pod\"\n volumes := pod.spec.volumes\n volume := volumes[_]\n # crsrcs.body.spec.containers[_].volumeMounts[_].name = volume.name\n volume.hostPath\n podname := cautils.getPodName(pod.metadata)\n obj := {\"volume\":volume,\"podname\": podname}\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"pod: %v has {%v,%v} ashostPath volume \n\n\n\", [podname, volume]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 7,\n\t\t\"alertObject\": [obj],\n\t\n\t}\n}\n\nisRWMount(mount) {\n not mount.readOnly\n}\nisRWMount(mount) {\n mount.readOnly == false\n}\n\n\n#handles majority of workload resources\ndeny[msga] {\n\n\twl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n volumes := wl.spec.template.spec.volumes\n volume := volumes[_]\n volume.hostPath\n wlname := cautils.getPodName(wl.metadata)\n obj := {\"volume\":volume,\"podname\": wlname}\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"%v: %v has {%v,%v} as hostPath volume\n\n\n\", [wl.kind,wlname, volume]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 7,\n\t\t\"alertObject\": [obj],\n\t\n\t}\n}\n\n\n"
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"guid": "82f19070-2826-4fe4-a079-f5f7e7a1b04d",
|
||||
"name": "[Builtin] instance-metadata-api-access",
|
||||
"attributes": {
|
||||
"m$K8sThreatMatrix": "Credential Access::Instance Metadata API"
|
||||
},
|
||||
"creationTime": "2021-04-25T10:48:48.861806",
|
||||
"rule": "package armo_builtins\n# Check for images from blacklisted repos\n\nmetadata_azure(z) = http.send({\n\t\"url\": \"http://169.254.169.254/metadata/instance?api-version=2020-09-01\",\n\t\"method\": \"get\",\n\t\"headers\": {\"Metadata\": \"true\"},\n\t\"raise_error\": true,\t\n})\n\nmetadata_gcp(z) = http.send({\n\t\"url\": \"http://169.254.169.254/computeMetadata/v1/?alt=json&recursive=true\",\n\t\"method\": \"get\",\n\t\"headers\": {\"Metadata-Flavor\": \"Google\"},\n\t\"raise_error\": true,\t\n})\n\nmetadata_aws(z) = metadata_object { \n\thostname := http.send({\n\t\"url\": \"http://169.254.169.254/latest/meta-data/local-hostname\",\n\t\"method\": \"get\",\n\t\"raise_error\": true,\t\n })\n\tmetadata_object := {\n\t\t\"raw_body\": hostname.raw_body,\n\t\t\"hostname\" : hostname.raw_body,\n\t\t\"status_code\" : hostname.status_code\n\t}\n}\n\nazure_metadata[msga] {\t\n\tmetadata_object := metadata_azure(\"aaa\")\n\tmetadata_object.status_code == 200\n\tnode_name := metadata_object.body.compute.name\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"Node '%s' has access to Instance Metadata Services of Azure.\", [node_name]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 1,\n\t\t\"alertObject\": [{\"nodeMetadata\":metadata_object.body}]\n\t}\n}\n\ngcp_metadata[msga] {\t\n\tmetadata_object := metadata_gcp(\"aaa\")\n\tmetadata_object.status_code == 200\n\tnode_name := metadata_object.body.instance.hostname\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"Node '%s' has access to Instance Metadata Services of GCP.\", [node_name]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 1,\n\t\t\"alertObject\": [{\"nodeMetadata\": metadata_object.raw_body}]\n\t}\n}\n\naws_metadata[msga] {\t\n\tmetadata_object := metadata_aws(\"aaa\")\n\tmetadata_object.status_code == 200\n\tnode_name := metadata_object.hostname\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"Node '%s' has access to Instance Metadata Services of AWS.\", [node_name]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 1,\n\t\t\"alertObject\": [{\"nodeMetadata\": metadata_object.raw_body}]\n\t}\n}",
|
||||
"ruleLanguage": "Rego",
|
||||
"match": [
|
||||
{
|
||||
"apiGroups": [
|
||||
"*"
|
||||
],
|
||||
"apiVersions": [
|
||||
"*"
|
||||
],
|
||||
"resources": [
|
||||
"nodes"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ruleDependencies": [],
|
||||
"description": "Checks if there is access from the nodes to cloud prividers instance metadata services",
|
||||
"remediation": "From https://attack.mitre.org/techniques/T1552/005/ :Option A: Disable or Remove Feature or Program, Option B: Filter Network Traffic",
|
||||
"ruleQuery": ""
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"guid": "[Builtin] 3b0467c9-488d-c244-99d0-90fbf600aaff",
|
||||
"name": "rule-deny-cronjobs",
|
||||
"creationTime": "2019-09-04T12:04:58.461455",
|
||||
"description": "determines if it's cronjob",
|
||||
"attributes": {
|
||||
"m$K8sThreatMatrix": "Persistence::Cronjob"
|
||||
},
|
||||
"ruleDependencies": [
|
||||
{
|
||||
"packageName":"cautils"
|
||||
}
|
||||
],
|
||||
"remediation": "",
|
||||
"match": [
|
||||
{
|
||||
"resources": [
|
||||
"CronJob"
|
||||
],
|
||||
"apiVersions": [
|
||||
"v1beta1"
|
||||
],
|
||||
"apiGroups": [
|
||||
"batch"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ruleLanguage": "Rego",
|
||||
"rule": "\npackage armo_builtins\n\n# import data.kubernetes.api.client as client\nimport data.cautils as cautils\n\n\n# alert cronjobs\n\n#handles cronjob\ndeny[msga] {\n\n\twl := input[_]\n\twl.kind == \"CronJob\"\n msga := {\n\t\t\"alertMessage\": sprintf(\"the following cronjobs are defined: %v\", [wl]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 2,\n\t\t\"alertObject\": wl\n\t\n\t}\n}\n"
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"guid": "",
|
||||
"name": "[Builtin] rule-privilege-escalation",
|
||||
"creationTime": "2019-09-04T12:04:58.461455",
|
||||
"description": "determines if pods/deployments defined as privileged true",
|
||||
"attributes": {
|
||||
"mitre": "Privilege Escalation",
|
||||
"mitreCode": "TA0004",
|
||||
"m$K8sThreatMatrix": "Privilege Escalation::privileged container"
|
||||
},
|
||||
"ruleDependencies": [
|
||||
],
|
||||
"remediation": "avoid defining pods as privilleged",
|
||||
"match": [
|
||||
{
|
||||
"resources": [
|
||||
"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job","Pod","CronJob"
|
||||
],
|
||||
"apiVersions": [
|
||||
"v1"
|
||||
],
|
||||
"apiGroups": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ruleLanguage": "Rego",
|
||||
"rule": "\npackage armo_builtins\n\nimport data.kubernetes.api.client as client\nimport data.designators as scope\nimport data.cautils as cautils\n\n\n# Deny mutating action unless user is in group owning the resource\n\n\n#privileged pods\ndeny[msga] {\n\n \n\tpod := input[_]\n\tcontainers := pod.spec.containers[_]\n\tcontainers.securityContext.privileged == true\n msga := {\n\t\t\"alertMessage\": sprintf(\"the following pods are defined as privileged: %v\", [pod]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 3,\n\t\t\"alertObject\": pod,\n\t\n\t}\n}\n\n\n#handles majority of workload resources\ndeny[msga] {\n\n\twl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n\tcontainers := wl.spec.template.spec.containers[_]\n\tcontainers.securityContext.privileged == true\n msga := {\n\t\t\"alertMessage\": sprintf(\"the following workloads are defined as privileged: %v\", [wl]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 3,\n\t\t\"alertObject\": wl,\n\t\n\t}\n}\n\n\n\n#handles cronjob\ndeny[msga] {\n\n\twl := input[_]\n\twl.kind == \"CronJob\"\n\tcontainers := wl.spec.jobTemplate.spec.template.spec.containers[_]\n\tcontainers.securityContext.privileged == true\n msga := {\n\t\t\"alertMessage\": sprintf(\"the following cronjobs are defined as privileged: %v\", [wl]),\n\t\t\"alert\": true,\n\t\t\"prevent\": false,\n\t\t\"alertScore\": 3,\n\t\t\"alertObject\": wl,\n\t\n\t}\n}\n\n"
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"guid": "3b0467c9-488d-c244-99d0-90fbf600aaff",
|
||||
"name": "[Builtin] alert-rw-hostpath",
|
||||
"creationTime": "2019-09-04T12:04:58.461455",
|
||||
"description": "determines if any workload contains a hostPath volume with rw permissions",
|
||||
"attributes": {
|
||||
"m$K8sThreatMatrix": "Persistance::Writable hostPath mount"
|
||||
},
|
||||
"ruleDependencies": [
|
||||
{
|
||||
"packageName":"cautils"
|
||||
}
|
||||
],
|
||||
"remediation": "consider if hostPath is really necessary- sensitive data like hostPath credentials might endanger cluster, if so consider encrypting the data",
|
||||
|
||||
"match": [
|
||||
{
|
||||
"resources": [
|
||||
"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job","Pod"
|
||||
],
|
||||
"apiVersions": [
|
||||
"v1"
|
||||
],
|
||||
"apiGroups": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ruleLanguage": "Rego",
|
||||
"rule": "\"\\npackage armo_builtins\\nimport data.kubernetes.api.client as client\\nimport data.cautils as cautils\\n\\n# input: pod\\n# apiversion: v1\\n# does: \\n#\\treturns hostPath volumes\\n#\\n#\\ndeny[msga] {\\n pod := input[_]\\n pod.kind == \\\"Pod\\\"\\n volumes := pod.spec.volumes\\n volume := volumes[_]\\n # crsrcs.body.spec.containers[_].volumeMounts[_].name = volume.name\\n volume.hostPath\\n podname := cautils.getPodName(pod.metadata)\\n obj := {\\\"volume\\\":volume,\\\"podname\\\": podname}\\n\\n\\tmsga := {\\n\\t\\t\\\"alertMessage\\\": sprintf(\\\"pod: %v has {%v,%v} ashostPath volume \\n\\n\\n\\\", [podname, volume]),\\n\\t\\t\\\"alert\\\": true,\\n\\t\\t\\\"prevent\\\": false,\\n\\t\\t\\\"alertScore\\\": 7,\\n\\t\\t\\\"alertObject\\\": [obj],\\n\\t\\n\\t}\\n}\\n\\nisRWMount(mount) {\\n not mount.readOnly\\n}\\nisRWMount(mount) {\\n mount.readOnly == false\\n}\\n\\n\\n#handles majority of workload resources\\ndeny[msga] {\\n\\n\\twl := input[_]\\n\\tspec_template_spec_patterns := {\\\"Deployment\\\",\\\"ReplicaSet\\\",\\\"DaemonSet\\\",\\\"StatefulSet\\\",\\\"Job\\\"}\\n\\tspec_template_spec_patterns[wl.kind]\\n volumes := wl.spec.template.spec.volumes\\n volume := volumes[_]\\n volume.hostPath\\n wlname := cautils.getPodName(wl.metadata)\\n obj := {\\\"volume\\\":volume,\\\"podname\\\": wlname}\\n\\n\\tmsga := {\\n\\t\\t\\\"alertMessage\\\": sprintf(\\\"%v: %v has {%v,%v} as hostPath volume\\n\\n\\n\\\", [wl.kind,wlname, volume]),\\n\\t\\t\\\"alert\\\": true,\\n\\t\\t\\\"prevent\\\": false,\\n\\t\\t\\\"alertScore\\\": 7,\\n\\t\\t\\\"alertObject\\\": [obj],\\n\\t\\n\\t}\\n}\\n\\n\\n\""
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/k8sinterface"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/open-policy-agent/opa/storage"
|
||||
"github.com/open-policy-agent/opa/storage/inmem"
|
||||
"github.com/open-policy-agent/opa/util"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
var (
|
||||
RegoDependenciesPath = "/resources/rego/dependencies"
|
||||
)
|
||||
|
||||
type RegoDependenciesData struct {
|
||||
K8sConfig RegoK8sConfig `json:"k8sconfig"`
|
||||
}
|
||||
|
||||
type RegoK8sConfig struct {
|
||||
Token string `json:"token"`
|
||||
IP string `json:"ip"`
|
||||
Host string `json:"host"`
|
||||
Port string `json:"port"`
|
||||
CrtFile string `json:"crtfile"`
|
||||
ClientCrtFile string `json:"clientcrtfile"`
|
||||
ClientKeyFile string `json:"clientkeyfile"`
|
||||
// ClientKeyFile string `json:"crtfile"`
|
||||
}
|
||||
|
||||
func NewRegoDependenciesDataMock() *RegoDependenciesData {
|
||||
return NewRegoDependenciesData(k8sinterface.GetK8sConfig())
|
||||
}
|
||||
|
||||
func NewRegoDependenciesData(k8sConfig *rest.Config) *RegoDependenciesData {
|
||||
|
||||
regoDependenciesData := RegoDependenciesData{
|
||||
K8sConfig: *NewRegoK8sConfig(k8sConfig),
|
||||
}
|
||||
return ®oDependenciesData
|
||||
}
|
||||
func NewRegoK8sConfig(k8sConfig *rest.Config) *RegoK8sConfig {
|
||||
|
||||
host := k8sConfig.Host
|
||||
if host == "" {
|
||||
ip := os.Getenv("KUBERNETES_SERVICE_HOST")
|
||||
port := os.Getenv("KUBERNETES_SERVICE_PORT")
|
||||
host = fmt.Sprintf("https://%s:%s", ip, port)
|
||||
}
|
||||
|
||||
token := ""
|
||||
if k8sConfig.BearerToken != "" {
|
||||
token = fmt.Sprintf("Bearer %s", k8sConfig.BearerToken)
|
||||
}
|
||||
|
||||
// crtFile := os.Getenv("KUBERNETES_CRT_PATH")
|
||||
// if crtFile == "" {
|
||||
// crtFile = k8sConfig.CAFile
|
||||
// // crtFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||
// }
|
||||
|
||||
// glog.Infof("===========================================================================")
|
||||
// glog.Infof(fmt.Sprintf("%v", k8sConfig.String()))
|
||||
// glog.Infof("===========================================================================")
|
||||
|
||||
regoK8sConfig := RegoK8sConfig{
|
||||
Token: token,
|
||||
Host: k8sConfig.Host,
|
||||
CrtFile: k8sConfig.CAFile,
|
||||
ClientCrtFile: k8sConfig.CertFile,
|
||||
ClientKeyFile: k8sConfig.KeyFile,
|
||||
}
|
||||
return ®oK8sConfig
|
||||
}
|
||||
func (data *RegoDependenciesData) TOStorage() (storage.Store, error) {
|
||||
var jsonObj map[string]interface{}
|
||||
bytesData, err := json.Marshal(*data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// glog.Infof("RegoDependenciesData: %s", bytesData)
|
||||
if err := util.UnmarshalJSON(bytesData, &jsonObj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return inmem.NewFromObject(jsonObj), nil
|
||||
}
|
||||
|
||||
// LoadRegoDependenciesFromDir loads the policies list from *.rego file in given directory
|
||||
func LoadRegoFiles(dir string) map[string]string {
|
||||
|
||||
modules := make(map[string]string)
|
||||
|
||||
// Compile the module. The keys are used as identifiers in error messages.
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err == nil && strings.HasSuffix(path, ".rego") && !info.IsDir() {
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
glog.Errorf("LoadRegoFiles, Failed to load: %s: %v", path, err)
|
||||
} else {
|
||||
modules[strings.Trim(filepath.Base(path), ".rego")] = string(content)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return modules
|
||||
}
|
||||
|
||||
// LoadRegoModules loads the policies from variables
|
||||
func LoadRegoModules() map[string]string {
|
||||
|
||||
modules := make(map[string]string)
|
||||
modules["cautils"] = RegoCAUtils
|
||||
modules["designators"] = RegoDesignators
|
||||
modules["kubernetes.api.client"] = RegoKubernetesApiClient
|
||||
|
||||
return modules
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadRegoDependenciesFromDir(t *testing.T) {
|
||||
dir, _ := os.Getwd()
|
||||
t.Errorf("%s", filepath.Join(dir, "rego/dependencies"))
|
||||
return
|
||||
// modules := LoadRegoDependenciesFromDir("")
|
||||
// if len(modules) == 0 {
|
||||
// t.Errorf("modules len == 0")
|
||||
// }
|
||||
}
|
||||
125
cautils/rbac.go
Normal file
125
cautils/rbac.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/rbac-utils/rbacscanner"
|
||||
"github.com/armosec/rbac-utils/rbacutils"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type RBACObjects struct {
|
||||
scanner *rbacscanner.RbacScannerFromK8sAPI
|
||||
}
|
||||
|
||||
func NewRBACObjects(scanner *rbacscanner.RbacScannerFromK8sAPI) *RBACObjects {
|
||||
return &RBACObjects{scanner: scanner}
|
||||
}
|
||||
|
||||
func (rbacObjects *RBACObjects) SetResourcesReport() (*reporthandling.PostureReport, error) {
|
||||
return &reporthandling.PostureReport{
|
||||
ReportID: uuid.NewString(),
|
||||
ReportGenerationTime: time.Now().UTC(),
|
||||
CustomerGUID: rbacObjects.scanner.CustomerGUID,
|
||||
ClusterName: rbacObjects.scanner.ClusterName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rbacObjects *RBACObjects) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
|
||||
resources, err := rbacObjects.scanner.ListResources()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allresources, err := rbacObjects.rbacObjectsToResources(resources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return allresources, nil
|
||||
}
|
||||
|
||||
func (rbacObjects *RBACObjects) rbacObjectsToResources(resources *rbacutils.RbacObjects) (map[string]workloadinterface.IMetadata, error) {
|
||||
allresources := map[string]workloadinterface.IMetadata{}
|
||||
|
||||
/*
|
||||
************************************************************************************************************************
|
||||
This code is adding a non valid ID ->
|
||||
(github.com/armosec/rbac-utils v0.0.11): "//SA2WLIDmap/SA2WLIDmap"
|
||||
(github.com/armosec/rbac-utils v0.0.12): "armo.rbac.com/v0beta1//SAID2WLIDmap/SAID2WLIDmap"
|
||||
|
||||
Should be investigated
|
||||
************************************************************************************************************************
|
||||
*/
|
||||
|
||||
// wrap rbac aggregated objects in IMetadata and add to allresources
|
||||
// TODO - DEPRECATE SA2WLIDmap
|
||||
SA2WLIDmapIMeta, err := rbacutils.SA2WLIDmapIMetadataWrapper(resources.SA2WLIDmap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allresources[SA2WLIDmapIMeta.GetID()] = SA2WLIDmapIMeta
|
||||
|
||||
SAID2WLIDmapIMeta, err := rbacutils.SAID2WLIDmapIMetadataWrapper(resources.SAID2WLIDmap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allresources[SAID2WLIDmapIMeta.GetID()] = SAID2WLIDmapIMeta
|
||||
|
||||
// convert rbac k8s resources to IMetadata and add to allresources
|
||||
for _, cr := range resources.ClusterRoles.Items {
|
||||
crmap, err := convertToMap(cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1" // TODO - is the the correct apiVersion?
|
||||
crIMeta := workloadinterface.NewWorkloadObj(crmap)
|
||||
crIMeta.SetKind("ClusterRole")
|
||||
allresources[crIMeta.GetID()] = crIMeta
|
||||
}
|
||||
for _, cr := range resources.Roles.Items {
|
||||
crmap, err := convertToMap(cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1" // TODO - is the the correct apiVersion?
|
||||
crIMeta := workloadinterface.NewWorkloadObj(crmap)
|
||||
crIMeta.SetKind("Role")
|
||||
allresources[crIMeta.GetID()] = crIMeta
|
||||
}
|
||||
for _, cr := range resources.ClusterRoleBindings.Items {
|
||||
crmap, err := convertToMap(cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1" // TODO - is the the correct apiVersion?
|
||||
crIMeta := workloadinterface.NewWorkloadObj(crmap)
|
||||
crIMeta.SetKind("ClusterRoleBinding")
|
||||
allresources[crIMeta.GetID()] = crIMeta
|
||||
}
|
||||
for _, cr := range resources.RoleBindings.Items {
|
||||
crmap, err := convertToMap(cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1" // TODO - is the the correct apiVersion?
|
||||
crIMeta := workloadinterface.NewWorkloadObj(crmap)
|
||||
crIMeta.SetKind("RoleBinding")
|
||||
allresources[crIMeta.GetID()] = crIMeta
|
||||
}
|
||||
return allresources, nil
|
||||
}
|
||||
|
||||
func convertToMap(obj interface{}) (map[string]interface{}, error) {
|
||||
var inInterface map[string]interface{}
|
||||
inrec, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(inrec, &inInterface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return inInterface, nil
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package cautils
|
||||
|
||||
const (
|
||||
ComponentIdentifier = "Posture"
|
||||
)
|
||||
135
cautils/reportv2tov1.go
Normal file
135
cautils/reportv2tov1.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
helpersv1 "github.com/armosec/opa-utils/reporthandling/helpers/v1"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
)
|
||||
|
||||
func ReportV2ToV1(opaSessionObj *OPASessionObj) {
|
||||
if len(opaSessionObj.PostureReport.FrameworkReports) > 0 {
|
||||
return // report already converted
|
||||
}
|
||||
|
||||
opaSessionObj.PostureReport.ClusterCloudProvider = opaSessionObj.Report.ClusterCloudProvider
|
||||
|
||||
frameworks := []reporthandling.FrameworkReport{}
|
||||
|
||||
if len(opaSessionObj.Report.SummaryDetails.Frameworks) > 0 {
|
||||
for _, fwv2 := range opaSessionObj.Report.SummaryDetails.Frameworks {
|
||||
fwv1 := reporthandling.FrameworkReport{}
|
||||
fwv1.Name = fwv2.GetName()
|
||||
fwv1.Score = fwv2.GetScore()
|
||||
fwv1.ControlReports = append(fwv1.ControlReports, controlReportV2ToV1(opaSessionObj, fwv2.GetName(), fwv2.Controls)...)
|
||||
frameworks = append(frameworks, fwv1)
|
||||
|
||||
}
|
||||
} else {
|
||||
fwv1 := reporthandling.FrameworkReport{}
|
||||
fwv1.Name = ""
|
||||
|
||||
fwv1.ControlReports = append(fwv1.ControlReports, controlReportV2ToV1(opaSessionObj, "", opaSessionObj.Report.SummaryDetails.Controls)...)
|
||||
frameworks = append(frameworks, fwv1)
|
||||
fwv1.Score = opaSessionObj.Report.SummaryDetails.Score
|
||||
}
|
||||
|
||||
// // remove unused data
|
||||
// opaSessionObj.Report = nil
|
||||
// opaSessionObj.ResourcesResult = nil
|
||||
|
||||
// setup counters and score
|
||||
for f := range frameworks {
|
||||
// // set exceptions
|
||||
// exceptions.SetFrameworkExceptions(frameworks, opap.Exceptions, cautils.ClusterName)
|
||||
|
||||
// set counters
|
||||
reporthandling.SetUniqueResourcesCounter(&frameworks[f])
|
||||
|
||||
// set default score
|
||||
// reporthandling.SetDefaultScore(&frameworks[f])
|
||||
}
|
||||
|
||||
// // update score
|
||||
// scoreutil := score.NewScore(opaSessionObj.AllResources)
|
||||
// scoreutil.Calculate(frameworks)
|
||||
|
||||
opaSessionObj.PostureReport.FrameworkReports = frameworks
|
||||
}
|
||||
|
||||
func controlReportV2ToV1(opaSessionObj *OPASessionObj, frameworkName string, controls map[string]reportsummary.ControlSummary) []reporthandling.ControlReport {
|
||||
controlRepors := []reporthandling.ControlReport{}
|
||||
for controlID, crv2 := range controls {
|
||||
crv1 := reporthandling.ControlReport{}
|
||||
crv1.ControlID = controlID
|
||||
crv1.BaseScore = crv2.ScoreFactor
|
||||
crv1.Name = crv2.GetName()
|
||||
crv1.Score = crv2.GetScore()
|
||||
crv1.Control_ID = controlID
|
||||
// crv1.Attributes = crv2.
|
||||
|
||||
// TODO - add fields
|
||||
crv1.Description = crv2.Description
|
||||
crv1.Remediation = crv2.Remediation
|
||||
|
||||
rulesv1 := map[string]reporthandling.RuleReport{}
|
||||
|
||||
for _, resourceID := range crv2.ListResourcesIDs().All() {
|
||||
if result, ok := opaSessionObj.ResourcesResult[resourceID]; ok {
|
||||
for _, rulev2 := range result.ListRulesOfControl(crv2.GetID(), "") {
|
||||
|
||||
if _, ok := rulesv1[rulev2.GetName()]; !ok {
|
||||
rulesv1[rulev2.GetName()] = reporthandling.RuleReport{
|
||||
Name: rulev2.GetName(),
|
||||
RuleStatus: reporthandling.RuleStatus{
|
||||
Status: "success",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
rulev1 := rulesv1[rulev2.GetName()]
|
||||
status := rulev2.GetStatus(&helpersv1.Filters{FrameworkNames: []string{frameworkName}})
|
||||
|
||||
if status.IsFailed() || status.IsExcluded() {
|
||||
|
||||
// rule response
|
||||
ruleResponse := reporthandling.RuleResponse{}
|
||||
ruleResponse.Rulename = rulev2.GetName()
|
||||
for i := range rulev2.Paths {
|
||||
if rulev2.Paths[i].FailedPath != "" {
|
||||
ruleResponse.FailedPaths = append(ruleResponse.FailedPaths, rulev2.Paths[i].FailedPath)
|
||||
}
|
||||
if rulev2.Paths[i].FixPath.Path != "" {
|
||||
ruleResponse.FixPaths = append(ruleResponse.FixPaths, rulev2.Paths[i].FixPath)
|
||||
}
|
||||
}
|
||||
ruleResponse.RuleStatus = string(status.Status())
|
||||
if len(rulev2.Exception) > 0 {
|
||||
ruleResponse.Exception = &rulev2.Exception[0]
|
||||
}
|
||||
|
||||
if fullRessource, ok := opaSessionObj.AllResources[resourceID]; ok {
|
||||
tmp := fullRessource.GetObject()
|
||||
workloadinterface.RemoveFromMap(tmp, "spec")
|
||||
ruleResponse.AlertObject.K8SApiObjects = append(ruleResponse.AlertObject.K8SApiObjects, tmp)
|
||||
}
|
||||
rulev1.RuleResponses = append(rulev1.RuleResponses, ruleResponse)
|
||||
}
|
||||
|
||||
rulev1.ListInputKinds = append(rulev1.ListInputKinds, resourceID)
|
||||
rulesv1[rulev2.GetName()] = rulev1
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(rulesv1) > 0 {
|
||||
for i := range rulesv1 {
|
||||
crv1.RuleReports = append(crv1.RuleReports, rulesv1[i])
|
||||
}
|
||||
}
|
||||
if len(crv1.RuleReports) == 0 {
|
||||
crv1.RuleReports = []reporthandling.RuleReport{}
|
||||
}
|
||||
controlRepors = append(controlRepors, crv1)
|
||||
}
|
||||
return controlRepors
|
||||
}
|
||||
194
cautils/scaninfo.go
Normal file
194
cautils/scaninfo.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
const (
|
||||
ScanCluster string = "cluster"
|
||||
ScanLocalFiles string = "yaml"
|
||||
localControlInputsFilename string = "controls-inputs.json"
|
||||
localExceptionsFilename string = "exceptions.json"
|
||||
)
|
||||
|
||||
type BoolPtrFlag struct {
|
||||
valPtr *bool
|
||||
}
|
||||
|
||||
func (bpf *BoolPtrFlag) Type() string {
|
||||
return "bool"
|
||||
}
|
||||
|
||||
func (bpf *BoolPtrFlag) String() string {
|
||||
if bpf.valPtr != nil {
|
||||
return fmt.Sprintf("%v", *bpf.valPtr)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (bpf *BoolPtrFlag) Get() *bool {
|
||||
return bpf.valPtr
|
||||
}
|
||||
|
||||
func (bpf *BoolPtrFlag) SetBool(val bool) {
|
||||
bpf.valPtr = &val
|
||||
}
|
||||
|
||||
func (bpf *BoolPtrFlag) Set(val string) error {
|
||||
switch val {
|
||||
case "true":
|
||||
bpf.SetBool(true)
|
||||
case "false":
|
||||
bpf.SetBool(false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RootInfo struct {
|
||||
Logger string // logger level
|
||||
CacheDir string // cached dir
|
||||
}
|
||||
type ScanInfo struct {
|
||||
Getters
|
||||
PolicyIdentifier []reporthandling.PolicyIdentifier
|
||||
UseExceptions string // Load file with exceptions configuration
|
||||
ControlsInputs string // Load file with inputs for controls
|
||||
UseFrom []string // Load framework from local file (instead of download). Use when running offline
|
||||
UseDefault bool // Load framework from cached file (instead of download). Use when running offline
|
||||
UseArtifactsFrom string // Load artifacts from local path. Use when running offline
|
||||
VerboseMode bool // Display all of the input resources and not only failed resources
|
||||
Format string // Format results (table, json, junit ...)
|
||||
Output string // Store results in an output file, Output file name
|
||||
FormatVersion string // Output object can be differnet between versions, this is for testing and backward compatibility
|
||||
ExcludedNamespaces string // used for host sensor namespace
|
||||
IncludeNamespaces string // DEPRECATED?
|
||||
InputPatterns []string // Yaml files input patterns
|
||||
Silent bool // Silent mode - Do not print progress logs
|
||||
FailThreshold float32 // Failure score threshold
|
||||
Submit bool // Submit results to Armo BE
|
||||
HostSensorEnabled BoolPtrFlag // Deploy ARMO K8s host sensor to collect data from certain controls
|
||||
HostSensorYamlPath string // Path to hostsensor file
|
||||
Local bool // Do not submit results
|
||||
Account string // account ID
|
||||
KubeContext string // context name
|
||||
FrameworkScan bool // false if scanning control
|
||||
ScanAll bool // true if scan all frameworks
|
||||
}
|
||||
|
||||
type Getters struct {
|
||||
ExceptionsGetter getter.IExceptionsGetter
|
||||
ControlsInputsGetter getter.IControlsInputsGetter
|
||||
PolicyGetter getter.IPolicyGetter
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) Init() {
|
||||
scanInfo.setUseFrom()
|
||||
scanInfo.setOutputFile()
|
||||
scanInfo.setUseArtifactsFrom()
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setUseArtifactsFrom() {
|
||||
if scanInfo.UseArtifactsFrom == "" {
|
||||
return
|
||||
}
|
||||
// UseArtifactsFrom must be a path without a filename
|
||||
dir, file := filepath.Split(scanInfo.UseArtifactsFrom)
|
||||
if dir == "" {
|
||||
scanInfo.UseArtifactsFrom = file
|
||||
} else if strings.Contains(file, ".json") {
|
||||
scanInfo.UseArtifactsFrom = dir
|
||||
}
|
||||
// set frameworks files
|
||||
files, err := ioutil.ReadDir(scanInfo.UseArtifactsFrom)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to read files from directory", helpers.String("dir", scanInfo.UseArtifactsFrom), helpers.Error(err))
|
||||
}
|
||||
framework := &reporthandling.Framework{}
|
||||
for _, f := range files {
|
||||
filePath := filepath.Join(scanInfo.UseArtifactsFrom, f.Name())
|
||||
file, err := os.ReadFile(filePath)
|
||||
if err == nil {
|
||||
if err := json.Unmarshal(file, framework); err == nil {
|
||||
scanInfo.UseFrom = append(scanInfo.UseFrom, filepath.Join(scanInfo.UseArtifactsFrom, f.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
// set config-inputs file
|
||||
scanInfo.ControlsInputs = filepath.Join(scanInfo.UseArtifactsFrom, localControlInputsFilename)
|
||||
// set exceptions
|
||||
scanInfo.UseExceptions = filepath.Join(scanInfo.UseArtifactsFrom, localExceptionsFilename)
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setUseExceptions() {
|
||||
if scanInfo.UseExceptions != "" {
|
||||
// load exceptions from file
|
||||
scanInfo.ExceptionsGetter = getter.NewLoadPolicy([]string{scanInfo.UseExceptions})
|
||||
} else {
|
||||
scanInfo.ExceptionsGetter = getter.GetArmoAPIConnector()
|
||||
}
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setUseFrom() {
|
||||
if scanInfo.UseDefault {
|
||||
for _, policy := range scanInfo.PolicyIdentifier {
|
||||
scanInfo.UseFrom = append(scanInfo.UseFrom, getter.GetDefaultPath(policy.Name+".json"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setOutputFile() {
|
||||
if scanInfo.Output == "" {
|
||||
return
|
||||
}
|
||||
if scanInfo.Format == "json" {
|
||||
if filepath.Ext(scanInfo.Output) != ".json" {
|
||||
scanInfo.Output += ".json"
|
||||
}
|
||||
}
|
||||
if scanInfo.Format == "junit" {
|
||||
if filepath.Ext(scanInfo.Output) != ".xml" {
|
||||
scanInfo.Output += ".xml"
|
||||
}
|
||||
}
|
||||
if scanInfo.Format == "pdf" {
|
||||
if filepath.Ext(scanInfo.Output) != ".pdf" {
|
||||
scanInfo.Output += ".pdf"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) GetScanningEnvironment() string {
|
||||
if len(scanInfo.InputPatterns) != 0 {
|
||||
return ScanLocalFiles
|
||||
}
|
||||
return ScanCluster
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) SetPolicyIdentifiers(policies []string, kind reporthandling.NotificationPolicyKind) {
|
||||
for _, policy := range policies {
|
||||
if !scanInfo.contains(policy) {
|
||||
newPolicy := reporthandling.PolicyIdentifier{}
|
||||
newPolicy.Kind = kind // reporthandling.KindFramework
|
||||
newPolicy.Name = policy
|
||||
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) contains(policyName string) bool {
|
||||
for _, policy := range scanInfo.PolicyIdentifier {
|
||||
if policy.Name == policyName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -13,7 +13,13 @@ func TestConvertLabelsToString(t *testing.T) {
|
||||
spilltedA := strings.Split(rsrt, ";")
|
||||
spilltedB := strings.Split(str, ";")
|
||||
for i := range spilltedA {
|
||||
if spilltedA[i] != spilltedB[i] {
|
||||
exists := false
|
||||
for j := range spilltedB {
|
||||
if spilltedB[j] == spilltedA[i] {
|
||||
exists = true
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
t.Errorf("%s != %s", spilltedA[i], spilltedB[i])
|
||||
}
|
||||
}
|
||||
|
||||
138
cautils/versioncheck.go
Normal file
138
cautils/versioncheck.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
pkgutils "github.com/armosec/utils-go/utils"
|
||||
)
|
||||
|
||||
const SKIP_VERSION_CHECK = "KUBESCAPE_SKIP_UPDATE_CHECK"
|
||||
|
||||
var BuildNumber string
|
||||
|
||||
const UnknownBuildNumber = "unknown"
|
||||
|
||||
type IVersionCheckHandler interface {
|
||||
CheckLatestVersion(*VersionCheckRequest) error
|
||||
}
|
||||
|
||||
func NewIVersionCheckHandler() IVersionCheckHandler {
|
||||
if BuildNumber == "" {
|
||||
logger.L().Warning("unknown build number, this might affect your scan results. Please make sure you are updated to latest version")
|
||||
}
|
||||
if v, ok := os.LookupEnv(SKIP_VERSION_CHECK); ok && pkgutils.StringToBool(v) {
|
||||
return NewVersionCheckHandlerMock()
|
||||
}
|
||||
return NewVersionCheckHandler()
|
||||
}
|
||||
|
||||
type VersionCheckHandlerMock struct {
|
||||
}
|
||||
|
||||
func NewVersionCheckHandlerMock() *VersionCheckHandlerMock {
|
||||
return &VersionCheckHandlerMock{}
|
||||
}
|
||||
|
||||
type VersionCheckHandler struct {
|
||||
versionURL string
|
||||
}
|
||||
type VersionCheckRequest struct {
|
||||
Client string `json:"client"` // kubescape
|
||||
ClientVersion string `json:"clientVersion"` // kubescape version
|
||||
Framework string `json:"framework"` // framework name
|
||||
FrameworkVersion string `json:"frameworkVersion"` // framework version
|
||||
ScanningTarget string `json:"target"` // scanning target- cluster/yaml
|
||||
}
|
||||
|
||||
type VersionCheckResponse struct {
|
||||
Client string `json:"client"` // kubescape
|
||||
ClientUpdate string `json:"clientUpdate"` // kubescape latest version
|
||||
Framework string `json:"framework"` // framework name
|
||||
FrameworkUpdate string `json:"frameworkUpdate"` // framework latest version
|
||||
Message string `json:"message"` // alert message
|
||||
}
|
||||
|
||||
func NewVersionCheckHandler() *VersionCheckHandler {
|
||||
return &VersionCheckHandler{
|
||||
versionURL: "https://us-central1-elated-pottery-310110.cloudfunctions.net/ksgf1v1",
|
||||
}
|
||||
}
|
||||
func NewVersionCheckRequest(buildNumber, frameworkName, frameworkVersion, scanningTarget string) *VersionCheckRequest {
|
||||
if buildNumber == "" {
|
||||
buildNumber = UnknownBuildNumber
|
||||
}
|
||||
if scanningTarget == "" {
|
||||
scanningTarget = "unknown"
|
||||
}
|
||||
return &VersionCheckRequest{
|
||||
Client: "kubescape",
|
||||
ClientVersion: buildNumber,
|
||||
Framework: frameworkName,
|
||||
FrameworkVersion: frameworkVersion,
|
||||
ScanningTarget: scanningTarget,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VersionCheckHandlerMock) CheckLatestVersion(versionData *VersionCheckRequest) error {
|
||||
logger.L().Info("Skipping version check")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckRequest) error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.L().Warning("failed to get latest version", helpers.Interface("error", err))
|
||||
}
|
||||
}()
|
||||
|
||||
latestVersion, err := v.getLatestVersion(versionData)
|
||||
if err != nil || latestVersion == nil {
|
||||
return fmt.Errorf("failed to get latest version")
|
||||
}
|
||||
|
||||
if latestVersion.ClientUpdate != "" {
|
||||
if BuildNumber != "" && BuildNumber < latestVersion.ClientUpdate {
|
||||
logger.L().Warning(warningMessage(latestVersion.ClientUpdate))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - Enable after supporting framework version
|
||||
// if latestVersion.FrameworkUpdate != "" {
|
||||
// fmt.Println(warningMessage(latestVersion.Framework, latestVersion.FrameworkUpdate))
|
||||
// }
|
||||
|
||||
if latestVersion.Message != "" {
|
||||
logger.L().Info(latestVersion.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VersionCheckHandler) getLatestVersion(versionData *VersionCheckRequest) (*VersionCheckResponse, error) {
|
||||
|
||||
reqBody, err := json.Marshal(*versionData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("in 'CheckLatestVersion' failed to json.Marshal, reason: %s", err.Error())
|
||||
}
|
||||
|
||||
resp, err := getter.HttpPost(http.DefaultClient, v.versionURL, map[string]string{"Content-Type": "application/json"}, reqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vResp := &VersionCheckResponse{}
|
||||
if err = getter.JSONDecoder(resp).Decode(vResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vResp, nil
|
||||
}
|
||||
|
||||
func warningMessage(release string) string {
|
||||
return fmt.Sprintf("current version '%s' is not updated to the latest release: '%s'", BuildNumber, release)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user