Compare commits

..

213 Commits

Author SHA1 Message Date
dwertent
2d59ba0943 update cmd struct 2022-03-10 17:02:09 +02:00
dwertent
33f92d1a5f update logger support 2022-03-10 11:19:05 +02:00
dwertent
4bd468f03e Merge remote-tracking branch 'armosec/dev' 2022-03-09 20:54:22 +02:00
dwertent
c6eaecd596 export to json 2022-03-09 20:53:55 +02:00
dwertent
a2a5b06024 adding http handler 2022-03-09 20:51:55 +02:00
David Wertenteil
825732f60f Merge pull request #444 from Akasurde/misspell_dev
Misc typo fixes
2022-03-09 20:12:19 +02:00
David Wertenteil
596ec17106 Merge pull request #445 from Akasurde/no_color_dev
cli: added support for no color
2022-03-09 20:01:24 +02:00
Abhijeet Kasurde
fbd0f352c4 cli: added support for no color
Using commandline flag users can now disable colored output
in logging.

Fixes: #434

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2022-03-09 21:25:48 +05:30
Abhijeet Kasurde
2600052735 Misc typo fixes
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2022-03-09 21:22:41 +05:30
dwertent
a985b2ce09 Add mock logger 2022-03-08 21:39:09 +02:00
dwertent
829c176644 Merge remote-tracking branch 'armosec/dev' into server-support 2022-03-08 14:52:00 +02:00
dwertent
7d7d247bc2 udpate go mod 2022-03-08 14:51:24 +02:00
dwertent
43ae8e2a81 Merge branch 'master' into server-support 2022-03-08 14:49:41 +02:00
David Wertenteil
b0f37e9465 Merge pull request #441 from xdavidel/update_io_apis
update vulns input / output api
2022-03-08 14:34:45 +02:00
David Wertenteil
396ef55267 Merge pull request #440 from dwertent/master
Use semver.Compare for version check
2022-03-08 10:12:59 +02:00
dwertent
4b07469bb2 use semver.Compare for version check 2022-03-08 10:10:06 +02:00
David Delarosa
260f7b06c1 update vulns input / output api 2022-03-07 17:03:14 +02:00
David Wertenteil
67ba28a3cb Merge pull request #433 from dwertent/master
call `setTenant` when submitting results
2022-03-06 11:29:34 +02:00
dwertent
a768d22a1d call setTenant when submitting results 2022-03-06 11:25:16 +02:00
David Wertenteil
ede88550da Merge pull request #432 from dwertent/master
Update roadmap
2022-03-06 11:03:54 +02:00
David Wertenteil
ab55a0d134 Merge pull request #431 from Bezbran/dev
Read Linux kernel variables from host sensor
2022-03-06 10:59:41 +02:00
Bezalel Brandwine
bfd7060044 read linux kernel variables from host sensor 2022-03-06 10:51:24 +02:00
dwertent
bf215a0f96 update roadmap 2022-03-06 10:50:45 +02:00
dwertent
a2e1fb36df update maintainers 2022-03-06 10:42:30 +02:00
dwertent
4e9c6f34b3 Update maintainers and roadmap 2022-03-06 10:38:03 +02:00
David Wertenteil
b08c0f2ec6 Merge pull request #429 from dwertent/master
Update readme
2022-03-06 09:54:41 +02:00
dwertent
4c0e358afc support submitting v2 2022-03-06 09:51:05 +02:00
dwertent
9ae21b064a update readme 2022-03-06 09:32:26 +02:00
dwertent
2df0c12e10 auto complete examples 2022-03-03 17:45:54 +02:00
David Wertenteil
d37025dc6c json v2 version 2022-03-03 16:17:25 +02:00
dwertent
0b01eb5ee4 swapped print versions 2022-03-03 14:26:34 +02:00
David Wertenteil
d537c56159 Update format output support to v2 2022-03-03 13:24:45 +02:00
dwertent
feb9e3af10 Merge remote-tracking branch 'armosec/dev' 2022-03-03 13:06:36 +02:00
dwertent
ec30ed8439 json v2 support 2022-03-03 13:05:36 +02:00
dwertent
cda9bb0e45 Update junit support 2022-03-03 12:34:03 +02:00
Ben Hirschberg
17f1c6b647 Merge pull request #424 from slashben/dev
added updated road-map
2022-03-01 17:27:39 +02:00
Ben Hirschberg
98079ec1ec added updated roadmap 2022-03-01 17:26:26 +02:00
David Wertenteil
16aaf9b5f8 extend exceptions support 2022-02-28 18:32:49 +02:00
dwertent
ff0264ee15 extend exceptions support 2022-02-28 18:19:17 +02:00
David Wertenteil
bea9bd64a4 Update junit support 2022-02-27 17:33:16 +02:00
dwertent
544a19906e update junit 2022-02-27 17:29:42 +02:00
dwertent
208bb25118 fixed junit support 2022-02-27 16:33:21 +02:00
David Wertenteil
fdb7e278c1 Merge pull request #419 from hayzer/fail-threshold-support-float
Change fail-threshold type to float
2022-02-27 14:56:27 +02:00
David Wertenteil
a132a49d57 extent host-sensor support 2022-02-27 14:45:30 +02:00
dwertent
23e73f5e88 extent host-sensor support 2022-02-27 14:30:32 +02:00
Tomasz Majer
fdcc5e9a66 Support float in fail-threshold 2022-02-27 11:14:05 +01:00
David Wertenteil
77e7b1a2cb Improvments 2022-02-24 18:58:31 +02:00
dwertent
db95da3742 udpate packages 2022-02-24 18:43:29 +02:00
dwertent
dc172a1476 fixed help message 2022-02-24 18:22:29 +02:00
dwertent
8694a929cf support output versions 2022-02-24 13:57:33 +02:00
dwertent
36b3840362 support host sensor from local file 2022-02-24 12:09:47 +02:00
dwertent
d5fcbe842f adding autocompletion 2022-02-24 09:50:12 +02:00
dwertent
155349dac0 adding resource table 2022-02-23 15:54:59 +02:00
David Wertenteil
7956a849d9 Fix argument validation for framework/control 2022-02-23 10:33:49 +02:00
Avinash Upadhyaya
0d1c4cdc02 fix: argument validation for framework/control 2022-02-23 10:24:14 +05:30
David Wertenteil
8c833a5df8 Ignore empty frameworks 2022-02-22 14:48:10 +02:00
dwertent
2c5bb977cb ignore empty framewprks 2022-02-22 14:46:25 +02:00
dwertent
cddf7dd8f6 handle errors 2022-02-22 11:03:44 +02:00
dwertent
306c18147e Merge remote-tracking branch 'upstream/dev' 2022-02-22 10:49:57 +02:00
David Wertenteil
84815eb97d fix uuid high-risk vulnerability 2022-02-22 10:26:56 +02:00
dwertent
890c13a91f stage url 2022-02-22 09:53:40 +02:00
fsl
3887ec8091 fix uuid high-risk vulnerability 2022-02-22 10:29:23 +08:00
fsl
726b06bb70 Merge remote-tracking branch 'origin/dev' into dev 2022-02-22 10:19:30 +08:00
dwertent
c8e07c283e Merge remote-tracking branch 'upstream/dev' 2022-02-21 17:24:32 +02:00
dwertent
88a5128c03 fixed pretty-print v1, expand list support 2022-02-21 17:22:01 +02:00
dwertent
01f2d3b04f Merge branch 'dev' of ssh://github.com/armosec/kubescape into dev 2022-02-21 14:48:53 +02:00
dwertent
fef85a4467 init printer for all pkg 2022-02-21 14:48:48 +02:00
David Wertenteil
e0eadc1f2d support for pdf format output
Resolves #282
2022-02-21 14:47:48 +02:00
dwertent
a881b73e8d Merge remote-tracking branch 'pdf/master' into dev 2022-02-21 10:21:05 +02:00
dwertent
606f5cfb62 fixed discord banner 2022-02-21 09:00:34 +02:00
Rotem Refael
40737d545b Merge pull request #403 from armosec/dev (update logger/cache/ docker build)
Add explicit protocol to solve known K8s 1.16 issue
Post scan message update
Support zap logger (KS_LOGGER_NAME)
Support cache dir location (--cache-dir/KS_CACHE_DIR)
Fixed docker release version
Issues:

Resolved option disable createTenant when using the offline scan option #397
Resolved Mutated release binaries #98
2022-02-20 21:33:15 +02:00
dwertent
990be3afe8 remove cat command 2022-02-20 20:55:22 +02:00
David Wertenteil
7020c2d025 Updating cache handling 2022-02-20 19:06:38 +02:00
dwertent
a6497c1252 fixed python build file 2022-02-20 19:04:16 +02:00
dwertent
9d528a8075 support setting default cach 2022-02-20 13:30:05 +02:00
David Wertenteil
5aec8b6f28 Fix release version in Dockerfile 2022-02-20 09:08:10 +02:00
Avinash Upadhyaya
830ee27169 build: fix release version in Dockerfile 2022-02-20 10:26:40 +05:30
alegrey91
5f2e5c6f4e docs: describe support for pdf format output 2022-02-17 18:17:12 +01:00
alegrey91
cf4317b5f6 feat(resulthandling): add support for pdf format output 2022-02-17 18:16:29 +01:00
David Wertenteil
2453aea6f3 Support zap logger 2022-02-17 17:49:24 +02:00
dwertent
e95b0f840a Support zap logger 2022-02-17 17:47:57 +02:00
David Wertenteil
83680d1207 AccountID handling 2022-02-17 09:30:25 +02:00
dwertent
fd135e9e49 Cache accountID only when submitting data 2022-02-17 09:23:17 +02:00
dwertent
e47eb9cb4e adding logs to python file 2022-02-16 14:02:28 +02:00
dwertent
d288fdc7f2 set tenant only when submiiting data 2022-02-16 13:59:29 +02:00
dwertent
9de73dab29 updalead sha 2022-02-16 13:15:44 +02:00
David Wertenteil
f0afc20ec6 Update post scan message 2022-02-16 10:03:03 +02:00
dwertent
bf75059347 update after scan messgae 2022-02-15 14:25:28 +02:00
David Wertenteil
78835a58c4 add explicit protocol TCP to host-sensor YAML 2022-02-12 19:02:59 +02:00
David Wertenteil
fdccae9a1e [Minor] Typo Correction 2022-02-12 19:01:10 +02:00
avicoder
6d97d42f67 Merge pull request #1 from avicoder/avicoder-typo-fix
[Minor] Fixed a Typo
2022-02-11 03:17:38 +08:00
avicoder
46001e4761 [Minor] Fixed a Typo
Useage to Usage
2022-02-11 03:17:10 +08:00
dwertent
37644e1f57 update readme 2022-02-10 21:05:08 +02:00
dwertent
8a04934fbd Adding readme and yaml 2022-02-10 20:41:28 +02:00
dwertent
31e1b3055f Prometheus support 2022-02-10 20:11:00 +02:00
Bezalel Brandwine
b4d712fcb1 add explicit protocol to solve known K8s issue:
https://github.com/kubernetes/kubernetes/issues/92332
2022-02-10 17:38:13 +02:00
Rotem Refael
7847a4593b Merge pull request #381 from armosec/dev
Minor updates
2022-02-10 16:33:43 +02:00
David Wertenteil
b2036e64f1 Merge pull request #380 from dwertent/master
Hot fix
2022-02-10 14:07:34 +02:00
Bezbran
fd0bbcccfe Merge pull request #16 from armosec/dev
Dev
2022-02-10 13:43:49 +02:00
dwertent
7caa47f949 Merge remote-tracking branch 'upstream/dev' 2022-02-10 13:41:16 +02:00
David Wertenteil
06b171901d Track host sensor pods reports 2022-02-10 13:34:05 +02:00
dwertent
e685fe2b7d update download readme 2022-02-10 11:42:42 +02:00
Bezalel Brandwine
7177e77a8d track host sensor pods tighten 2022-02-10 11:23:55 +02:00
dwertent
4cda32771b fixed url scanning 2022-02-10 09:24:46 +02:00
dwertent
f896b65a87 fixed eks cluster name 2022-02-10 08:55:28 +02:00
David Wertenteil
3fff1b750a fixed Set image version 2022-02-09 14:38:05 +02:00
dwertent
2380317953 fixed Set image version 2022-02-09 14:16:32 +02:00
Rotem Refael
bd9ade4d15 Merge pull request #370 from armosec/dev
Features:
Adding a logger
Support config command

Minor fixes
2022-02-09 14:02:21 +02:00
dwertent
659d3533ee disable cosign 2022-02-09 13:29:46 +02:00
Rotem Refael
37c242576e Merge pull request #373 from Bezbran/dev
[housekeeper] add readiness checks
2022-02-09 09:45:54 +02:00
Bezalel Brandwine
e9a22a23e7 [housekeeper] add readiness checks 2022-02-09 09:35:39 +02:00
David Wertenteil
ae3816c1e0 [host sensor] fix name of log field 2022-02-09 09:00:57 +02:00
Bezalel Brandwine
e4661a5ae2 [host sensor] fix name of log field 2022-02-09 08:50:47 +02:00
Bezbran
539d1889fe Merge pull request #15 from armosec/dev
Dev
2022-02-09 08:23:54 +02:00
David Wertenteil
2dd5f05f1a Replace call to fmt.print to logger call 2022-02-08 22:10:07 +02:00
dwertent
60c9b38de4 replace print by logger 2022-02-08 22:08:41 +02:00
dwertent
8b66b068ea remove publish image file 2022-02-08 15:55:53 +02:00
dwertent
1507bc3f04 update helm readme 2022-02-08 15:54:05 +02:00
David Wertenteil
1e0baba919 fixed dev version 2022-02-08 15:15:29 +02:00
dwertent
4c9f47b1e1 fixed dev version 2022-02-08 15:14:10 +02:00
David Wertenteil
b66446b7eb Update auth urls 2022-02-08 14:47:28 +02:00
dwertent
f1726e21ae ignore casign 2022-02-08 14:45:23 +02:00
dwertent
8d48f8ad86 adding logger 2022-02-08 14:23:54 +02:00
dwertent
8b280f272e update auth url 2022-02-08 14:21:39 +02:00
David Wertenteil
b92d4256ad Fixed build.yaml and do not push docker in other repos 2022-02-08 14:13:10 +02:00
dwertent
914a04a386 fixed builld dev 2022-02-08 14:02:53 +02:00
dwertent
12f3dd7db6 update build 2022-02-08 13:45:31 +02:00
dwertent
427032ab94 testing workflow 2022-02-08 11:27:41 +02:00
David Wertenteil
b55aaaa34d chore: sign container image with cosign 2022-02-08 11:00:45 +02:00
David Wertenteil
7cde877452 Minor fixes 2022-02-07 17:41:25 +02:00
dwertent
e399012f73 fixed glob files 2022-02-07 17:36:47 +02:00
dwertent
fe1d2646bd fixed glob files 2022-02-07 16:08:57 +02:00
dwertent
ea98bfbe9a Merge remote-tracking branch 'upstream/dev' 2022-02-07 15:38:11 +02:00
dwertent
7bc3277634 support scanning all with yaml files 2022-02-07 15:37:57 +02:00
dwertent
22e94c5a29 typo 2022-02-07 15:37:30 +02:00
dwertent
aa8cf0ff15 fixed python build 2022-02-07 11:18:23 +02:00
dwertent
a22f97bd13 fixed python build 2022-02-07 10:56:39 +02:00
David Wertenteil
3fd2d1629d Adding logger and submitting exceptions 2022-02-06 19:05:00 +02:00
dwertent
cd04204a5c fixed url 2022-02-06 18:19:35 +02:00
dwertent
eee55376e7 Merge remote-tracking branch 'upstream/dev' 2022-02-06 17:14:28 +02:00
dwertent
d3c0972d70 submit exceptions 2022-02-06 17:12:59 +02:00
David Wertenteil
e3f5fa8e35 update version of rbac pkg - show highest parent in SAID2WLID map 2022-02-06 16:36:20 +02:00
dwertent
03c540b68c support armosec login 2022-02-06 16:35:39 +02:00
yiscah
7f2f53b06c update version of rbac pkg - show highest parent in SAID2WLID map 2022-02-06 13:50:48 +02:00
dwertent
8064826b53 init logger 2022-02-06 12:01:26 +02:00
dwertent
8bdff31693 replace print with logger 2022-02-06 11:13:33 +02:00
Batuhan Apaydın
6f05b4137b chore: sign container image with cosign
Signed-off-by: Batuhan Apaydın <batuhan.apaydin@trendyol.com>
2022-02-04 12:16:07 +03:00
dwertent
4207f3d6d1 using logger 2022-02-03 18:12:35 +02:00
dwertent
98dbda696d update exception support 2022-02-03 16:44:57 +02:00
dwertent
7a34c94542 adding logger 2022-02-03 15:42:37 +02:00
David Wertenteil
789b93776d support fixPaths 2022-02-03 15:28:24 +02:00
yiscah
c93ee64630 allow case of both failpath and fixpath exist 2022-02-03 13:13:18 +02:00
yiscah
f54c3ad85c Merge branch 'dev' of ssh://github.com/armosec/kubescape into dev 2022-02-03 13:03:27 +02:00
David Wertenteil
a9fcd00723 Support image-vulnerabilities related controls 2022-02-03 12:48:41 +02:00
yiscah
fb7cc4284e support fixPaths 2022-02-03 12:29:28 +02:00
dwertent
e7a0755c25 report ClusterAPIServerInfo 2022-02-03 10:32:00 +02:00
dwertent
d1e02dc298 update config command 2022-02-02 14:24:53 +02:00
dwertent
69814039ca using accountID instead of customerGUID 2022-02-02 08:12:41 +02:00
dwertent
2ffb7fcdb4 support view/set/delete commands 2022-02-01 17:16:27 +02:00
dwertent
d1695b7f10 support vuln scan 2022-01-31 18:41:45 +02:00
raziel
5f0f9a9eae correct nil derefrance 2022-01-31 17:13:48 +02:00
dwertent
aa2dedb76f Merge branch 'master' into vuln-support 2022-01-30 14:03:46 +02:00
David Wertenteil
d630811386 Minor fixes 2022-01-30 11:21:54 +02:00
David Wertenteil
407e35c9d8 Merge pull request #356 from dwertent/master
load cloud provider from env
2022-01-30 11:09:45 +02:00
dwertent
bb7f38ce31 load cloud provider from env 2022-01-30 11:09:02 +02:00
David Wertenteil
1ffb2d360a Merge pull request #351 from wim-de-groot/master
Fixes(#333) Added length check to prevent panic
2022-01-30 08:43:36 +02:00
David Wertenteil
5cbadc02c5 Minor fixes 2022-01-30 08:17:43 +02:00
dwertent
9aa8d9edf0 Merge remote-tracking branch 'upstream/dev' 2022-01-30 08:14:31 +02:00
dwertent
db84380844 print summary and scoe 2022-01-30 08:14:10 +02:00
dwertent
bbb0d2154f fixed version print 2022-01-30 08:06:50 +02:00
Wim de Groot
2a937ac7c0 Merge branch 'dev' into master 2022-01-27 09:36:52 +01:00
Wim de Groot
a96652094e Added length check to prevent panic (#333)
Signed-off-by: Wim de Groot <34519486+degrootwim@users.noreply.github.com>
2022-01-26 14:57:19 +01:00
David Wertenteil
dbf3de57f6 Prevent index out of range [0] when control report does not have rules 2022-01-26 10:19:04 +02:00
Rotem Refael
c00bc0ebbb Merge pull request #348 from armosec/dev
Fixing null pointer crash in GKE
2022-01-24 17:15:30 +02:00
Ben Hirschberg
b08e5a2c32 Merge pull request #347 from slashben/dev
Fixing null pointer exception in case of in-cluster installation
2022-01-24 16:47:54 +02:00
Ben Hirschberg
09db5d94e1 Fixing null pointer exception in case of in-cluster installation 2022-01-24 16:47:12 +02:00
Rotem Refael
033e8f6b44 Merge pull request #346 from armosec/dev
returning rbac submit code + fix crash
2022-01-24 12:40:33 +02:00
Ben Hirschberg
bef40f0e6c Merge pull request #344 from slashben/dev
returning RBAC submit table
2022-01-24 09:13:28 +02:00
Ben Hirschberg
aa2f69125f returning rbac submit code 2022-01-24 09:12:13 +02:00
Ben Hirschberg
d30f3960a7 Merge pull request #341 from slashben/dev
fixing the crash around submit rbac
2022-01-23 21:28:10 +02:00
Ben Hirschberg
5f43da94ba fixing the crash around submit rbac 2022-01-23 20:17:03 +02:00
Rotem Refael
2aa8a0c935 Merge pull request #340 from Anthirian/dev
Fix typo in namespace
2022-01-23 13:43:42 +02:00
Ben Hirschberg
c02f8c6cb5 Merge pull request #338 from vfarcic/dev
Video
2022-01-23 08:18:44 +02:00
Geert Smelt
aa0be474e2 Fix typo in namespace 2022-01-21 15:40:00 +01:00
Viktor Farcic
c0161c9b33 Video 2022-01-20 19:54:57 +01:00
Quirino Gervacio
71404f2205 Prevent index out of range [0] when control report do not have a rule report 2022-01-20 04:51:04 +08:00
Rotem Refael
514da1e2db Merge pull request #332 from armosec/dev
Emergency fix: checking kube context before use
2022-01-19 16:53:14 +02:00
Ben Hirschberg
75dfceb5da Merge pull request #331 from slashben/dev
checking if local context exists before using
2022-01-19 16:38:22 +02:00
Ben Hirschberg
1ae76b4377 checking if context exists 2022-01-19 16:37:32 +02:00
dwertent
72860deb0f update struct, adding mock struct 2022-01-11 10:43:53 +02:00
David Wertenteil
639c694c13 Merge pull request #5 from slashben/master
Armo interface
2022-01-11 09:35:12 +02:00
Ben Hirschberg
f34f6dc51e GetImageVulnerabilty is working 2022-01-11 00:42:26 +02:00
Ben Hirschberg
b93e7b9abf take only the first result 2022-01-10 22:48:00 +02:00
Ben Hirschberg
39b95eff4f got full auth cycle 2022-01-10 19:50:07 +02:00
Ben Hirschberg
83246a1802 generalizing url settings 2022-01-10 08:10:40 +02:00
Ben Hirschberg
f255df0198 login first part 2022-01-10 07:39:54 +02:00
Ben Hirschberg
52b78a7e73 Merge branch 'armosec:master' into master 2022-01-09 16:47:41 +02:00
Ben Hirschberg
b7842f98f0 Merge branch 'armosec:master' into master 2021-12-29 16:28:35 +02:00
Ben Hirschberg
1b2514e3ec git push origin masterMerge branch 'armosec-master' 2021-11-01 16:02:51 +02:00
Ben Hirschberg
0da4f40b48 merge 2021-11-01 16:02:23 +02:00
Benyamin Hirschberg
5591bf09d9 Merge branch 'armosec-master' 2021-10-15 22:24:04 +03:00
Benyamin Hirschberg
da94651656 merged original armosec 2021-10-15 22:23:47 +03:00
Ben Hirschberg
86b6a1d88a complete zip release and install with hash 2021-10-04 08:36:53 +03:00
Ben Hirschberg
f903e13d7b adding hash to release zip 2021-10-04 08:19:44 +03:00
Ben Hirschberg
015206a760 release id 2021-10-03 22:21:54 +03:00
Ben Hirschberg
0aff119260 fix bad reference 2021-10-03 22:13:00 +03:00
Ben Hirschberg
ddb8608501 moving up the publishing step 2021-10-03 22:11:20 +03:00
Ben Hirschberg
0d75a273f0 post release event 2021-10-03 22:06:27 +03:00
Ben Hirschberg
4f07d23dd6 moving to post release 2021-10-03 21:50:35 +03:00
Ben Hirschberg
79baa0d66e hashing 2021-10-03 21:36:37 +03:00
Ben Hirschberg
d5ca49ef9b removing not needed plugin 2021-10-03 21:23:00 +03:00
Ben Hirschberg
536d7fb3c5 try another release plugin 2021-10-03 21:20:25 +03:00
Ben Hirschberg
f66fd1f38c hash for release 2021-10-03 21:09:53 +03:00
178 changed files with 7017 additions and 3739 deletions

View File

@@ -2,7 +2,7 @@ name: build
on:
push:
branches: [ master ]
branches: [ master ]
jobs:
once:
name: Create release
@@ -41,6 +41,7 @@ jobs:
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
@@ -52,7 +53,7 @@ jobs:
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
- name: Upload Release binaries
- name: Upload release binaries
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
@@ -63,32 +64,71 @@ jobs:
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
if: ${{ github.repository == 'armosec/kubescape' }}
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 name
run: echo quay.io/armosec/kubescape:v2.0.${{ github.run_number }} > build_tag.txt
- 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 $(cat build_tag.txt) --build-arg run_number=${{ github.run_number }}
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 $(cat build_tag.txt) quay.io/armosec/kubescape: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 $(cat build_tag.txt)
docker push quay.io/armosec/kubescape:latest
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 }}

View File

@@ -25,7 +25,8 @@ jobs:
env:
RELEASE: v2.0.${{ github.run_number }}
ArmoBEServer: api.armo.cloud
ArmoERServer: report.euprod1.cyberarmorsoft.com
ArmoAuthServer: auth.armo.cloud
ArmoERServer: report.armo.cloud
ArmoWebsite: portal.armo.cloud
CGO_ENABLED: 0
run: python3 --version && python3 build.py
@@ -42,30 +43,42 @@ jobs:
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' }}
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 name
run: echo quay.io/armosec/kubescape:dev-v2.0.${{ github.run_number }} > build_tag.txt
- 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 $(cat build_tag.txt) --build-arg run_number=${{ github.run_number }}
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: # Or as an environment variable
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 $(cat build_tag.txt)
docker push ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }}

View File

@@ -26,6 +26,7 @@ jobs:
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

17
.github/workflows/post-release.yaml vendored Normal file
View 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

3
.gitignore vendored
View File

@@ -3,4 +3,5 @@
*debug*
*vender*
*.pyc*
.idea
.idea
ca.srl

View File

@@ -2,8 +2,9 @@
The following table lists Kubescape project maintainers
| Name | GitHub | Email | Organization | Repositories/Area of Expertise | Added/Renewed On |
| Name | GitHub | Email | Organization | Role | 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 |
| [Ben Hirschberg](https://www.linkedin.com/in/benyamin-ben-hirschberg-66141890) | [@slashben](https://github.com/slashben) | ben@armosec.io | [ARMO](https://www.armosec.io/) | VP R&D | 2021-09-01 |
| [Rotem Refael](https://www.linkedin.com/in/rotem-refael) | [@rotemamsa](https://github.com/rotemamsa) | rrefael@armosec.io | [ARMO](https://www.armosec.io/) | Team Leader | 2021-10-11 |
| [David Wertenteil](https://www.linkedin.com/in/david-wertenteil-0ba277b9) | [@dwertent](https://github.com/dwertent) | dwertent@armosec.io | [ARMO](https://www.armosec.io/) | Kubescape CLI Developer | 2021-09-01 |
| [Bezalel Brandwine](https://www.linkedin.com/in/bezalel-brandwine) | [@Bezbran](https://github.com/Bezbran) | bbrandwine@armosec.io | [ARMO](https://www.armosec.io/) | Kubescape SaaS Developer | 2021-09-01 |

213
README.md
View File

@@ -3,6 +3,8 @@
[![build](https://github.com/armosec/kubescape/actions/workflows/build.yaml/badge.svg)](https://github.com/armosec/kubescape/actions/workflows/build.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/armosec/kubescape)](https://goreportcard.com/report/github.com/armosec/kubescape)
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.
@@ -46,23 +48,30 @@ We invite you to our team! We are excited about this project and want to return
Want to contribute? Want to discuss something? Have an issue?
* Feel free to pick a task from the [roadmap](docs/roadmap.md) or suggest a feature of your own. [Contact us](MAINTAINERS.md) directly for more information :)
* Open a issue, we are trying to respond within 48 hours
* [Join us](https://armosec.github.io/kubescape/) in a discussion on our discord server!
[<img src="docs/discord-banner.png" width="100" alt="logo" align="center">](https://armosec.github.io/kubescape/)
![discord](https://img.shields.io/discord/893048809884643379)
# 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)
* [Scanning Kubernetes YAML files](https://youtu.be/Ox6DaR7_4ZI)
* [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
@@ -87,32 +96,18 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser
brew install kubescape
```
## Flags
| flag | default | description | options |
|-----------------------------|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------|
| `-e`/`--exclude-namespaces` | Scan all namespaces | Namespaces to exclude from scanning. Recommended to exclude `kube-system` and `kube-public` namespaces | |
| `--include-namespaces` | Scan all namespaces | Scan specific namespaces | |
| `-s`/`--silent` | Display progress messages | Silent progress messages | |
| `-t`/`--fail-threshold` | `100` (do not fail) | fail command (return exit code 1) if result is above threshold | `0` -> `100` |
| `-f`/`--format` | `pretty-printer` | Output format | `pretty-printer`/`json`/`junit`/`prometheus` |
| `-o`/`--output` | print to stdout | Save scan result in file | |
| `--use-from` | | Load local framework object from specified path. If not used will download latest |
| `--use-artifacts-from` | | Load artifacts (frameworks, control-config, exceptions) from local directory. If not used will download them | |
| `--use-default` | `false` | Load local framework object from default path. If not used will download latest | `true`/`false` |
| `--exceptions` | | Path to an [exceptions obj](examples/exceptions.json). If not set will download exceptions from Armo management portal |
| `--controls-config` | | Path to a controls-config obj. If not set will download controls-config from ARMO management portal | |
| `--submit` | `false` | If set, Kubescape will send the scan results to Armo management portal where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not sent | `true`/`false` |
| `--keep-local` | `false` | Kubescape will not send scan results to Armo management portal. Use this flag if you ran with the `--submit` flag in the past and you do not want to submit your current scan results | `true`/`false` |
| `--account` | | Armo portal account ID. Default will load account ID from configMap or config file | |
| `--kube-context` | current-context | Cluster context to scan | |
| `--verbose` | `false` | Display all of the input resources and not only failed resources | `true`/`false` |
## Usage & Examples
### Examples
#### Scan a running Kubernetes cluster and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
```
kubescape scan --submit --enable-host-scan
```
> Read [here](https://hub.armo.cloud/docs/host-sensor) more about the `enable-host-scan` flag
#### Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
```
kubescape scan framework nsa --submit
@@ -132,208 +127,140 @@ kubescape scan control "Privileged container"
#### Scan specific namespaces
```
kubescape scan framework nsa --include-namespaces development,staging,production
kubescape scan --include-namespaces development,staging,production
```
#### Scan cluster and exclude some namespaces
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
kubescape scan --exclude-namespaces kube-system,kube-public
```
#### Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI)
```
kubescape scan framework nsa *.yaml
kubescape scan *.yaml
```
#### Scan kubernetes manifest files from a public github repository
```
kubescape scan framework nsa https://github.com/armosec/kubescape
kubescape scan https://github.com/armosec/kubescape
```
#### Display all scanned resources (including the resources who passed)
```
kubescape scan framework nsa --verbose
kubescape scan --verbose
```
#### Output in `json` format
> Add the `--format-version v2` flag
```
kubescape scan framework nsa --format json --output results.json
kubescape scan --format json --format-version v2 --output results.json
```
#### Output in `junit xml` format
```
kubescape scan framework nsa --format junit --output results.xml
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 framework nsa --format prometheus
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 framework nsa --exceptions examples/exceptions/exclude-kube-namespaces.json
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 framework nsa -
helm template [NAME] [CHART] [flags] --dry-run | kubescape scan -
```
e.g.
```
helm template bitnami/mysql --generate-name --dry-run | kubescape scan framework nsa -
helm template bitnami/mysql --generate-name --dry-run | kubescape scan -
```
### Offline Support
### Offline/Air-gaped Environment Support
[Video tutorial](https://youtu.be/IGXL9s37smM)
It is possible to run Kubescape offline!
First download the framework and then scan with `--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 nsa.json
```
2. Scan using the downloaded framework
```
kubescape scan framework nsa --use-from nsa.json
```
You can also download all artifacts to a local path and then load them using `--use-artifacts-from` flag
#### 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
2. Scan using the downloaded artifacts
3. Scan using the downloaded artifacts
```
kubescape scan framework nsa --use-artifacts-from path/to/local/dir
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)
You can scan your cluster periodically by adding a `CronJob` that will repeatedly trigger kubescape
```
helm install kubescape examples/helm_chart/
```
[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 framework nsa /app/example.yaml
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
First, scan your cluster using the `json` format flag: `kubescape scan framework <name> --format json --output path/to/results.json`.
> Support forward compatibility by using the `--format-version v2` flag
Now you can submit the results to the Kubaescape SaaS version -
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
```
# How to build
## Build using python (3.7^) script
Kubescape can be built using:
``` sh
python build.py
```
Note: In order to built using the above script, one must set the environment
variables in this script:
+ RELEASE
+ ArmoBEServer
+ ArmoERServer
+ ArmoWebsite
## Build using go
Note: development (and the release process) is done with Go `1.17`
1. Clone Project
```
git clone https://github.com/armosec/kubescape.git kubescape && cd "$_"
```
2. Build
```
go build -o kubescape .
```
3. Run
```
./kubescape scan --submit --enable-host-scan
```
4. Enjoy :zany_face:
## 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 .
```
# 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/Press-Room/News-Highlights/Article/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
* Network policies
* Symlink Exchange Can Allow Host Filesystem Access (CVE-2021-25741)
## Technology
Kubescape based on OPA engine: https://github.com/open-policy-agent/opa and ARMO's posture controls.
The tools retrieves Kubernetes objects from the API server and runs a set of [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.

View File

@@ -8,6 +8,7 @@ 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:
@@ -37,7 +38,7 @@ def main():
print("Building Kubescape")
# print environment variables
print(os.environ)
# print(os.environ)
# Set some variables
packageName = getPackageName()
@@ -46,25 +47,45 @@ def main():
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 -X %s=%s -X %s=%s -X %s=%s -X %s=%s" \
% (buildUrl, releaseVersion, BE_SERVER_CONST, ArmoBEServer,
ER_SERVER_CONST, ArmoERServer, WEBSITE_CONST, ArmoWebsite)
status = subprocess.call(["go", "build", "-o", "%s/%s" % (buildDir, packageName), "-ldflags" ,ldflags])
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")
sha1 = hashlib.sha1()
with open(buildDir + "/" + packageName, "rb") as kube:
sha1.update(kube.read())
with open(buildDir + "/" + packageName + ".sha1", "w") as kube_sha:
kube_sha.write(sha1.hexdigest())
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")

View File

@@ -1,9 +1,9 @@
FROM golang:1.17-alpine as builder
#ENV GOPROXY=https://goproxy.io,direct
ARG run_number
ARG image_version
ENV RELEASE=v1.0.${run_number}
ENV RELEASE=$image_version
ENV GO111MODULE=
@@ -21,7 +21,6 @@ ADD . .
RUN python build.py
RUN ls -ltr build/ubuntu-latest
RUN cat /work/build/ubuntu-latest/kubescape.sha1
FROM alpine
COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kubescape

13
build/README.md Normal file
View 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 .
```

View File

@@ -23,25 +23,31 @@ func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName +
// ======================================================================================
type ConfigObj struct {
CustomerGUID string `json:"customerGUID"`
Token string `json:"invitationParam"`
CustomerAdminEMail string `json:"adminMail"`
ClusterName string `json:"clusterName"`
}
func (co *ConfigObj) Json() []byte {
if b, err := json.Marshal(co); err == nil {
return b
}
return []byte{}
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
co.ClusterName = "" // remove cluster name before saving to file
b, err := json.Marshal(co)
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
@@ -56,10 +62,12 @@ func (co *ConfigObj) Config() []byte {
type ITenantConfig interface {
// set
SetTenant() error
UpdateCachedConfig() error
DeleteCachedConfig() error
// getters
GetClusterName() string
GetCustomerGUID() string
GetAccountID() string
GetConfigObj() *ConfigObj
// GetBackendAPI() getter.IBackend
// GenerateURL()
@@ -76,7 +84,8 @@ type LocalConfig struct {
configObj *ConfigObj
}
func NewLocalConfig(backendAPI getter.IBackend, customerGUID, clusterName string) *LocalConfig {
func NewLocalConfig(
backendAPI getter.IBackend, customerGUID, clusterName string) *LocalConfig {
var configObj *ConfigObj
lc := &LocalConfig{
@@ -93,46 +102,52 @@ func NewLocalConfig(backendAPI getter.IBackend, customerGUID, clusterName string
lc.configObj = configObj
}
if customerGUID != "" {
lc.configObj.CustomerGUID = customerGUID // override config customerGUID
lc.configObj.AccountID = customerGUID // override config customerGUID
}
if clusterName != "" {
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
}
if lc.configObj.CustomerGUID != "" {
if err := lc.SetTenant(); err != nil {
fmt.Println(err)
}
}
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) GetCustomerGUID() string { return lc.configObj.CustomerGUID }
func (lc *LocalConfig) SetCustomerGUID(customerGUID string) { lc.configObj.CustomerGUID = customerGUID }
func (lc *LocalConfig) GetClusterName() string { return lc.configObj.ClusterName }
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
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
}
updateConfigFile(lc.configObj)
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
backendAPI.SetCustomerGUID(configObj.CustomerGUID)
tenantResponse, err := backendAPI.GetCustomerGUID()
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.CustomerGUID = tenantResponse.TenantID
configObj.AccountID = tenantResponse.TenantID
}
} else {
if err != nil && !strings.Contains(err.Error(), "already exists") {
@@ -154,8 +169,11 @@ 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_ACCOUNT // Account ID
KS_CACHE // path to cached files
*/
type ClusterConfig struct {
@@ -187,32 +205,30 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
c.configObj = configObj
}
if customerGUID != "" {
c.configObj.CustomerGUID = customerGUID // override config customerGUID
c.configObj.AccountID = customerGUID // override config customerGUID
}
if clusterName != "" {
c.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
}
if c.configObj.CustomerGUID != "" {
if err := c.SetTenant(); err != nil {
fmt.Println(err)
}
}
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) GetCustomerGUID() string { return c.configObj.CustomerGUID }
func (c *ClusterConfig) SetCustomerGUID(customerGUID string) { c.configObj.CustomerGUID = customerGUID }
func (c *ClusterConfig) IsConfigFound() bool {
return existsConfigFile() || c.existsConfigMap()
}
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
func (c *ClusterConfig) IsConfigFound() bool { return existsConfigFile() || c.existsConfigMap() }
func (c *ClusterConfig) SetTenant() error {
@@ -220,17 +236,34 @@ func (c *ClusterConfig) SetTenant() error {
if err := getTenantConfigFromBE(c.backendAPI, c.configObj); err != nil {
return err
}
// update/create config
if c.existsConfigMap() {
c.updateConfigMap()
} else {
c.createConfigMap()
}
updateConfigFile(c.configObj)
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
}
@@ -291,27 +324,6 @@ func GetValueFromConfigJson(key string) (string, error) {
}
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{})
@@ -409,6 +421,10 @@ func readConfig(dat []byte) (*ConfigObj, error) {
if err := json.Unmarshal(dat, configObj); err != nil {
return nil, err
}
if configObj.AccountID == "" {
configObj.AccountID = configObj.CustomerGUID
}
configObj.CustomerGUID = ""
return configObj, nil
}
@@ -421,8 +437,7 @@ func (clusterConfig *ClusterConfig) IsSubmitted() bool {
func (clusterConfig *ClusterConfig) IsRegistered() bool {
// get from armoBE
clusterConfig.backendAPI.SetCustomerGUID(clusterConfig.GetCustomerGUID())
tenantResponse, err := clusterConfig.backendAPI.GetCustomerGUID()
tenantResponse, err := clusterConfig.backendAPI.GetTenant()
if err == nil && tenantResponse != nil {
if tenantResponse.AdminMail != "" { // this customer already belongs to some user
return true
@@ -431,16 +446,7 @@ func (clusterConfig *ClusterConfig) IsRegistered() bool {
return false
}
func (clusterConfig *ClusterConfig) DeleteConfig() error {
if err := clusterConfig.DeleteConfigMap(); err != nil {
return err
}
if err := DeleteConfigFile(); err != nil {
return err
}
return nil
}
func (clusterConfig *ClusterConfig) DeleteConfigMap() error {
func (clusterConfig *ClusterConfig) deleteConfigMap() error {
return clusterConfig.k8s.KubernetesClient.CoreV1().ConfigMaps(clusterConfig.configMapNamespace).Delete(context.Background(), clusterConfig.configMapName, metav1.DeleteOptions{})
}
@@ -465,3 +471,16 @@ func getConfigMapNamespace() string {
}
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
}
}

View File

@@ -13,11 +13,11 @@ type K8SResources map[string][]string
type OPASessionObj struct {
K8SResources *K8SResources // input k8s objects
Frameworks []reporthandling.Framework // list of frameworks to scan
Policies []reporthandling.Framework // list of frameworks to scan
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<rtesource ID>]<resource>
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<rtesource ID>]<resource result>
PostureReport *reporthandling.PostureReport // scan results v1
Report *reporthandlingv2.PostureReport // scan results v2
PostureReport *reporthandling.PostureReport // scan results v1 - Remove
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
RegoInputData RegoInputData // input passed to rgo for scanning. map[<control name>][<input arguments>]
}
@@ -25,7 +25,7 @@ type OPASessionObj struct {
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources) *OPASessionObj {
return &OPASessionObj{
Report: &reporthandlingv2.PostureReport{},
Frameworks: frameworks,
Policies: frameworks,
K8SResources: k8sResources,
AllResources: make(map[string]workloadinterface.IMetadata),
ResourcesResult: make(map[string]resourcesresults.Result),
@@ -38,7 +38,7 @@ func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SRe
func NewOPASessionObjMock() *OPASessionObj {
return &OPASessionObj{
Frameworks: nil,
Policies: nil,
K8SResources: nil,
AllResources: make(map[string]workloadinterface.IMetadata),
ResourcesResult: make(map[string]resourcesresults.Result),

View File

@@ -2,6 +2,7 @@ package cautils
import (
pkgcautils "github.com/armosec/utils-go/utils"
"golang.org/x/mod/semver"
"github.com/armosec/opa-utils/reporthandling"
)
@@ -15,7 +16,7 @@ func NewPolicies() *Policies {
func (policies *Policies) Set(frameworks []reporthandling.Framework, version string) {
for i := range frameworks {
if frameworks[i].Name != "" {
if frameworks[i].Name != "" && len(frameworks[i].Controls) > 0 {
policies.Frameworks = append(policies.Frameworks, frameworks[i].Name)
}
for j := range frameworks[i].Controls {
@@ -30,6 +31,7 @@ func (policies *Policies) Set(frameworks []reporthandling.Framework, version str
policies.Controls[frameworks[i].Controls[j].ControlID] = frameworks[i].Controls[j]
}
}
}
}
@@ -49,14 +51,15 @@ func ruleWithArmoOpaDependency(attributes map[string]interface{}) bool {
func isRuleKubescapeVersionCompatible(attributes map[string]interface{}, version string) bool {
if from, ok := attributes["useFromKubescapeVersion"]; ok && from != nil {
if version != "" {
if from.(string) > BuildNumber {
if semver.Compare(from.(string), BuildNumber) > 0 {
return false
}
}
}
if until, ok := attributes["useUntilKubescapeVersion"]; ok && until != nil {
if version != "" {
if until.(string) <= BuildNumber {
if semver.Compare(BuildNumber, until.(string)) >= 0 {
return false
}
} else {

View File

@@ -1,7 +1,6 @@
package cautils
import (
"fmt"
"os"
"time"
@@ -10,20 +9,10 @@ import (
"github.com/mattn/go-isatty"
)
var silent = false
func SetSilentMode(s bool) {
silent = s
}
func IsSilent() bool {
return silent
}
var FailureDisplay = color.New(color.Bold, color.FgHiRed).FprintfFunc()
var WarningDisplay = color.New(color.Bold, color.FgCyan).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 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()
@@ -31,41 +20,8 @@ var DescriptionDisplay = color.New(color.Faint, color.FgWhite).FprintfFunc()
var Spinner *spinner.Spinner
func ScanStartDisplay() {
if IsSilent() {
return
}
InfoDisplay(os.Stderr, "ARMO security scanner starting\n")
}
func SuccessTextDisplay(str string) {
if IsSilent() {
return
}
SuccessDisplay(os.Stderr, "[success] ")
SimpleDisplay(os.Stderr, fmt.Sprintf("%s\n", str))
}
func ErrorDisplay(str string) {
if IsSilent() {
return
}
FailureDisplay(os.Stderr, "[Error] ")
SimpleDisplay(os.Stderr, fmt.Sprintf("%s\n", str))
}
func ProgressTextDisplay(str string) {
if IsSilent() {
return
}
InfoDisplay(os.Stderr, "[progress] ")
SimpleDisplay(os.Stderr, fmt.Sprintf("%s\n", str))
}
func StartSpinner() {
if !IsSilent() && isatty.IsTerminal(os.Stdout.Fd()) {
if isatty.IsTerminal(os.Stdout.Fd()) {
Spinner = spinner.New(spinner.CharSets[7], 100*time.Millisecond) // Build our new spinner
Spinner.Start()
}

239
cautils/fileutils.go Normal file
View 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
}

View File

@@ -1,11 +1,12 @@
package resourcehandler
package cautils
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func onlineBoutiquePath() string {
@@ -14,32 +15,26 @@ func onlineBoutiquePath() string {
}
func TestListFiles(t *testing.T) {
workDir, err := os.Getwd()
fmt.Printf("\n------------------\n%s,%v\n--------------\n", workDir, err)
filesPath := onlineBoutiquePath()
fmt.Printf("\n------------------\n%s\n--------------\n", filesPath)
files, errs := listFiles([]string{filesPath})
if len(errs) > 0 {
t.Error(errs)
}
expected := 12
if len(files) != expected {
t.Errorf("wrong number of files, expected: %d, found: %d", expected, len(files))
}
assert.Equal(t, 0, len(errs))
assert.Equal(t, 12, len(files))
}
func TestLoadFiles(t *testing.T) {
files, _ := listFiles([]string{onlineBoutiquePath()})
loadFiles(files)
_, 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])
if err != nil {
t.Errorf("%v", err)
}
assert.NoError(t, err)
}
func TestMapResources(t *testing.T) {
// policyHandler := &PolicyHandler{}

View File

@@ -1,15 +1,18 @@
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"
"github.com/golang/glog"
)
// =======================================================================================================================
@@ -18,42 +21,58 @@ import (
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"
// Changes in this URLs variable names, or in the usage is affecting the build process! BE CAREFUL
armoERURL = "report.armo.cloud"
armoBEURL = "api.armo.cloud"
armoFEURL = "portal.armo.cloud"
armoAUTHURL = "auth.armo.cloud"
armoDevERURL = "report.eudev3.cyberarmorsoft.com"
armoDevBEURL = "eggdashbe.eudev3.cyberarmorsoft.com"
armoDevFEURL = "armoui-dev.eudev3.cyberarmorsoft.com"
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
erURL string
feURL string
customerGUID string
httpClient *http.Client
apiURL string
authURL string
erURL string
feURL string
accountID string
clientID string
secretKey string
feToken FeLoginResponse
authCookie string
loggedIn bool
}
var globalArmoAPIConnecctor *ArmoAPI
var globalArmoAPIConnector *ArmoAPI
func SetARMOAPIConnector(armoAPI *ArmoAPI) {
globalArmoAPIConnecctor = 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 globalArmoAPIConnecctor == nil {
glog.Error("returning nil API connector")
if globalArmoAPIConnector == nil {
// logger.L().Error("returning nil API connector")
SetARMOAPIConnector(NewARMOAPIProd())
}
return globalArmoAPIConnecctor
return globalArmoAPIConnector
}
func NewARMOAPIDev() *ArmoAPI {
apiObj := newArmoAPI()
apiObj.apiURL = armoDevBEURL
apiObj.authURL = armoDevAUTHURL
apiObj.erURL = armoDevERURL
apiObj.feURL = armoDevFEURL
@@ -66,16 +85,29 @@ func NewARMOAPIProd() *ArmoAPI {
apiObj.apiURL = armoBEURL
apiObj.erURL = armoERURL
apiObj.feURL = armoFEURL
apiObj.authURL = armoAUTHURL
return apiObj
}
func NewARMOAPICustomized(armoERURL, armoBEURL, armoFEURL string) *ArmoAPI {
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
}
@@ -83,22 +115,46 @@ func NewARMOAPICustomized(armoERURL, armoBEURL, armoFEURL string) *ArmoAPI {
func newArmoAPI() *ArmoAPI {
return &ArmoAPI{
httpClient: &http.Client{Timeout: time.Duration(61) * time.Second},
loggedIn: false,
}
}
func (armoAPI *ArmoAPI) SetCustomerGUID(customerGUID string) {
armoAPI.customerGUID = customerGUID
}
func (armoAPI *ArmoAPI) GetFrontendURL() string {
return armoAPI.feURL
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) GetReportReceiverURL() string {
return armoAPI.erURL
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 := HttpGetter(armoAPI.httpClient, armoAPI.getFrameworkURL(name), nil)
respStr, err := armoAPI.Get(armoAPI.getFrameworkURL(name), nil)
if err != nil {
return nil, nil
}
@@ -113,7 +169,7 @@ func (armoAPI *ArmoAPI) GetFramework(name string) (*reporthandling.Framework, er
}
func (armoAPI *ArmoAPI) GetFrameworks() ([]reporthandling.Framework, error) {
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getListFrameworkURL(), nil)
respStr, err := armoAPI.Get(armoAPI.getListFrameworkURL(), nil)
if err != nil {
return nil, nil
}
@@ -134,7 +190,7 @@ func (armoAPI *ArmoAPI) GetControl(policyName string) (*reporthandling.Control,
func (armoAPI *ArmoAPI) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
exceptions := []armotypes.PostureExceptionPolicy{}
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getExceptionsURL(clusterName), nil)
respStr, err := armoAPI.Get(armoAPI.getExceptionsURL(clusterName), nil)
if err != nil {
return nil, err
}
@@ -146,12 +202,12 @@ func (armoAPI *ArmoAPI) GetExceptions(clusterName string) ([]armotypes.PostureEx
return exceptions, nil
}
func (armoAPI *ArmoAPI) GetCustomerGUID() (*TenantResponse, error) {
url := armoAPI.getCustomerURL()
if armoAPI.customerGUID != "" {
url = fmt.Sprintf("%s?customerGUID=%s", url, armoAPI.customerGUID)
func (armoAPI *ArmoAPI) GetTenant() (*TenantResponse, error) {
url := armoAPI.getAccountURL()
if armoAPI.accountID != "" {
url = fmt.Sprintf("%s?customerGUID=%s", url, armoAPI.accountID)
}
respStr, err := HttpGetter(armoAPI.httpClient, url, nil)
respStr, err := armoAPI.Get(url, nil)
if err != nil {
return nil, err
}
@@ -159,17 +215,19 @@ func (armoAPI *ArmoAPI) GetCustomerGUID() (*TenantResponse, error) {
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.customerGUID == "" {
if armoAPI.accountID == "" {
return accountConfig, nil
}
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getAccountConfig(clusterName), nil)
respStr, err := armoAPI.Get(armoAPI.getAccountConfig(clusterName), nil)
if err != nil {
return nil, err
}
@@ -191,7 +249,7 @@ func (armoAPI *ArmoAPI) GetControlsInputs(clusterName string) (map[string][]stri
}
func (armoAPI *ArmoAPI) ListCustomFrameworks() ([]string, error) {
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getListFrameworkURL(), nil)
respStr, err := armoAPI.Get(armoAPI.getListFrameworkURL(), nil)
if err != nil {
return nil, err
}
@@ -211,7 +269,7 @@ func (armoAPI *ArmoAPI) ListCustomFrameworks() ([]string, error) {
}
func (armoAPI *ArmoAPI) ListFrameworks() ([]string, error) {
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getListFrameworkURL(), nil)
respStr, err := armoAPI.Get(armoAPI.getListFrameworkURL(), nil)
if err != nil {
return nil, err
}
@@ -236,9 +294,71 @@ func (armoAPI *ArmoAPI) ListControls(l ListType) ([]string, error) {
return nil, fmt.Errorf("control api is not public")
}
type TenantResponse struct {
TenantID string `json:"tenantId"`
Token string `json:"token"`
Expires string `json:"expires"`
AdminMail string `json:"adminMail,omitempty"`
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
}

View File

@@ -1,6 +1,10 @@
package getter
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
@@ -13,7 +17,7 @@ func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
u.Host = armoAPI.apiURL
u.Path = "api/v1/armoFrameworks"
q := u.Query()
q.Add("customerGUID", armoAPI.customerGUID)
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
if isNativeFramework(frameworkName) {
q.Add("frameworkName", strings.ToUpper(frameworkName))
} else {
@@ -31,7 +35,7 @@ func (armoAPI *ArmoAPI) getListFrameworkURL() string {
u.Host = armoAPI.apiURL
u.Path = "api/v1/armoFrameworks"
q := u.Query()
q.Add("customerGUID", armoAPI.customerGUID)
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
u.RawQuery = q.Encode()
return u.String()
@@ -43,7 +47,7 @@ func (armoAPI *ArmoAPI) getExceptionsURL(clusterName string) string {
u.Path = "api/v1/armoPostureExceptions"
q := u.Query()
q.Add("customerGUID", armoAPI.customerGUID)
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
// if clusterName != "" { // TODO - fix customer name support in Armo BE
// q.Add("clusterName", clusterName)
// }
@@ -52,6 +56,23 @@ func (armoAPI *ArmoAPI) getExceptionsURL(clusterName string) string {
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"
@@ -59,7 +80,7 @@ func (armoAPI *ArmoAPI) getAccountConfig(clusterName string) string {
u.Path = "api/v1/armoCustomerConfiguration"
q := u.Query()
q.Add("customerGUID", armoAPI.customerGUID)
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
if clusterName != "" { // TODO - fix customer name support in Armo BE
q.Add("clusterName", clusterName)
}
@@ -68,10 +89,81 @@ func (armoAPI *ArmoAPI) getAccountConfig(clusterName string) string {
return u.String()
}
func (armoAPI *ArmoAPI) getCustomerURL() 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"
}

View 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"`
}

View File

@@ -24,8 +24,15 @@ type IExceptionsGetter interface {
GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error)
}
type IBackend interface {
GetCustomerGUID() (*TenantResponse, error)
SetCustomerGUID(customerGUID string)
GetAccountID() string
GetClientID() string
GetSecretKey() string
SetAccountID(accountID string)
SetClientID(clientID string)
SetSecretKey(secretKey string)
GetTenant() (*TenantResponse, error)
}
type IControlsInputsGetter interface {

View File

@@ -13,15 +13,11 @@ import (
)
func GetDefaultPath(name string) string {
defaultfilePath := filepath.Join(DefaultLocalStore, name)
if homeDir, err := os.UserHomeDir(); err == nil {
defaultfilePath = filepath.Join(homeDir, defaultfilePath)
}
return defaultfilePath
return filepath.Join(DefaultLocalStore, name)
}
func SaveInFile(policy interface{}, pathStr string) error {
encodedData, err := json.Marshal(policy)
encodedData, err := json.MarshalIndent(policy, "", " ")
if err != nil {
return err
}
@@ -51,13 +47,31 @@ func JSONDecoder(origin string) *json.Decoder {
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
}
addHeaders(req, headers)
setHeaders(req, headers)
resp, err := httpClient.Do(req)
if err != nil {
@@ -76,7 +90,7 @@ func HttpPost(httpClient *http.Client, fullURL string, headers map[string]string
if err != nil {
return "", err
}
addHeaders(req, headers)
setHeaders(req, headers)
resp, err := httpClient.Do(req)
if err != nil {
return "", err
@@ -88,10 +102,10 @@ func HttpPost(httpClient *http.Client, fullURL string, headers map[string]string
return respStr, nil
}
func addHeaders(req *http.Request, headers map[string]string) {
func setHeaders(req *http.Request, headers map[string]string) {
if len(headers) >= 0 { // might be nil
for k, v := range headers {
req.Header.Add(k, v)
req.Header.Set(k, v)
}
}
}
@@ -106,21 +120,22 @@ func httpRespToString(resp *http.Response) (string, error) {
if resp.ContentLength > 0 {
strBuilder.Grow(int(resp.ContentLength))
}
bytesNum, err := io.Copy(&strBuilder, resp.Body)
_, err := io.Copy(&strBuilder, resp.Body)
respStr := strBuilder.String()
if err != nil {
respStrNewLen := len(respStr)
if respStrNewLen > 1024 {
respStrNewLen = 1024
}
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)
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 request failed. URL: '%s', HTTP-ERROR: '%s', BODY: '%s', HTTP-HEADERS: %v, HTTP-BODY-BUFFER-LENGTH: %v", resp.Request.URL.RequestURI(), resp.Status, respStr[:respStrNewLen], resp.Header, bytesNum)
err = fmt.Errorf("http-error: '%s', reason: '%s'", resp.Status, respStr[:respStrNewLen])
}
return respStr, err

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/armosec/armoapi-go/armotypes"
@@ -13,7 +14,15 @@ import (
// =======================================================================================================================
// ============================================== LoadPolicy =============================================================
// =======================================================================================================================
const DefaultLocalStore = ".kubescape"
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 {

View File

@@ -0,0 +1,31 @@
package helpers
import "time"
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} }
func Time() *StringObj {
return &StringObj{key: "time", value: time.Now().Format("2006-01-02 15:04:05")}
}

View 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
}

View 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
}

81
cautils/logger/methods.go Normal file
View File

@@ -0,0 +1,81 @@
package logger
import (
"os"
"strings"
"github.com/armosec/kubescape/cautils/logger/helpers"
"github.com/armosec/kubescape/cautils/logger/nonelogger"
"github.com/armosec/kubescape/cautils/logger/prettylogger"
"github.com/armosec/kubescape/cautils/logger/zaplogger"
)
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
LoggerName() string
}
var l ILogger
// Return initialized logger. If logger not initialized, will call InitializeLogger() with the default value
func L() ILogger {
if l == nil {
InitDefaultLogger()
}
return l
}
/* InitLogger initialize desired logger
Use:
InitLogger("<logger name>")
Supported logger names (call ListLoggersNames() for listing supported loggers)
- "zap": Logger from package "go.uber.org/zap"
- "pretty", "colorful": Human friendly colorful logger
- "none", "mock", "empty", "ignore": Logger will not print anything
Default:
- "pretty"
e.g.
InitLogger("none") -> will initialize the mock logger
*/
func InitLogger(loggerName string) {
switch strings.ToLower(loggerName) {
case zaplogger.LoggerName:
l = zaplogger.NewZapLogger()
case prettylogger.LoggerName, "colorful":
l = prettylogger.NewPrettyLogger()
case nonelogger.LoggerName, "mock", "empty", "ignore":
l = nonelogger.NewNoneLogger()
default:
InitDefaultLogger()
}
}
func InitDefaultLogger() {
l = prettylogger.NewPrettyLogger()
}
func DisableColor(flag bool) {
prettylogger.DisableColor(flag)
}
func ListLoggersNames() []string {
return []string{prettylogger.LoggerName, zaplogger.LoggerName, nonelogger.LoggerName}
}

View File

@@ -0,0 +1,28 @@
package nonelogger
import (
"os"
"github.com/armosec/kubescape/cautils/logger/helpers"
)
const LoggerName string = "none"
type NoneLogger struct {
}
func NewNoneLogger() *NoneLogger {
return &NoneLogger{}
}
func (nl *NoneLogger) GetLevel() string { return "" }
func (nl *NoneLogger) LoggerName() string { return LoggerName }
func (nl *NoneLogger) SetWriter(w *os.File) {}
func (nl *NoneLogger) GetWriter() *os.File { return nil }
func (nl *NoneLogger) SetLevel(level string) error { return nil }
func (nl *NoneLogger) Fatal(msg string, details ...helpers.IDetails) {}
func (nl *NoneLogger) Error(msg string, details ...helpers.IDetails) {}
func (nl *NoneLogger) Warning(msg string, details ...helpers.IDetails) {}
func (nl *NoneLogger) Success(msg string, details ...helpers.IDetails) {}
func (nl *NoneLogger) Info(msg string, details ...helpers.IDetails) {}
func (nl *NoneLogger) Debug(msg string, details ...helpers.IDetails) {}

View File

@@ -0,0 +1,37 @@
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
}
func DisableColor(flag bool) {
if flag {
color.NoColor = true
}
}

View File

@@ -0,0 +1,82 @@
package prettylogger
import (
"fmt"
"os"
"sync"
"github.com/armosec/kubescape/cautils/logger/helpers"
)
const LoggerName string = "pretty"
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) LoggerName() string { return LoggerName }
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: %v", details[i].Key(), details[i].Value())
if i < len(details)-1 {
s += "; "
}
}
return s
}

View File

@@ -0,0 +1,79 @@
package zaplogger
import (
"os"
"github.com/armosec/kubescape/cautils/logger/helpers"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
const LoggerName string = "zap"
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 (zl *ZapLogger) LoggerName() string { return LoggerName }
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
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/rbac-utils/rbacscanner"
"github.com/armosec/rbac-utils/rbacutils"
uuid "github.com/satori/go.uuid"
"github.com/google/uuid"
)
type RBACObjects struct {
@@ -21,7 +21,7 @@ func NewRBACObjects(scanner *rbacscanner.RbacScannerFromK8sAPI) *RBACObjects {
func (rbacObjects *RBACObjects) SetResourcesReport() (*reporthandling.PostureReport, error) {
return &reporthandling.PostureReport{
ReportID: uuid.NewV4().String(),
ReportID: uuid.NewString(),
ReportGenerationTime: time.Now().UTC(),
CustomerGUID: rbacObjects.scanner.CustomerGUID,
ClusterName: rbacObjects.scanner.ClusterName,
@@ -51,23 +51,22 @@ func (rbacObjects *RBACObjects) rbacObjectsToResources(resources *rbacutils.Rbac
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
*/
// 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)

View File

@@ -5,7 +5,6 @@ import (
"github.com/armosec/opa-utils/reporthandling"
helpersv1 "github.com/armosec/opa-utils/reporthandling/helpers/v1"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/armosec/opa-utils/score"
)
func ReportV2ToV1(opaSessionObj *OPASessionObj) {
@@ -22,7 +21,6 @@ func ReportV2ToV1(opaSessionObj *OPASessionObj) {
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)
@@ -30,10 +28,10 @@ func ReportV2ToV1(opaSessionObj *OPASessionObj) {
} else {
fwv1 := reporthandling.FrameworkReport{}
fwv1.Name = ""
fwv1.Score = 0
fwv1.ControlReports = append(fwv1.ControlReports, controlReportV2ToV1(opaSessionObj, "", opaSessionObj.Report.SummaryDetails.Controls)...)
frameworks = append(frameworks, fwv1)
fwv1.Score = opaSessionObj.Report.SummaryDetails.Score
}
// // remove unused data
@@ -49,36 +47,14 @@ func ReportV2ToV1(opaSessionObj *OPASessionObj) {
reporthandling.SetUniqueResourcesCounter(&frameworks[f])
// set default score
reporthandling.SetDefaultScore(&frameworks[f])
// reporthandling.SetDefaultScore(&frameworks[f])
}
// update score
scoreutil := score.NewScore(opaSessionObj.AllResources)
scoreutil.Calculate(frameworks)
// // update score
// scoreutil := score.NewScore(opaSessionObj.AllResources)
// scoreutil.Calculate(frameworks)
opaSessionObj.PostureReport.FrameworkReports = frameworks
// opaSessionObj.Report.SummaryDetails.Score = 0
// for i := range frameworks {
// for j := range frameworks[i].ControlReports {
// // frameworks[i].ControlReports[j].Score
// for w := range opaSessionObj.Report.SummaryDetails.Frameworks {
// if opaSessionObj.Report.SummaryDetails.Frameworks[w].Name == frameworks[i].Name {
// opaSessionObj.Report.SummaryDetails.Frameworks[w].Score = frameworks[i].Score
// }
// if c, ok := opaSessionObj.Report.SummaryDetails.Frameworks[w].Controls[frameworks[i].ControlReports[j].ControlID]; ok {
// c.Score = frameworks[i].ControlReports[j].Score
// opaSessionObj.Report.SummaryDetails.Frameworks[w].Controls[frameworks[i].ControlReports[j].ControlID] = c
// }
// }
// if c, ok := opaSessionObj.Report.SummaryDetails.Controls[frameworks[i].ControlReports[j].ControlID]; ok {
// c.Score = frameworks[i].ControlReports[j].Score
// opaSessionObj.Report.SummaryDetails.Controls[frameworks[i].ControlReports[j].ControlID] = c
// }
// }
// opaSessionObj.Report.SummaryDetails.Score += opaSessionObj.PostureReport.FrameworkReports[i].Score
// }
// opaSessionObj.Report.SummaryDetails.Score /= float32(len(opaSessionObj.Report.SummaryDetails.Frameworks))
}
func controlReportV2ToV1(opaSessionObj *OPASessionObj, frameworkName string, controls map[string]reportsummary.ControlSummary) []reporthandling.ControlReport {
@@ -88,9 +64,9 @@ func controlReportV2ToV1(opaSessionObj *OPASessionObj, frameworkName string, con
crv1.ControlID = controlID
crv1.BaseScore = crv2.ScoreFactor
crv1.Name = crv2.GetName()
crv1.Score = crv2.GetScore()
crv1.Control_ID = controlID
// crv1.Attributes = crv2.
crv1.Score = crv2.GetScore()
// TODO - add fields
crv1.Description = crv2.Description
@@ -120,7 +96,12 @@ func controlReportV2ToV1(opaSessionObj *OPASessionObj, frameworkName string, con
ruleResponse := reporthandling.RuleResponse{}
ruleResponse.Rulename = rulev2.GetName()
for i := range rulev2.Paths {
ruleResponse.FailedPaths = append(ruleResponse.FailedPaths, rulev2.Paths[i].FailedPath)
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 {

View File

@@ -4,12 +4,13 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"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"
)
@@ -52,6 +53,13 @@ func (bpf *BoolPtrFlag) Set(val string) error {
return nil
}
type RootInfo struct {
Logger string // logger level
LoggerName string // logger name ("pretty"/"zap"/"none")
CacheDir string // cached dir
DisableColor bool // Disable Color
}
type ScanInfo struct {
Getters
PolicyIdentifier []reporthandling.PolicyIdentifier
@@ -63,13 +71,16 @@ type ScanInfo struct {
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 uint16 // Failure score threshold
FailThreshold float32 // Failure score threshold
Submit bool // Submit results to Armo BE
HostSensor BoolPtrFlag // Deploy ARMO K8s host sensor to collect data from certain controls
ReportID string // Report id of the current scan
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
@@ -103,7 +114,7 @@ func (scanInfo *ScanInfo) setUseArtifactsFrom() {
// set frameworks files
files, err := ioutil.ReadDir(scanInfo.UseArtifactsFrom)
if err != nil {
log.Fatal(err)
logger.L().Fatal("failed to read files from directory", helpers.String("dir", scanInfo.UseArtifactsFrom), helpers.Error(err))
}
framework := &reporthandling.Framework{}
for _, f := range files {
@@ -152,6 +163,11 @@ func (scanInfo *ScanInfo) setOutputFile() {
scanInfo.Output += ".xml"
}
}
if scanInfo.Format == "pdf" {
if filepath.Ext(scanInfo.Output) != ".pdf" {
scanInfo.Output += ".pdf"
}
}
}
func (scanInfo *ScanInfo) GetScanningEnvironment() string {

View File

@@ -7,7 +7,10 @@ import (
"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"
"golang.org/x/mod/semver"
)
const SKIP_VERSION_CHECK = "KUBESCAPE_SKIP_UPDATE_CHECK"
@@ -22,7 +25,7 @@ type IVersionCheckHandler interface {
func NewIVersionCheckHandler() IVersionCheckHandler {
if BuildNumber == "" {
WarningDisplay(os.Stderr, "Warning: unknown build number, this might affect your scan results. Please make sure you are updated to latest version.\n")
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()
@@ -78,14 +81,14 @@ func NewVersionCheckRequest(buildNumber, frameworkName, frameworkVersion, scanni
}
func (v *VersionCheckHandlerMock) CheckLatestVersion(versionData *VersionCheckRequest) error {
fmt.Println("Skipping version check")
logger.L().Info("Skipping version check")
return nil
}
func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckRequest) error {
defer func() {
if err := recover(); err != nil {
WarningDisplay(os.Stderr, "failed to get latest version\n")
logger.L().Warning("failed to get latest version", helpers.Interface("error", err))
}
}()
@@ -95,8 +98,8 @@ func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckReques
}
if latestVersion.ClientUpdate != "" {
if BuildNumber != "" && BuildNumber < latestVersion.ClientUpdate {
WarningDisplay(os.Stderr, warningMessage(latestVersion.Client, latestVersion.ClientUpdate), "\n")
if BuildNumber != "" && semver.Compare(BuildNumber, latestVersion.ClientUpdate) >= 0 {
logger.L().Warning(warningMessage(latestVersion.ClientUpdate))
}
}
@@ -106,7 +109,7 @@ func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckReques
// }
if latestVersion.Message != "" {
InfoDisplay(os.Stderr, latestVersion.Message, "\n")
logger.L().Info(latestVersion.Message)
}
return nil
@@ -131,6 +134,6 @@ func (v *VersionCheckHandler) getLatestVersion(versionData *VersionCheckRequest)
return vResp, nil
}
func warningMessage(kind, release string) string {
return fmt.Sprintf("Warning: '%s' is not updated to the latest release: '%s'", kind, release)
func warningMessage(release string) string {
return fmt.Sprintf("current version '%s' is not updated to the latest release: '%s'", BuildNumber, release)
}

View File

@@ -0,0 +1,38 @@
package cautils
import (
"testing"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/opa-utils/reporthandling"
"github.com/stretchr/testify/assert"
)
func TestGetKubernetesObjects(t *testing.T) {
}
var rule_v1_0_131 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useUntilKubescapeVersion": "v1.0.132"}}}
var rule_v1_0_132 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.132", "useUntilKubescapeVersion": "v1.0.133"}}}
var rule_v1_0_133 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.133", "useUntilKubescapeVersion": "v1.0.134"}}}
var rule_v1_0_134 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.134"}}}
func TestIsRuleKubescapeVersionCompatible(t *testing.T) {
// local build- no build number
// should use only rules that don't have "until"
buildNumberMock := ""
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_131.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_132.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_133.Attributes, buildNumberMock))
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
// should only use rules that version is in range of use
buildNumberMock = "v1.0.133"
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_131.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_132.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_133.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
}

View File

@@ -0,0 +1,7 @@
package clihandler
func CliDelete() error {
tenant := getTenantConfig("", "", getKubernetesApi()) // change k8sinterface
return tenant.DeleteCachedConfig()
}

35
clihandler/clidelete.go Normal file
View File

@@ -0,0 +1,35 @@
package clihandler
import (
"fmt"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
)
func DeleteExceptions(accountID string, exceptions []string) error {
// load cached config
getTenantConfig(accountID, "", getKubernetesApi())
// login kubescape SaaS
armoAPI := getter.GetArmoAPIConnector()
if err := armoAPI.Login(); err != nil {
return err
}
for i := range exceptions {
exceptionName := exceptions[i]
if exceptionName == "" {
continue
}
logger.L().Info("Deleting exception", helpers.String("name", exceptionName))
if err := armoAPI.DeleteException(exceptionName); err != nil {
return fmt.Errorf("failed to delete exception '%s', reason: %s", exceptionName, err.Error())
}
logger.L().Success("Exception deleted successfully")
}
return nil
}

View File

@@ -8,6 +8,8 @@ import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
)
var downloadFunc = map[string]func(*cautils.DownloadInfo) error{
@@ -67,7 +69,7 @@ func downloadArtifacts(downloadInfo *cautils.DownloadInfo) error {
}
for artifact := range artifacts {
if err := downloadArtifact(&cautils.DownloadInfo{Target: artifact, Path: downloadInfo.Path, FileName: fmt.Sprintf("%s.json", artifact)}, artifacts); err != nil {
fmt.Printf("error downloading %s, error: %s", artifact, err)
logger.L().Error("error downloading", helpers.String("artifact", artifact), helpers.Error(err))
}
}
return nil
@@ -75,7 +77,8 @@ func downloadArtifacts(downloadInfo *cautils.DownloadInfo) error {
func downloadConfigInputs(downloadInfo *cautils.DownloadInfo) error {
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
controlsInputsGetter := getConfigInputsGetter(downloadInfo.Name, tenant.GetCustomerGUID(), nil)
controlsInputsGetter := getConfigInputsGetter(downloadInfo.Name, tenant.GetAccountID(), nil)
controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetClusterName())
if err != nil {
return err
@@ -88,16 +91,17 @@ func downloadConfigInputs(downloadInfo *cautils.DownloadInfo) error {
if err != nil {
return err
}
fmt.Printf("'%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("path", filepath.Join(downloadInfo.Path, downloadInfo.FileName)))
return nil
}
func downloadExceptions(downloadInfo *cautils.DownloadInfo) error {
var err error
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
exceptionsGetter := getExceptionsGetter("")
exceptions := []armotypes.PostureExceptionPolicy{}
if tenant.GetCustomerGUID() != "" {
if tenant.GetAccountID() != "" {
exceptions, err = exceptionsGetter.GetExceptions(tenant.GetClusterName())
if err != nil {
return err
@@ -111,14 +115,15 @@ func downloadExceptions(downloadInfo *cautils.DownloadInfo) error {
if err != nil {
return err
}
fmt.Printf("'%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("path", filepath.Join(downloadInfo.Path, downloadInfo.FileName)))
return nil
}
func downloadFramework(downloadInfo *cautils.DownloadInfo) error {
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
g := getPolicyGetter(nil, tenant.GetCustomerGUID(), true, nil)
g := getPolicyGetter(nil, tenant.GetAccountID(), true, nil)
if downloadInfo.Name == "" {
// if framework name not specified - download all frameworks
@@ -127,11 +132,12 @@ func downloadFramework(downloadInfo *cautils.DownloadInfo) error {
return err
}
for _, fw := range frameworks {
err = getter.SaveInFile(fw, filepath.Join(downloadInfo.Path, (strings.ToLower(fw.Name)+".json")))
downloadTo := filepath.Join(downloadInfo.Path, (strings.ToLower(fw.Name) + ".json"))
err = getter.SaveInFile(fw, downloadTo)
if err != nil {
return err
}
fmt.Printf("'%s': '%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, fw.Name, filepath.Join(downloadInfo.Path, (strings.ToLower(fw.Name)+".json")))
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", fw.Name), helpers.String("path", downloadTo))
}
// return fmt.Errorf("missing framework name")
} else {
@@ -142,11 +148,12 @@ func downloadFramework(downloadInfo *cautils.DownloadInfo) error {
if err != nil {
return err
}
err = getter.SaveInFile(framework, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
downloadTo := filepath.Join(downloadInfo.Path, downloadInfo.FileName)
err = getter.SaveInFile(framework, downloadTo)
if err != nil {
return err
}
fmt.Printf("'%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", framework.Name), helpers.String("path", downloadTo))
}
return nil
}
@@ -154,7 +161,8 @@ func downloadFramework(downloadInfo *cautils.DownloadInfo) error {
func downloadControl(downloadInfo *cautils.DownloadInfo) error {
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
g := getPolicyGetter(nil, tenant.GetCustomerGUID(), false, nil)
g := getPolicyGetter(nil, tenant.GetAccountID(), false, nil)
if downloadInfo.Name == "" {
// TODO - support
@@ -167,10 +175,11 @@ func downloadControl(downloadInfo *cautils.DownloadInfo) error {
if err != nil {
return err
}
err = getter.SaveInFile(controls, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
downloadTo := filepath.Join(downloadInfo.Path, downloadInfo.FileName)
err = getter.SaveInFile(controls, downloadTo)
if err != nil {
return err
}
fmt.Printf("'%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", downloadInfo.Name), helpers.String("path", downloadTo))
return nil
}

View File

@@ -1,17 +1,24 @@
package clihandler
import (
"encoding/json"
"fmt"
"sort"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/clihandler/cliobjects"
)
var listFunc = map[string]func(*cautils.ListPolicies) ([]string, error){
var listFunc = map[string]func(*cliobjects.ListPolicies) ([]string, error){
"controls": listControls,
"frameworks": listFrameworks,
"exceptions": listExceptions,
}
var listFormatFunc = map[string]func(*cliobjects.ListPolicies, []string){
"pretty-print": prettyPrintListFormat,
"json": jsonListFormat,
}
func ListSupportCommands() []string {
@@ -21,7 +28,7 @@ func ListSupportCommands() []string {
}
return commands
}
func CliList(listPolicies *cautils.ListPolicies) error {
func CliList(listPolicies *cliobjects.ListPolicies) error {
if f, ok := listFunc[listPolicies.Target]; ok {
policies, err := f(listPolicies)
if err != nil {
@@ -29,30 +36,53 @@ func CliList(listPolicies *cautils.ListPolicies) error {
}
sort.Strings(policies)
sep := "\n * "
usageCmd := strings.TrimSuffix(listPolicies.Target, "s")
fmt.Printf("Supported %s:%s%s\n", listPolicies.Target, sep, strings.Join(policies, sep))
fmt.Printf("\nUseage:\n")
fmt.Printf("$ kubescape scan %s \"name\"\n", usageCmd)
fmt.Printf("$ kubescape scan %s \"name-0\",\"name-1\"\n\n", usageCmd)
listFormatFunc[listPolicies.Format](listPolicies, policies)
return nil
}
return fmt.Errorf("unknown command to download")
}
func listFrameworks(listPolicies *cautils.ListPolicies) ([]string, error) {
func listFrameworks(listPolicies *cliobjects.ListPolicies) ([]string, error) {
tenant := getTenantConfig(listPolicies.Account, "", getKubernetesApi()) // change k8sinterface
g := getPolicyGetter(nil, tenant.GetCustomerGUID(), true, nil)
g := getPolicyGetter(nil, tenant.GetAccountID(), true, nil)
return listFrameworksNames(g), nil
}
func listControls(listPolicies *cautils.ListPolicies) ([]string, error) {
func listControls(listPolicies *cliobjects.ListPolicies) ([]string, error) {
tenant := getTenantConfig(listPolicies.Account, "", getKubernetesApi()) // change k8sinterface
g := getPolicyGetter(nil, tenant.GetCustomerGUID(), false, nil)
g := getPolicyGetter(nil, tenant.GetAccountID(), false, nil)
l := getter.ListName
if listPolicies.ListIDs {
l = getter.ListID
}
return g.ListControls(l)
}
func listExceptions(listPolicies *cliobjects.ListPolicies) ([]string, error) {
// load tenant config
getTenantConfig(listPolicies.Account, "", getKubernetesApi())
var exceptionsNames []string
armoAPI := getExceptionsGetter("")
exceptions, err := armoAPI.GetExceptions("")
if err != nil {
return exceptionsNames, err
}
for i := range exceptions {
exceptionsNames = append(exceptionsNames, exceptions[i].Name)
}
return exceptionsNames, nil
}
func prettyPrintListFormat(listPolicies *cliobjects.ListPolicies, policies []string) {
sep := "\n * "
fmt.Printf("Supported %s:%s%s\n", listPolicies.Target, sep, strings.Join(policies, sep))
}
func jsonListFormat(listPolicies *cliobjects.ListPolicies, policies []string) {
j, _ := json.MarshalIndent(policies, "", " ")
fmt.Printf("%s\n", j)
}

View File

@@ -1,7 +1,8 @@
package cautils
package cliobjects
type ListPolicies struct {
Target string
ListIDs bool
Account string
Format string
}

View File

@@ -0,0 +1,7 @@
package cliobjects
type SetConfig struct {
Account string
ClientID string
SecretKey string
}

View File

@@ -0,0 +1,9 @@
package cliobjects
type Submit struct {
Account string
}
type Delete struct {
Account string
}

22
clihandler/cliset.go Normal file
View File

@@ -0,0 +1,22 @@
package clihandler
import (
"github.com/armosec/kubescape/clihandler/cliobjects"
)
func CliSetConfig(setConfig *cliobjects.SetConfig) error {
tenant := getTenantConfig("", "", getKubernetesApi())
if setConfig.Account != "" {
tenant.GetConfigObj().AccountID = setConfig.Account
}
if setConfig.SecretKey != "" {
tenant.GetConfigObj().SecretKey = setConfig.SecretKey
}
if setConfig.ClientID != "" {
tenant.GetConfigObj().ClientID = setConfig.ClientID
}
return tenant.UpdateCachedConfig()
}

60
clihandler/clisubmit.go Normal file
View File

@@ -0,0 +1,60 @@
package clihandler
import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
)
func Submit(submitInterfaces cliinterfaces.SubmitInterfaces) error {
// list resources
postureReport, err := submitInterfaces.SubmitObjects.SetResourcesReport()
if err != nil {
return err
}
allresources, err := submitInterfaces.SubmitObjects.ListAllResources()
if err != nil {
return err
}
// report
if err := submitInterfaces.Reporter.ActionSendReport(&cautils.OPASessionObj{PostureReport: postureReport, AllResources: allresources}); err != nil {
return err
}
logger.L().Success("Data has been submitted successfully")
submitInterfaces.Reporter.DisplayReportURL()
return nil
}
func SubmitExceptions(accountID, excPath string) error {
logger.L().Info("submitting exceptions", helpers.String("path", excPath))
// load cached config
tenantConfig := getTenantConfig(accountID, "", getKubernetesApi())
if err := tenantConfig.SetTenant(); err != nil {
logger.L().Error("failed setting account ID", helpers.Error(err))
}
// load exceptions from file
loader := getter.NewLoadPolicy([]string{excPath})
exceptions, err := loader.GetExceptions("")
if err != nil {
return err
}
// login kubescape SaaS
armoAPI := getter.GetArmoAPIConnector()
if err := armoAPI.Login(); err != nil {
return err
}
if err := armoAPI.PostExceptions(exceptions); err != nil {
return err
}
logger.L().Success("Exceptions submitted successfully")
return nil
}

12
clihandler/cliview.go Normal file
View File

@@ -0,0 +1,12 @@
package clihandler
import (
"fmt"
"os"
)
func CliView() error {
tenant := getTenantConfig("", "", getKubernetesApi()) // change k8sinterface
fmt.Fprintf(os.Stderr, "%s\n", tenant.GetConfigObj().Config())
return nil
}

View File

@@ -1,18 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
)
// clusterCmd represents the cluster command
var clusterCmd = &cobra.Command{
Use: "cluster",
Short: "Set configuration for cluster",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
func init() {
configCmd.AddCommand(clusterCmd)
}

View File

@@ -1,49 +0,0 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/spf13/cobra"
)
var getCmd = &cobra.Command{
Use: "get <key>",
Short: "Get configuration in cluster",
Long: ``,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 || len(args) > 1 {
return fmt.Errorf("requires one argument")
}
keyValue := strings.Split(args[0], "=")
if len(keyValue) != 1 {
return fmt.Errorf("requires one argument")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
keyValue := strings.Split(args[0], "=")
key := keyValue[0]
k8s := k8sinterface.NewKubernetesApi()
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, "")
val, err := clusterConfig.GetValueByKeyFromConfigMap(key)
if err != nil {
if err.Error() == "value does not exist." {
fmt.Printf("Could net get value from configmap, reason: %s\n", err)
return nil
}
return err
}
fmt.Println(key + "=" + val)
return nil
},
}
func init() {
clusterCmd.AddCommand(getCmd)
}

View File

@@ -1,44 +0,0 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/spf13/cobra"
)
var setCmd = &cobra.Command{
Use: "set <key>=<value>",
Short: "Set configuration in cluster",
Long: ``,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 || len(args) > 1 {
return fmt.Errorf("requires one argument: <key>=<value>")
}
keyValue := strings.Split(args[0], "=")
if len(keyValue) != 2 {
return fmt.Errorf("requires one argument: <key>=<value>")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
keyValue := strings.Split(args[0], "=")
key := keyValue[0]
data := keyValue[1]
k8s := k8sinterface.NewKubernetesApi()
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, "")
if err := clusterConfig.SetKeyValueInConfigmap(key, data); err != nil {
return err
}
fmt.Println("Value added successfully.")
return nil
},
}
func init() {
clusterCmd.AddCommand(setCmd)
}

View File

@@ -0,0 +1,49 @@
package completion
import (
"os"
"strings"
"github.com/spf13/cobra"
)
var completionCmdExamples = `
# Enable BASH shell autocompletion
$ source <(kubescape completion bash)
$ echo 'source <(kubescape completion bash)' >> ~/.bashrc
# Enable ZSH shell autocompletion
$ source <(kubectl completion zsh)
$ echo 'source <(kubectl completion zsh)' >> "${fpath[1]}/_kubectl"
`
func GetCompletionCmd() *cobra.Command {
completionCmd := &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate autocompletion script",
Long: "To load completions",
Example: completionCmdExamples,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
Run: func(cmd *cobra.Command, args []string) {
switch strings.ToLower(args[0]) {
case "bash":
cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
}
},
}
return completionCmd
}
// func init() {
// rootCmd.AddCommand(completionCmd)
// }

View File

@@ -1,18 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
)
// configCmd represents the config command
var configCmd = &cobra.Command{
Use: "config",
Short: "Set configuration",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
func init() {
rootCmd.AddCommand(configCmd)
}

View File

@@ -0,0 +1,44 @@
package config
import (
"github.com/spf13/cobra"
)
var (
configExample = `
# View cached configurations
kubescape config view
# Delete cached configurations
kubescape config delete
# Set cached configurations
kubescape config set --help
`
setConfigExample = `
# Set account id
kubescape config set accountID <account id>
# Set client id
kubescape config set clientID <client id>
# Set access key
kubescape config set secretKey <access key>
`
)
func GetConfigCmd() *cobra.Command {
// configCmd represents the config command
configCmd := &cobra.Command{
Use: "config",
Short: "handle cached configurations",
Example: configExample,
}
configCmd.AddCommand(getDeleteCmd())
configCmd.AddCommand(getSetCmd())
configCmd.AddCommand(getSetCmd())
return configCmd
}

View File

@@ -0,0 +1,20 @@
package config
import (
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/spf13/cobra"
)
func getDeleteCmd() *cobra.Command {
return &cobra.Command{
Use: "delete",
Short: "Delete cached configurations",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
if err := clihandler.CliDelete(); err != nil {
logger.L().Fatal(err.Error())
}
},
}
}

View File

@@ -0,0 +1,69 @@
package config
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliobjects"
"github.com/spf13/cobra"
)
func getSetCmd() *cobra.Command {
// configCmd represents the config command
configSetCmd := &cobra.Command{
Use: "set",
Short: fmt.Sprintf("Set configurations, supported: %s", strings.Join(stringKeysToSlice(supportConfigSet), "/")),
Example: setConfigExample,
ValidArgs: stringKeysToSlice(supportConfigSet),
RunE: func(cmd *cobra.Command, args []string) error {
setConfig, err := parseSetArgs(args)
if err != nil {
return err
}
if err := clihandler.CliSetConfig(setConfig); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
return configSetCmd
}
var supportConfigSet = map[string]func(*cliobjects.SetConfig, string){
"accountID": func(s *cliobjects.SetConfig, account string) { s.Account = account },
"clientID": func(s *cliobjects.SetConfig, clientID string) { s.ClientID = clientID },
"secretKey": func(s *cliobjects.SetConfig, secretKey string) { s.SecretKey = secretKey },
}
func stringKeysToSlice(m map[string]func(*cliobjects.SetConfig, string)) []string {
l := []string{}
for i := range m {
l = append(l, i)
}
return l
}
func parseSetArgs(args []string) (*cliobjects.SetConfig, error) {
var key string
var value string
if len(args) == 1 {
if keyValue := strings.Split(args[0], "="); len(keyValue) == 2 {
key = keyValue[0]
value = keyValue[1]
}
} else if len(args) == 2 {
key = args[0]
value = args[1]
}
setConfig := &cliobjects.SetConfig{}
if setConfigFunc, ok := supportConfigSet[key]; ok {
setConfigFunc(setConfig, value)
} else {
return setConfig, fmt.Errorf("key '%s' unknown . supported: %s", key, strings.Join(stringKeysToSlice(supportConfigSet), "/"))
}
return setConfig, nil
}

View File

@@ -0,0 +1,22 @@
package config
import (
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/spf13/cobra"
)
func getViewCmd() *cobra.Command {
// configCmd represents the config command
return &cobra.Command{
Use: "view",
Short: "View cached configurations",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
if err := clihandler.CliView(); err != nil {
logger.L().Fatal(err.Error())
}
},
}
}

View File

@@ -1,122 +0,0 @@
package cmd
import (
"fmt"
"io"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/opa-utils/reporthandling"
"github.com/spf13/cobra"
)
var (
controlExample = `
# Scan the 'privileged container' control
kubescape scan control "privileged container"
# Scan list of controls separated with a comma
kubescape scan control "privileged container","allowed hostpath"
# Scan list of controls using the control ID separated with a comma
kubescape scan control C-0058,C-0057
Run 'kubescape list controls' for the list of supported controls
Control documentation:
https://hub.armo.cloud/docs/controls
`
)
// controlCmd represents the control command
var controlCmd = &cobra.Command{
Use: "control <control names list>/<control ids list>",
Short: "The controls you wish to use. Run 'kubescape list controls' for the list of supported controls",
Example: controlExample,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
controls := strings.Split(args[0], ",")
if len(controls) > 1 {
if controls[1] == "" {
return fmt.Errorf("usage: <control-0>,<control-1>")
}
}
} else {
return fmt.Errorf("requires at least one control name")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
flagValidationControl()
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
if len(args) == 0 {
// scanInfo.SetPolicyIdentifiers(getter.NativeFrameworks, reporthandling.KindFramework)
scanInfo.ScanAll = true
} else { // expected control or list of control sepparated by ","
// Read controls from input args
scanInfo.SetPolicyIdentifiers(strings.Split(args[0], ","), reporthandling.KindControl)
if len(args) > 1 {
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
} else { // store stdin to file - do NOT move to separate function !!
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
return err
}
scanInfo.InputPatterns = []string{tempFile.Name()}
}
}
}
scanInfo.FrameworkScan = false
scanInfo.Init()
cautils.SetSilentMode(scanInfo.Silent)
err := clihandler.ScanCliSetup(&scanInfo)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
return nil
},
}
func init() {
scanInfo = cautils.ScanInfo{}
scanCmd.AddCommand(controlCmd)
}
func flagValidationControl() {
if 100 < scanInfo.FailThreshold {
fmt.Println("bad argument: out of range threshold")
os.Exit(1)
}
}
func setScanForFirstControl(controls []string) []reporthandling.PolicyIdentifier {
newPolicy := reporthandling.PolicyIdentifier{}
newPolicy.Kind = reporthandling.KindControl
newPolicy.Name = controls[0]
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
return scanInfo.PolicyIdentifier
}
func SetScanForGivenControls(controls []string) []reporthandling.PolicyIdentifier {
for _, control := range controls {
control := strings.TrimLeft(control, " ")
newPolicy := reporthandling.PolicyIdentifier{}
newPolicy.Kind = reporthandling.KindControl
newPolicy.Name = control
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
}
return scanInfo.PolicyIdentifier
}

View File

@@ -0,0 +1,31 @@
package delete
import (
"github.com/armosec/kubescape/clihandler/cliobjects"
"github.com/spf13/cobra"
)
var deleteInfo cliobjects.Delete
var deleteExceptionsExamples = `
# Delete single exception
kubescape delete exceptions "exception name"
# Delete multiple exceptions
kubescape delete exceptions "first exception;second exception;third exception"
`
func GetDeleteCmd() *cobra.Command {
var deleteCmd = &cobra.Command{
Use: "delete <command>",
Short: "Delete configurations in Kubescape SaaS version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
deleteCmd.AddCommand(getExceptionsCmd())
return deleteCmd
}

View File

@@ -0,0 +1,33 @@
package delete
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/spf13/cobra"
)
func getExceptionsCmd() *cobra.Command {
return &cobra.Command{
Use: "exceptions <exception name>",
Short: "Delete exceptions from Kubescape SaaS version. Run 'kubescape list exceptions' for all exceptions names",
Example: deleteExceptionsExamples,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("missing exceptions names")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
exceptionsNames := strings.Split(args[0], ";")
if len(exceptionsNames) == 0 {
logger.L().Fatal("missing exceptions names")
}
if err := clihandler.DeleteExceptions(deleteInfo.Account, exceptionsNames); err != nil {
logger.L().Fatal(err.Error())
}
},
}
}

View File

@@ -1,11 +1,12 @@
package cmd
package download
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/spf13/cobra"
)
@@ -16,12 +17,12 @@ var (
downloadExample = `
# Download all artifacts and save them in the default path (~/.kubescape)
kubescape download artifacts
download
# Download all artifacts and save them in /tmp path
kubescape download artifacts --output /tmp
# Download the NSA framework. Run 'kubescape list frameworks' for all frameworks names
kubescape download frameworks nsa
kubescape download framework nsa
# Download the "Allowed hostPath" control. Run 'kubescape list controls' for all controls names
kubescape download control "Allowed hostPath"
@@ -37,39 +38,40 @@ var (
`
)
var downloadCmd = &cobra.Command{
Use: "download <policy> <policy name>",
Short: fmt.Sprintf("Download %s", strings.Join(clihandler.DownloadSupportCommands(), ",")),
Long: ``,
Example: downloadExample,
Args: func(cmd *cobra.Command, args []string) error {
supported := strings.Join(clihandler.DownloadSupportCommands(), ",")
if len(args) < 1 {
return fmt.Errorf("policy type required, supported: %v", supported)
}
if cautils.StringInSlice(clihandler.DownloadSupportCommands(), args[0]) == cautils.ValueNotFound {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
downloadInfo.Target = args[0]
if len(args) >= 2 {
downloadInfo.Name = args[1]
}
if err := clihandler.CliDownload(&downloadInfo); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
return nil
},
}
func init() {
// cobra.OnInitialize(initConfig)
func GeDownloadCmd() *cobra.Command {
downloadCmd := &cobra.Command{
Use: "download <policy> <policy name>",
Short: fmt.Sprintf("Download %s", strings.Join(clihandler.DownloadSupportCommands(), ",")),
Long: ``,
Example: downloadExample,
Args: func(cmd *cobra.Command, args []string) error {
supported := strings.Join(clihandler.DownloadSupportCommands(), ",")
if len(args) < 1 {
return fmt.Errorf("policy type required, supported: %v", supported)
}
if cautils.StringInSlice(clihandler.DownloadSupportCommands(), args[0]) == cautils.ValueNotFound {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
rootCmd.AddCommand(downloadCmd)
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
if filepath.Ext(downloadInfo.Path) == ".json" {
downloadInfo.Path, downloadInfo.FileName = filepath.Split(downloadInfo.Path)
}
downloadInfo.Target = args[0]
if len(args) >= 2 {
downloadInfo.Name = args[1]
}
if err := clihandler.CliDownload(&downloadInfo); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
return downloadCmd
}

View File

@@ -1,132 +0,0 @@
package cmd
import (
"fmt"
"io"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/opa-utils/reporthandling"
"github.com/spf13/cobra"
)
var (
frameworkExample = `
# Scan all frameworks and submit the results
kubescape scan --submit
# Scan the NSA framework
kubescape scan framework nsa
# Scan the NSA and MITRE framework
kubescape scan framework nsa,mitre
# Scan all frameworks
kubescape scan framework all
# Scan kubernetes YAML manifest files
kubescape scan framework nsa *.yaml
# Scan and save the results in the JSON format
kubescape scan --format json --output results.json
# Save scan results in JSON format
kubescape scan --format json --output results.json
# Display all resources
kubescape scan --verbose
Run 'kubescape list frameworks' for the list of supported frameworks
`
)
var frameworkCmd = &cobra.Command{
Use: "framework <framework names list> [`<glob pattern>`/`-`] [flags]",
Short: "The framework you wish to use. Run 'kubescape list frameworks' for the list of supported frameworks",
Example: frameworkExample,
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
// ValidArgs: getter.NativeFrameworks,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
frameworks := strings.Split(args[0], ",")
if len(frameworks) > 1 {
if frameworks[1] == "" {
return fmt.Errorf("usage: <framework-0>,<framework-1>")
}
}
} else {
return fmt.Errorf("requires at least one framework name")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
flagValidationFramework()
var frameworks []string
if len(args) == 0 { // scan all frameworks
// frameworks = getter.NativeFrameworks
scanInfo.ScanAll = true
} else {
// Read frameworks from input args
frameworks = strings.Split(args[0], ",")
if cautils.StringInSlice(frameworks, "all") != cautils.ValueNotFound {
scanInfo.ScanAll = true
frameworks = []string{}
}
if len(args) > 1 {
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
} else { // store stdin to file - do NOT move to separate function !!
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
return err
}
scanInfo.InputPatterns = []string{tempFile.Name()}
}
}
}
scanInfo.FrameworkScan = true
scanInfo.SetPolicyIdentifiers(frameworks, reporthandling.KindFramework)
scanInfo.Init()
cautils.SetSilentMode(scanInfo.Silent)
err := clihandler.ScanCliSetup(&scanInfo)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n\n", err)
os.Exit(1)
}
return nil
},
}
func init() {
scanCmd.AddCommand(frameworkCmd)
scanInfo = cautils.ScanInfo{}
scanInfo.FrameworkScan = true
}
// func SetScanForFirstFramework(frameworks []string) []reporthandling.PolicyIdentifier {
// newPolicy := reporthandling.PolicyIdentifier{}
// newPolicy.Kind = reporthandling.KindFramework
// newPolicy.Name = frameworks[0]
// scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
// return scanInfo.PolicyIdentifier
// }
func flagValidationFramework() {
if scanInfo.Submit && scanInfo.Local {
fmt.Println("You can use `keep-local` or `submit`, but not both")
os.Exit(1)
}
if 100 < scanInfo.FailThreshold {
fmt.Println("bad argument: out of range threshold")
os.Exit(1)
}
}

View File

@@ -1,66 +0,0 @@
package cmd
import (
"fmt"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/clihandler"
"github.com/spf13/cobra"
)
var (
listExample = `
# List default supported frameworks names
kubescape list frameworks
# List all supported frameworks names
kubescape list frameworks --account <account id>
# List all supported controls names
kubescape list controls
# List all supported controls ids
kubescape list controls --id
Control documentation:
https://hub.armo.cloud/docs/controls
`
)
var listPolicies = cautils.ListPolicies{}
var listCmd = &cobra.Command{
Use: "list <policy> [flags]",
Short: "List frameworks/controls will list the supported frameworks and controls",
Long: ``,
Example: listExample,
Args: func(cmd *cobra.Command, args []string) error {
supported := strings.Join(clihandler.ListSupportCommands(), ",")
if len(args) < 1 {
return fmt.Errorf("policy type requeued, supported: %s", supported)
}
if cautils.StringInSlice(clihandler.ListSupportCommands(), args[0]) == cautils.ValueNotFound {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
listPolicies.Target = args[0]
if err := clihandler.CliList(&listPolicies); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
return nil
},
}
func init() {
// cobra.OnInitialize(initConfig)
rootCmd.AddCommand(listCmd)
listCmd.PersistentFlags().StringVarP(&listPolicies.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
listCmd.PersistentFlags().BoolVarP(&listPolicies.ListIDs, "id", "", false, "List control ID's instead of controls names")
}

View File

@@ -0,0 +1,66 @@
package list
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliobjects"
"github.com/spf13/cobra"
)
var (
listExample = `
# List default supported frameworks names
kubescape list frameworks
# List all supported frameworks names
kubescape list frameworks --account <account id>
# List all supported controls names
kubescape list controls
# List all supported controls ids
kubescape list controls --id
Control documentation:
https://hub.armo.cloud/docs/controls
`
)
func GetListCmd() *cobra.Command {
var listPolicies = cliobjects.ListPolicies{}
listCmd := &cobra.Command{
Use: "list <policy> [flags]",
Short: "List frameworks/controls will list the supported frameworks and controls",
Long: ``,
Example: listExample,
Args: func(cmd *cobra.Command, args []string) error {
supported := strings.Join(clihandler.ListSupportCommands(), ",")
if len(args) < 1 {
return fmt.Errorf("policy type requeued, supported: %s", supported)
}
if cautils.StringInSlice(clihandler.ListSupportCommands(), args[0]) == cautils.ValueNotFound {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
listPolicies.Target = args[0]
if err := clihandler.CliList(&listPolicies); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
listCmd.PersistentFlags().StringVar(&listPolicies.Account, "account", "", "Armo portal account ID. Default will load account ID from configMap or config file")
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-printer'/'json'")
listCmd.PersistentFlags().BoolVarP(&listPolicies.ListIDs, "id", "", false, "List control ID's instead of controls names")
return listCmd
}

View File

@@ -1,17 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
)
var localCmd = &cobra.Command{
Use: "local",
Short: "Set configuration locally (for config.json)",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
func init() {
configCmd.AddCommand(localCmd)
}

View File

@@ -1,45 +0,0 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/spf13/cobra"
)
var localGetCmd = &cobra.Command{
Use: "get <key>",
Short: "Get configuration locally",
Long: ``,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 || len(args) > 1 {
return fmt.Errorf("requires one argument")
}
keyValue := strings.Split(args[0], "=")
if len(keyValue) != 1 {
return fmt.Errorf("requires one argument")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
keyValue := strings.Split(args[0], "=")
key := keyValue[0]
val, err := cautils.GetValueFromConfigJson(key)
if err != nil {
if err.Error() == "value does not exist." {
fmt.Printf("Could net get value from: %s, reason: %s\n", cautils.ConfigFileFullPath(), err)
return nil
}
return err
}
fmt.Println(key + "=" + val)
return nil
},
}
func init() {
localCmd.AddCommand(localGetCmd)
}

View File

@@ -1,40 +0,0 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/spf13/cobra"
)
var localSetCmd = &cobra.Command{
Use: "set <key>=<value>",
Short: "Set configuration locally",
Long: ``,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 || len(args) > 1 {
return fmt.Errorf("requires one argument: <key>=<value>")
}
keyValue := strings.Split(args[0], "=")
if len(keyValue) != 2 {
return fmt.Errorf("requires one argument: <key>=<value>")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
keyValue := strings.Split(args[0], "=")
key := keyValue[0]
data := keyValue[1]
if err := cautils.SetKeyValueInConfigJson(key, data); err != nil {
return err
}
fmt.Println("Value added successfully.")
return nil
},
}
func init() {
localCmd.AddCommand(localSetCmd)
}

View File

@@ -1,53 +0,0 @@
package cmd
import (
"fmt"
"os"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
"github.com/armosec/rbac-utils/rbacscanner"
"github.com/spf13/cobra"
)
// rabcCmd represents the RBAC command
var rabcCmd = &cobra.Command{
Use: "rbac \nExample:\n$ kubescape submit rbac",
Short: "Submit cluster's Role-Based Access Control(RBAC)",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
k8s := k8sinterface.NewKubernetesApi()
// get config
clusterConfig, err := getSubmittedClusterConfig(k8s)
if err != nil {
return err
}
// list RBAC
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetCustomerGUID(), clusterConfig.GetClusterName()))
// submit resources
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: rbacObjects,
Reporter: r,
}
if err := clihandler.Submit(submitInterfaces); err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}
func init() {
submitCmd.AddCommand(rabcCmd)
}

View File

@@ -1,107 +0,0 @@
package cmd
import (
"encoding/json"
"fmt"
"os"
"time"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
"github.com/armosec/opa-utils/reporthandling"
uuid "github.com/satori/go.uuid"
"github.com/spf13/cobra"
)
type ResultsObject struct {
filePath string
customerGUID string
clusterName string
}
func NewResultsObject(customerGUID, clusterName, filePath string) *ResultsObject {
return &ResultsObject{
filePath: filePath,
customerGUID: customerGUID,
clusterName: clusterName,
}
}
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandling.PostureReport, error) {
// load framework results from json file
frameworkReports, err := loadResultsFromFile(resultsObject.filePath)
if err != nil {
return nil, err
}
return &reporthandling.PostureReport{
FrameworkReports: frameworkReports,
ReportID: uuid.NewV4().String(),
ReportGenerationTime: time.Now().UTC(),
CustomerGUID: resultsObject.customerGUID,
ClusterName: resultsObject.clusterName,
}, nil
}
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
return map[string]workloadinterface.IMetadata{}, nil
}
var resultsCmd = &cobra.Command{
Use: "results <json file>\nExample:\n$ kubescape submit results path/to/results.json",
Short: "Submit a pre scanned results file. The file must be in json format",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("missing results file")
}
k8s := k8sinterface.NewKubernetesApi()
// get config
clusterConfig, err := getSubmittedClusterConfig(k8s)
if err != nil {
return err
}
resultsObjects := NewResultsObject(clusterConfig.GetCustomerGUID(), clusterConfig.GetClusterName(), args[0])
// submit resources
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: resultsObjects,
Reporter: r,
}
if err := clihandler.Submit(submitInterfaces); err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}
func init() {
submitCmd.AddCommand(resultsCmd)
}
func loadResultsFromFile(filePath string) ([]reporthandling.FrameworkReport, error) {
frameworkReports := []reporthandling.FrameworkReport{}
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
if err = json.Unmarshal(f, &frameworkReports); err != nil {
frameworkReport := reporthandling.FrameworkReport{}
if err = json.Unmarshal(f, &frameworkReport); err != nil {
return frameworkReports, err
}
frameworkReports = append(frameworkReports, frameworkReport)
}
return frameworkReports, nil
}

View File

@@ -1,29 +1,52 @@
package cmd
import (
"flag"
"fmt"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/golang/glog"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
"github.com/armosec/kubescape/clihandler/cmd/completion"
"github.com/armosec/kubescape/clihandler/cmd/config"
"github.com/armosec/kubescape/clihandler/cmd/delete"
"github.com/armosec/kubescape/clihandler/cmd/download"
"github.com/armosec/kubescape/clihandler/cmd/list"
"github.com/armosec/kubescape/clihandler/cmd/scan"
"github.com/armosec/kubescape/clihandler/cmd/submit"
"github.com/armosec/kubescape/clihandler/cmd/version"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"
)
var cfgFile string
var armoBEURLs = ""
var armoBEURLsDep = ""
var rootInfo cautils.RootInfo
const envFlagUsage = "Send report results to specific URL. Format:<ReportReceiver>,<Backend>,<Frontend>.\n\t\tExample:report.armo.cloud,api.armo.cloud,portal.armo.cloud"
var ksExamples = `
# Scan command
kubescape scan --submit
# List supported frameworks
kubescape list frameworks
# Download artifacts (air-gapped environment support)
kubescape download artifacts
# View cached configurations
kubescape config view
`
var rootCmd = &cobra.Command{
Use: "kubescape",
Short: "Kubescape is a tool for testing Kubernetes security posture",
Long: `Kubescape is a tool for testing Kubernetes security posture based on NSA \ MITRE ATT&CK® specifications.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
flag.Parse()
InitArmoBEConnector()
return nil
},
Use: "kubescape",
Version: cautils.BuildNumber,
Short: "Kubescape is a tool for testing Kubernetes security posture",
Long: `Based on NSA \ MITRE ATT&CK® and other frameworks specifications`,
Example: ksExamples,
}
func Execute() {
@@ -31,34 +54,103 @@ func Execute() {
}
func init() {
rootCmd.PersistentFlags().StringVarP(&scanInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
flag.CommandLine.StringVar(&armoBEURLs, "environment", "", envFlagUsage)
rootCmd.PersistentFlags().StringVar(&armoBEURLs, "environment", "", envFlagUsage)
rootCmd.AddCommand(scan.GetScanCommand())
rootCmd.AddCommand(download.GeDownloadCmd())
rootCmd.AddCommand(delete.GetDeleteCmd())
rootCmd.AddCommand(list.GetListCmd())
rootCmd.AddCommand(submit.GetSubmitCmd())
rootCmd.AddCommand(completion.GetCompletionCmd())
rootCmd.AddCommand(version.GetVersionCmd())
rootCmd.AddCommand(config.GetConfigCmd())
cobra.OnInitialize(initLogger, initLoggerLevel, initEnvironment, initCacheDir)
rootCmd.PersistentFlags().StringVar(&armoBEURLsDep, "environment", "", envFlagUsage)
rootCmd.PersistentFlags().StringVar(&armoBEURLs, "env", "", envFlagUsage)
rootCmd.PersistentFlags().MarkDeprecated("environment", "use 'env' instead")
rootCmd.PersistentFlags().MarkHidden("environment")
rootCmd.PersistentFlags().MarkHidden("env")
rootCmd.PersistentFlags().StringVar(&rootInfo.LoggerName, "logger-name", "", fmt.Sprintf("Logger name. Supported: %s [$KS_LOGGER_NAME]", strings.Join(logger.ListLoggersNames(), "/")))
rootCmd.PersistentFlags().MarkHidden("logger-name")
rootCmd.PersistentFlags().StringVarP(&rootInfo.Logger, "logger", "l", helpers.InfoLevel.String(), fmt.Sprintf("Logger level. Supported: %s [$KS_LOGGER]", strings.Join(helpers.SupportedLevels(), "/")))
rootCmd.PersistentFlags().StringVar(&rootInfo.CacheDir, "cache-dir", getter.DefaultLocalStore, "Cache directory [$KS_CACHE_DIR]")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable Color output for logging")
}
func InitArmoBEConnector() {
func initLogger() {
logger.DisableColor(rootInfo.DisableColor)
if rootInfo.LoggerName == "" {
if l := os.Getenv("KS_LOGGER_NAME"); l != "" {
rootInfo.LoggerName = l
} else {
if isatty.IsTerminal(os.Stdout.Fd()) {
rootInfo.LoggerName = "pretty"
} else {
rootInfo.LoggerName = "zap"
}
}
}
logger.InitLogger(rootInfo.LoggerName)
}
func initLoggerLevel() {
if rootInfo.Logger != helpers.InfoLevel.String() {
} else if l := os.Getenv("KS_LOGGER"); l != "" {
rootInfo.Logger = l
}
if err := logger.L().SetLevel(rootInfo.Logger); err != nil {
logger.L().Fatal(fmt.Sprintf("supported levels: %s", strings.Join(helpers.SupportedLevels(), "/")), helpers.Error(err))
}
}
func initCacheDir() {
if rootInfo.CacheDir == getter.DefaultLocalStore {
getter.DefaultLocalStore = rootInfo.CacheDir
} else if cacheDir := os.Getenv("KS_CACHE_DIR"); cacheDir != "" {
getter.DefaultLocalStore = cacheDir
} else {
return // using default cache dir location
}
logger.L().Debug("cache dir updated", helpers.String("path", getter.DefaultLocalStore))
}
func initEnvironment() {
if armoBEURLsDep != "" {
armoBEURLs = armoBEURLsDep
}
urlSlices := strings.Split(armoBEURLs, ",")
if len(urlSlices) > 3 {
glog.Errorf("Too many URLs")
os.Exit(1)
if len(urlSlices) != 1 && len(urlSlices) < 3 {
logger.L().Fatal("expected at least 3 URLs (report, api, frontend, auth)")
}
switch len(urlSlices) {
case 1:
switch urlSlices[0] {
case "dev":
case "dev", "development":
getter.SetARMOAPIConnector(getter.NewARMOAPIDev())
case "stage", "staging":
getter.SetARMOAPIConnector(getter.NewARMOAPIStaging())
case "":
getter.SetARMOAPIConnector(getter.NewARMOAPIProd())
default:
glog.Errorf("--environment flag usage: %s", envFlagUsage)
os.Exit(1)
logger.L().Fatal("--environment flag usage: " + envFlagUsage)
}
case 2:
glog.Errorf("--environment flag usage: %s", envFlagUsage)
os.Exit(1)
case 3:
getter.SetARMOAPIConnector(getter.NewARMOAPICustomized(urlSlices[0], urlSlices[1], urlSlices[2]))
logger.L().Fatal("--environment flag usage: " + envFlagUsage)
case 3, 4:
var armoAUTHURL string
armoERURL := urlSlices[0] // mandatory
armoBEURL := urlSlices[1] // mandatory
armoFEURL := urlSlices[2] // mandatory
if len(urlSlices) <= 4 {
armoAUTHURL = urlSlices[3]
}
getter.SetARMOAPIConnector(getter.NewARMOAPICustomized(armoERURL, armoBEURL, armoFEURL, armoAUTHURL))
}
}

View File

@@ -1,64 +0,0 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/spf13/cobra"
)
var scanInfo cautils.ScanInfo
// scanCmd represents the scan command
var scanCmd = &cobra.Command{
Use: "scan <command>",
Short: "Scan the current running cluster or yaml files",
Long: `The action you want to perform`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
if !strings.EqualFold(args[0], "framework") && !strings.EqualFold(args[0], "control") {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: framework, control", args[0])
}
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
scanInfo.ScanAll = true
// frameworks := getter.NativeFrameworks
// frameworkArgs := []string{strings.Join(frameworks, ",")}
frameworkCmd.RunE(cmd, []string{"all"})
}
},
}
func frameworkInitConfig() {
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
}
func init() {
cobra.OnInitialize(frameworkInitConfig)
rootCmd.AddCommand(scanCmd)
rootCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system,kube-public")
scanCmd.PersistentFlags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above which the command fails and returns exit code 1")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer"/"json"/"junit"/"prometheus"`)
scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to Armo backend. Use this flag if you ran with the '--submit' flag in the past and you do not want to submit your current scan results")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
scanCmd.PersistentFlags().BoolVar(&scanInfo.VerboseMode, "verbose", false, "Display all of the input resources and not only failed resources")
scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest")
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Send the scan results to Armo management portal where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not submitted")
hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensor, "enable-host-scan", "", "Deploy ARMO K8s host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valueable data from cluster nodes for certain controls")
hostF.NoOptDefVal = "true"
hostF.DefValue = "false, for no TTY in stdin"
}

View File

@@ -0,0 +1,124 @@
package scan
import (
"fmt"
"io"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/opa-utils/reporthandling"
"github.com/spf13/cobra"
)
var (
controlExample = `
# Scan the 'privileged container' control
kubescape scan control "privileged container"
# Scan list of controls separated with a comma
kubescape scan control "privileged container","allowed hostpath"
# Scan list of controls using the control ID separated with a comma
kubescape scan control C-0058,C-0057
Run 'kubescape list controls' for the list of supported controls
Control documentation:
https://hub.armo.cloud/docs/controls
`
)
// controlCmd represents the control command
func getControlCmd() *cobra.Command {
return &cobra.Command{
Use: "control <control names list>/<control ids list>",
Short: "The controls you wish to use. Run 'kubescape list controls' for the list of supported controls",
Example: controlExample,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
controls := strings.Split(args[0], ",")
if len(controls) > 1 {
for _, control := range controls {
if control == "" {
return fmt.Errorf("usage: <control-0>,<control-1>")
}
}
}
} else {
return fmt.Errorf("requires at least one control name")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
var scanInfo cautils.ScanInfo
// flagValidationControl(scanInfo)
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
if len(args) == 0 {
scanInfo.ScanAll = true
} else { // expected control or list of control sepparated by ","
// Read controls from input args
scanInfo.SetPolicyIdentifiers(strings.Split(args[0], ","), reporthandling.KindControl)
if len(args) > 1 {
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
} else { // store stdin to file - do NOT move to separate function !!
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
return err
}
scanInfo.InputPatterns = []string{tempFile.Name()}
}
}
}
scanInfo.FrameworkScan = false
results, err := clihandler.Scan(&scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
results.HandleResults()
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
return fmt.Errorf("scan risk-score %.2f is above permitted threshold %.2f", results.GetRiskScore(), scanInfo.FailThreshold)
}
return nil
},
}
}
// func flagValidationControl() {
// if 100 < scanInfo.FailThreshold {
// logger.L().Fatal("bad argument: out of range threshold")
// }
// }
// func setScanForFirstControl(scanInfo, controls []string) []reporthandling.PolicyIdentifier {
// newPolicy := reporthandling.PolicyIdentifier{}
// newPolicy.Kind = reporthandling.KindControl
// newPolicy.Name = controls[0]
// scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
// return scanInfo.PolicyIdentifier
// }
// func SetScanForGivenControls(scanInfo, controls []string) []reporthandling.PolicyIdentifier {
// for _, control := range controls {
// control := strings.TrimLeft(control, " ")
// newPolicy := reporthandling.PolicyIdentifier{}
// newPolicy.Kind = reporthandling.KindControl
// newPolicy.Name = control
// scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
// }
// return scanInfo.PolicyIdentifier
// }

View File

@@ -0,0 +1,131 @@
package scan
import (
"fmt"
"io"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/opa-utils/reporthandling"
"github.com/spf13/cobra"
)
var (
frameworkExample = `
# Scan all frameworks and submit the results
kubescape scan framework all --submit
# Scan the NSA framework
kubescape scan framework nsa
# Scan the NSA and MITRE framework
kubescape scan framework nsa,mitre
# Scan all frameworks
kubescape scan framework all
# Scan kubernetes YAML manifest files
kubescape scan framework nsa *.yaml
Run 'kubescape list frameworks' for the list of supported frameworks
`
)
func getFrameworkCmd() *cobra.Command {
return &cobra.Command{
Use: "framework <framework names list> [`<glob pattern>`/`-`] [flags]",
Short: "The framework you wish to use. Run 'kubescape list frameworks' for the list of supported frameworks",
Example: frameworkExample,
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
frameworks := strings.Split(args[0], ",")
if len(frameworks) > 1 {
for _, framework := range frameworks {
if framework == "" {
return fmt.Errorf("usage: <framework-0>,<framework-1>")
}
}
}
} else {
return fmt.Errorf("requires at least one framework name")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
var scanInfo cautils.ScanInfo
flagValidationFramework(&scanInfo)
scanInfo.FrameworkScan = true
var frameworks []string
if len(args) == 0 { // scan all frameworks
scanInfo.ScanAll = true
} else {
// Read frameworks from input args
frameworks = strings.Split(args[0], ",")
if cautils.StringInSlice(frameworks, "all") != cautils.ValueNotFound {
scanInfo.ScanAll = true
frameworks = []string{}
}
if len(args) > 1 {
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
} else { // store stdin to file - do NOT move to separate function !!
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
return err
}
scanInfo.InputPatterns = []string{tempFile.Name()}
}
}
}
scanInfo.FrameworkScan = true
scanInfo.SetPolicyIdentifiers(frameworks, reporthandling.KindFramework)
results, err := clihandler.Scan(&scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
results.HandleResults()
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
return fmt.Errorf("scan risk-score %.2f is above permitted threshold %.2f", results.GetRiskScore(), scanInfo.FailThreshold)
}
return nil
},
}
}
// func init() {
// scanCmd.AddCommand(frameworkCmd)
// scanInfo = cautils.ScanInfo{}
// }
// func SetScanForFirstFramework(frameworks []string) []reporthandling.PolicyIdentifier {
// newPolicy := reporthandling.PolicyIdentifier{}
// newPolicy.Kind = reporthandling.KindFramework
// newPolicy.Name = frameworks[0]
// scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
// return scanInfo.PolicyIdentifier
// }
func flagValidationFramework(scanInfo *cautils.ScanInfo) {
if scanInfo.Submit && scanInfo.Local {
logger.L().Fatal("you can use `keep-local` or `submit`, but not both")
}
if 100 < scanInfo.FailThreshold {
logger.L().Fatal("bad argument: out of range threshold")
}
}

View File

@@ -0,0 +1,98 @@
package scan
import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/spf13/cobra"
)
var scanCmdExamples = `
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defind frameworks
# Scan current cluster with all frameworks
kubescape scan --submit --enable-host-scan
# Scan kubernetes YAML manifest files
kubescape scan *.yaml
# Scan and save the results in the JSON format
kubescape scan --format json --output results.json
# Display all resources
kubescape scan --verbose
# Scan different clusters from the kubectl context
kubescape scan --kube-context <kubernetes context>
`
func GetScanCommand() *cobra.Command {
var scanInfo cautils.ScanInfo
// scanCmd represents the scan command
scanCmd := &cobra.Command{
Use: "scan",
Short: "Scan the current running cluster or yaml files",
Long: `The action you want to perform`,
Example: scanCmdExamples,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
if args[0] != "framework" && args[0] != "control" {
scanInfo.ScanAll = true
return getFrameworkCmd().RunE(cmd, append([]string{"all"}, args...))
}
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
scanInfo.ScanAll = true
return getFrameworkCmd().RunE(cmd, []string{"all"})
}
return nil
},
PreRun: func(cmd *cobra.Command, args []string) {
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
},
PostRun: func(cmd *cobra.Command, args []string) {
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
},
}
scanCmd.PersistentFlags().StringVarP(&scanInfo.Account, "account", "", "", "ARMO portal account ID. Default will load account ID from configMap or config file")
scanCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system,kube-public")
scanCmd.PersistentFlags().Float32VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above which the command fails and returns exit code 1")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer","json","junit","prometheus","pdf"`)
scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to ARMO backend. Use this flag if you ran with the '--submit' flag in the past and you do not want to submit your current scan results")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
scanCmd.PersistentFlags().BoolVar(&scanInfo.VerboseMode, "verbose", false, "Display all of the input resources and not only failed resources")
scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest")
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Send the scan results to ARMO management portal where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not submitted")
scanCmd.PersistentFlags().StringVar(&scanInfo.HostSensorYamlPath, "host-scan-yaml", "", "Override default host sensor DaemonSet. Use this flag cautiously")
scanCmd.PersistentFlags().StringVar(&scanInfo.FormatVersion, "format-version", "v1", "Output object can be differnet between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
// Deprecated flags - remove 1.May.2022
scanCmd.PersistentFlags().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
// hidden flags
scanCmd.PersistentFlags().MarkHidden("host-scan-yaml") // this flag should be used very cautiously. We prefer users will not use it at all unless the DaemonSet can not run pods on the nodes
scanCmd.PersistentFlags().MarkHidden("silent") // this flag should be deprecated since we added the --logger support
// scanCmd.PersistentFlags().MarkHidden("format-version") // meant for testing different output approaches and not for common use
hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensorEnabled, "enable-host-scan", "", "Deploy ARMO K8s host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valuable data from cluster nodes for certain controls. Yaml file: https://raw.githubusercontent.com/armosec/kubescape/master/hostsensorutils/hostsensor.yaml")
hostF.NoOptDefVal = "true"
hostF.DefValue = "false, for no TTY in stdin"
scanCmd.AddCommand(getControlCmd())
scanCmd.AddCommand(getFrameworkCmd())
return scanCmd
}

View File

@@ -1,31 +0,0 @@
package cmd
import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/spf13/cobra"
)
var submitCmd = &cobra.Command{
Use: "submit <command>",
Short: "Submit an object to the Kubescape SaaS version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
func init() {
rootCmd.AddCommand(submitCmd)
}
func getSubmittedClusterConfig(k8s *k8sinterface.KubernetesApi) (*cautils.ClusterConfig, error) {
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, scanInfo.KubeContext) // TODO - support none cluster env submit
if clusterConfig.GetCustomerGUID() != "" {
if err := clusterConfig.SetTenant(); err != nil {
return clusterConfig, err
}
}
return clusterConfig, nil
}

View File

@@ -0,0 +1,27 @@
package submit
import (
"fmt"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/spf13/cobra"
)
func getExceptionsCmd() *cobra.Command {
return &cobra.Command{
Use: "exceptions <full path to exceptins file>",
Short: "Submit exceptions to the Kubescape SaaS version",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("missing full path to exceptions file")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
if err := clihandler.SubmitExceptions(submitInfo.Account, args[0]); err != nil {
logger.L().Fatal(err.Error())
}
},
}
}

View File

@@ -0,0 +1,65 @@
package submit
import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
"github.com/armosec/rbac-utils/rbacscanner"
"github.com/spf13/cobra"
)
// getRBACCmd represents the RBAC command
func getRBACCmd() *cobra.Command {
return &cobra.Command{
Use: "rbac \nExample:\n$ kubescape submit rbac",
Short: "Submit cluster's Role-Based Access Control(RBAC)",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
k8s := k8sinterface.NewKubernetesApi()
// get config
clusterConfig := getTenantConfig(submitInfo.Account, "", k8s)
if err := clusterConfig.SetTenant(); err != nil {
logger.L().Error("failed setting account ID", helpers.Error(err))
}
// list RBAC
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetAccountID(), clusterConfig.GetClusterName()))
// submit resources
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: rbacObjects,
Reporter: r,
}
if err := clihandler.Submit(submitInterfaces); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
}
// getKubernetesApi
func getKubernetesApi() *k8sinterface.KubernetesApi {
if !k8sinterface.IsConnectedToCluster() {
return nil
}
return k8sinterface.NewKubernetesApi()
}
func getTenantConfig(Account, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return cautils.NewLocalConfig(getter.GetArmoAPIConnector(), Account, clusterName)
}
return cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), Account, clusterName)
}

View File

@@ -0,0 +1,117 @@
package submit
import (
"encoding/json"
"fmt"
"os"
"time"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
"github.com/armosec/kubescape/resultshandling/reporter"
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
reporterv2 "github.com/armosec/kubescape/resultshandling/reporter/v2"
"github.com/armosec/opa-utils/reporthandling"
"github.com/google/uuid"
"github.com/spf13/cobra"
)
var formatVersion string
type ResultsObject struct {
filePath string
customerGUID string
clusterName string
}
func NewResultsObject(customerGUID, clusterName, filePath string) *ResultsObject {
return &ResultsObject{
filePath: filePath,
customerGUID: customerGUID,
clusterName: clusterName,
}
}
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandling.PostureReport, error) {
// load framework results from json file
frameworkReports, err := loadResultsFromFile(resultsObject.filePath)
if err != nil {
return nil, err
}
return &reporthandling.PostureReport{
FrameworkReports: frameworkReports,
ReportID: uuid.NewString(),
ReportGenerationTime: time.Now().UTC(),
CustomerGUID: resultsObject.customerGUID,
ClusterName: resultsObject.clusterName,
}, nil
}
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
return map[string]workloadinterface.IMetadata{}, nil
}
func getResultsCmd() *cobra.Command {
var resultsCmd = &cobra.Command{
Use: "results <json file>\nExample:\n$ kubescape submit results path/to/results.json --format-version v2",
Short: "Submit a pre scanned results file. The file must be in json format",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("missing results file")
}
k8s := getKubernetesApi()
// get config
clusterConfig := getTenantConfig(submitInfo.Account, "", k8s)
if err := clusterConfig.SetTenant(); err != nil {
logger.L().Error("failed setting account ID", helpers.Error(err))
}
resultsObjects := NewResultsObject(clusterConfig.GetAccountID(), clusterConfig.GetClusterName(), args[0])
// submit resources
var r reporter.IReport
switch formatVersion {
case "v2":
r = reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), "")
default:
logger.L().Warning("Deprecated results version. run with '--format-version' flag", helpers.String("your version", formatVersion), helpers.String("latest version", "v2"))
r = reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
}
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: resultsObjects,
Reporter: r,
}
if err := clihandler.Submit(submitInterfaces); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
resultsCmd.PersistentFlags().StringVar(&formatVersion, "format-version", "v1", "Output object can be differnet between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
return resultsCmd
}
func loadResultsFromFile(filePath string) ([]reporthandling.FrameworkReport, error) {
frameworkReports := []reporthandling.FrameworkReport{}
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
if err = json.Unmarshal(f, &frameworkReports); err != nil {
frameworkReport := reporthandling.FrameworkReport{}
if err = json.Unmarshal(f, &frameworkReport); err != nil {
return frameworkReports, err
}
frameworkReports = append(frameworkReports, frameworkReport)
}
return frameworkReports, nil
}

View File

@@ -0,0 +1,29 @@
package submit
import (
"github.com/armosec/kubescape/clihandler/cliobjects"
"github.com/spf13/cobra"
)
var submitInfo cliobjects.Submit
var submitCmdExamples = `
`
func GetSubmitCmd() *cobra.Command {
submitCmd := &cobra.Command{
Use: "submit <command>",
Short: "Submit an object to the Kubescape SaaS version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
submitCmd.PersistentFlags().StringVarP(&submitInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
submitCmd.AddCommand(getExceptionsCmd())
submitCmd.AddCommand(getResultsCmd())
submitCmd.AddCommand(getRBACCmd())
return submitCmd
}

View File

@@ -1,24 +0,0 @@
package cmd
import (
"fmt"
"github.com/armosec/kubescape/cautils"
"github.com/spf13/cobra"
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "Get current version",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
v := cautils.NewIVersionCheckHandler()
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
fmt.Println("Your current version is: " + cautils.BuildNumber)
return nil
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}

View File

@@ -0,0 +1,24 @@
package version
import (
"fmt"
"os"
"github.com/armosec/kubescape/cautils"
"github.com/spf13/cobra"
)
func GetVersionCmd() *cobra.Command {
versionCmd := &cobra.Command{
Use: "version",
Short: "Get current version",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
v := cautils.NewIVersionCheckHandler()
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
fmt.Fprintln(os.Stdout, "Your current version is: "+cautils.BuildNumber)
return nil
},
}
return versionCmd
}

View File

@@ -5,16 +5,14 @@ import (
"io/fs"
"os"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/resultshandling/printer"
printerv1 "github.com/armosec/kubescape/resultshandling/printer/v1"
// printerv2 "github.com/armosec/kubescape/resultshandling/printer/v2"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
"github.com/armosec/kubescape/hostsensorutils"
"github.com/armosec/kubescape/opaprocessor"
"github.com/armosec/kubescape/policyhandler"
@@ -22,6 +20,7 @@ import (
"github.com/armosec/kubescape/resultshandling"
"github.com/armosec/kubescape/resultshandling/reporter"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/resources"
"github.com/mattn/go-isatty"
)
@@ -35,27 +34,39 @@ type componentInterfaces struct {
func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
// ================== setup k8s interface object ======================================
var k8s *k8sinterface.KubernetesApi
if scanInfo.GetScanningEnvironment() == cautils.ScanCluster {
k8s = getKubernetesApi()
if k8s == nil {
fmt.Println("Failed connecting to Kubernetes cluster")
os.Exit(1)
logger.L().Fatal("failed connecting to Kubernetes cluster")
}
}
// ================== setup tenant object ======================================
tenantConfig := getTenantConfig(scanInfo.Account, scanInfo.KubeContext, k8s)
// Set submit behavior AFTER loading tenant config
setSubmitBehavior(scanInfo, tenantConfig)
if scanInfo.Submit {
// submit - Create tenant & Submit report
if err := tenantConfig.SetTenant(); err != nil {
logger.L().Error(err.Error())
}
}
// ================== version testing ======================================
v := cautils.NewIVersionCheckHandler()
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, policyIdentifierNames(scanInfo.PolicyIdentifier), "", scanInfo.GetScanningEnvironment()))
// ================== setup host sensor object ======================================
hostSensorHandler := getHostSensorHandler(scanInfo, k8s)
if err := hostSensorHandler.Init(); err != nil {
errMsg := "failed to init host sensor"
if scanInfo.VerboseMode {
errMsg = fmt.Sprintf("%s: %v", errMsg, err)
}
cautils.ErrorDisplay(errMsg)
logger.L().Error("failed to init host sensor", helpers.Error(err))
hostSensorHandler = &hostsensorutils.HostSensorHandlerMock{}
}
// excluding hostsensor namespace
@@ -63,19 +74,28 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
scanInfo.ExcludedNamespaces = fmt.Sprintf("%s,%s", scanInfo.ExcludedNamespaces, hostSensorHandler.GetNamespace())
}
resourceHandler := getResourceHandler(scanInfo, tenantConfig, k8s, hostSensorHandler)
// ================== setup registry adaptors ======================================
registryAdaptors, err := resourcehandler.NewRegistryAdaptors()
if err != nil {
logger.L().Error("failed to initialize registry adaptors", helpers.Error(err))
}
// ================== setup resource collector object ======================================
resourceHandler := getResourceHandler(scanInfo, tenantConfig, k8s, hostSensorHandler, registryAdaptors)
// ================== setup reporter & printer objects ======================================
// reporting behavior - setup reporter
reportHandler := getReporter(tenantConfig, scanInfo.Submit)
v := cautils.NewIVersionCheckHandler()
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, policyIdentifierNames(scanInfo.PolicyIdentifier), "", scanInfo.GetScanningEnvironment()))
reportHandler := getReporter(tenantConfig, scanInfo.ReportID, scanInfo.Submit, scanInfo.FrameworkScan, len(scanInfo.InputPatterns) == 0)
// setup printer
printerHandler := printerv1.GetPrinter(scanInfo.Format, scanInfo.VerboseMode)
// printerHandler = printerv2.GetPrinter(scanInfo.Format, scanInfo.VerboseMode)
printerHandler := resultshandling.NewPrinter(scanInfo.Format, scanInfo.FormatVersion, scanInfo.VerboseMode)
printerHandler.SetWriter(scanInfo.Output)
// ================== return interface ======================================
return componentInterfaces{
tenantConfig: tenantConfig,
resourceHandler: resourceHandler,
@@ -85,25 +105,24 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
}
}
func ScanCliSetup(scanInfo *cautils.ScanInfo) error {
cautils.ScanStartDisplay()
func Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
logger.L().Info("ARMO security scanner starting")
// ===================== Initialization =====================
scanInfo.Init() // initialize scan info
interfaces := getInterfaces(scanInfo)
// setPolicyGetter(scanInfo, interfaces.clusterConfig.GetCustomerGUID())
processNotification := make(chan *cautils.OPASessionObj)
reportResults := make(chan *cautils.OPASessionObj)
cautils.ClusterName = interfaces.tenantConfig.GetClusterName() // TODO - Deprecated
cautils.CustomerGUID = interfaces.tenantConfig.GetCustomerGUID() // TODO - Deprecated
cautils.ClusterName = interfaces.tenantConfig.GetClusterName() // TODO - Deprecated
cautils.CustomerGUID = interfaces.tenantConfig.GetAccountID() // TODO - Deprecated
interfaces.report.SetClusterName(interfaces.tenantConfig.GetClusterName())
interfaces.report.SetCustomerGUID(interfaces.tenantConfig.GetCustomerGUID())
interfaces.report.SetCustomerGUID(interfaces.tenantConfig.GetAccountID())
downloadReleasedPolicy := getter.NewDownloadReleasedPolicy() // download config inputs from github release
// set policy getter only after setting the customerGUID
scanInfo.Getters.PolicyGetter = getPolicyGetter(scanInfo.UseFrom, interfaces.tenantConfig.GetCustomerGUID(), scanInfo.FrameworkScan, downloadReleasedPolicy)
scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(scanInfo.ControlsInputs, interfaces.tenantConfig.GetCustomerGUID(), downloadReleasedPolicy)
scanInfo.Getters.PolicyGetter = getPolicyGetter(scanInfo.UseFrom, interfaces.tenantConfig.GetAccountID(), scanInfo.FrameworkScan, downloadReleasedPolicy)
scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(scanInfo.ControlsInputs, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(scanInfo.UseExceptions)
// TODO - list supported frameworks/controls
@@ -111,84 +130,60 @@ func ScanCliSetup(scanInfo *cautils.ScanInfo) error {
scanInfo.SetPolicyIdentifiers(listFrameworksNames(scanInfo.Getters.PolicyGetter), reporthandling.KindFramework)
}
//
// remove host scanner components
defer func() {
if err := interfaces.hostSensorHandler.TearDown(); err != nil {
errMsg := "failed to tear down host sensor"
if scanInfo.VerboseMode {
errMsg = fmt.Sprintf("%s: %v", errMsg, err)
}
cautils.ErrorDisplay(errMsg)
logger.L().Error("failed to tear down host sensor", helpers.Error(err))
}
}()
// cli handler setup
go func() {
// policy handler setup
policyHandler := policyhandler.NewPolicyHandler(&processNotification, interfaces.resourceHandler)
resultsHandling := resultshandling.NewResultsHandler(interfaces.report, interfaces.printerHandler)
if err := Scan(policyHandler, scanInfo); err != nil {
fmt.Println(err)
os.Exit(1)
}
}()
// processor setup - rego run
go func() {
opaprocessorObj := opaprocessor.NewOPAProcessorHandler(&processNotification, &reportResults)
opaprocessorObj.ProcessRulesListenner()
}()
resultsHandling := resultshandling.NewResultsHandler(&reportResults, interfaces.report, interfaces.printerHandler)
score := resultsHandling.HandleResults(scanInfo)
// print report url
interfaces.report.DisplayReportURL()
if score > float32(scanInfo.FailThreshold) {
return fmt.Errorf("scan risk-score %.2f is above permitted threshold %d", score, scanInfo.FailThreshold)
// ===================== policies & resources =====================
policyHandler := policyhandler.NewPolicyHandler(interfaces.resourceHandler)
scanData, err := CollectResources(policyHandler, scanInfo)
if err != nil {
return resultsHandling, err
}
return nil
// ========================= opa testing =====================
deps := resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), interfaces.tenantConfig.GetClusterName())
reportResults := opaprocessor.NewOPAProcessor(scanData, deps)
if err := reportResults.ProcessRulesListenner(); err != nil {
// TODO - do something
return resultsHandling, err
}
// ========================= results handling =====================
resultsHandling.SetData(scanData)
// if resultsHandling.GetRiskScore() > float32(scanInfo.FailThreshold) {
// return resultsHandling, fmt.Errorf("scan risk-score %.2f is above permitted threshold %.2f", resultsHandling.GetRiskScore(), scanInfo.FailThreshold)
// }
return resultsHandling, nil
}
func Scan(policyHandler *policyhandler.PolicyHandler, scanInfo *cautils.ScanInfo) error {
func CollectResources(policyHandler *policyhandler.PolicyHandler, scanInfo *cautils.ScanInfo) (*cautils.OPASessionObj, error) {
policyNotification := &reporthandling.PolicyNotification{
NotificationType: reporthandling.TypeExecPostureScan,
Rules: scanInfo.PolicyIdentifier,
Designators: armotypes.PortalDesignator{},
Rules: scanInfo.PolicyIdentifier,
KubescapeNotification: reporthandling.KubescapeNotification{
Designators: armotypes.PortalDesignator{},
NotificationType: reporthandling.TypeExecPostureScan,
},
}
switch policyNotification.NotificationType {
switch policyNotification.KubescapeNotification.NotificationType {
case reporthandling.TypeExecPostureScan:
if err := policyHandler.HandleNotificationRequest(policyNotification, scanInfo); err != nil {
return err
collectedResources, err := policyHandler.CollectResources(policyNotification, scanInfo)
if err != nil {
return nil, err
}
return collectedResources, nil
default:
return fmt.Errorf("notification type '%s' Unknown", policyNotification.NotificationType)
return nil, fmt.Errorf("notification type '%s' Unknown", policyNotification.KubescapeNotification.NotificationType)
}
return nil
}
func Submit(submitInterfaces cliinterfaces.SubmitInterfaces) error {
// list resources
postureReport, err := submitInterfaces.SubmitObjects.SetResourcesReport()
if err != nil {
return err
}
allresources, err := submitInterfaces.SubmitObjects.ListAllResources()
if err != nil {
return err
}
// report
if err := submitInterfaces.Reporter.ActionSendReport(&cautils.OPASessionObj{PostureReport: postureReport, AllResources: allresources}); err != nil {
return err
}
fmt.Printf("\nData has been submitted successfully")
submitInterfaces.Reporter.DisplayReportURL()
return nil
return nil, nil
}
func askUserForHostSensor() bool {
@@ -200,8 +195,8 @@ func askUserForHostSensor() bool {
if ssss, err := os.Stdin.Stat(); err == nil {
// fmt.Printf("Found stdin type: %s\n", ssss.Mode().Type())
if ssss.Mode().Type()&(fs.ModeDevice|fs.ModeCharDevice) > 0 { //has TTY
fmt.Printf("Would you like to scan K8s nodes? [y/N]. This is required to collect valuable data for certain controls\n")
fmt.Printf("Use --enable-host-scan flag to suppress this message\n")
fmt.Fprintf(os.Stderr, "Would you like to scan K8s nodes? [y/N]. This is required to collect valuable data for certain controls\n")
fmt.Fprintf(os.Stderr, "Use --enable-host-scan flag to suppress this message\n")
var b []byte = make([]byte, 1)
if n, err := os.Stdin.Read(b); err == nil {
if n > 0 && len(b) > 0 && (b[0] == 'y' || b[0] == 'Y') {

View File

@@ -7,10 +7,11 @@ import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
"github.com/armosec/kubescape/hostsensorutils"
"github.com/armosec/kubescape/resourcehandler"
"github.com/armosec/kubescape/resultshandling/reporter"
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
reporterv2 "github.com/armosec/kubescape/resultshandling/reporter/v2"
"github.com/armosec/opa-utils/reporthandling"
@@ -42,25 +43,37 @@ func getExceptionsGetter(useExceptions string) getter.IExceptionsGetter {
func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, submit bool) *cautils.RBACObjects {
if submit {
return cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, tenantConfig.GetCustomerGUID(), tenantConfig.GetClusterName()))
return cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, tenantConfig.GetAccountID(), tenantConfig.GetClusterName()))
}
return nil
}
func getReporter(tenantConfig cautils.ITenantConfig, submit bool) reporter.IReport {
if submit {
// return reporterv1.NewReportEventReceiver(tenantConfig.GetConfigObj())
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj())
func getReporter(tenantConfig cautils.ITenantConfig, reportID string, submit, fwScan, clusterScan bool) reporter.IReport {
if submit && clusterScan {
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj(), reportID)
}
return reporterv1.NewReportMock()
if tenantConfig.GetAccountID() == "" && fwScan && clusterScan {
// Add link only when scanning a cluster using a framework
return reporterv2.NewReportMock(reporterv2.NO_SUBMIT_QUERY, "run kubescape with the '--submit' flag")
}
var message string
if !fwScan {
message = "Kubescape does not submit scan results when scanning controls"
}
if !clusterScan {
message = "Kubescape will submit scan results only when scanning a cluster (not YAML files)"
}
return reporterv2.NewReportMock("", message)
}
func getResourceHandler(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, hostSensorHandler hostsensorutils.IHostSensor) resourcehandler.IResourceHandler {
func getResourceHandler(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, hostSensorHandler hostsensorutils.IHostSensor, registryAdaptors *resourcehandler.RegistryAdaptors) resourcehandler.IResourceHandler {
if len(scanInfo.InputPatterns) > 0 || k8s == nil {
return resourcehandler.NewFileResourceHandler(scanInfo.InputPatterns)
// scanInfo.HostSensor.SetBool(false)
return resourcehandler.NewFileResourceHandler(scanInfo.InputPatterns, registryAdaptors)
}
getter.GetArmoAPIConnector()
rbacObjects := getRBACHandler(tenantConfig, k8s, scanInfo.Submit)
return resourcehandler.NewK8sResourceHandler(k8s, getFieldSelector(scanInfo), hostSensorHandler, rbacObjects)
return resourcehandler.NewK8sResourceHandler(k8s, getFieldSelector(scanInfo), hostSensorHandler, rbacObjects, registryAdaptors)
}
func getHostSensorHandler(scanInfo *cautils.ScanInfo, k8s *k8sinterface.KubernetesApi) hostsensorutils.IHostSensor {
@@ -70,14 +83,14 @@ func getHostSensorHandler(scanInfo *cautils.ScanInfo, k8s *k8sinterface.Kubernet
hasHostSensorControls := true
// we need to determined which controls needs host sensor
if scanInfo.HostSensor.Get() == nil && hasHostSensorControls {
scanInfo.HostSensor.SetBool(askUserForHostSensor())
cautils.WarningDisplay(os.Stderr, "Warning: Kubernetes cluster nodes scanning is disabled. This is required to collect valuable data for certain controls. You can enable it using the --enable-host-scan flag\n")
if scanInfo.HostSensorEnabled.Get() == nil && hasHostSensorControls {
scanInfo.HostSensorEnabled.SetBool(askUserForHostSensor())
logger.L().Warning("Kubernetes cluster nodes scanning is disabled. This is required to collect valuable data for certain controls. You can enable it using the --enable-host-scan flag")
}
if hostSensorVal := scanInfo.HostSensor.Get(); hostSensorVal != nil && *hostSensorVal {
hostSensorHandler, err := hostsensorutils.NewHostSensorHandler(k8s)
if hostSensorVal := scanInfo.HostSensorEnabled.Get(); hostSensorVal != nil && *hostSensorVal {
hostSensorHandler, err := hostsensorutils.NewHostSensorHandler(k8s, scanInfo.HostSensorYamlPath)
if err != nil {
cautils.WarningDisplay(os.Stderr, fmt.Sprintf("Warning: failed to create host sensor: %v\n", err.Error()))
logger.L().Warning(fmt.Sprintf("failed to create host sensor: %s", err.Error()))
return &hostsensorutils.HostSensorHandlerMock{}
}
return hostSensorHandler
@@ -109,7 +122,7 @@ func policyIdentifierNames(pi []reporthandling.PolicyIdentifier) string {
return policiesNames
}
// setSubmitBehavior - Setup the desired cluster behavior regarding submittion to the Armo BE
// setSubmitBehavior - Setup the desired cluster behavior regarding submitting to the Armo BE
func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig) {
/*
@@ -135,14 +148,8 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
// Submit report
scanInfo.Submit = true
}
} else { // config not found in cache (not submitted)
if scanInfo.Submit {
// submit - Create tenant & Submit report
if err := tenantConfig.SetTenant(); err != nil {
fmt.Println(err)
}
}
}
}
// setPolicyGetter set the policy getter - local file/github release/ArmoAPI
@@ -152,7 +159,6 @@ func getPolicyGetter(loadPoliciesFromFile []string, accountID string, frameworkS
}
if accountID != "" && frameworkScope {
g := getter.GetArmoAPIConnector() // download policy from ARMO backend
g.SetCustomerGUID(accountID)
return g
}
if downloadReleasedPolicy == nil {
@@ -183,7 +189,6 @@ func getConfigInputsGetter(ControlsInputs string, accountID string, downloadRele
}
if accountID != "" {
g := getter.GetArmoAPIConnector() // download config from ARMO backend
g.SetCustomerGUID(accountID)
return g
}
if downloadReleasedPolicy == nil {
@@ -197,7 +202,7 @@ func getConfigInputsGetter(ControlsInputs string, accountID string, downloadRele
func getDownloadReleasedPolicy(downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull policy, fallback to cache
cautils.WarningDisplay(os.Stderr, "Warning: failed to get policies from github release, loading policies from cache\n")
logger.L().Warning("failed to get policies from github release, loading policies from cache", helpers.Error(err))
return getter.NewLoadPolicy(getDefaultFrameworksPaths())
} else {
return downloadReleasedPolicy
@@ -214,8 +219,8 @@ func getDefaultFrameworksPaths() []string {
func listFrameworksNames(policyGetter getter.IPolicyGetter) []string {
fw, err := policyGetter.ListFrameworks()
if err != nil {
fw = getDefaultFrameworksPaths()
if err == nil {
return fw
}
return fw
return getter.NativeFrameworks
}

View File

@@ -0,0 +1,90 @@
package containerscan
import (
"bytes"
"math/rand"
"time"
"github.com/francoispqt/gojay"
)
// GenerateContainerScanReportMock - generate a scan result
func GenerateContainerScanReportMock() ScanResultReport {
ds := ScanResultReport{
WLID: "wlid://cluster-k8s-geriatrix-k8s-demo3/namespace-whisky-app/deployment-whisky4all-shipping",
CustomerGUID: "1231bcb1-49ce-4a67-bdd3-5da7a393ae08",
ImgTag: "dreg.armo.cloud:443/demoservice:v16",
ImgHash: "docker-pullable://dreg.armo.cloud:443/demoservice@sha256:754f3cfca915a07ed10655a301dd7a8dc5526a06f9bd06e7c932f4d4108a8296",
Timestamp: time.Now().UnixNano(),
}
ds.Layers = make(LayersList, 0)
layer := ScanResultLayer{}
GenerateContainerScanLayer(&layer)
ds.Layers = append(ds.Layers, layer)
return ds
}
// GenerateContainerScanReportMock - generate a scan result
func GenerateContainerScanReportNoVulMock() ScanResultReport {
ds := ScanResultReport{
WLID: "wlid://cluster-k8s-geriatrix-k8s-demo3/namespace-whisky-app/deployment-whisky4all-shipping",
CustomerGUID: "1231bcb1-49ce-4a67-bdd3-5da7a393ae08",
ImgTag: "dreg.armo.cloud:443/demoservice:v16",
ImgHash: "docker-pullable://dreg.armo.cloud:443/demoservice@sha256:754f3cfca915a07ed10655a301dd7a8dc5526a06f9bd06e7c932f4d4108a8296",
Timestamp: time.Now().UnixNano(),
ContainerName: "shipping",
}
ds.Layers = make(LayersList, 0)
layer := ScanResultLayer{LayerHash: "aaa"}
ds.Layers = append(ds.Layers, layer)
return ds
}
var hash = []rune("abcdef0123456789")
var nums = []rune("0123456789")
func randSeq(n int, bank []rune) string {
rand.Seed(time.Now().UnixNano())
b := make([]rune, n)
for i := range b {
b[i] = bank[rand.Intn(len(bank))]
}
return string(b)
}
// GenerateContainerScanLayer - generate a layer with random vuls
func GenerateContainerScanLayer(layer *ScanResultLayer) {
layer.LayerHash = randSeq(32, hash)
layer.Vulnerabilities = make(VulnerabilitiesList, 0)
layer.Packages = make(LinuxPkgs, 0)
vuls := rand.Intn(10) + 1
for i := 0; i < vuls; i++ {
v := Vulnerability{}
GenerateVulnerability(&v)
layer.Vulnerabilities = append(layer.Vulnerabilities, v)
}
pkg := LinuxPackage{PackageName: "coreutils"}
pkg.Files = make(PkgFiles, 0)
pf := PackageFile{Filename: "aa"}
pkg.Files = append(pkg.Files, pf)
layer.Packages = append(layer.Packages, pkg)
}
// GenerateVulnerability - generate a vul (just diff "cve"'s)
func GenerateVulnerability(v *Vulnerability) error {
baseVul := " { \"name\": \"CVE-2014-9471\", \"imageTag\": \"debian:8\", \"link\": \"https://security-tracker.debian.org/tracker/CVE-2014-9471\", \"description\": \"The parse_datetime function in GNU coreutils allows remote attackers to cause a denial of service (crash) or possibly execute arbitrary code via a crafted date string, as demonstrated by the sdf\", \"severity\": \"Low\", \"metadata\": { \"NVD\": { \"CVSSv2\": { \"Score\": 7.5, \"Vectors\": \"AV:N/AC:L/Au:N/C:P/I:P\" } } }, \"fixedIn\": [ { \"name\": \"coreutils\", \"imageTag\": \"debian:8\", \"version\": \"8.23-1\" } ] }"
b := []byte(baseVul)
r := bytes.NewReader(b)
er := gojay.NewDecoder(r).DecodeObject(v)
v.RelatedPackageName = "coreutils"
v.Severity = HighSeverity
v.Relevancy = Irelevant
v.Name = "CVE-" + randSeq(4, nums) + "-" + randSeq(4, nums)
return er
}

View File

@@ -0,0 +1,90 @@
package containerscan
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/francoispqt/gojay"
)
func TestDecodeScanWIthDangearousArtifacts(t *testing.T) {
rhs := &ScanResultReport{}
er := gojay.NewDecoder(strings.NewReader(nginxScanJSON)).DecodeObject(rhs)
if er != nil {
t.Errorf("decode failed due to: %v", er.Error())
}
sumObj := rhs.Summarize()
if sumObj.Registry != "" {
t.Errorf("sumObj.Registry = %v", sumObj.Registry)
}
if sumObj.VersionImage != "nginx:1.18.0" {
t.Errorf("sumObj.VersionImage = %v", sumObj.Registry)
}
if sumObj.ImgTag != "nginx:1.18.0" {
t.Errorf("sumObj.ImgTag = %v", sumObj.ImgTag)
}
if sumObj.Status != "Success" {
t.Errorf("sumObj.Status = %v", sumObj.Status)
}
if len(sumObj.ListOfDangerousArtifcats) != 3 {
t.Errorf("sumObj.ListOfDangerousArtifcats = %v", sumObj.ListOfDangerousArtifcats)
}
}
func TestUnmarshalScanReport(t *testing.T) {
ds := GenerateContainerScanReportMock()
str1 := ds.AsFNVHash()
rhs := &ScanResultReport{}
bolB, _ := json.Marshal(ds)
r := bytes.NewReader(bolB)
er := gojay.NewDecoder(r).DecodeObject(rhs)
if er != nil {
t.Errorf("marshalling failed due to: %v", er.Error())
}
if rhs.AsFNVHash() != str1 {
t.Errorf("marshalling failed different values after marshal:\nOriginal:\n%v\nParsed:\n%v\n\n===\n", string(bolB), rhs)
}
}
func TestUnmarshalScanReport1(t *testing.T) {
ds := Vulnerability{}
if err := GenerateVulnerability(&ds); err != nil {
t.Errorf("%v\n%v\n", ds, err)
}
}
func TestGetByPkgNameSuccess(t *testing.T) {
ds := GenerateContainerScanReportMock()
a := ds.Layers[0].GetFilesByPackage("coreutils")
if a != nil {
fmt.Printf("%+v\n", *a)
}
}
func TestGetByPkgNameMissing(t *testing.T) {
ds := GenerateContainerScanReportMock()
a := ds.Layers[0].GetFilesByPackage("s")
if a != nil && len(*a) > 0 {
t.Errorf("expected - no such package should be in that layer %v\n\n; found - %v", ds, a)
}
}
func TestCalculateFixed(t *testing.T) {
res := CalculateFixed([]FixedIn{{
Name: "",
ImgTag: "",
Version: "",
}})
if 0 != res {
t.Errorf("wrong fix status: %v", res)
}
}

View File

@@ -0,0 +1,37 @@
package containerscan
const (
//defines Relevancy as enum-like
Unknown = "Unknown"
Relevant = "Relevant"
Irelevant = "Irelevant"
NoSP = "No signature profile to compare"
//Clair Severities
UnknownSeverity = "Unknown"
NegligibleSeverity = "Negligible"
LowSeverity = "Low"
MediumSeverity = "Medium"
HighSeverity = "High"
CriticalSeverity = "Critical"
ContainerScanRedisPrefix = "_containerscan"
)
var KnownSeverities = map[string]bool{
UnknownSeverity: true,
NegligibleSeverity: true,
LowSeverity: true,
MediumSeverity: true,
HighSeverity: true,
CriticalSeverity: true,
}
func CalculateFixed(Fixes []FixedIn) int {
for _, fix := range Fixes {
if fix.Version != "None" && fix.Version != "" {
return 1
}
}
return 0
}

View File

@@ -0,0 +1,51 @@
package containerscan
import (
"strings"
"github.com/armosec/armoapi-go/armotypes"
)
func (layer *ScanResultLayer) GetFilesByPackage(pkgname string) (files *PkgFiles) {
for _, pkg := range layer.Packages {
if pkg.PackageName == pkgname {
return &pkg.Files
}
}
return &PkgFiles{}
}
func (layer *ScanResultLayer) GetPackagesNames() []string {
pkgsNames := []string{}
for _, pkg := range layer.Packages {
pkgsNames = append(pkgsNames, pkg.PackageName)
}
return pkgsNames
}
func (scanresult *ScanResultReport) GetDesignatorsNContext() (*armotypes.PortalDesignator, []armotypes.ArmoContext) {
designatorsObj := armotypes.AttributesDesignatorsFromWLID(scanresult.WLID)
designatorsObj.Attributes["containerName"] = scanresult.ContainerName
designatorsObj.Attributes["customerGUID"] = scanresult.CustomerGUID
contextObj := armotypes.DesignatorToArmoContext(designatorsObj, "designators")
return designatorsObj, contextObj
}
func (scanresult *ScanResultReport) Validate() bool {
if scanresult.CustomerGUID == "" || (scanresult.ImgHash == "" && scanresult.ImgTag == "") || scanresult.Timestamp <= 0 {
return false
}
//TODO validate layers & vuls
return true
}
func (v *Vulnerability) IsRCE() bool {
desc := strings.ToLower(v.Description)
isRCE := strings.Contains(v.Description, "RCE")
return isRCE || strings.Contains(desc, "remote code execution") || strings.Contains(desc, "remote command execution") || strings.Contains(desc, "arbitrary code") || strings.Contains(desc, "code execution") || strings.Contains(desc, "code injection") || strings.Contains(desc, "command injection") || strings.Contains(desc, "inject arbitrary commands")
}

View File

@@ -0,0 +1,141 @@
package containerscan
import (
"github.com/armosec/armoapi-go/armotypes"
cautils "github.com/armosec/utils-k8s-go/armometadata"
)
// ToFlatVulnerabilities - returnsgit p
func (scanresult *ScanResultReport) ToFlatVulnerabilities() []*ElasticContainerVulnerabilityResult {
vuls := make([]*ElasticContainerVulnerabilityResult, 0)
vul2indx := make(map[string]int)
scanID := scanresult.AsFNVHash()
designatorsObj, ctxList := scanresult.GetDesignatorsNContext()
for _, layer := range scanresult.Layers {
for _, vul := range layer.Vulnerabilities {
esLayer := ESLayer{LayerHash: layer.LayerHash, ParentLayerHash: layer.ParentLayerHash}
if indx, isOk := vul2indx[vul.Name]; isOk {
vuls[indx].Layers = append(vuls[indx].Layers, esLayer)
continue
}
result := &ElasticContainerVulnerabilityResult{WLID: scanresult.WLID,
Timestamp: scanresult.Timestamp,
Designators: *designatorsObj,
Context: ctxList}
result.Vulnerability = vul
result.Layers = make([]ESLayer, 0)
result.Layers = append(result.Layers, esLayer)
result.ContainerScanID = scanID
result.IsFixed = CalculateFixed(vul.Fixes)
result.RelevantLinks = append(result.RelevantLinks, "https://nvd.nist.gov/vuln/detail/"+vul.Name)
result.RelevantLinks = append(result.RelevantLinks, vul.Link)
result.Vulnerability.Link = "https://nvd.nist.gov/vuln/detail/" + vul.Name
result.Categories.IsRCE = result.IsRCE()
vuls = append(vuls, result)
vul2indx[vul.Name] = len(vuls) - 1
}
}
// find first introduced
for i, v := range vuls {
earlyLayer := ""
for _, layer := range v.Layers {
if layer.ParentLayerHash == earlyLayer {
earlyLayer = layer.LayerHash
}
}
vuls[i].IntroducedInLayer = earlyLayer
}
return vuls
}
func (scanresult *ScanResultReport) Summarize() *ElasticContainerScanSummaryResult {
designatorsObj, ctxList := scanresult.GetDesignatorsNContext()
summary := &ElasticContainerScanSummaryResult{
Designators: *designatorsObj,
Context: ctxList,
CustomerGUID: scanresult.CustomerGUID,
ImgTag: scanresult.ImgTag,
ImgHash: scanresult.ImgHash,
WLID: scanresult.WLID,
Timestamp: scanresult.Timestamp,
ContainerName: scanresult.ContainerName,
ContainerScanID: scanresult.AsFNVHash(),
ListOfDangerousArtifcats: scanresult.ListOfDangerousArtifcats,
}
summary.Cluster = designatorsObj.Attributes[armotypes.AttributeCluster]
summary.Namespace = designatorsObj.Attributes[armotypes.AttributeNamespace]
imageInfo, e2 := cautils.ImageTagToImageInfo(scanresult.ImgTag)
if e2 == nil {
summary.Registry = imageInfo.Registry
summary.VersionImage = imageInfo.VersionImage
}
summary.PackagesName = make([]string, 0)
severitiesStats := map[string]SeverityStats{}
uniqueVulsMap := make(map[string]bool)
for _, layer := range scanresult.Layers {
summary.PackagesName = append(summary.PackagesName, (layer.GetPackagesNames())...)
for _, vul := range layer.Vulnerabilities {
if _, isOk := uniqueVulsMap[vul.Name]; isOk {
continue
}
uniqueVulsMap[vul.Name] = true
// TODO: maybe add all severities just to have a placeholders
if !KnownSeverities[vul.Severity] {
vul.Severity = UnknownSeverity
}
vulnSeverityStats, ok := severitiesStats[vul.Severity]
if !ok {
vulnSeverityStats = SeverityStats{Severity: vul.Severity}
}
vulnSeverityStats.TotalCount++
summary.TotalCount++
isFixed := CalculateFixed(vul.Fixes) > 0
if isFixed {
vulnSeverityStats.FixAvailableOfTotalCount++
summary.FixAvailableOfTotalCount++
}
isRCE := vul.IsRCE()
if isRCE {
vulnSeverityStats.RCECount++
summary.RCECount++
}
if vul.Relevancy == Relevant {
vulnSeverityStats.RelevantCount++
summary.RelevantCount++
if isFixed {
vulnSeverityStats.FixAvailableForRelevantCount++
summary.FixAvailableForRelevantCount++
}
}
severitiesStats[vul.Severity] = vulnSeverityStats
}
}
summary.Status = "Success"
// if criticalStats, hasCritical := severitiesStats[CriticalSeverity]; hasCritical && criticalStats.TotalCount > 0 {
// summary.Status = "Fail"
// }
// if highStats, hasHigh := severitiesStats[HighSeverity]; hasHigh && highStats.RelevantCount > 0 {
// summary.Status = "Fail"
// }
for sever := range severitiesStats {
summary.SeveritiesStats = append(summary.SeveritiesStats, severitiesStats[sever])
}
return summary
}

View File

@@ -0,0 +1,89 @@
package containerscan
import "github.com/armosec/armoapi-go/armotypes"
type ElasticContainerVulnerabilityResult struct {
Designators armotypes.PortalDesignator `json:"designators"`
Context []armotypes.ArmoContext `json:"context"`
WLID string `json:"wlid"`
ContainerScanID string `json:"containersScanID"`
Layers []ESLayer `json:"layers"`
Timestamp int64 `json:"timestamp"`
IsFixed int `json:"isFixed"`
IntroducedInLayer string `json:"layerHash"`
RelevantLinks []string `json:"links"` // shitty SE practice
Vulnerability `json:",inline"`
}
type ESLayer struct {
LayerHash string `json:"layerHash"`
ParentLayerHash string `json:"parentLayerHash"`
}
type SeverityStats struct {
Severity string `json:"severity,omitempty"`
TotalCount int64 `json:"total"`
FixAvailableOfTotalCount int64 `json:"fixedTotal"`
RelevantCount int64 `json:"totalRelevant"`
FixAvailableForRelevantCount int64 `json:"fixedRelevant"`
RCECount int64 `json:"rceTotal"`
UrgentCount int64 `json:"urgent"`
NeglectedCount int64 `json:"neglected"`
HealthStatus string `json:"healthStatus"`
}
type ElasticContainerScanSeveritySummary struct {
Designators armotypes.PortalDesignator `json:"designators"`
Context []armotypes.ArmoContext `json:"context"`
SeverityStats
CustomerGUID string `json:"customerGUID"`
ContainerScanID string `json:"containersScanID"`
Timestamp int64 `json:"timestamp"`
WLID string `json:"wlid"`
ImgTag string `json:"imageTag"`
ImgHash string `json:"imageHash"`
Cluster string `json:"cluster"`
Namespace string `json:"namespace"`
ContainerName string `json:"containerName"`
Status string `json:"status"`
Registry string `json:"registry"`
VersionImage string `json:"versionImage"`
Version string `json:"version"`
DayDate string `json:"dayDate"`
}
type ElasticContainerScanSummaryResult struct {
SeverityStats
Designators armotypes.PortalDesignator `json:"designators"`
Context []armotypes.ArmoContext `json:"context"`
CustomerGUID string `json:"customerGUID"`
ContainerScanID string `json:"containersScanID"`
Timestamp int64 `json:"timestamp"`
WLID string `json:"wlid"`
ImgTag string `json:"imageTag"`
ImgHash string `json:"imageHash"`
Cluster string `json:"cluster"`
Namespace string `json:"namespace"`
ContainerName string `json:"containerName"`
PackagesName []string `json:"packages"`
ListOfDangerousArtifcats []string `json:"listOfDangerousArtifcats"`
Status string `json:"status"`
Registry string `json:"registry"`
VersionImage string `json:"versionImage"`
SeveritiesStats []SeverityStats `json:"severitiesStats"`
Version string `json:"version"`
}
func (summary *ElasticContainerScanSummaryResult) Validate() bool {
return summary.CustomerGUID != "" && summary.ContainerScanID != "" && (summary.ImgTag != "" || summary.ImgHash != "") && summary.Timestamp > 0
}

View File

@@ -0,0 +1,246 @@
package containerscan
import (
"github.com/francoispqt/gojay"
)
/*
responsible on fast unmarshaling of various COMMON containerscan structures and substructures
*/
// UnmarshalJSONObject - File inside a pkg
func (file *PackageFile) UnmarshalJSONObject(dec *gojay.Decoder, key string) (err error) {
switch key {
case "name":
err = dec.String(&(file.Filename))
}
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 *PackageFile) NKeys() int {
return 0
}
// UnmarshalJSONObject--- Package
func (pkgnx *LinuxPackage) UnmarshalJSONObject(dec *gojay.Decoder, key string) (err error) {
switch key {
case "packageName":
err = dec.String(&(pkgnx.PackageName))
case "version":
err = dec.String(&(pkgnx.PackageVersion))
case "files":
err = dec.Array(&(pkgnx.Files))
}
return err
}
func (file *LinuxPackage) NKeys() int {
return 0
}
func (pkgs *LinuxPkgs) UnmarshalJSONArray(dec *gojay.Decoder) error {
lae := LinuxPackage{}
if err := dec.Object(&lae); err != nil {
return err
}
*pkgs = append(*pkgs, lae)
return nil
}
//--------Vul fixed in----------------------------------
func (fx *FixedIn) UnmarshalJSONObject(dec *gojay.Decoder, key string) (err error) {
switch key {
case "name":
err = dec.String(&(fx.Name))
case "imageTag":
err = dec.String(&(fx.ImgTag))
case "version":
err = dec.String(&(fx.Version))
}
return err
}
func (t *VulFixes) UnmarshalJSONArray(dec *gojay.Decoder) error {
lae := FixedIn{}
if err := dec.Object(&lae); err != nil {
return err
}
*t = append(*t, lae)
return nil
}
func (file *FixedIn) NKeys() int {
return 0
}
//------ VULNERABIlITy ---------------------
// Name string `json:"name"`
// ImgHash string `json:"imageHash"`
// ImgTag string `json:"imageTag",omitempty`
// RelatedPackageName string `json:"packageName"`
// PackageVersion string `json:"packageVersion"`
// Link string `json:"link"`
// Description string `json:"description"`
// Severity string `json:"severity"`
// Metadata interface{} `json:"metadata",omitempty`
// Fixes VulFixes `json:"fixedIn",omitempty`
// Relevancy string `json:"relevant"` // use the related enum
func (v *Vulnerability) UnmarshalJSONObject(dec *gojay.Decoder, key string) (err error) {
switch key {
case "name":
err = dec.String(&(v.Name))
case "imageTag":
err = dec.String(&(v.ImgTag))
case "imageHash":
err = dec.String(&(v.ImgHash))
case "packageName":
err = dec.String(&(v.RelatedPackageName))
case "packageVersion":
err = dec.String(&(v.PackageVersion))
case "link":
err = dec.String(&(v.Link))
case "description":
err = dec.String(&(v.Description))
case "severity":
err = dec.String(&(v.Severity))
case "relevant":
err = dec.String(&(v.Relevancy))
case "fixedIn":
err = dec.Array(&(v.Fixes))
case "metadata":
err = dec.Interface(&(v.Metadata))
}
return err
}
func (t *VulnerabilitiesList) UnmarshalJSONArray(dec *gojay.Decoder) error {
lae := Vulnerability{}
if err := dec.Object(&lae); err != nil {
return err
}
*t = append(*t, lae)
return nil
}
func (v *Vulnerability) NKeys() int {
return 0
}
//---------Layer Object----------------------------------
// type ScanResultLayer struct {
// LayerHash string `json:layerHash`
// Vulnerabilities []Vulnerability `json:vulnerabilities`
// Packages []LinuxPackage `json:packageToFile`
// }
func (scan *ScanResultLayer) UnmarshalJSONObject(dec *gojay.Decoder, key string) (err error) {
switch key {
// case "timestamp":
// err = dec.Time(&(reporter.Timestamp), time.RFC3339)
// reporter.Timestamp = reporter.Timestamp.Local()
case "layerHash":
err = dec.String(&(scan.LayerHash))
case "parentLayerHash":
err = dec.String(&(scan.ParentLayerHash))
case "vulnerabilities":
err = dec.Array(&(scan.Vulnerabilities))
case "packageToFile":
err = dec.Array(&(scan.Packages))
}
return err
}
func (t *LayersList) UnmarshalJSONArray(dec *gojay.Decoder) error {
lae := ScanResultLayer{}
if err := dec.Object(&lae); err != nil {
return err
}
*t = append(*t, lae)
return nil
}
func (scan *ScanResultLayer) NKeys() int {
return 0
}
//---------------------SCAN RESULT--------------------------------------------------------------------------
// type ScanResultReport struct {
// CustomerGUID string `json:customerGuid`
// ImgTag string `json:imageTag,omitempty`
// ImgHash string `json:imageHash`
// WLID string `json:wlid`
// Timestamp int `json:customerGuid`
// Layers []ScanResultLayer `json:layers`
// ContainerName
// }
func (scan *ScanResultReport) UnmarshalJSONObject(dec *gojay.Decoder, key string) (err error) {
switch key {
// case "timestamp":
// err = dec.Time(&(reporter.Timestamp), time.RFC3339)
// reporter.Timestamp = reporter.Timestamp.Local()
case "customerGUID":
err = dec.String(&(scan.CustomerGUID))
case "imageTag":
err = dec.String(&(scan.ImgTag))
case "imageHash":
err = dec.String(&(scan.ImgHash))
case "wlid":
err = dec.String(&(scan.WLID))
case "containerName":
err = dec.String(&(scan.ContainerName))
case "timestamp":
err = dec.Int64(&(scan.Timestamp))
case "layers":
err = dec.Array(&(scan.Layers))
case "listOfDangerousArtifcats":
err = dec.SliceString(&(scan.ListOfDangerousArtifcats))
}
return err
}
func (scan *ScanResultReport) NKeys() int {
return 0
}

View File

@@ -0,0 +1,525 @@
package containerscan
var nginxScanJSON = `
{
"customerGUID": "1e3a88bf-92ce-44f8-914e-cbe71830d566",
"imageTag": "nginx:1.18.0",
"imageHash": "",
"wlid": "wlid://cluster-test/namespace-test/deployment-davidg",
"containerName": "nginx-1",
"timestamp": 1628091365,
"layers": [
{
"layerHash": "sha256:f7ec5a41d630a33a2d1db59b95d89d93de7ae5a619a3a8571b78457e48266eba",
"parentLayerHash": "",
"vulnerabilities": [
{
"name": "CVE-2009-0854",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "dash",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-0854",
"description": "Untrusted search path vulnerability in dash 0.5.4, when used as a login shell, allows local users to execute arbitrary code via a Trojan horse .profile file in the current working directory.",
"severity": "Medium",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2019-13627",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "libgcrypt20",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-13627",
"description": "It was discovered that there was a ECDSA timing attack in the libgcrypt20 cryptographic library. Version affected: 1.8.4-5, 1.7.6-2+deb9u3, and 1.6.3-2+deb8u4. Versions fixed: 1.8.5-2 and 1.6.3-2+deb8u7.",
"severity": "Medium",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2021-33560",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "libgcrypt20",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-33560",
"description": "Libgcrypt before 1.8.8 and 1.9.x before 1.9.3 mishandles ElGamal encryption because it lacks exponent blinding to address a side-channel attack against mpi_powm, and the window size is not chosen appropriately. (There is also an interoperability problem because the selection of the k integer value does not properly consider the differences between basic ElGamal encryption and generalized ElGamal encryption.) This, for example, affects use of ElGamal in OpenPGP.",
"severity": "High",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:1.8.4-5+deb10u1"
}
],
"relevant": ""
},
{
"name": "CVE-2021-3345",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "libgcrypt20",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3345",
"description": "_gcry_md_block_write in cipher/hash-common.c in Libgcrypt version 1.9.0 has a heap-based buffer overflow when the digest final function sets a large count value. It is recommended to upgrade to 1.9.1 or later.",
"severity": "High",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2010-0834",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "base-files",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-0834",
"description": "The base-files package before 5.0.0ubuntu7.1 on Ubuntu 9.10 and before 5.0.0ubuntu20.10.04.2 on Ubuntu 10.04 LTS, as shipped on Dell Latitude 2110 netbooks, does not require authentication for package installation, which allows remote archive servers and man-in-the-middle attackers to execute arbitrary code via a crafted package.",
"severity": "High",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2018-6557",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "base-files",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-6557",
"description": "The MOTD update script in the base-files package in Ubuntu 18.04 LTS before 10.1ubuntu2.2, and Ubuntu 18.10 before 10.1ubuntu6 incorrectly handled temporary files. A local attacker could use this issue to cause a denial of service, or possibly escalate privileges if kernel symlink restrictions were disabled.",
"severity": "High",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2013-0223",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "coreutils",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-0223",
"description": "The SUSE coreutils-i18n.patch for GNU coreutils allows context-dependent attackers to cause a denial of service (segmentation fault and crash) via a long string to the join command, when using the -i switch, which triggers a stack-based buffer overflow in the alloca function.",
"severity": "Low",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2015-4041",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "coreutils",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-4041",
"description": "The keycompare_mb function in sort.c in sort in GNU Coreutils through 8.23 on 64-bit platforms performs a size calculation without considering the number of bytes occupied by multibyte characters, which allows attackers to cause a denial of service (heap-based buffer overflow and application crash) or possibly have unspecified other impact via long UTF-8 strings.",
"severity": "High",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2009-4135",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "coreutils",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-4135",
"description": "The distcheck rule in dist-check.mk in GNU coreutils 5.2.1 through 8.1 allows local users to gain privileges via a symlink attack on a file in a directory tree under /tmp.",
"severity": "Medium",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2015-4042",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "coreutils",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-4042",
"description": "Integer overflow in the keycompare_mb function in sort.c in sort in GNU Coreutils through 8.23 might allow attackers to cause a denial of service (application crash) or possibly have unspecified other impact via long strings.",
"severity": "Critical",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2013-0221",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "coreutils",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-0221",
"description": "The SUSE coreutils-i18n.patch for GNU coreutils allows context-dependent attackers to cause a denial of service (segmentation fault and crash) via a long string to the sort command, when using the (1) -d or (2) -M switch, which triggers a stack-based buffer overflow in the alloca function.",
"severity": "Medium",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2013-0222",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "coreutils",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-0222",
"description": "The SUSE coreutils-i18n.patch for GNU coreutils allows context-dependent attackers to cause a denial of service (segmentation fault and crash) via a long string to the uniq command, which triggers a stack-based buffer overflow in the alloca function.",
"severity": "Low",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2016-2781",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "coreutils",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-2781",
"description": "chroot in GNU coreutils, when used with --userspec, allows local users to escape to the parent session via a crafted TIOCSTI ioctl call, which pushes characters to the terminal's input buffer.",
"severity": "Medium",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2017-18018",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "coreutils",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-18018",
"description": "In GNU Coreutils through 8.29, chown-core.c in chown and chgrp does not prevent replacement of a plain file with a symlink during use of the POSIX \"-R -L\" options, which allows local users to modify the ownership of arbitrary files by leveraging a race condition.",
"severity": "Medium",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2021-20193",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "tar",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-20193",
"description": "A flaw was found in the src/list.c of tar 1.33 and earlier. This flaw allows an attacker who can submit a crafted input file to tar to cause uncontrolled consumption of memory. The highest threat from this vulnerability is to system availability.",
"severity": "Medium",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2005-2541",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "tar",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2005-2541",
"description": "Tar 1.15.1 does not properly warn the user when extracting setuid or setgid files, which may allow local users or remote attackers to gain privileges.",
"severity": "High",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2019-9923",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "tar",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9923",
"description": "pax_decode_header in sparse.c in GNU Tar before 1.32 had a NULL pointer dereference when parsing certain archives that have malformed extended headers.",
"severity": "High",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2018-1000654",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "libtasn1-6",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000654",
"description": "GNU Libtasn1-4.13 libtasn1-4.13 version libtasn1-4.13, libtasn1-4.12 contains a DoS, specifically CPU usage will reach 100% when running asn1Paser against the POC due to an issue in _asn1_expand_object_id(p_tree), after a long time, the program will be killed. This attack appears to be exploitable via parsing a crafted file.",
"severity": "High",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2011-3374",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "apt",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-3374",
"description": "It was found that apt-key in apt, all versions, do not correctly validate gpg keys with the master keyring, leading to a potential man-in-the-middle attack.",
"severity": "Medium",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2021-37600",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "util-linux",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-37600",
"description": "An integer overflow in util-linux through 2.37.1 can potentially cause a buffer overflow if an attacker were able to use system resources in a way that leads to a large number in the /proc/sysvipc/sem file.",
"severity": "Unknown",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2007-0822",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "util-linux",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-0822",
"description": "umount, when running with the Linux 2.6.15 kernel on Slackware Linux 10.2, allows local users to trigger a NULL dereference and application crash by invoking the program with a pathname for a USB pen drive that was mounted and then physically removed, which might allow the users to obtain sensitive information, including core file contents.",
"severity": "Low",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2004-1349",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "gzip",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2004-1349",
"description": "gzip before 1.3 in Solaris 8, when called with the -f or -force flags, will change the permissions of files that are hard linked to the target files, which allows local users to view or modify these files.",
"severity": "Low",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2004-0603",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "gzip",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2004-0603",
"description": "gzexe in gzip 1.3.3 and earlier will execute an argument when the creation of a temp file fails instead of exiting the program, which could allow remote attackers or local users to execute arbitrary commands, a different vulnerability than CVE-1999-1332.",
"severity": "High",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2010-0002",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "bash",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-0002",
"description": "The /etc/profile.d/60alias.sh script in the Mandriva bash package for Bash 2.05b, 3.0, 3.2, 3.2.48, and 4.0 enables the --show-control-chars option in LS_OPTIONS, which allows local users to send escape sequences to terminal emulators, or hide the existence of a file, via a crafted filename.",
"severity": "Low",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
},
{
"name": "CVE-2019-18276",
"imageHash": "sha256:c2c45d506085d300b72a6d4b10e3dce104228080a2cf095fc38333afe237e2be",
"imageTag": "",
"packageName": "bash",
"packageVersion": "",
"link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276",
"description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.",
"severity": "High",
"metadata": null,
"fixedIn": [
{
"name": "",
"imageTag": "",
"version": "0:0"
}
],
"relevant": ""
}
],
"packageToFile": null
},
{
"layerHash": "sha256:0b20d28b5eb3007f70c43cdd8efcdb04016aa193192e5911cda5b7590ffaa635",
"parentLayerHash": "sha256:f7ec5a41d630a33a2d1db59b95d89d93de7ae5a619a3a8571b78457e48266eba",
"vulnerabilities": [],
"packageToFile": null
},
{
"layerHash": "sha256:1576642c97761adf346890bf67c43473217160a9a203ef47d0bc6020af652798",
"parentLayerHash": "sha256:0b20d28b5eb3007f70c43cdd8efcdb04016aa193192e5911cda5b7590ffaa635",
"vulnerabilities": [],
"packageToFile": null
},
{
"layerHash": "sha256:c12a848bad84d57e3f5faafab5880484434aee3bf8bdde4d519753b7c81254fd",
"parentLayerHash": "sha256:1576642c97761adf346890bf67c43473217160a9a203ef47d0bc6020af652798",
"vulnerabilities": [],
"packageToFile": null
},
{
"layerHash": "sha256:03f221d9cf00a7077231c6dcac3c95182727c7e7fd44fd2b2e882a01dcda2d70",
"parentLayerHash": "sha256:c12a848bad84d57e3f5faafab5880484434aee3bf8bdde4d519753b7c81254fd",
"vulnerabilities": [],
"packageToFile": null
}
],
"listOfDangerousArtifcats": [
"bin/dash",
"bin/bash",
"usr/bin/curl"
]
}
`

View File

@@ -0,0 +1,93 @@
package containerscan
import (
"fmt"
"hash/fnv"
)
//!!!!!!!!!!!!EVERY CHANGE IN THESE STRUCTURES => CHANGE gojayunmarshaller ASWELL!!!!!!!!!!!!!!!!!!!!!!!!
// ScanResultReport - the report given from scanner to event receiver
type ScanResultReport struct {
CustomerGUID string `json:"customerGUID"`
ImgTag string `json:"imageTag"`
ImgHash string `json:"imageHash"`
WLID string `json:"wlid"`
ContainerName string `json:"containerName"`
Timestamp int64 `json:"timestamp"`
Layers LayersList `json:"layers"`
ListOfDangerousArtifcats []string `json:"listOfDangerousArtifcats"`
}
// ScanResultLayer - represents a single layer from container scan result
type ScanResultLayer struct {
LayerHash string `json:"layerHash"`
ParentLayerHash string `json:"parentLayerHash"`
Vulnerabilities VulnerabilitiesList `json:"vulnerabilities"`
Packages LinuxPkgs `json:"packageToFile"`
}
type VulnerabilityCategory struct {
IsRCE bool `json:"isRce"`
}
// Vulnerability - a vul object
type Vulnerability struct {
Name string `json:"name"`
ImgHash string `json:"imageHash"`
ImgTag string `json:"imageTag"`
RelatedPackageName string `json:"packageName"`
PackageVersion string `json:"packageVersion"`
Link string `json:"link"`
Description string `json:"description"`
Severity string `json:"severity"`
Metadata interface{} `json:"metadata"`
Fixes VulFixes `json:"fixedIn"`
Relevancy string `json:"relevant"` // use the related enum
UrgentCount int `json:"urgent"`
NeglectedCount int `json:"neglected"`
HealthStatus string `json:"healthStatus"`
Categories VulnerabilityCategory `json:"categories"`
}
// FixedIn when and which pkg was fixed (which version as well)
type FixedIn struct {
Name string `json:"name"`
ImgTag string `json:"imageTag"`
Version string `json:"version"`
}
// LinuxPackage- Linux package representation
type LinuxPackage struct {
PackageName string `json:"packageName"`
Files PkgFiles `json:"files"`
PackageVersion string `json:"version"`
}
// PackageFile - s.e
type PackageFile struct {
Filename string `json:"name"`
}
// types to provide unmarshalling:
//VulnerabilitiesList -s.e
type LayersList []ScanResultLayer
//VulnerabilitiesList -s.e
type VulnerabilitiesList []Vulnerability
//LinuxPkgs - slice of linux pkgs
type LinuxPkgs []LinuxPackage
//VulFixes - information bout when/how this vul was fixed
type VulFixes []FixedIn
//PkgFiles - slice of files belong to specific pkg
type PkgFiles []PackageFile
func (v *ScanResultReport) AsFNVHash() string {
hasher := fnv.New64a()
hasher.Write([]byte(fmt.Sprintf("%v", *v)))
return fmt.Sprintf("%v", hasher.Sum64())
}

7
core/kscore.go Normal file
View File

@@ -0,0 +1,7 @@
package core
// Scan
// List
// Download
// Config
// Submit

View File

@@ -11,7 +11,7 @@ source #287
### Relation to this proposal
There are multiple changes and design decisions needs to be made before Kubescape will support the before outlined controls. However, a focal point the whole picutre is the ability to access vulnerabilty databases of container images. We anticiapte that most container image repositories will support image vulnerabilty scanning, some major players are already do. Since there is no a single API available which all of these data sources support it is important to create an adaption layer within Kubescape so different datasources can serve Kubescape's goals.
There are multiple changes and design decisions needs to be made before Kubescape will support the before outlined controls. However, a focal point the whole picutre is the ability to access vulnerability databases of container images. We anticipate that most container image repositories will support image vulnerability scanning, some major players are already do. Since there is no a single API available which all of these data sources support it is important to create an adaption layer within Kubescape so different datasources can serve Kubescape's goals.
## High level design of Kubescape
@@ -21,7 +21,7 @@ There are multiple changes and design decisions needs to be made before Kubescap
* OPA engine: the [OPA](https://github.com/open-policy-agent/opa) rego interpreter
* Rules processor: Kubescape component, it enumerates and runs the controls while also preparing the all the input data that the controls need for running
* Data sources: set of different modules providing data to the Rules processor so it can run the controls with them. Examples: Kubernetes objects, cloud vendor API objects and adding in this proposal the vulnerability infomration
* Cloud Image Vulnerability adaption interface: the subject of this proposal, it gives a common interface for different registry/vulnerabilty vendors to adapt to.
* Cloud Image Vulnerability adaption interface: the subject of this proposal, it gives a common interface for different registry/vulnerability vendors to adapt to.
* CIV adaptors: specific implementation of the CIV interface, example Harbor adaption
```
-----------------------
@@ -89,7 +89,7 @@ type ContainerImageScanStatus struct {
LastScanDate time.Time
}
type ContainerImageVulnerability struct {
type ContainerImageVulnerabilityReport struct {
ImageID ContainerImageIdentifier
// TBD
}
@@ -110,7 +110,7 @@ type IContainerImageVulnerabilityAdaptor interface {
GetImagesScanStatus(imageIDs []ContainerImageIdentifier) ([]ContainerImageScanStatus, error)
GetImagesVulnerabilties(imageIDs []ContainerImageIdentifier) ([]ContainerImageVulnerability, error)
GetImagesVulnerabilties(imageIDs []ContainerImageIdentifier) ([]ContainerImageVulnerabilityReport, error)
GetImagesInformation(imageIDs []ContainerImageIdentifier) ([]ContainerImageInformation, error)
}

View File

@@ -1,91 +0,0 @@
# Kubescape Release
## Input
### Scan a running Kubernetes cluster
* Scan your Kubernetes cluster. Ignore `kube-system` and `kube-public` namespaces
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
```
* Scan your Kubernetes cluster
```
kubescape scan framework nsa
```
### Scan a local Kubernetes manifest
* Scan single Kubernetes manifest file <img src="new-feature.svg">
```
kubescape scan framework nsa <my-workload.yaml>
```
* Scan many Kubernetes manifest files <img src="new-feature.svg">
```
kubescape scan framework nsa <my-workload-1.yaml> <my-workload-2.yaml>
```
* Scan all Kubernetes manifest files in directory <img src="new-feature.svg">
```
kubescape scan framework nsa *.yaml
```
* Scan Kubernetes manifest from stdout <img src="new-feature.svg">
```
cat <my-workload.yaml> | kubescape scan framework nsa -
```
* Scan Kubernetes manifest url <img src="new-feature.svg">
```
kubescape scan framework nsa https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/master/release/kubernetes-manifests.yaml
```
### Scan HELM chart
* Render the helm chart using [`helm template`](https://helm.sh/docs/helm/helm_template/) and pass to stdout <img src="new-feature.svg">
```
helm template [CHART] [flags] --generate-name --dry-run | kubescape scan framework nsa -
```
### Scan on-prem (offline)
* Scan using a framework from the local file system
```
kubescape scan framework --use-from <path>
```
* Scan using the framework from the default location in file system
```
kubescape scan framework --use-default
```
## Output formats
By default, the output is user friendly.
For the sake of automation, it is possible to receive the result in a `json` or `junit xml` format.
* Output in `json` format <img src="new-feature.svg">
```
kubescape scan framework nsa --format json --output results.json
```
* Output in `junit xml` format <img src="new-feature.svg">
```
kubescape scan framework nsa --format junit --output results.xml
```
## Download
* Download and save in file <img src="new-feature.svg">
```
kubescape download framework nsa --output nsa.json
```
* Download and save in default file (`~/.kubescape/<framework name>.json`)
```
kubescape download framework nsa
```

View File

@@ -1,22 +1,66 @@
# Kubescape project roadmap
## Planning principles
## Proposals
* [Container registry integration](/docs/proposals/container-image-vulnerability-adaptor.md)
Kubescape roadmap items are labeled based on where the feature is used and by their maturity.
## Planed features
* Image vulnerablity scanning based controls
* Assited remidiation (telling where/what to fix)
* Git integration for pull requests
* Integration with container registries
* Custom controls and regos
* API server configuration validation
The features serve different stages of the workflow of the users:
* **Development phase** (writing Kubernetes manifests) - example: VS Code extension is used while editing YAMLs
* **CI phase** (integrating manifests to GIT repo) - example: GitHub action validating HELM charts on PRs
* **CD phase** (deploying applications in Kubernetes) - example: running cluster scan after a new deployment
* **Monitoring phase** (scanning application in Kubernetes) - example: Prometheus scraping the cluster security risk
The items in Kubescape roadmap are split to 3 major groups based on the feature planning maturity:
* [Planning](#planning) - we have tickets open for these issues with more or less clear vision of design
* [Backlog](#backlog) - feature which were discussed at a high level but are not ready for development
* [Wishlist](#wishlist) - features we are dreaming of 😀 and want to push them gradually forward
## Planning 👷
* ##### Integration with image registries
We want to expand Kubescape to integrate with differnet image registries and read image vulnerability information from there. This will allow Kubescape to give contextual security information about vulnerabilities [Container registry integration](/docs/proposals/container-image-vulnerability-adaptor.md)
* ##### Kubescape as a microservice
Create a REST API for Kubescape so it can run constantly in a cluster and other components like Prometheus can scrape results
* ##### Kubescape CLI control over cluster operations
Add functionality to Kubescape CLI to trigger operations in Kubescape cluster components (example: trigger images scans and etc.)
* ##### Produce md/HTML reports
Create scan reports for different output formats
* ##### Git integration for pull requests
Create insightful GitHub actions for Kubescape
## Backlog 📅
* ##### JSON path for HELM charts
Today Kubescape can point to issues in the Kubernetes object, we want to develop this feature so Kubescape will be able to point to the misconfigured source file (HELM)
* ##### Create Kubescape HELM plugin
* ##### Kubescape based admission controller
Implement admission controller API for Kubescape microservice to enable users to use Kubescape rules as policies
## Wishlist 💭
* ##### Integrate with other Kubernetes CLI tools
Use Kubescape as a YAML validator for `kubectl` and others.
* ##### Kubernetes audit log integration
Connect Kubescape to audit log stream to enable it to produce more contextual security information based on how the API service is used.
* ##### TUI for Kubescape
Interactive terminal based user interface which helps to analyze and fix issues
* ##### Scanning images with GO for vulnerabilities
Images scanners cannot determine which packages were used to build Go executables and we want to scan them for vulnerabilities
* ##### Scanning Dockerfile-s for security best practices
Scan image or Dockerfile to determine whether it is using security best practices (like root containers)
* ##### Custom controls and rules
Enable users to define their own Rego base rules
* ##### More CI/CD tool integration
Jenkins and etc. 😀
## Completed features 🎓
* Kubelet configuration validation
## Completed features
* API server configuration validation
* Image vulnerability scanning based controls
* Assisted remediation (telling where/what to fix)
* Integration with Prometheus
* Confiugration of controls (customizing rules for a given environment)
* Installation in the cluster for continous monitoring
* Installation in the cluster for continuous monitoring
* Host scanner
* Cloud vendor API integration
* Custom exceptions

View File

@@ -1,87 +0,0 @@
<img src="kubescape.png" width="300" alt="logo" align="center">
# More detailed look on command line arguments and options
## Simple run:
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
```
## Flags
| 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 |
| `-t`/`--fail-threshold` | `0` (do not fail) | fail command (return exit code 1) if result is below threshold| `0` -> `100` |
| `-f`/`--format` | `pretty-printer` | Output format | `pretty-printer`/`json`/`junit` |
| `-o`/`--output` | print to stdout | Save scan result in file |
| `--use-from` | | Load local framework object from specified path. If not used will download latest |
| `--use-default` | `false` | Load local framework object from default path. If not used will download latest | `true`/`false` |
| `--exceptions` | | Path to an [exceptions obj](examples/exceptions.json). If not set will download exceptions from Armo management portal |
| `--results-locally` | `false` | Kubescape sends scan results to Armo management portal to allow users to control exceptions and maintain chronological scan results. Use this flag if you do not wish to use these features | `true`/`false`|
## Usage & Examples
### Examples
* 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
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
```
* Scan local `yaml`/`json` files before deploying
```
kubescape scan framework nsa *.yaml
```
* Scan `yaml`/`json` files from url
```
kubescape scan framework nsa https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/master/release/kubernetes-manifests.yaml
```
* Output in `json` format
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format json --output results.json
```
* Output in `junit xml` format
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format junit --output results.xml
```
* Scan with exceptions, objects with exceptions will be presented as `warning` and not `fail`
```
kubescape scan framework nsa --exceptions examples/exceptions.json
```
### Helm Support
* 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 framework nsa -
```
for example:
```
helm template bitnami/mysql --generate-name --dry-run | kubescape scan framework nsa -
```
### Offline Support <img src="docs/new-feature.svg">
It is possible to run Kubescape offline!
First download the framework and then scan with `--use-from` flag
* Download and save in file, if file name not specified, will store save to `~/.kubescape/<framework name>.json`
```
kubescape download framework nsa --output nsa.json
```
* Scan using the downloaded framework
```
kubescape scan framework nsa --use-from nsa.json
```
Kubescape is an open source project, we welcome your feedback and ideas for improvement. Were also aiming to collaborate with the Kubernetes community to help make the tests themselves more robust and complete as Kubernetes develops.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -1,85 +0,0 @@
# Periodically Kubescape Scanning
You can scan your cluster periodically by adding a `CronJob` that will repeatedly trigger kubescape
* Setup [scanning & submitting](#scanning-and-submitting)
* Setup [scanning without submitting](#scanning-without-submitting)
## Scanning And Submitting
If you wish to periodically scan and submit the result to the [Kubescape SaaS version](https://portal.armo.cloud/) where you can benefit the features the SaaS version provides, please follow this instructions ->
1. Apply kubescape namespace
```
kubectl apply ks-namespace.yaml
```
2. Apply serviceAccount and roles
```
kubectl apply ks-serviceAccount.yaml
```
3. Setup and apply configMap
Before you apply the configMap you need to set the account ID and cluster name in the `ks-configMap.yaml` file.
* Set cluster name:
Run `kubectl config current-context` and set the result in the `data.clusterName` field
* Set account ID:
1. Navigate to the [Kubescape SaaS version](https://portal.armo.cloud/) and login/sign up for free
2. Click the `Add Cluster` button on the top right of the page
</br>
<img src="screenshots/add-cluster.png" alt="add-cluster">
3. Copy the value of `--account` and set it in the `data.customerGUID` field
</br>
<img src="screenshots/account.png" alt="account">
Make sure the configMap looks as following;
```
kind: ConfigMap
apiVersion: v1
metadata:
name: kubescape
labels:
app: kubescape
namespace: kubescape
data:
config.json: |
{
"customerGUID": "XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX",
"clusterName": "my-awesome-cluster-name"
}
```
Finally, apply the configMap
```
kubectl apply ks-configMap.yaml
```
4. Apply CronJob
Before you apply the cronJob, make sure the scanning frequency suites your needs
```
kubectl apply ks-cronJob-submit.yaml
```
## Scanning Without Submitting
If you wish to periodically scan but not submit the scan results, follow this instructions ->
1. Apply kubescape namespace
```
kubectl apply ks-namespace.yaml
```
2. Apply serviceAccount and roles
```
kubectl apply ks-serviceAccount.yaml
```
3. Apply CronJob
Before you apply the cronJob, make sure the scanning frequency suites your needs
```
kubectl apply ks-cronJob-non-submit.yaml
```

View File

@@ -1,14 +0,0 @@
# ------------------- Kubescape User/Customer ID ------------------- #
kind: ConfigMap
apiVersion: v1
metadata:
name: kubescape
labels:
app: kubescape
namespace: kubescape
data:
config.json: |
{
"customerGUID": "<ID>",
"clusterName": "<cluster name>"
}

View File

@@ -1,32 +0,0 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: kubescape
labels:
app: kubescape
namespace: kubescape
spec:
# ┌────────────────── timezone (optional)
# | ┌───────────── minute (0 - 59)
# | │ ┌───────────── hour (0 - 23)
# | │ │ ┌───────────── day of the month (1 - 31)
# | │ │ │ ┌───────────── month (1 - 12)
# | │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# | │ │ │ │ │ 7 is also Sunday on some systems)
# | │ │ │ │ │
# | │ │ │ │ │
# CRON_TZ=UTC * * * * *
schedule: "0 0 1 * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: kubescape
image: quay.io/armosec/kubescape:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c"]
args:
- kubescape scan framework nsa
restartPolicy: OnFailure
serviceAccountName: kubescape-discovery

View File

@@ -1,40 +0,0 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: kubescape
labels:
app: kubescape
namespace: kubescape
spec:
# ┌────────────────── timezone (optional)
# | ┌───────────── minute (0 - 59)
# | │ ┌───────────── hour (0 - 23)
# | │ │ ┌───────────── day of the month (1 - 31)
# | │ │ │ ┌───────────── month (1 - 12)
# | │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# | │ │ │ │ │ 7 is also Sunday on some systems)
# | │ │ │ │ │
# | │ │ │ │ │
# CRON_TZ=UTC * * * * *
schedule: "0 0 1 * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: kubescape
image: quay.io/armosec/kubescape:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c"]
args:
- kubescape scan framework nsa --submit
volumeMounts:
- name: kubescape-config-volume
mountPath: /root/.kubescape/config.json
subPath: config.json
restartPolicy: OnFailure
serviceAccountName: kubescape-discovery
volumes:
- name: kubescape-config-volume
configMap:
name: kubescape

View File

@@ -1,7 +0,0 @@
# ------------------- Kubescape User/Customer ID ------------------- #
kind: Namespace
apiVersion: v1
metadata:
name: kubescape
labels:
app: kubescape

Some files were not shown because too many files have changed in this diff Show More