Compare commits

...

96 Commits

Author SHA1 Message Date
David Wertenteil
3e2314a269 Bump v3 (#1449)
* bump version

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* change default view

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* fixed tests

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* fixed go mod

Signed-off-by: David Wertenteil <dwertent@armosec.io>

---------

Signed-off-by: David Wertenteil <dwertent@armosec.io>
2023-10-22 17:43:51 +03:00
David Wertenteil
c143d10130 Cloud provider detection (#1448)
* set cloud provider using nodes

* use scan metadata for scanning scope

* code cleanup

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* handle error

Signed-off-by: David Wertenteil <dwertent@armosec.io>

---------

Signed-off-by: David Wertenteil <dwertent@armosec.io>
2023-10-22 15:47:57 +03:00
David Wertenteil
d5407466d5 Preparing Kubescape for v3 (#1403)
* wip: minor cli fixes

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* wip: change default view

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* wip: reduce default topWorkloadsNumber to 3

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* update gif

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* default view for controls and frameworks

---------

Signed-off-by: David Wertenteil <dwertent@armosec.io>
2023-10-22 15:39:58 +03:00
rcohencyberarmor
052c042dac Support backend access key (#1404)
Support backend access key

Signed-off-by: Amir Malka <amirm@armosec.io>
2023-10-22 15:10:08 +03:00
Daniel Grunberger
72b64127c7 Restructure img cmd (#1398)
* feat: add kubescape patch command

Signed-off-by: Anubhav Gupta <mail.anubhav06@gmail.com>

* update patch command output format

Signed-off-by: Anubhav Gupta <mail.anubhav06@gmail.com>

* fix patch command logic

Signed-off-by: Anubhav Gupta <mail.anubhav06@gmail.com>

* update docs & minor changes

Signed-off-by: Anubhav Gupta <mail.anubhav06@gmail.com>

* restructure cmd

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* fix tests

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* readme

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* fix version code

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* go mod

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

---------

Signed-off-by: Anubhav Gupta <mail.anubhav06@gmail.com>
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
Co-authored-by: Anubhav Gupta <mail.anubhav06@gmail.com>
Co-authored-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-10-22 14:51:39 +03:00
Matthias Bertschy
a938b3523f Merge pull request #1446 from kubescape/deps-policy
add env-dependencies-policy to security insights
2023-10-20 09:32:51 +02:00
Matthias Bertschy
915d5d993b add env-dependencies-policy to security insights
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-20 09:30:41 +02:00
rcohencyberarmor
e2044338c8 print result to stdout when format argument specified (#1438)
* print result to stdout when format argument specified

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* print pretty format to stdout if format is not specified

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* remove not relevant log

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

---------

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>
Co-authored-by: rcohencyberarmor <rcohen@armosec.io>
2023-10-19 13:07:58 +03:00
Matthias Bertschy
e4110837c7 Merge pull request #1443 from kubescape/sarif-format
image scanning - replace driver name in sarif format
2023-10-18 14:33:40 +02:00
Matthias Bertschy
33452517fe Merge pull request #1444 from kubescape/actions
add more missing permissions for actions
2023-10-18 14:32:29 +02:00
Matthias Bertschy
df602af7cf add more missing permissions for actions
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-18 12:41:04 +02:00
Daniel Grunberger
bc327a0d86 set log to error
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-10-18 12:23:46 +03:00
Daniel Grunberger
77888c12a0 rm if statement
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-10-18 12:22:59 +03:00
Daniel Grunberger
df56af843e replace driver name
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-10-18 12:18:53 +03:00
David Wertenteil
1f6ffdfd24 Merge pull request #1441 from kubescape/act
actions needs write on id-token in pr-scanner
2023-10-18 09:21:28 +03:00
Matthias Bertschy
6a0a7b84a2 actions needs write on id-token in pr-scanner
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-18 07:28:52 +02:00
Matthias Bertschy
f4bb8485cd Merge pull request #1436 from kubescape/ghactions
use distroless debug as entrypoint.sh requires a shell
2023-10-17 16:25:32 +02:00
David Wertenteil
33ec257aa8 Merge pull request #1439 from kubescape/bump
bump deps for vulnerabilities
2023-10-17 15:54:15 +03:00
Matthias Bertschy
f31304db7e bump deps for vulnerabilities
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-17 14:36:26 +02:00
Matthias Bertschy
b98d80a912 Merge pull request #1421 from kubescape/winwin
build windows exec without libgit
2023-10-17 10:09:32 +02:00
Matthias Bertschy
ae7b25a9ae Merge pull request #1437 from kubescape/act
fixing GH actions permissions
2023-10-17 10:05:49 +02:00
Matthias Bertschy
4f2d13b151 fixing GH actions permissions
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-17 09:53:46 +02:00
Matthias Bertschy
b6b4d6bb46 use distroless debug as entrypoint.sh requires a shell
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-17 09:42:59 +02:00
Matthias Bertschy
de76c98231 Merge pull request #1435 from kubescape/act
remove extra permissions on GH action
2023-10-17 08:31:07 +02:00
Matthias Bertschy
0b7d8cd45e remove extra permissions on GH action
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-17 07:36:38 +02:00
Matthias Bertschy
48c86037fd Merge pull request #1433 from kubescape/artifacthub
add ArtifactHub to README
2023-10-16 17:36:45 +02:00
Matthias Bertschy
69b28823d9 add ArtifactHub to README
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-16 16:27:38 +02:00
Matthias Bertschy
554286e803 Merge pull request #1432 from kubescape/fossa
add FOSSA badge to README
2023-10-16 14:32:38 +02:00
Matthias Bertschy
971e775476 add FOSSA badge to README
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-16 14:20:12 +02:00
Matthias Bertschy
95133af9f4 build windows exec without libgit
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-16 11:16:30 +02:00
Matthias Bertschy
3efa40e808 use go-gitlog as an alternative to git2go (#1393)
* use go-gitlog as an alternative to git2go

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>

* set RELEASE to something to avoid failing binary-build step

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>

---------

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-15 15:16:05 +03:00
David Wertenteil
a5d1fa3f66 Merge pull request #1274 from nvuillam/patch-1
Update installation.md to add instructions to install a previous version
2023-10-15 12:02:30 +03:00
David Wertenteil
3d71246580 Merge pull request #1430 from kubescape/token
add top level permissions: read-all for openssf
2023-10-15 11:58:54 +03:00
YiscahLevySilas1
31a0bd9266 use ControlConfigInputs, deprecate ConfigInputs (#1419)
* use ControlConfigInputs, deprecate ConfigInputs

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>

* update opa-utils version

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>

---------

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>
2023-10-15 11:58:10 +03:00
Matthias Bertschy
143a4d9818 add top level permissions: read-all for openssf
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-13 15:20:52 +02:00
Matthias Bertschy
0d889bf454 Merge pull request #1429 from kubescape/ssf
correct security-insights stage -> status
2023-10-13 07:41:07 +02:00
Matthias Bertschy
f7a5f76285 correct security-insights stage -> status
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-13 07:31:55 +02:00
Matthias Bertschy
1e527b4174 Merge pull request #1428 from slashben/security-slam
Fix in the README for changelog name
2023-10-13 07:08:24 +02:00
Ben
5bf1d6b3c4 Fix in the README for changelog name
Signed-off-by: Ben <ben@armosec.io>
2023-10-12 22:24:36 +03:00
Matthias Bertschy
f622bd0a0e Merge pull request #1422 from kubescape/dependabot/go_modules/httphandler/golang.org/x/net-0.17.0
Bump golang.org/x/net from 0.14.0 to 0.17.0 in /httphandler
2023-10-12 16:59:01 +02:00
Ben Hirschberg
f70cf68e4d Merge pull request #1427 from slashben/security-slam
Adding project security and governance as per maintainer decision
2023-10-12 17:19:19 +03:00
Ben
accc8a3834 Adding project security and governance as per maintainer decision
Signed-off-by: Ben <ben@armosec.io>
2023-10-12 17:14:28 +03:00
Matthias Bertschy
83b6686496 Merge pull request #1426 from kubescape/Adding-scorecard-badge
Adding scorecard badge
2023-10-12 15:56:26 +02:00
David Wertenteil
934f72203e Update README.md
Signed-off-by: David Wertenteil <dwertent@armosec.io>
2023-10-12 16:50:43 +03:00
David Wertenteil
506da9dc22 Merge pull request #1425 from kubescape/scorecard
Create scorecard.yml
2023-10-12 16:40:49 +03:00
David Wertenteil
2cfd4f3b31 Create scorecard.yml
Signed-off-by: David Wertenteil <dwertent@armosec.io>
2023-10-12 16:39:38 +03:00
dependabot[bot]
7aa7a9bbda Bump golang.org/x/net from 0.14.0 to 0.17.0 in /httphandler
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.14.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-12 12:25:57 +00:00
Matthias Bertschy
5968f97583 Merge pull request #1417 from kubescape/fixtests
drop build tags for tests (will soon deprecate them)
2023-10-11 17:25:54 +02:00
Matthias Bertschy
6070f0f126 Merge pull request #1418 from kubescape/fixci
use correct variable for IMAGE_TAG
2023-10-11 16:17:23 +02:00
Matthias Bertschy
a52ca0e47d use correct variable for IMAGE_TAG
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-11 16:05:55 +02:00
Matthias Bertschy
2da0293ee2 Merge pull request #1416 from kubescape/fixci
add missing dependency on retag in binary-build
2023-10-11 14:18:59 +02:00
Matthias Bertschy
ed1219baf1 drop build tags for tests (will soon deprecate them)
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-11 14:03:53 +02:00
Matthias Bertschy
f073ce0f42 add missing dependency on retag in binary-build
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-11 13:56:50 +02:00
Matthias Bertschy
9e6efd04ce Merge pull request #1413 from VaibhavMalik4187/remove-codesee-workflow
Removed the Codesee workflow
2023-10-10 07:29:11 +02:00
VaibhavMalik4187
8908a4e8cf Removed the Codesee workflow
Fixes: https://github.com/kubescape/kubescape/issues/1405

Signed-off-by: VaibhavMalik4187 <vaibhavmalik2018@gmail.com>
2023-10-10 07:38:51 +05:30
Matthias Bertschy
425f278300 Merge pull request #1412 from kubescape/fixci
force docker-build in absence of release label
2023-10-09 11:04:59 +02:00
Matthias Bertschy
5b1aa1501f force docker-build in absence of release label
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-09 10:13:53 +02:00
David Wertenteil
9bb2136ada Merge pull request #1411 from kubescape/fixed-wf
Adding check-secret to build image wf
2023-10-08 12:42:14 +03:00
David Wertenteil
bbabd5373a Adding check-secret to build image wf
Signed-off-by: David Wertenteil <dwertent@armosec.io>
2023-10-08 12:18:19 +03:00
Matthias Bertschy
24c48ab58f Merge pull request #1407 from kubescape/dependabot/go_modules/github.com/cyphar/filepath-securejoin-0.2.4
Bump github.com/cyphar/filepath-securejoin from 0.2.3 to 0.2.4
2023-10-05 10:26:13 +02:00
dependabot[bot]
fd6347fac2 Bump github.com/cyphar/filepath-securejoin from 0.2.3 to 0.2.4
Bumps [github.com/cyphar/filepath-securejoin](https://github.com/cyphar/filepath-securejoin) from 0.2.3 to 0.2.4.
- [Release notes](https://github.com/cyphar/filepath-securejoin/releases)
- [Commits](https://github.com/cyphar/filepath-securejoin/compare/v0.2.3...v0.2.4)

---
updated-dependencies:
- dependency-name: github.com/cyphar/filepath-securejoin
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-10-02 17:41:05 +02:00
YiscahLevySilas1
269945c08c split failedPath to deletePaths and reviewPaths (#1402)
* support delete paths and review paths

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>

* update armoapi + opa-utils

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>

* fix test

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>

* go mod tidy

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>

* support failedPaths until all controls replace with review/delete paths

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>

* fix test

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>

* fix test

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>

---------

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>
2023-10-02 17:03:02 +03:00
Rotem Refael
10c7c428e4 add Openssf badge (#1400)
* add Openssf badge

Signed-off-by: rotemamsa <rotem@armosec.io>
2023-09-28 13:23:17 +02:00
Matthias Bertschy
34f0b64946 Merge pull request #1399 from kubescape/noratelimit
disable rate limiting for storage client
2023-09-27 17:07:45 +02:00
rcohencyberarmor
884af50c0b Support control cluster from cli (#1391)
* adding operator CLI to kubescape

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* support http requet for trigger in cluster operator

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* create interface for create request payload

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* logs + go mod update

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* docs

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* add relevant system tests

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* linter corrections

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* code review corrections

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* remove non relevant system tests - after code review corrections

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* PR corrections

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* PR corrections

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* change log

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* remove from examples

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* change log

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

* test correction

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>

---------

Signed-off-by: rcohencyberarmor <rcohen@armosec.io>
Co-authored-by: rcohencyberarmor <rcohen@armosec.io>
2023-09-27 16:31:04 +03:00
Matthias Bertschy
e97103494f disable rate limiting for storage client
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-09-21 16:32:28 +02:00
Matthias Bertschy
3e7a6b516b Separate docker builds for kubescape and kubescape-cli (#1390)
* create a separate Dockerfile for httphandler

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>

* add Dockerfile for cli, edit README

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>

* modify gh action to use new cli Dockerfile

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>

---------

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2023-09-18 17:13:08 +03:00
Amir Malka
8257e31232 Save scan results in storage and support scanning a deleted resource (#1376)
* store scan results in storage

Signed-off-by: Amir Malka <amirm@armosec.io>

* store scan results in storage

Signed-off-by: Amir Malka <amirm@armosec.io>

* save resources in their namespaces, load namespace from env var, extend the config obj

Signed-off-by: Amir Malka <amirm@armosec.io>

* setting context name

Signed-off-by: Amir Malka <amirm@armosec.io>

* updated k8s-interface

Signed-off-by: Amir Malka <amirm@armosec.io>

* scanning a deleted resource

Signed-off-by: Amir Malka <amirm@armosec.io>

* cr changes

Signed-off-by: Amir Malka <amirm@armosec.io>

* cr changes

Signed-off-by: Amir Malka <amirm@armosec.io>

* fix

Signed-off-by: Amir Malka <amirm@armosec.io>

* remove unused constants

Signed-off-by: Amir Malka <amirm@armosec.io>

* use t.Setenv

Signed-off-by: Amir Malka <amirm@armosec.io>

* added tests for rbac triplet slugs

Signed-off-by: Amir Malka <amirm@armosec.io>

* updated namespace logic

Signed-off-by: Amir Malka <amirm@armosec.io>

* fix test

Signed-off-by: Amir Malka <amirm@armosec.io>

---------

Signed-off-by: Amir Malka <amirm@armosec.io>
2023-09-14 10:03:36 +03:00
David Wertenteil
bfc2304a95 Merge pull request #1388 from kubescape/remove-armo-url
Remove ARMO server url
2023-09-13 14:26:41 +03:00
Daniel Grunberger
96337edc67 add new line (#1389)
* add new line

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* rename ks-cloud-operator

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

---------

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
Co-authored-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-13 14:26:11 +03:00
DRAGON2002
a3f80d91bf fix: format headers (#1383)
Signed-off-by: DRAGON <anantvijay3@gmail.com>
2023-09-13 13:27:56 +03:00
Amir Malka
69c84cdf56 remove ARMO server url
Signed-off-by: Amir Malka <amirm@armosec.io>
2023-09-13 09:43:38 +03:00
David Wertenteil
a6cca30eb0 Merge pull request #1381 from XDRAGON2002/issue_1380
feat: darken table borders
2023-09-11 12:09:17 +03:00
DRAGON
c74c5f1970 feat: darken table borders
Signed-off-by: DRAGON <anantvijay3@gmail.com>
2023-09-10 21:48:15 +05:30
David Wertenteil
e611cec238 Merge pull request #1379 from kubescape/fix-kubecontext-for-httphandler
fix setting context name in httphandler
2023-09-10 16:17:36 +03:00
Amir Malka
4372ca320a fix setting context name in httphandler
Signed-off-by: Amir Malka <amirm@armosec.io>
2023-09-10 16:16:01 +03:00
David Wertenteil
c490dcc9cb Merge pull request #1372 from kubescape/fix-image-printer
Print up until 4 images in vulnerability summary
2023-09-07 10:08:04 +03:00
Amir Malka
c914ab1034 revert e2e test branch (#1373)
Signed-off-by: Amir Malka <amirm@armosec.io>
2023-09-05 14:31:56 +03:00
Daniel Grunberger
b39ce4caae print up until 4 imgs
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-05 11:47:44 +03:00
David Wertenteil
076aa7f8fe Merge pull request #1370 from kubescape/provide-log-msg
validate sarif format
2023-09-03 18:41:10 +03:00
Daniel Grunberger
df035ea5fc Fix scan command (#1369)
* bump version

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* bump version for httphandler

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* fix args validation

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* errors as const

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

---------

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
Co-authored-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-03 17:22:14 +03:00
Daniel Grunberger
58553688e9 validate sarif format
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-03 16:51:52 +03:00
Daniel Grunberger
776173653d bump version (#1368)
* bump version

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* bump version for httphandler

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

---------

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
Co-authored-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-03 15:31:57 +03:00
Daniel Grunberger
26c47d501c move context flag to root (#1367)
* move context flag to root

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* rm from httphandler

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

---------

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
Co-authored-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-03 10:39:15 +03:00
Vicky Aryan
6a8a338945 Error Fixed when downloading on azure cloud vm environment (#720)
* install.ps1 is modified

* cloud vm error fixed
2023-09-01 08:03:12 +03:00
Daniel Grunberger
53f23b663b Logger fixes (#1362)
* fix rbac log

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* fix logger logic

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* use const

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* use const for zap

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

---------

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
Co-authored-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-08-30 19:17:41 +03:00
Amir Malka
592e0e2b43 Service discovery (#1359)
* remove hardcoded urls

Signed-off-by: Amir Malka <amirm@armosec.io>

* update

Signed-off-by: Amir Malka <amirm@armosec.io>

* fix test

Signed-off-by: Amir Malka <amirm@armosec.io>

* update providers docs

Signed-off-by: Amir Malka <amirm@armosec.io>

* fix

Signed-off-by: Amir Malka <amirm@armosec.io>

* hardcoded systests branch

Signed-off-by: Amir Malka <amirm@armosec.io>

* fix

Signed-off-by: Amir Malka <amirm@armosec.io>

* added logs

Signed-off-by: Amir Malka <amirm@armosec.io>

* added logs

Signed-off-by: Amir Malka <amirm@armosec.io>

* create config path if it does not exist

Signed-off-by: Amir Malka <amirm@armosec.io>

* fix

Signed-off-by: Amir Malka <amirm@armosec.io>

* fix

Signed-off-by: Amir Malka <amirm@armosec.io>

---------

Signed-off-by: Amir Malka <amirm@armosec.io>
2023-08-30 09:54:50 +03:00
David Wertenteil
92449bf564 core(cmd): adding corrections to cmd (#1357)
* adding corrections to cmd

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* remove decorative line

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* wip: changed results indicator

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* replace status test with icons

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* print workloads in a different line

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* update display

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* deprecate commands

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* removed unused functions

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* fixed tests

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* update cloud provider detection

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* rename column name

Signed-off-by: David Wertenteil <dwertent@armosec.io>

---------

Signed-off-by: David Wertenteil <dwertent@armosec.io>
2023-08-29 09:50:22 +03:00
David Wertenteil
8d1547163b Beautify install.sh script logs (#1356)
* Check k8s access before running

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* refactor script

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* fixed color background

Signed-off-by: David Wertenteil <dwertent@armosec.io>

---------

Signed-off-by: David Wertenteil <dwertent@armosec.io>
2023-08-24 15:18:10 +03:00
David Wertenteil
d16abf376d Merge pull request #1335 from kubescape/dwertent-patch-1
Create dependabot.yaml
2023-08-24 10:27:58 +03:00
Amir Malka
150967eae8 Refactor backend integration (#1355)
* refactor BE integration

Signed-off-by: Amir Malka <amirm@armosec.io>
2023-08-23 15:36:08 +03:00
Daniel Grunberger
150dc61ec7 fix panic & provide msg (#1353)
* fix pani & provide msgf

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* support sarif

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* new line

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* validate format

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* refactor

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* validate printer tests

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* use sarif const

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* small refactor

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* unify switch

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

---------

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
Co-authored-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-08-22 15:22:57 +03:00
Daniel Grunberger
7b46cdd480 Improve cluster scan cli (#1352)
* start improvements

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* cta

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* refactor

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* fixes

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* http handler go mod

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* set control type

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* move to func

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* move to func

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* use color for vuln summary

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

---------

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
Co-authored-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-08-22 15:21:01 +03:00
YiscahLevySilas1
b67fd95e31 support paths from related resources (#1351)
* support paths from related resources

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>

* fix test

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>

---------

Signed-off-by: YiscahLevySilas1 <yiscahls@armosec.io>
2023-08-22 08:06:45 +03:00
David Wertenteil
b88e4f6169 Create dependabot.yaml
Signed-off-by: David Wertenteil <dwertent@armosec.io>
2023-08-07 16:48:46 +03:00
Nicolas Vuillamy
31c4badf1c Update installation.md to add instructions to install a previous version via SH 2023-07-09 21:22:47 +02:00
256 changed files with 6977 additions and 8169 deletions

View File

@@ -1,2 +0,0 @@
git2go
kubescape

11
.github/dependabot.yaml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

View File

@@ -1,4 +1,5 @@
name: 00-pr_scanner
permissions: read-all
on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review]
@@ -12,7 +13,7 @@ on:
- 'docs/*'
- 'build/*'
- '.github/*'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@@ -20,21 +21,47 @@ concurrency:
jobs:
pr-scanner:
permissions:
actions: read
checks: read
contents: read
deployments: read
id-token: write
issues: read
discussions: read
packages: read
pages: read
pull-requests: write
repository-projects: read
security-events: read
statuses: read
uses: ./.github/workflows/a-pr-scanner.yaml
with:
RELEASE: ""
CLIENT: test
secrets: inherit
binary-build:
permissions:
actions: read
checks: read
contents: read
deployments: read
discussions: read
id-token: write
issues: read
packages: write
pages: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
with:
COMPONENT_NAME: kubescape
CGO_ENABLED: 1
GO111MODULE: ""
GO_VERSION: "1.20"
RELEASE: ""
RELEASE: "latest"
CLIENT: test
ARCH_MATRIX: '[ "" ]'
OS_MATRIX: '[ "ubuntu-20.04" ]'

View File

@@ -1,4 +1,5 @@
name: 02-create_release
permissions: read-all
on:
push:
tags:
@@ -15,6 +16,20 @@ jobs:
with:
SUB_STRING: "-rc"
binary-build:
permissions:
actions: read
checks: read
contents: read
deployments: read
discussions: read
id-token: write
issues: read
packages: write
pages: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
needs: [retag]
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
with:
@@ -27,7 +42,19 @@ jobs:
secrets: inherit
create-release:
permissions:
actions: read
checks: read
contents: write
deployments: read
discussions: read
id-token: write
issues: read
packages: read
pages: read
pull-requests: read
repository-projects: read
statuses: read
security-events: read
needs: [retag, binary-build]
uses: ./.github/workflows/c-create-release.yaml
with:
@@ -37,14 +64,24 @@ jobs:
secrets: inherit
publish-image:
permissions:
id-token: write
packages: write
actions: read
checks: read
contents: read
deployments: read
discussions: read
id-token: write
issues: read
packages: write
pages: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
uses: ./.github/workflows/d-publish-image.yaml
needs: [create-release, retag]
with:
client: "image-release"
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
image_name: "quay.io/${{ github.repository_owner }}/kubescape-cli"
image_tag: ${{ needs.retag.outputs.NEW_TAG }}
support_platforms: true
cosign: true

View File

@@ -1,4 +1,5 @@
name: 03-post_release
permissions: read-all
on:
release:
types: [published]

View File

@@ -1,4 +1,5 @@
name: 04-publish_krew_plugin
permissions: read-all
on:
push:
tags:

View File

@@ -1,4 +1,5 @@
name: a-pr-scanner
permissions: read-all
on:
workflow_call:
inputs:
@@ -68,7 +69,7 @@ jobs:
- name: Convert coverage count to lcov format
uses: jandelgado/gcov2lcov-action@v1
- name: Submit coverage tests to Coveralls
continue-on-error: true
uses: coverallsapp/github-action@v1

View File

@@ -1,5 +1,5 @@
name: b-binary-build-and-e2e-tests
permissions: read-all
on:
workflow_dispatch:
inputs:
@@ -38,7 +38,7 @@ on:
BINARY_TESTS:
type: string
required: false
default: '[ "scan_nsa", "scan_mitre", "scan_with_exceptions", "scan_repository", "scan_local_file", "scan_local_glob_files", "scan_local_list_of_files", "scan_nsa_and_submit_to_backend", "scan_mitre_and_submit_to_backend", "scan_local_repository_and_submit_to_backend", "scan_repository_from_url_and_submit_to_backend", "scan_with_exception_to_backend", "scan_with_custom_framework", "scan_customer_configuration", "host_scanner", "scan_compliance_score" ]'
default: '[ "scan_nsa", "scan_mitre", "scan_with_exceptions", "scan_repository", "scan_local_file", "scan_local_glob_files", "scan_local_list_of_files", "scan_nsa_and_submit_to_backend", "scan_mitre_and_submit_to_backend", "scan_local_repository_and_submit_to_backend", "scan_repository_from_url_and_submit_to_backend", "scan_with_exception_to_backend", "scan_with_custom_framework", "scan_customer_configuration", "host_scanner", "scan_compliance_score", "control_cluster_from_CLI_config_scan_exclude_namespaces", "control_cluster_from_CLI_config_scan_include_namespaces", "control_cluster_from_CLI_config_scan_host_scanner_enabled", "control_cluster_from_CLI_config_scan_MITRE_framework", "control_cluster_from_CLI_vulnerabilities_scan_default", "control_cluster_from_CLI_vulnerabilities_scan_include_namespaces" ]'
workflow_call:
inputs:
@@ -71,7 +71,7 @@ on:
type: string
required: false
default: '[ "", "arm64"]'
jobs:
wf-preparation:
name: secret-validator
@@ -115,7 +115,20 @@ jobs:
echo "ARCH_MATRIX=$input" >> $GITHUB_OUTPUT
env:
input: ${{ inputs.ARCH_MATRIX }}
check-secret:
name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
runs-on: ubuntu-latest
outputs:
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
steps:
- name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
id: check-secret-set
env:
QUAYIO_REGISTRY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
run: |
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
binary-build:
name: Create cross-platform build
@@ -194,11 +207,6 @@ jobs:
echo "DOCKER_CMD=${DOCKER_CMD}" >> $GITHUB_ENV;
if: matrix.os == 'ubuntu-20.04' && matrix.arch != ''
- name: Install MSYS2 & libgit2 (Windows)
shell: pwsh
run: .\build.ps1 all
if: matrix.os == 'windows-latest'
- name: Install pkg-config (macOS)
run: brew install pkg-config
if: matrix.os == 'macos-latest'
@@ -208,11 +216,11 @@ jobs:
if: matrix.os != 'windows-latest'
- name: Test core pkg
run: ${{ env.DOCKER_CMD }} go test "-tags=static,gitenabled" -v ./...
run: ${{ env.DOCKER_CMD }} go test -v ./...
if: "!startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-20.04' && matrix.arch == '' || startsWith(github.ref, 'refs/tags') && (matrix.os != 'macos-latest' || matrix.arch != 'arm64')"
- name: Test httphandler pkg
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test "-tags=static,gitenabled" -v ./...'
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test -v ./...'
if: "!startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-20.04' && matrix.arch == '' || startsWith(github.ref, 'refs/tags') && (matrix.os != 'macos-latest' || matrix.arch != 'arm64')"
- name: Build
@@ -268,6 +276,39 @@ jobs:
path: build/
if-no-files-found: error
build-http-image:
permissions:
contents: read
id-token: write
packages: write
pull-requests: read
needs: [check-secret]
uses: kubescape/workflows/.github/workflows/incluster-comp-pr-merged.yaml@main
with:
IMAGE_NAME: quay.io/${{ github.repository_owner }}/kubescape
IMAGE_TAG: ${{ inputs.RELEASE }}
COMPONENT_NAME: kubescape
CGO_ENABLED: 0
GO111MODULE: "on"
BUILD_PLATFORM: linux/amd64,linux/arm64
GO_VERSION: "1.20"
REQUIRED_TESTS: '[
"ks_microservice_create_2_cronjob_mitre_and_nsa_proxy",
"ks_microservice_triggering_with_cron_job",
"ks_microservice_update_cronjob_schedule",
"ks_microservice_delete_cronjob",
"ks_microservice_create_2_cronjob_mitre_and_nsa",
"ks_microservice_ns_creation",
"ks_microservice_on_demand",
"ks_microservice_mitre_framework_on_demand",
"ks_microservice_nsa_and_mitre_framework_demand",
"scan_compliance_score"
]'
COSIGN: true
HELM_E2E_TEST: true
FORCE: true
secrets: inherit
run-tests:
strategy:
fail-fast: false

View File

@@ -1,5 +1,5 @@
name: build-image
permissions: read-all
on:
workflow_dispatch:
inputs:
@@ -19,16 +19,23 @@ on:
required: false
default: false
jobs:
publish-image:
build-http-image:
permissions:
id-token: write
packages: write
contents: read
uses: ./.github/workflows/d-publish-image.yaml
pull-requests: read
uses: kubescape/workflows/.github/workflows/incluster-comp-pr-merged.yaml@main
with:
client: ${{ inputs.CLIENT }}
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
image_tag: ${{ inputs.IMAGE_TAG }}
support_platforms: ${{ inputs.PLATFORMS }}
cosign: ${{ inputs.CO_SIGN }}
secrets: inherit
IMAGE_NAME: quay.io/${{ github.repository_owner }}/kubescape
IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
COMPONENT_NAME: kubescape
CGO_ENABLED: 0
GO111MODULE: "on"
BUILD_PLATFORM: ${{ inputs.PLATFORMS && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
GO_VERSION: "1.20"
REQUIRED_TESTS: '[]'
COSIGN: ${{ inputs.CO_SIGN }}
HELM_E2E_TEST: false
FORCE: true
secrets: inherit

View File

@@ -1,4 +1,5 @@
name: c-create_release
permissions: read-all
on:
workflow_call:
inputs:

View File

@@ -1,30 +0,0 @@
# This workflow was added by CodeSee. Learn more at https://codesee.io/
# This is v2.0 of this workflow file
on:
pull_request_target:
types: [opened, synchronize, reopened]
paths-ignore:
- '**.yaml'
- '**.yml'
- '**.md'
- '**.sh'
- 'website/*'
- 'examples/*'
- 'docs/*'
- 'build/*'
- '.github/*'
name: CodeSee
permissions: read-all
jobs:
codesee:
runs-on: ubuntu-latest
continue-on-error: true
name: Analyze the repo with CodeSee
steps:
- uses: Codesee-io/codesee-action@v2
with:
codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }}
codesee-url: https://app.codesee.io

View File

@@ -1,14 +1,13 @@
name: pr-agent
permissions: read-all
on:
issue_comment:
permissions:
issues: write
pull-requests: write
jobs:
pr_agent:
permissions:
issues: write
pull-requests: write
runs-on: ubuntu-latest
name: Run pr agent on every pull request, respond to user comments
steps:
@@ -19,5 +18,3 @@ jobs:
env:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,4 +1,5 @@
name: d-publish-image
permissions: read-all
on:
workflow_call:
inputs:
@@ -38,7 +39,8 @@ jobs:
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
run: |
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
build-image:
build-cli-image:
needs: [check-secret]
if: needs.check-secret.outputs.is-secret-set == 'true'
name: Build image and upload to registry
@@ -56,12 +58,16 @@ jobs:
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
- name: Build and push image
if: ${{ inputs.support_platforms }}
run: docker buildx build . --file build/Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --push --platform linux/amd64,linux/arm64
- name: Build and push image without amd64/arm64 support
if: ${{ !inputs.support_platforms }}
run: docker buildx build . --file build/Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --push
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3.0.2
id: download-artifact
with:
path: .
- name: chmod +x
run: chmod +x -v kubescape-*/kubescape-*
- name: Build and push image for linux/amd64
run: docker buildx build . --file build/kubescape-cli.Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --build-arg ks_binary=kubescape-ubuntu-latest/kubescape-ubuntu-latest --push --platform linux/amd64
- name: Build and push image for linux/arm64
run: docker buildx build . --file build/kubescape-cli.Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --build-arg ks_binary=kubescape-arm64-ubuntu-latest/kubescape-arm64-ubuntu-latest --push --platform linux/arm64
- name: Install cosign
uses: sigstore/cosign-installer@4079ad3567a89f68395480299c77e40170430341 # ratchet:sigstore/cosign-installer@main
with:

72
.github/workflows/scorecard.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '0 00 * * 1'
push:
branches: [ "master" ]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
# Uncomment the permissions below if installing in a private repository.
# contents: read
# actions: read
steps:
- name: "Checkout code"
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecard on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4
with:
sarif_file: results.sarif

View File

@@ -1,3 +1,4 @@
permissions: read-all
on:
issues:
types: [opened, labeled]

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
*.vs*
*kubescape*
!*Dockerfile*
*debug*
*vendor*
*.pyc*
@@ -7,4 +8,4 @@
.history
ca.srl
*.out
ks
ks

23
ADOPTERS.md Normal file
View File

@@ -0,0 +1,23 @@
# Adopters
# Well-known companies
Well-known companies who are using and/or contributing to Kubescape are (in alphabetical order):
* Accenture
* Amazon.com
* IBM
* Intel
* Meetup
* RedHat
* Scaleway
# Users
If you want to be listed here and share with others your experience, open a PR and add the bellow table:
| Name | Company | Use case | Contact for questions (optional) |
| ---- | ------- | -------- | -------------------------------- |
| Yonathan Amzallag | ARMO | Vulnerability monitoring | yonatana@armosec.io |

65
GOVERNANCE.md Normal file
View File

@@ -0,0 +1,65 @@
# Governance of Kubescape
## Overview
The Kubescape project is an open-source initiative dedicated to improve security and best practices in Kubernetes environments. This document outlines the governance structure of the Kubescape project and provides guidance for its community contributors.
## Decision Making
### Maintainers
- Maintainers are responsible for the smooth operation of the project.
- They review and merge pull requests, manage releases, and ensure the quality and stability of the codebase.
- Maintainers are chosen based on their ongoing contributions and their demonstrated commitment to the project.
- Everyone who had at least 5 code contribution in the last 12 month can submit her/himself for joining the maintainer team
- Maintainers who are not taken part in the project work (code, reviews, discussions) for 12 month are automaticaly removed from the maintainer team
### Committers
- Committers are contributors who have made significant and consistent contributions to the project.
- They have the ability to merge minor pull requests if assigned by maintainers.
- A contributor can be proposed as a committer by any existing maintainer. The proposal will be reviewed and voted on by the existing maintainers.
### Community Members
- Anyone can become a community member by contributing to the project. This can be in the form of code contributions, documentation, or any other form of project support.
## Processes
### Proposing Changes
1. Open an issue on the project repository to discuss the proposed change.
2. Once there is consensus around the proposed change, create a pull request.
3. Pull requests will be reviewed by committers and/or maintainers.
4. Once the pull request has received approval, it can be merged into the main codebase.
### Conflict Resolution
1. In case of any conflicts, it is primarily the responsibility of the parties involved to resolve it.
2. If the conflict cannot be resolved, it will be escalated to the maintainers for resolution.
3. Maintainers' decision will be final in case of unresolved conflicts.
## Roles and Responsibilities
### Maintainers
- Ensure the quality and stability of the project.
- Resolve conflicts.
- Provide direction and set priorities for the project.
### Committers
- Review and merge minor pull requests.
- Assist maintainers in project tasks.
- Promote best practices within the community.
### Community Members
- Contribute to the project in any form.
- Participate in discussions and provide feedback.
- Respect the code of conduct and governance of the project.
## Changes to the Governance Document
Proposed changes to this governance document should follow the same process as any other code change to the Kubescape project (see "Proposing Changes").

View File

@@ -1,11 +1,12 @@
# Maintainers
The following table lists the Kubescape project maintainers:
The following table lists the Kubescape project core maintainers:
| Name | GitHub | Organization | Added/Renewed On |
| --- | --- | --- | --- |
| [Matthias Bertschy](https://www.linkedin.com/in/matthias-bertschy-b427b815/) | [@matthyx](https://github.com/matthyx) | [ARMO](https://www.armosec.io/) | 2023-01-01 |
| [Craig Box](https://www.linkedin.com/in/crbnz/) | [@craigbox](https://github.com/craigbox) | [ARMO](https://www.armosec.io/) | 2022-10-31 |
| [Ben Hirschberg](https://www.linkedin.com/in/benyamin-ben-hirschberg-66141890) | [@slashben](https://github.com/slashben) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
| [Rotem Refael](https://www.linkedin.com/in/rotem-refael) | [@rotemamsa](https://github.com/rotemamsa) | [ARMO](https://www.armosec.io/) | 2021-10-11 |
| [David Wertenteil](https://www.linkedin.com/in/david-wertenteil-0ba277b9) | [@dwertent](https://github.com/dwertent) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
| [Bezalel Brandwine](https://www.linkedin.com/in/bezalel-brandwine) | [@Bezbran](https://github.com/Bezbran) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
| [Craig Box](https://www.linkedin.com/in/crbnz/) | [@craigbox](https://github.com/craigbox) | [ARMO](https://www.armosec.io/) | 2022-10-31 |

View File

@@ -4,6 +4,10 @@
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/kubescape/kubescape)
[![GitHub](https://img.shields.io/github/license/kubescape/kubescape)](https://github.com/kubescape/kubescape/blob/master/LICENSE)
[![CNCF](https://shields.io/badge/CNCF-Sandbox%20project-blue?logo=linux-foundation&style=flat)](https://landscape.cncf.io/card-mode?project=sandbox&selected=kubescape)
[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/kubescape)](https://artifacthub.io/packages/search?repo=kubescape)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fkubescape%2Fkubescape.svg?type=shield&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2Fkubescape%2Fkubescape?ref=badge_shield&issueType=license)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/6944/badge)](https://www.bestpractices.dev/projects/6944)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/kubescape/kubescape/badge)](https://securityscorecards.dev/viewer/?uri=github.com/kubescape/kubescape)
[![Twitter Follow](https://img.shields.io/twitter/follow/kubescape?style=social)](https://twitter.com/kubescape)
# Kubescape
@@ -23,7 +27,7 @@ Kubescape scans clusters, YAML files, and Helm charts. It detects misconfigurati
Kubescape was created by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository) and is a [Cloud Native Computing Foundation (CNCF) sandbox project](https://www.cncf.io/sandbox-projects/).
## Demo
<img src="docs/img/demo.gif">
<img src="docs/img/demo-v3.gif">
_Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape! 😀_
@@ -69,7 +73,11 @@ We hold [community meetings](https://zoom.us/j/95174063585) on Zoom, on the firs
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
## Contributions
### Adopters
See [here](ADOPTERS.md) a list of adopters.
## Contributions
Thanks to all our contributors! Check out our [CONTRIBUTING](CONTRIBUTING.md) file to learn how to join them.
@@ -83,6 +91,10 @@ Thanks to all our contributors! Check out our [CONTRIBUTING](CONTRIBUTING.md) f
<img src = "https://contrib.rocks/image?repo=kubescape/kubescape"/>
</a>
## Changelog
Kubescape changes are tracked on the [release](https://github.com/kubescape/kubescape/releases) page
## License
Copyright 2021-2023, the Kubescape Authors. All rights reserved. Kubescape is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details.

52
SECURITY-INSIGHTS.yml Normal file
View File

@@ -0,0 +1,52 @@
header:
schema-version: 1.0.0
last-updated: '2023-10-12'
last-reviewed: '2023-10-12'
expiration-date: '2024-10-12T01:00:00.000Z'
project-url: https://github.com/kubescape/kubescape/
project-release: '1.0.0'
project-lifecycle:
status: active
bug-fixes-only: false
core-maintainers:
- github:slashben
- github:craigbox
- github:matthyx
- github:dwertent
contribution-policy:
accepts-pull-requests: true
accepts-automated-pull-requests: false
code-of-conduct: https://github.com/kubescape/kubescape/blob/master/CODE_OF_CONDUCT.md
dependencies:
third-party-packages: true
dependencies-lists:
- https://github.com/kubescape/kubescape/blob/master/go.mod
- https://github.com/kubescape/kubescape/blob/master/httphandler/go.mod
env-dependencies-policy:
policy-url: https://github.com/kubescape/kubescape/blob/master/docs/environment-dependencies-policy.md
documentation:
- https://github.com/kubescape/kubescape/tree/master/docs
distribution-points:
- https://github.com/kubescape/kubescape/
security-artifacts:
threat-model:
threat-model-created: false
security-testing:
- tool-type: sca
tool-name: Dependabot
tool-version: latest
integration:
ad-hoc: false
ci: true
before-release: true
comment: |
Dependabot is enabled for this repo.
security-contacts:
- type: email
value: cncf-kubescape-maintainers@lists.cncf.io
vulnerability-reporting:
accepts-vulnerability-reports: true
security-policy: https://github.com/kubescape/kubescape/security/policy
email-contact: cncf-kubescape-maintainers@lists.cncf.io
comment: |
The first and best way to report a vulnerability is by using private security issues in GitHub.

7
SECURITY.md Normal file
View File

@@ -0,0 +1,7 @@
# Reporting Security Issues
To report a security issue or vulnerability, submit a [private vulnerability report via GitHub](https://github.com/kubescape/kubescape/security/advisories/new) to the repository maintainers with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
The maintainers will respond within 7 working days of your report. If the issue is confirmed as a vulnerability, we will open a Security Advisory and acknowledge your contributions as part of it. This project follows a 90 day disclosure timeline.
Other contacts: cncf-kubescape-maintainers@lists.cncf.io

View File

@@ -5,7 +5,7 @@ import platform
import subprocess
import tarfile
BASE_GETTER_CONST = "github.com/kubescape/kubescape/v2/core/cautils/getter"
BASE_GETTER_CONST = "github.com/kubescape/kubescape/v3/core/cautils/getter"
CURRENT_PLATFORM = platform.system()
platformSuffixes = {
@@ -41,10 +41,10 @@ def main():
# Set some variables
package_name = get_package_name()
build_url = "github.com/kubescape/kubescape/v2/core/cautils.BuildNumber"
build_url = "github.com/kubescape/kubescape/v3/core/cautils.BuildNumber"
release_version = os.getenv("RELEASE")
client_var = "github.com/kubescape/kubescape/v2/core/cautils.Client"
client_var = "github.com/kubescape/kubescape/v3/core/cautils.Client"
client_name = os.getenv("CLIENT")
# Create build directory
@@ -65,6 +65,9 @@ def main():
ldflags += " -X {}={}".format(client_var, client_name)
build_command = ["go", "build", "-buildmode=pie", "-tags=static,gitenabled", "-o", ks_file, "-ldflags" ,ldflags]
if CURRENT_PLATFORM == "Windows":
os.putenv("CGO_ENABLED", "0")
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)))

View File

@@ -1,39 +1,22 @@
FROM golang:1.20-bullseye as builder
FROM --platform=$BUILDPLATFORM golang:1.20-bullseye as builder
ENV GO111MODULE=on CGO_ENABLED=0
WORKDIR /work
ARG TARGETOS TARGETARCH
RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
cd httphandler && GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/ksserver .
FROM gcr.io/distroless/static-debian11:nonroot
USER nonroot
WORKDIR /home/nonroot/
COPY --from=builder /out/ksserver /usr/bin/ksserver
ARG image_version client
ENV GO111MODULE=on CGO_ENABLED=1 PYTHONUNBUFFERED=1 RELEASE=$image_version CLIENT=$client
# Install required python/pip
RUN apt update
RUN apt install -y cmake python3-pip
RUN pip3 install --no-cache --upgrade pip setuptools
WORKDIR /work
ADD . .
# install libgit2
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
make libgit2
# build kubescape server
WORKDIR /work/httphandler
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
python3 build.py
RUN ls -ltr build/
# build kubescape cmd
WORKDIR /work
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
python3 build.py
RUN /work/build/kubescape-ubuntu-latest download artifacts -o /work/artifacts
FROM gcr.io/distroless/base-debian11:nonroot
COPY --from=builder /work/artifacts/ /home/nonroot/.kubescape
COPY --from=builder /work/httphandler/build/kubescape-ubuntu-latest /usr/bin/ksserver
COPY --from=builder /work/build/kubescape-ubuntu-latest /usr/bin/kubescape
ENV RELEASE=$image_version CLIENT=$client
ENTRYPOINT ["ksserver"]

View File

@@ -0,0 +1,3 @@
.git
git2go
kubescape*

View File

@@ -7,7 +7,13 @@
git clone https://github.com/kubescape/kubescape.git kubescape && cd "$_"
```
2. Build
2. Build kubescape CLI Docker image
```
make all
docker buildx build -t kubescape-cli -f build/kubescape-cli.Dockerfile --build-arg="ks_binary=kubescape" --load .
```
3. Build kubescape Docker image
```
docker buildx build -t kubescape -f build/Dockerfile --load .
```
docker build -t kubescape -f build/Dockerfile .
```

View File

@@ -0,0 +1,12 @@
FROM gcr.io/distroless/base-debian11:debug-nonroot
USER nonroot
WORKDIR /home/nonroot/
ARG image_version client ks_binary
ENV RELEASE=$image_version CLIENT=$client
COPY $ks_binary /usr/bin/kubescape
RUN ["kubescape", "download", "artifacts"]
ENTRYPOINT ["kubescape"]

View File

@@ -0,0 +1,2 @@
.git
git2go

View File

@@ -5,7 +5,7 @@ import (
"os"
"strings"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/spf13/cobra"
)

View File

@@ -3,8 +3,8 @@ package config
import (
"fmt"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/spf13/cobra"
)
@@ -23,14 +23,8 @@ var (
# Set account id
%[1]s config set accountID <account id>
# Set client id
%[1]s config set clientID <client id>
# Set access key
%[1]s config set secretKey <access key>
# Set cloudAPIURL
%[1]s config set cloudAPIURL <cloud API URL>
# Set cloud report URL
%[1]s config set cloudReportURL <cloud Report URL>
`, cautils.ExecName())
)

View File

@@ -4,8 +4,8 @@ import (
"context"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/core/meta"
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)

View File

@@ -5,8 +5,8 @@ import (
"strings"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/core/meta"
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
@@ -33,13 +33,10 @@ func getSetCmd(ks meta.IKubescape) *cobra.Command {
}
var supportConfigSet = map[string]func(*metav1.SetConfig, string){
"accessKey": func(s *metav1.SetConfig, accessKey string) { s.AccessKey = accessKey },
"accountID": func(s *metav1.SetConfig, account string) { s.Account = account },
"clientID": func(s *metav1.SetConfig, clientID string) { s.ClientID = clientID },
"secretKey": func(s *metav1.SetConfig, secretKey string) { s.SecretKey = secretKey },
"cloudAPIURL": func(s *metav1.SetConfig, cloudAPIURL string) { s.CloudAPIURL = cloudAPIURL },
"cloudAuthURL": func(s *metav1.SetConfig, cloudAuthURL string) { s.CloudAuthURL = cloudAuthURL },
"cloudReportURL": func(s *metav1.SetConfig, cloudReportURL string) { s.CloudReportURL = cloudReportURL },
"cloudUIURL": func(s *metav1.SetConfig, cloudUIURL string) { s.CloudUIURL = cloudUIURL },
}
func stringKeysToSlice(m map[string]func(*metav1.SetConfig, string)) []string {

View File

@@ -4,8 +4,8 @@ import (
"os"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/core/meta"
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)

View File

@@ -1,37 +0,0 @@
package delete
import (
"fmt"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
var deleteExceptionsExamples = fmt.Sprintf(`
# Delete single exception
%[1]s delete exceptions "exception name"
# Delete multiple exceptions
%[1]s delete exceptions "first exception;second exception;third exception"
`, cautils.ExecName())
func GetDeleteCmd(ks meta.IKubescape) *cobra.Command {
var deleteInfo v1.Delete
var deleteCmd = &cobra.Command{
Use: "delete <command>",
Short: "Delete configurations in Kubescape SaaS version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
deleteCmd.AddCommand(getExceptionsCmd(ks, &deleteInfo))
return deleteCmd
}

View File

@@ -1,47 +0,0 @@
package delete
import (
"fmt"
"strings"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
func getExceptionsCmd(ks meta.IKubescape, deleteInfo *v1.Delete) *cobra.Command {
return &cobra.Command{
Use: "exceptions <exception name>",
Short: fmt.Sprintf("Delete exceptions from Kubescape SaaS version. Run '%[1]s list exceptions' for all exceptions names", cautils.ExecName()),
Example: deleteExceptionsExamples,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("missing exceptions names")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
if err := flagValidationDelete(deleteInfo); err != nil {
logger.L().Fatal(err.Error())
}
exceptionsNames := strings.Split(args[0], ";")
if len(exceptionsNames) == 0 {
logger.L().Fatal("missing exceptions names")
}
if err := ks.DeleteExceptions(&v1.DeleteExceptions{Credentials: deleteInfo.Credentials, Exceptions: exceptionsNames}); err != nil {
logger.L().Fatal(err.Error())
}
},
}
}
// Check if the flag entered are valid
func flagValidationDelete(deleteInfo *v1.Delete) error {
// Validate the user's credentials
return deleteInfo.Credentials.Validate()
}

View File

@@ -7,10 +7,10 @@ import (
"strings"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/core"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/core"
"github.com/kubescape/kubescape/v3/core/meta"
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)
@@ -83,9 +83,10 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
},
}
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.AccessKey, "accessKey", "", "", "Kubescape SaaS access key. Default will load access key from cache")
downloadCmd.PersistentFlags().MarkDeprecated("client-id", "Client ID is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
downloadCmd.PersistentFlags().MarkDeprecated("secret-key", "Secret Key is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
return downloadCmd
@@ -95,5 +96,5 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
func flagValidationDownload(downloadInfo *v1.DownloadInfo) error {
// Validate the user's credentials
return downloadInfo.Credentials.Validate()
return cautils.ValidateAccountID(downloadInfo.AccountID)
}

View File

@@ -5,9 +5,9 @@ import (
"errors"
"fmt"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
@@ -27,7 +27,7 @@ func GetFixCmd(ks meta.IKubescape) *cobra.Command {
fixCmd := &cobra.Command{
Use: "fix <report output file>",
Short: "Fix misconfiguration in files",
Short: "Propose a fix for the misconfiguration found when scanning Kubernetes manifest files",
Long: ``,
Example: fixCmdExamples,
RunE: func(cmd *cobra.Command, args []string) error {

View File

@@ -6,10 +6,10 @@ import (
"strings"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/core"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/core"
"github.com/kubescape/kubescape/v3/core/meta"
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)
@@ -63,7 +63,8 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
return nil
},
}
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
listCmd.PersistentFlags().StringVarP(&listPolicies.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
listCmd.PersistentFlags().StringVarP(&listPolicies.AccessKey, "accessKey", "", "", "Kubescape SaaS access key. Default will load access key from cache")
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-print'/'json'")
listCmd.PersistentFlags().MarkDeprecated("id", "Control ID's are included in list outputs")
@@ -74,5 +75,5 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
func flagValidationList(listPolicies *v1.ListPolicies) error {
// Validate the user's credentials
return listPolicies.Credentials.Validate()
return cautils.ValidateAccountID(listPolicies.AccountID)
}

View File

@@ -0,0 +1,56 @@
package operator
import (
"fmt"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/core"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/spf13/cobra"
)
var operatorScanConfigExamples = fmt.Sprintf(`
# Run a configuration scan
%[1]s operator scan configurations
`, cautils.ExecName())
func getOperatorScanConfigCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command {
configCmd := &cobra.Command{
Use: "configurations",
Short: "Trigger configuration scanning from the Kubescape-Operator microservice",
Long: ``,
Example: operatorScanConfigExamples,
Args: func(cmd *cobra.Command, args []string) error {
operatorInfo.Subcommands = append(operatorInfo.Subcommands, "config")
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo)
if err != nil {
return err
}
logger.L().Start("Kubescape-Operator Triggering for configuration scanning")
_, err = operatorAdapter.OperatorScan()
if err != nil {
logger.L().StopError("Failed to triggering Kubescape-Operator for configuration scanning", helpers.Error(err))
return err
}
logger.L().StopSuccess("Triggered Kubescape-Operator for configuration scanning")
return nil
},
}
configScanInfo := &cautils.ConfigScanInfo{}
operatorInfo.OperatorScanInfo = configScanInfo
configCmd.PersistentFlags().StringSliceVar(&configScanInfo.IncludedNamespaces, "include-namespaces", nil, "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
configCmd.PersistentFlags().StringSliceVar(&configScanInfo.ExcludedNamespaces, "exclude-namespaces", nil, "Namespaces to exclude from scanning. e.g: --exclude-namespaces ns-a,ns-b. Notice, when running with `exclude-namespace` kubescape does not scan cluster-scoped objects.")
configCmd.PersistentFlags().StringSliceVar(&configScanInfo.Frameworks, "frameworks", nil, "Load frameworks for configuration scanning")
configCmd.PersistentFlags().BoolVarP(&configScanInfo.HostScanner, "enable-host-scan", "", false, "Deploy Kubescape host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valuable data from cluster nodes for certain controls. Yaml file: https://github.com/kubescape/kubescape/blob/master/core/pkg/hostsensorutils/hostsensor.yaml")
return configCmd
}

56
cmd/operator/operator.go Normal file
View File

@@ -0,0 +1,56 @@
package operator
import (
"errors"
"fmt"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/spf13/cobra"
)
const (
scanSubCommand string = "scan"
)
var operatorExamples = fmt.Sprintf(`
# Trigger a configuration scan
%[1]s operator scan configurations
# Trigger a vulnerabilities scan
%[1]s operator scan vulnerabilities
`, cautils.ExecName())
func GetOperatorCmd(ks meta.IKubescape) *cobra.Command {
var operatorInfo cautils.OperatorInfo
operatorCmd := &cobra.Command{
Use: "operator",
Short: "The operator is used to communicate with the Kubescape-Operator within the cluster components.",
Long: ``,
Example: operatorExamples,
Args: func(cmd *cobra.Command, args []string) error {
operatorInfo.Subcommands = append(operatorInfo.Subcommands, "operator")
if len(args) < 2 {
return errors.New("For the operator sub-command, you need to provide at least one additional sub-command. Refer to the examples above.")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("For the operator sub-command, you need to provide at least one additional sub-command. Refer to the examples above.")
}
if args[0] != scanSubCommand {
return errors.New(fmt.Sprintf("For the operator sub-command, only %s is supported. Refer to the examples above.", scanSubCommand))
}
return nil
},
}
operatorCmd.AddCommand(getOperatorScanCmd(ks, operatorInfo))
return operatorCmd
}

45
cmd/operator/scan.go Normal file
View File

@@ -0,0 +1,45 @@
package operator
import (
"errors"
"fmt"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/spf13/cobra"
)
const (
vulnerabilitiesSubCommand string = "vulnerabilities"
configurationsSubCommand string = "configurations"
)
func getOperatorScanCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command {
operatorCmd := &cobra.Command{
Use: "scan",
Short: "Scan your cluster using the Kubescape-operator within the cluster components",
Long: ``,
Example: operatorExamples,
Args: func(cmd *cobra.Command, args []string) error {
operatorInfo.Subcommands = append(operatorInfo.Subcommands, "scan")
if len(args) < 1 {
return errors.New("for operator scan sub command, you must pass at least 1 more sub commands, see above examples")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("for operator scan sub command, you must pass at least 1 more sub commands, see above examples")
}
if (args[0] != vulnerabilitiesSubCommand) && (args[0] != configurationsSubCommand) {
return errors.New(fmt.Sprintf("For the operator sub-command, only %s and %s are supported. Refer to the examples above.", vulnerabilitiesSubCommand, configurationsSubCommand))
}
return nil
},
}
operatorCmd.AddCommand(getOperatorScanConfigCmd(ks, operatorInfo))
operatorCmd.AddCommand(getOperatorScanVulnerabilitiesCmd(ks, operatorInfo))
return operatorCmd
}

View File

@@ -0,0 +1,56 @@
package operator
import (
"fmt"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/core"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/spf13/cobra"
)
var operatorScanVulnerabilitiesExamples = fmt.Sprintf(`
# Trigger a vulnerabilities scan
%[1]s operator scan vulnerabilities
`, cautils.ExecName())
func getOperatorScanVulnerabilitiesCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command {
configCmd := &cobra.Command{
Use: "vulnerabilities",
Short: "Vulnerabilities use for scan your cluster vulnerabilities using Kubescape operator in the in cluster components",
Long: ``,
Example: operatorScanVulnerabilitiesExamples,
Args: func(cmd *cobra.Command, args []string) error {
operatorInfo.Subcommands = append(operatorInfo.Subcommands, "vulnerabilities")
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo)
if err != nil {
return err
}
logger.L().Start("Triggering the Kubescape-Operator for vulnerability scanning")
_, err = operatorAdapter.OperatorScan()
if err != nil {
logger.L().StopError("Failed to trigger the Kubescape-Operator for vulnerability scanning", helpers.Error(err))
return err
}
logger.L().StopSuccess("Triggered Kubescape-Operator for vulnerability scanning. View the scanning results once they are ready using the following command: \"kubectl get vulnerabilitysummaries\"")
return err
},
}
vulnerabilitiesScanInfo := &cautils.VulnerabilitiesScanInfo{
ClusterName: k8sinterface.GetContextName(),
}
operatorInfo.OperatorScanInfo = vulnerabilitiesScanInfo
configCmd.PersistentFlags().StringSliceVar(&vulnerabilitiesScanInfo.IncludeNamespaces, "include-namespaces", nil, "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
return configCmd
}

141
cmd/patch/README.md Normal file
View File

@@ -0,0 +1,141 @@
# Patch Command
The patch command is used for patching container images with vulnerabilities.
It uses [copa](https://github.com/project-copacetic/copacetic) and [buildkit](https://github.com/moby/buildkit) under the hood for patching the container images, and [grype](https://github.com/anchore/grype) as the engine for scanning the images (at the moment).
## Usage
```bash
kubescape patch --image <image-name> [flags]
```
The patch command can be run in 2 ways:
1. **With sudo privileges**
You will need to start `buildkitd` if it is not already running
```bash
sudo buildkitd &
sudo kubescape patch --image <image-name>
```
2. **Without sudo privileges**
```bash
export BUILDKIT_VERSION=v0.11.4
export BUILDKIT_PORT=8888
docker run \
--detach \
--rm \
--privileged \
-p 127.0.0.1:$BUILDKIT_PORT:$BUILDKIT_PORT/tcp \
--name buildkitd \
--entrypoint buildkitd \
"moby/buildkit:$BUILDKIT_VERSION" \
--addr tcp://0.0.0.0:$BUILDKIT_PORT
kubescape patch \
-i <image-name> \
-a tcp://0.0.0.0:$BUILDKIT_PORT
```
### Flags
| Flag | Description | Required | Default |
| -------------- | ------------------------------------------------------ | -------- | ----------------------------------- |
| -i, --image | Image name to be patched (should be in canonical form) | Yes | |
| -a, --addr | Address of the buildkitd service | No | unix:///run/buildkit/buildkitd.sock |
| -t, --tag | Tag of the resultant patched image | No | image_name-patched |
| --timeout | Timeout for the patching process | No | 5m |
| -u, --username | Username for the image registry login | No | |
| -p, --password | Password for the image registry login | No | |
| -f, --format | Output file format. | No | |
| -o, --output | Output file. Print output to file and not stdout | No | |
| -v, --verbose | Display full report. Default to false | No | |
| -h, --help | help for patch | No | |
## Example
We will demonstrate how to use the patch command with an example of [nginx](https://www.nginx.com/) image.
### Pre-requisites
- [docker](https://docs.docker.com/desktop/install/linux-install/#generic-installation-steps) daemon must be installed and running.
- [buildkit](https://github.com/moby/buildkit) daemon must be installed
### Steps
1. Run `buildkitd` service:
```bash
sudo buildkitd
```
2. In a seperate terminal, run the `kubescape patch` command:
```bash
sudo kubescape patch --image docker.io/library/nginx:1.22
```
3. You will get an output like below:
```bash
✅ Successfully scanned image: docker.io/library/nginx:1.22
✅ Patched image successfully. Loaded image: nginx:1.22-patched
✅ Successfully re-scanned image: nginx:1.22-patched
| Severity | Vulnerability | Component | Version | Fixed In |
| -------- | -------------- | ------------- | ----------------------- | -------- |
| Critical | CVE-2023-23914 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| Critical | CVE-2019-8457 | libdb5.3 | 5.3.28+dfsg1-0.8 | wont-fix |
| High | CVE-2022-42916 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-1304 | libext2fs2 | 1.46.2-2 | wont-fix |
| High | CVE-2022-42916 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-1304 | e2fsprogs | 1.46.2-2 | wont-fix |
| High | CVE-2022-1304 | libcom-err2 | 1.46.2-2 | wont-fix |
| High | CVE-2023-27533 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2023-27534 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2023-27533 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-43551 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-3715 | bash | 5.1-2+deb11u1 | wont-fix |
| High | CVE-2023-27534 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-43551 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2021-33560 | libgcrypt20 | 1.8.7-6 | wont-fix |
| High | CVE-2023-2953 | libldap-2.4-2 | 2.4.57+dfsg-3+deb11u1 | wont-fix |
| High | CVE-2022-1304 | libss2 | 1.46.2-2 | wont-fix |
| High | CVE-2020-22218 | libssh2-1 | 1.9.0-2 | wont-fix |
| High | CVE-2023-29491 | libtinfo6 | 6.2+20201114-2+deb11u1 | wont-fix |
| High | CVE-2022-2309 | libxml2 | 2.9.10+dfsg-6.7+deb11u4 | wont-fix |
| High | CVE-2022-4899 | libzstd1 | 1.4.8+dfsg-2.1 | wont-fix |
| High | CVE-2022-1304 | logsave | 1.46.2-2 | wont-fix |
| High | CVE-2023-29491 | ncurses-base | 6.2+20201114-2+deb11u1 | wont-fix |
| High | CVE-2023-29491 | ncurses-bin | 6.2+20201114-2+deb11u1 | wont-fix |
| High | CVE-2023-31484 | perl-base | 5.32.1-4+deb11u2 | wont-fix |
| High | CVE-2020-16156 | perl-base | 5.32.1-4+deb11u2 | wont-fix |
Vulnerability summary - 161 vulnerabilities found:
Image: nginx:1.22-patched
* 3 Critical
* 24 High
* 31 Medium
* 103 Other
Most vulnerable components:
* curl (7.74.0-1.3+deb11u7) - 1 Critical, 4 High, 5 Medium, 1 Low, 3 Negligible
* libcurl4 (7.74.0-1.3+deb11u7) - 1 Critical, 4 High, 5 Medium, 1 Low, 3 Negligible
* libtiff5 (4.2.0-1+deb11u4) - 7 Medium, 10 Negligible, 2 Unknown
* libxml2 (2.9.10+dfsg-6.7+deb11u4) - 1 High, 2 Medium
* perl-base (5.32.1-4+deb11u2) - 2 High, 2 Negligible
What now?
─────────
* Run with '--verbose'/'-v' flag for detailed vulnerabilities view
* Install Kubescape in your cluster for continuous monitoring and a full vulnerability report: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-cloud-operator
```
## Limitations
- The patch command can only fix OS-level vulnerability. It cannot fix application-level vulnerabilities. This is a limitation of copa. The reason behind this is that application level vulnerabilities are best suited to be fixed by the developers of the application.
Hence, this is not really a limitation but a design decision.
- No support for windows containers given the dependency on buildkit.

144
cmd/patch/patch.go Normal file
View File

@@ -0,0 +1,144 @@
package patch
import (
"context"
"errors"
"fmt"
"strings"
"time"
ref "github.com/distribution/distribution/reference"
"github.com/docker/distribution/reference"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/pkg/imagescan"
"github.com/spf13/cobra"
)
var patchCmdExamples = fmt.Sprintf(`
# Patch the nginx:1.22 image
1) sudo buildkitd # start buildkitd service, run in seperate terminal
2) sudo %[1]s patch --image docker.io/library/nginx:1.22 # patch the image
# The patch command can also be run without sudo privileges
# Documentation: https://github.com/kubescape/kubescape/tree/master/cmd/patch
`, cautils.ExecName())
func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
var patchInfo metav1.PatchInfo
var scanInfo cautils.ScanInfo
patchCmd := &cobra.Command{
Use: "patch --image <image>:<tag> [flags]",
Short: "Patch container images with vulnerabilities ",
Long: `Patch command is for automatically patching images with vulnerabilities.`,
Example: patchCmdExamples,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return fmt.Errorf("the command takes no arguments")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := shared.ValidateImageScanInfo(&scanInfo); err != nil {
return err
}
if err := validateImagePatchInfo(&patchInfo); err != nil {
return err
}
results, err := ks.Patch(context.Background(), &patchInfo, &scanInfo)
if err != nil {
return err
}
if imagescan.ExceedsSeverityThreshold(results, imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)) {
shared.TerminateOnExceedingSeverity(&scanInfo, logger.L())
}
return nil
},
}
patchCmd.PersistentFlags().StringVarP(&patchInfo.Image, "image", "i", "", "Application image name and tag to patch")
patchCmd.PersistentFlags().StringVarP(&patchInfo.PatchedImageTag, "tag", "t", "", "Tag for the patched image. Defaults to '<image-tag>-patched' ")
patchCmd.PersistentFlags().StringVarP(&patchInfo.BuildkitAddress, "address", "a", "unix:///run/buildkit/buildkitd.sock", "Address of buildkitd service, defaults to local buildkitd.sock")
patchCmd.PersistentFlags().DurationVar(&patchInfo.Timeout, "timeout", 5*time.Minute, "Timeout for the operation, defaults to '5m'")
patchCmd.PersistentFlags().StringVarP(&patchInfo.Username, "username", "u", "", "Username for registry login")
patchCmd.PersistentFlags().StringVarP(&patchInfo.Password, "password", "p", "", "Password for registry login")
patchCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "", `Output file format. Supported formats: "pretty-printer", "json", "sarif"`)
patchCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
patchCmd.PersistentFlags().BoolVarP(&scanInfo.VerboseMode, "verbose", "v", false, "Display full report. Default to false")
patchCmd.PersistentFlags().StringVarP(&scanInfo.FailThresholdSeverity, "severity-threshold", "s", "", "Severity threshold is the severity of a vulnerability at which the command fails and returns exit code 1")
return patchCmd
}
// validateImagePatchInfo validates the image patch info for the `patch` command
func validateImagePatchInfo(patchInfo *metav1.PatchInfo) error {
if patchInfo.Image == "" {
return errors.New("image tag is required")
}
// Convert image to canonical format (required by copacetic for patching images)
patchInfoImage, err := cautils.NormalizeImageName(patchInfo.Image)
if err != nil {
return nil
}
// Parse the image full name to get image name and tag
named, err := ref.ParseNamed(patchInfoImage)
if err != nil {
return err
}
// If no tag or digest is provided, default to 'latest'
if ref.IsNameOnly(named) {
logger.L().Warning("Image name has no tag or digest, using latest as tag")
named = ref.TagNameOnly(named)
}
patchInfo.Image = named.String()
// If no patched image tag is provided, default to '<image-tag>-patched'
if patchInfo.PatchedImageTag == "" {
taggedName, ok := named.(ref.Tagged)
if !ok {
return errors.New("unexpected error while parsing image tag")
}
patchInfo.ImageTag = taggedName.Tag()
if patchInfo.ImageTag == "" {
logger.L().Warning("No tag provided, defaulting to 'patched'")
patchInfo.PatchedImageTag = "patched"
} else {
patchInfo.PatchedImageTag = fmt.Sprintf("%s-%s", patchInfo.ImageTag, "patched")
}
}
// Extract the "image" name from the canonical Image URL
// If it's an official docker image, we store just the "image-name". Else if a docker repo then we store as "repo/image". Else complete URL
ref, _ := reference.ParseNormalizedNamed(patchInfo.Image)
imageName := named.Name()
if strings.Contains(imageName, "docker.io/library/") {
imageName = reference.Path(ref)
imageName = imageName[strings.LastIndex(imageName, "/")+1:]
} else if strings.Contains(imageName, "docker.io/") {
imageName = reference.Path(ref)
}
patchInfo.ImageName = imageName
return nil
}

View File

@@ -6,20 +6,21 @@ import (
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/cmd/completion"
"github.com/kubescape/kubescape/v2/cmd/config"
"github.com/kubescape/kubescape/v2/cmd/delete"
"github.com/kubescape/kubescape/v2/cmd/download"
"github.com/kubescape/kubescape/v2/cmd/fix"
"github.com/kubescape/kubescape/v2/cmd/list"
"github.com/kubescape/kubescape/v2/cmd/scan"
"github.com/kubescape/kubescape/v2/cmd/submit"
"github.com/kubescape/kubescape/v2/cmd/update"
"github.com/kubescape/kubescape/v2/cmd/version"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/core"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v3/cmd/completion"
"github.com/kubescape/kubescape/v3/cmd/config"
"github.com/kubescape/kubescape/v3/cmd/download"
"github.com/kubescape/kubescape/v3/cmd/fix"
"github.com/kubescape/kubescape/v3/cmd/list"
"github.com/kubescape/kubescape/v3/cmd/operator"
"github.com/kubescape/kubescape/v3/cmd/patch"
"github.com/kubescape/kubescape/v3/cmd/scan"
"github.com/kubescape/kubescape/v3/cmd/update"
"github.com/kubescape/kubescape/v3/cmd/version"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
"github.com/kubescape/kubescape/v3/core/core"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/spf13/cobra"
)
@@ -27,11 +28,11 @@ import (
var rootInfo cautils.RootInfo
var ksExamples = fmt.Sprintf(`
# Scan command
# Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations
%[1]s scan
# List supported frameworks
%[1]s list frameworks
# List supported controls
%[1]s list controls
# Download artifacts (air-gapped environment support)
%[1]s download artifacts
@@ -51,6 +52,13 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
Use: "kubescape",
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://hub.armosec.io/docs",
Example: ksExamples,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
k8sinterface.SetClusterContextName(rootInfo.KubeContext)
initLogger()
initLoggerLevel()
initEnvironment()
initCacheDir()
},
}
if cautils.IsKrewPlugin() {
@@ -63,9 +71,10 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd.SetUsageTemplate(newUsageTemplate)
}
rootCmd.PersistentFlags().StringVar(&rootInfo.KSCloudBEURLsDep, "environment", "", envFlagUsage)
rootCmd.PersistentFlags().StringVar(&rootInfo.KSCloudBEURLs, "env", "", envFlagUsage)
rootCmd.PersistentFlags().MarkDeprecated("environment", "use 'env' instead")
rootCmd.PersistentFlags().StringVar(&rootInfo.DiscoveryServerURL, "server", "", "Backend discovery server URL")
rootCmd.PersistentFlags().MarkDeprecated("environment", "'environment' is no longer supported, Use 'server' instead. Feel free to contact the Kubescape maintainers for more information.")
rootCmd.PersistentFlags().MarkDeprecated("env", "'env' is no longer supported, Use 'server' instead. Feel free to contact the Kubescape maintainers for more information.")
rootCmd.PersistentFlags().MarkHidden("environment")
rootCmd.PersistentFlags().MarkHidden("env")
@@ -74,22 +83,31 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd.PersistentFlags().StringVarP(&rootInfo.Logger, "logger", "l", helpers.InfoLevel.String(), fmt.Sprintf("Logger level. Supported: %s [$KS_LOGGER]", strings.Join(helpers.SupportedLevels(), "/")))
rootCmd.PersistentFlags().StringVar(&rootInfo.CacheDir, "cache-dir", getter.DefaultLocalStore, "Cache directory [$KS_CACHE_DIR]")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable Color output for logging")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.EnableColor, "enable-color", "", false, "Force enable Color output for logging")
cobra.OnInitialize(initLogger, initLoggerLevel, initEnvironment, initCacheDir)
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable color output for logging")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.EnableColor, "enable-color", "", false, "Force enable color output for logging")
rootCmd.PersistentFlags().StringVarP(&rootInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
// Supported commands
rootCmd.AddCommand(scan.GetScanCommand(ks))
rootCmd.AddCommand(download.GetDownloadCmd(ks))
rootCmd.AddCommand(delete.GetDeleteCmd(ks))
rootCmd.AddCommand(list.GetListCmd(ks))
rootCmd.AddCommand(submit.GetSubmitCmd(ks))
rootCmd.AddCommand(completion.GetCompletionCmd())
rootCmd.AddCommand(version.GetVersionCmd())
rootCmd.AddCommand(config.GetConfigCmd(ks))
rootCmd.AddCommand(update.GetUpdateCmd())
rootCmd.AddCommand(fix.GetFixCmd(ks))
rootCmd.AddCommand(patch.GetPatchCmd(ks))
rootCmd.AddCommand(operator.GetOperatorCmd(ks))
// deprecated commands
rootCmd.AddCommand(&cobra.Command{
Use: "submit",
Deprecated: "This command is deprecated. Contact Kubescape maintainers for more information.",
})
rootCmd.AddCommand(&cobra.Command{
Use: "delete",
Deprecated: "This command is deprecated. Contact Kubescape maintainers for more information.",
})
return rootCmd
}

View File

@@ -5,15 +5,19 @@ import (
"os"
"strings"
v1 "github.com/kubescape/backend/pkg/client/v1"
"github.com/kubescape/backend/pkg/servicediscovery"
sdClientV1 "github.com/kubescape/backend/pkg/servicediscovery/v1"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/go-logger/iconlogger"
"github.com/kubescape/go-logger/zaplogger"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
"github.com/mattn/go-isatty"
)
const envFlagUsage = "Send report results to specific URL. Format:<ReportReceiver>,<Backend>,<Frontend>.\n\t\tExample:report.armo.cloud,api.armo.cloud,portal.armo.cloud"
func initLogger() {
logger.DisableColor(rootInfo.DisableColor)
logger.EnableColor(rootInfo.EnableColor)
@@ -23,9 +27,9 @@ func initLogger() {
rootInfo.LoggerName = l
} else {
if isatty.IsTerminal(os.Stdout.Fd()) {
rootInfo.LoggerName = "pretty"
rootInfo.LoggerName = iconlogger.LoggerName
} else {
rootInfo.LoggerName = "zap"
rootInfo.LoggerName = zaplogger.LoggerName
}
}
}
@@ -56,40 +60,51 @@ func initCacheDir() {
logger.L().Debug("cache dir updated", helpers.String("path", getter.DefaultLocalStore))
}
func initEnvironment() {
if rootInfo.KSCloudBEURLs == "" {
rootInfo.KSCloudBEURLs = rootInfo.KSCloudBEURLsDep
if rootInfo.DiscoveryServerURL == "" {
return
}
urlSlices := strings.Split(rootInfo.KSCloudBEURLs, ",")
if len(urlSlices) != 1 && len(urlSlices) < 3 {
logger.L().Fatal("expected at least 3 URLs (report, api, frontend, auth)")
}
switch len(urlSlices) {
case 1:
switch urlSlices[0] {
case "dev", "development":
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIDev())
case "stage", "staging":
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIStaging())
case "":
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIProd())
default:
logger.L().Fatal("--environment flag usage: " + envFlagUsage)
}
case 2:
logger.L().Fatal("--environment flag usage: " + envFlagUsage)
case 3, 4:
var ksAuthURL string
ksEventReceiverURL := urlSlices[0] // mandatory
ksBackendURL := urlSlices[1] // mandatory
ksFrontendURL := urlSlices[2] // mandatory
if len(urlSlices) >= 4 {
ksAuthURL = urlSlices[3]
}
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPICustomized(
ksBackendURL, ksAuthURL,
getter.WithReportURL(ksEventReceiverURL),
getter.WithFrontendURL(ksFrontendURL),
))
logger.L().Debug("fetching URLs from service discovery server", helpers.String("server", rootInfo.DiscoveryServerURL))
client, err := sdClientV1.NewServiceDiscoveryClientV1(rootInfo.DiscoveryServerURL)
if err != nil {
logger.L().Fatal("failed to create service discovery client", helpers.Error(err), helpers.String("server", rootInfo.DiscoveryServerURL))
return
}
services, err := servicediscovery.GetServices(
client,
)
if err != nil {
logger.L().Fatal("failed to to get services from server", helpers.Error(err), helpers.String("server", rootInfo.DiscoveryServerURL))
return
}
logger.L().Debug("configuring service discovery URLs", helpers.String("cloudAPIURL", services.GetApiServerUrl()), helpers.String("cloudReportURL", services.GetReportReceiverHttpUrl()))
tenant := cautils.GetTenantConfig("", "", "", "", nil)
if services.GetApiServerUrl() != "" {
tenant.GetConfigObj().CloudAPIURL = services.GetApiServerUrl()
}
if services.GetReportReceiverHttpUrl() != "" {
tenant.GetConfigObj().CloudReportURL = services.GetReportReceiverHttpUrl()
}
if err = tenant.UpdateCachedConfig(); err != nil {
logger.L().Error("failed to update cached config", helpers.Error(err))
}
ksCloud, err := v1.NewKSCloudAPI(
services.GetApiServerUrl(),
services.GetReportReceiverHttpUrl(),
"",
"",
)
if err != nil {
logger.L().Fatal("failed to create KS Cloud client", helpers.Error(err))
return
}
getter.SetKSCloudAPIConnector(ksCloud)
}

View File

@@ -11,8 +11,9 @@ import (
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/spf13/cobra"
)
@@ -91,6 +92,7 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
}
scanInfo.FrameworkScan = false
scanInfo.SetScanType(cautils.ScanTypeControl)
if err := validateControlScanInfo(scanInfo); err != nil {
return err
@@ -128,7 +130,7 @@ func validateControlScanInfo(scanInfo *cautils.ScanInfo) error {
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
}
if err := validateSeverity(severity); severity != "" && err != nil {
if err := shared.ValidateSeverity(severity); severity != "" && err != nil {
return err
}
return nil

View File

@@ -15,9 +15,10 @@ import (
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/spf13/cobra"
)
@@ -42,7 +43,10 @@ var (
Run '%[1]s list frameworks' for the list of supported frameworks
`, cautils.ExecName())
ErrUnknownSeverity = errors.New("unknown severity")
ErrSecurityViewNotSupported = errors.New("security view is not supported for framework scan")
ErrBadThreshold = errors.New("bad argument: out of range threshold")
ErrKeepLocalOrSubmit = errors.New("you can use `keep-local` or `submit`, but not both")
ErrOmitRawResourcesOrSubmit = errors.New("you can use `omit-raw-resources` or `submit`, but not both")
)
func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
@@ -136,12 +140,13 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
return nil
},
}
}
// countersExceedSeverityThreshold returns true if severity of failed controls exceed the set severity threshold, else returns false
func countersExceedSeverityThreshold(severityCounters reportsummary.ISeverityCounters, scanInfo *cautils.ScanInfo) (bool, error) {
targetSeverity := scanInfo.FailThresholdSeverity
if err := validateSeverity(targetSeverity); err != nil {
if err := shared.ValidateSeverity(targetSeverity); err != nil {
return false, err
}
@@ -195,36 +200,29 @@ func enforceSeverityThresholds(severityCounters reportsummary.ISeverityCounters,
}
}
// validateSeverity returns an error if a given severity is not known, nil otherwise
func validateSeverity(severity string) error {
for _, val := range reporthandlingapis.GetSupportedSeverities() {
if strings.EqualFold(severity, val) {
return nil
}
}
return ErrUnknownSeverity
}
// validateFrameworkScanInfo validates the scan info struct for the `scan framework` command
func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
if scanInfo.View == string(cautils.SecurityViewType) {
scanInfo.View = string(cautils.ResourceViewType)
}
if scanInfo.Submit && scanInfo.Local {
return fmt.Errorf("you can use `keep-local` or `submit`, but not both")
return ErrKeepLocalOrSubmit
}
if 100 < scanInfo.ComplianceThreshold || 0 > scanInfo.ComplianceThreshold {
return fmt.Errorf("bad argument: out of range threshold")
return ErrBadThreshold
}
if 100 < scanInfo.FailThreshold || 0 > scanInfo.FailThreshold {
return fmt.Errorf("bad argument: out of range threshold")
return ErrBadThreshold
}
if scanInfo.Submit && scanInfo.OmitRawResources {
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
return ErrOmitRawResourcesOrSubmit
}
severity := scanInfo.FailThresholdSeverity
if err := validateSeverity(severity); severity != "" && err != nil {
if err := shared.ValidateSeverity(severity); severity != "" && err != nil {
return err
}
// Validate the user's credentials
return scanInfo.Credentials.Validate()
return cautils.ValidateAccountID(scanInfo.AccountID)
}

View File

@@ -5,40 +5,34 @@ import (
"fmt"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/iconlogger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/core"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling"
"github.com/kubescape/kubescape/v2/pkg/imagescan"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/pkg/imagescan"
"github.com/spf13/cobra"
)
type imageScanInfo struct {
Username string
Password string
}
// TODO(vladklokun): document image scanning on the Kubescape Docs Hub?
var (
imageExample = fmt.Sprintf(`
This command is still in BETA. Feel free to contact the kubescape maintainers for more information.
Scan an image for vulnerabilities.
# Scan the 'nginx' image
%[1]s scan image "nginx"
# Image scan documentation:
# https://hub.armosec.io/docs/images
# Scan the 'nginx' image and see the full report
%[1]s scan image "nginx" -v
`, cautils.ExecName())
)
// imageCmd represents the image command
func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgScanInfo *imageScanInfo) *cobra.Command {
// getImageCmd returns the scan image command
func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
var imgCredentials shared.ImageCredentials
cmd := &cobra.Command{
Use: "image <IMAGE_NAME>",
Use: "image <image>:<tag> [flags]",
Short: "Scan an image for vulnerabilities",
Example: imageExample,
Args: func(cmd *cobra.Command, args []string) error {
@@ -48,70 +42,31 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgScanInfo *im
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateImageScanInfo(scanInfo); err != nil {
if err := shared.ValidateImageScanInfo(scanInfo); err != nil {
return err
}
failOnSeverity := imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)
ctx := context.Background()
logger.InitLogger(iconlogger.LoggerName)
dbCfg, _ := imagescan.NewDefaultDBConfig()
svc := imagescan.NewScanService(dbCfg)
creds := imagescan.RegistryCredentials{
Username: imgScanInfo.Username,
Password: imgScanInfo.Password,
imgScanInfo := &metav1.ImageScanInfo{
Image: args[0],
Username: imgCredentials.Username,
Password: imgCredentials.Password,
}
userInput := args[0]
logger.L().Start(fmt.Sprintf("Scanning image: %s", userInput))
scanResults, err := svc.Scan(ctx, userInput, creds)
results, err := ks.ScanImage(context.Background(), imgScanInfo, scanInfo)
if err != nil {
logger.L().StopError(fmt.Sprintf("Failed to scan image: %s", userInput))
return err
}
logger.L().StopSuccess(fmt.Sprintf("Successfully scanned image: %s", userInput))
scanInfo.SetScanType(cautils.ScanTypeImage)
outputPrinters := core.GetOutputPrinters(scanInfo, ctx)
uiPrinter := core.GetUIPrinter(ctx, scanInfo)
resultsHandler := resultshandling.NewResultsHandler(nil, outputPrinters, uiPrinter)
resultsHandler.ImageScanData = []cautils.ImageScanData{
{
PresenterConfig: scanResults,
Image: userInput,
},
if imagescan.ExceedsSeverityThreshold(results, imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)) {
shared.TerminateOnExceedingSeverity(scanInfo, logger.L())
}
resultsHandler.HandleResults(ctx)
if imagescan.ExceedsSeverityThreshold(scanResults, failOnSeverity) {
terminateOnExceedingSeverity(scanInfo, logger.L())
}
return err
return nil
},
}
cmd.PersistentFlags().StringVarP(&imgScanInfo.Username, "username", "u", "", "Username for registry login")
cmd.PersistentFlags().StringVarP(&imgScanInfo.Password, "password", "p", "", "Password for registry login")
cmd.PersistentFlags().StringVarP(&imgCredentials.Username, "username", "u", "", "Username for registry login")
cmd.PersistentFlags().StringVarP(&imgCredentials.Password, "password", "p", "", "Password for registry login")
return cmd
}
// validateImageScanInfo validates the ScanInfo struct for the `image` command
func validateImageScanInfo(scanInfo *cautils.ScanInfo) error {
severity := scanInfo.FailThresholdSeverity
if err := validateSeverity(severity); severity != "" && err != nil {
return err
}
return nil
}

View File

@@ -6,10 +6,9 @@ import (
"fmt"
"strings"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
"github.com/kubescape/kubescape/v3/core/meta"
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/spf13/cobra"
)
@@ -17,14 +16,14 @@ import (
var scanCmdExamples = fmt.Sprintf(`
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
# Scan current cluster with all frameworks
# Scan current cluster
%[1]s scan
# Scan kubernetes YAML manifest files
# Scan kubernetes manifest files
%[1]s scan .
# Scan and save the results in the JSON format
%[1]s scan --format json --output results.json --format-version=v2
%[1]s scan --format json --output results.json
# Display all resources
%[1]s scan --verbose
@@ -39,18 +38,9 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
// scanCmd represents the scan command
scanCmd := &cobra.Command{
Use: "scan",
Short: "Scan the current running cluster or yaml files",
Short: "Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations",
Long: `The action you want to perform`,
Example: scanCmdExamples,
Args: func(cmd *cobra.Command, args []string) error {
// setting input patterns for framework scan is only relevancy for non-security view
if len(args) > 0 && scanInfo.View != string(cautils.SecurityViewType) {
if args[0] != "framework" && args[0] != "control" {
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{strings.Join(getter.NativeFrameworks, ",")}, args...))
}
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if scanInfo.View == string(cautils.SecurityViewType) {
setSecurityViewScanInfo(args, &scanInfo)
@@ -58,23 +48,17 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
return securityScan(scanInfo, ks)
}
if len(args) == 0 {
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, []string{strings.Join(getter.NativeFrameworks, ",")})
if len(args) == 0 || (args[0] != "framework" && args[0] != "control") {
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{strings.Join(getter.NativeFrameworks, ",")}, args...))
}
return nil
},
PreRun: func(cmd *cobra.Command, args []string) {
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
},
PostRun: func(cmd *cobra.Command, args []string) {
// TODO - revert context
},
}
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
scanCmd.PersistentFlags().BoolVar(&scanInfo.CreateAccount, "create-account", false, "Create a Kubescape SaaS account ID account ID is not found in cache. After creating the account, the account ID will be saved in cache. In addition, the scanning results will be uploaded to the Kubescape SaaS")
scanCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
scanCmd.PersistentFlags().StringVarP(&scanInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
@@ -89,7 +73,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to configured backend.")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.VerboseMode, "verbose", "v", false, "Display all of the input resources and not only failed resources")
scanCmd.PersistentFlags().StringVar(&scanInfo.View, "view", string(cautils.ResourceViewType), fmt.Sprintf("View results based on the %s/%s/%s. default is --view=%s", cautils.ResourceViewType, cautils.ControlViewType, cautils.SecurityViewType, cautils.ResourceViewType))
scanCmd.PersistentFlags().StringVar(&scanInfo.View, "view", string(cautils.SecurityViewType), fmt.Sprintf("View results based on the %s/%s/%s. default is --view=%s", cautils.ResourceViewType, cautils.ControlViewType, cautils.SecurityViewType, cautils.SecurityViewType))
scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest")
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest")
scanCmd.PersistentFlags().StringVar(&scanInfo.HostSensorYamlPath, "host-scan-yaml", "", "Override default host scanner DaemonSet. Use this flag cautiously")
@@ -103,10 +87,9 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
scanCmd.PersistentFlags().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
scanCmd.PersistentFlags().MarkDeprecated("fail-threshold", "use '--compliance-threshold' flag instead. Flag will be removed at 1.Dec.2023")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
scanCmd.PersistentFlags().MarkDeprecated("client-id", "login to Kubescape SaaS will be unsupported, please contact the Kubescape maintainers for more information")
scanCmd.PersistentFlags().MarkDeprecated("secret-key", "login to Kubescape SaaS will be unsupported, please contact the Kubescape maintainers for more information")
scanCmd.PersistentFlags().MarkDeprecated("client-id", "Client ID is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
scanCmd.PersistentFlags().MarkDeprecated("create-account", "Create account is no longer supported. In case of a missing Account ID and a configured backend server, a new account id will be generated automatically by Kubescape. Feel free to contact the Kubescape maintainers for more information.")
scanCmd.PersistentFlags().MarkDeprecated("secret-key", "Secret Key is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
// hidden flags
scanCmd.PersistentFlags().MarkHidden("omit-raw-resources")
@@ -119,17 +102,16 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
hostF.NoOptDefVal = "true"
hostF.DefValue = "false, for no TTY in stdin"
scanCmd.PersistentFlags().MarkHidden("enable-host-scan")
scanCmd.PersistentFlags().MarkDeprecated("enable-host-scan", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-cloud-operator. The flag will be removed at 1.Dec.2023")
scanCmd.PersistentFlags().MarkDeprecated("enable-host-scan", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator. The flag will be removed at 1.Dec.2023")
scanCmd.PersistentFlags().MarkHidden("host-scan-yaml") // this flag should be used very cautiously. We prefer users will not use it at all unless the DaemonSet can not run pods on the nodes
scanCmd.PersistentFlags().MarkDeprecated("host-scan-yaml", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-cloud-operator. The flag will be removed at 1.Dec.2023")
scanCmd.PersistentFlags().MarkDeprecated("host-scan-yaml", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator. The flag will be removed at 1.Dec.2023")
scanCmd.AddCommand(getControlCmd(ks, &scanInfo))
scanCmd.AddCommand(getFrameworkCmd(ks, &scanInfo))
scanCmd.AddCommand(getWorkloadCmd(ks, &scanInfo))
isi := &imageScanInfo{}
scanCmd.AddCommand(getImageCmd(ks, &scanInfo, isi))
scanCmd.AddCommand(getImageCmd(ks, &scanInfo))
return scanCmd
}

View File

@@ -5,7 +5,8 @@ import (
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
@@ -112,7 +113,7 @@ func TestExceedsSeverity(t *testing.T) {
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "unknown"},
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
Want: false,
Error: ErrUnknownSeverity,
Error: shared.ErrUnknownSeverity,
},
}

View File

@@ -3,7 +3,8 @@ package scan
import (
"testing"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
)
// Test_validateControlScanInfo tests how scan info is validated for the `scan control` command
@@ -26,7 +27,7 @@ func Test_validateControlScanInfo(t *testing.T) {
{
"Unknown severity should be invalid for scan info",
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
ErrUnknownSeverity,
shared.ErrUnknownSeverity,
},
}
@@ -66,7 +67,17 @@ func Test_validateFrameworkScanInfo(t *testing.T) {
{
"Unknown severity should be invalid for scan info",
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
ErrUnknownSeverity,
shared.ErrUnknownSeverity,
},
{
"Security view should be invalid for scan info",
&cautils.ScanInfo{View: string(cautils.SecurityViewType)},
nil,
},
{
"Empty view should be valid for scan info",
&cautils.ScanInfo{},
nil,
},
}
@@ -86,35 +97,6 @@ func Test_validateFrameworkScanInfo(t *testing.T) {
}
}
func Test_validateSeverity(t *testing.T) {
testCases := []struct {
Description string
Input string
Want error
}{
{"low should be a valid severity", "low", nil},
{"Low should be a valid severity", "Low", nil},
{"medium should be a valid severity", "medium", nil},
{"Medium should be a valid severity", "Medium", nil},
{"high should be a valid severity", "high", nil},
{"Critical should be a valid severity", "Critical", nil},
{"critical should be a valid severity", "critical", nil},
{"Unknown should be an invalid severity", "Unknown", ErrUnknownSeverity},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
input := testCase.Input
want := testCase.Want
got := validateSeverity(input)
if got != want {
t.Errorf("got: %v, want: %v", got, want)
}
})
}
}
func Test_validateWorkloadIdentifier(t *testing.T) {
testCases := []struct {
Description string

View File

@@ -7,8 +7,8 @@ import (
"strings"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/opa-utils/objectsenvelopes"
@@ -17,8 +17,6 @@ import (
var (
workloadExample = fmt.Sprintf(`
This command is still in BETA. Feel free to contact the kubescape maintainers for more information.
Scan a workload for misconfigurations and image vulnerabilities.
# Scan an workload

View File

@@ -3,7 +3,7 @@ package scan
import (
"testing"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v3/core/cautils"
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/opa-utils/objectsenvelopes"
)

18
cmd/shared/image_scan.go Normal file
View File

@@ -0,0 +1,18 @@
package shared
import "github.com/kubescape/kubescape/v3/core/cautils"
type ImageCredentials struct {
Username string
Password string
}
// ValidateImageScanInfo validates the ScanInfo struct for image scanning commands
func ValidateImageScanInfo(scanInfo *cautils.ScanInfo) error {
severity := scanInfo.FailThresholdSeverity
if err := ValidateSeverity(severity); severity != "" && err != nil {
return err
}
return nil
}

28
cmd/shared/scan.go Normal file
View File

@@ -0,0 +1,28 @@
package shared
import (
"fmt"
"strings"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
reporthandlingapis "github.com/kubescape/opa-utils/reporthandling/apis"
)
var ErrUnknownSeverity = fmt.Errorf("unknown severity. Supported severities are: %s", strings.Join(reporthandlingapis.GetSupportedSeverities(), ", "))
// ValidateSeverity returns an error if a given severity is not known, nil otherwise
func ValidateSeverity(severity string) error {
for _, val := range reporthandlingapis.GetSupportedSeverities() {
if strings.EqualFold(severity, val) {
return nil
}
}
return ErrUnknownSeverity
}
// TerminateOnExceedingSeverity terminates the program if the result exceeds the severity threshold
func TerminateOnExceedingSeverity(scanInfo *cautils.ScanInfo, l helpers.ILogger) {
l.Fatal("result exceeds severity threshold", helpers.String("Set severity threshold", scanInfo.FailThresholdSeverity))
}

124
cmd/shared/scan_test.go Normal file
View File

@@ -0,0 +1,124 @@
package shared
import (
"context"
"os"
"reflect"
"testing"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/opa-utils/reporthandling/apis"
)
type spyLogMessage struct {
Message string
Details map[string]string
}
type spyLogger struct {
setItems []spyLogMessage
}
func (l *spyLogger) Error(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Success(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Warning(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Info(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Debug(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) SetLevel(level string) error { return nil }
func (l *spyLogger) GetLevel() string { return "" }
func (l *spyLogger) SetWriter(w *os.File) {}
func (l *spyLogger) GetWriter() *os.File { return &os.File{} }
func (l *spyLogger) LoggerName() string { return "" }
func (l *spyLogger) Ctx(_ context.Context) helpers.ILogger { return l }
func (l *spyLogger) Start(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) StopSuccess(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) StopError(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Fatal(msg string, details ...helpers.IDetails) {
firstDetail := details[0]
detailsMap := map[string]string{firstDetail.Key(): firstDetail.Value().(string)}
newMsg := spyLogMessage{msg, detailsMap}
l.setItems = append(l.setItems, newMsg)
}
func (l *spyLogger) GetSpiedItems() []spyLogMessage {
return l.setItems
}
func TestTerminateOnExceedingSeverity(t *testing.T) {
expectedMessage := "result exceeds severity threshold"
expectedKey := "Set severity threshold"
testCases := []struct {
Description string
ExpectedMessage string
ExpectedKey string
ExpectedValue string
Logger *spyLogger
}{
{
"Should log the Critical threshold that was set in scan info",
expectedMessage,
expectedKey,
apis.SeverityCriticalString,
&spyLogger{},
},
{
"Should log the High threshold that was set in scan info",
expectedMessage,
expectedKey,
apis.SeverityHighString,
&spyLogger{},
},
}
for _, tc := range testCases {
t.Run(
tc.Description,
func(t *testing.T) {
want := []spyLogMessage{
{tc.ExpectedMessage, map[string]string{tc.ExpectedKey: tc.ExpectedValue}},
}
scanInfo := &cautils.ScanInfo{FailThresholdSeverity: tc.ExpectedValue}
TerminateOnExceedingSeverity(scanInfo, tc.Logger)
got := tc.Logger.GetSpiedItems()
if !reflect.DeepEqual(got, want) {
t.Errorf("got: %v, want: %v", got, want)
}
},
)
}
}
func TestValidateSeverity(t *testing.T) {
testCases := []struct {
Description string
Input string
Want error
}{
{"low should be a valid severity", "low", nil},
{"Low should be a valid severity", "Low", nil},
{"medium should be a valid severity", "medium", nil},
{"Medium should be a valid severity", "Medium", nil},
{"high should be a valid severity", "high", nil},
{"Critical should be a valid severity", "Critical", nil},
{"critical should be a valid severity", "critical", nil},
{"Unknown should be an invalid severity", "Unknown", ErrUnknownSeverity},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
input := testCase.Input
want := testCase.Want
got := ValidateSeverity(input)
if got != want {
t.Errorf("got: %v, want: %v", got, want)
}
})
}
}

View File

@@ -1,35 +0,0 @@
package submit
import (
"context"
"fmt"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
func getExceptionsCmd(ks meta.IKubescape, submitInfo *metav1.Submit) *cobra.Command {
return &cobra.Command{
Use: "exceptions <full path to exceptions file>",
Short: "Submit exceptions to the Kubescape SaaS version",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("missing full path to exceptions file")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
if err := flagValidationSubmit(submitInfo); err != nil {
logger.L().Fatal(err.Error())
}
if err := ks.SubmitExceptions(context.TODO(), &submitInfo.Credentials, args[0]); err != nil {
logger.L().Fatal(err.Error())
}
},
}
}

View File

@@ -1,98 +0,0 @@
package submit
import (
"context"
"fmt"
"github.com/google/uuid"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
reporterv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter/v2"
"github.com/kubescape/rbac-utils/rbacscanner"
"github.com/spf13/cobra"
)
var (
rbacExamples = fmt.Sprintf(`
# Submit cluster's Role-Based Access Control(RBAC)
%[1]s submit rbac
# Submit cluster's Role-Based Access Control(RBAC) with account ID
%[1]s submit rbac --account <account-id>
`, cautils.ExecName())
)
// getRBACCmd represents the RBAC command
func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
return &cobra.Command{
Use: "rbac",
Deprecated: "This command is deprecated and will not be supported after 1/Jan/2023. Please use the 'scan' command instead.",
Example: rbacExamples,
Short: "Submit cluster's Role-Based Access Control(RBAC)",
Long: ``,
RunE: func(_ *cobra.Command, args []string) error {
if err := flagValidationSubmit(submitInfo); err != nil {
return err
}
k8s := k8sinterface.NewKubernetesApi()
// get config
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
if err := clusterConfig.SetTenant(); err != nil {
logger.L().Error("failed setting account ID", helpers.Error(err))
}
if clusterConfig.GetAccountID() == "" {
return fmt.Errorf("account ID is not set, run '%[1]s submit rbac --account <account-id>'", cautils.ExecName())
}
// list RBAC
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetAccountID(), clusterConfig.GetContextName()))
// submit resources
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextRBAC)
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: rbacObjects,
Reporter: r,
}
if err := ks.Submit(context.TODO(), submitInterfaces); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
}
// getKubernetesApi
func getKubernetesApi() *k8sinterface.KubernetesApi {
if !k8sinterface.IsConnectedToCluster() {
return nil
}
return k8sinterface.NewKubernetesApi()
}
func getTenantConfig(credentials *cautils.Credentials, clusterName string, customClusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
}
return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
}
// Check if the flag entered are valid
func flagValidationSubmit(submitInfo *v1.Submit) error {
// Validate the user's credentials
return submitInfo.Credentials.Validate()
}

View File

@@ -1,106 +0,0 @@
package submit
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/google/uuid"
"github.com/kubescape/kubescape/v2/core/cautils"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
reporterv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter/v2"
"github.com/spf13/cobra"
)
var formatVersion string
type ResultsObject struct {
filePath string
customerGUID string
clusterName string
}
func NewResultsObject(customerGUID, clusterName, filePath string) *ResultsObject {
return &ResultsObject{
filePath: filePath,
customerGUID: customerGUID,
clusterName: clusterName,
}
}
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandlingv2.PostureReport, error) {
// load framework results from json file
report, err := loadResultsFromFile(resultsObject.filePath)
if err != nil {
return nil, err
}
return report, nil
}
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
return map[string]workloadinterface.IMetadata{}, nil
}
func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
var resultsCmd = &cobra.Command{
Use: fmt.Sprintf("results <json file>\nExample:\n$ %[1]s submit results path/to/results.json --format-version v2", cautils.ExecName()),
Short: "Submit a pre scanned results file. The file must be in json format",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
if err := flagValidationSubmit(submitInfo); err != nil {
return err
}
if len(args) == 0 {
return fmt.Errorf("missing results file")
}
k8s := getKubernetesApi()
// get config
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
if err := clusterConfig.SetTenant(); err != nil {
logger.L().Error("failed setting account ID", helpers.Error(err))
}
resultsObjects := NewResultsObject(clusterConfig.GetAccountID(), clusterConfig.GetContextName(), args[0])
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextScan)
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: resultsObjects,
Reporter: r,
}
if err := ks.Submit(context.TODO(), submitInterfaces); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
resultsCmd.PersistentFlags().StringVar(&formatVersion, "format-version", "v2", "Output object can be different between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
return resultsCmd
}
func loadResultsFromFile(filePath string) (*reporthandlingv2.PostureReport, error) {
report := &reporthandlingv2.PostureReport{}
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
if err = json.Unmarshal(f, report); err != nil {
return report, fmt.Errorf("failed to unmarshal results file: %s, make sure you run kubescape with '--format=json --format-version=v2'", err.Error())
}
return report, nil
}

View File

@@ -1,40 +0,0 @@
package submit
import (
"fmt"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
var submitCmdExamples = fmt.Sprintf(`
# Submit Kubescape scan results file
%[1]s submit results
# Submit exceptions file to Kubescape SaaS
%[1]s submit exceptions
`, cautils.ExecName())
func GetSubmitCmd(ks meta.IKubescape) *cobra.Command {
var submitInfo metav1.Submit
submitCmd := &cobra.Command{
Use: "submit <command>",
Short: "Submit an object to the Kubescape SaaS version",
Long: ``,
Example: submitCmdExamples,
Run: func(cmd *cobra.Command, args []string) {
},
}
submitCmd.PersistentFlags().StringVarP(&submitInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
submitCmd.PersistentFlags().StringVarP(&submitInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
submitCmd.PersistentFlags().StringVarP(&submitInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
submitCmd.AddCommand(getExceptionsCmd(ks, &submitInfo))
submitCmd.AddCommand(getResultsCmd(ks, &submitInfo))
submitCmd.AddCommand(getRBACCmd(ks, &submitInfo))
return submitCmd
}

View File

@@ -8,7 +8,8 @@ import (
"fmt"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/spf13/cobra"
)
@@ -24,16 +25,16 @@ var updateCmdExamples = fmt.Sprintf(`
func GetUpdateCmd() *cobra.Command {
updateCmd := &cobra.Command{
Use: "update",
Short: "Update your version",
Short: "Update to latest release version",
Long: ``,
Example: updateCmdExamples,
RunE: func(_ *cobra.Command, args []string) error {
//Checking the user's version of kubescape to the latest release
if cautils.BuildNumber == cautils.LatestReleaseVersion {
//your version == latest version
logger.L().Info(("You are in the latest version"))
logger.L().Info(("Nothing to update, you are running the latest version"), helpers.String("Version", cautils.BuildNumber))
} else {
fmt.Printf("please refer to our installation docs in the following link: %s", installationLink)
fmt.Printf("Please refer to our installation docs in the following link: %s\n", installationLink)
}
return nil
},

View File

@@ -5,7 +5,8 @@ import (
"fmt"
"os"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/spf13/cobra"
)
@@ -19,10 +20,10 @@ func GetVersionCmd() *cobra.Command {
v := cautils.NewIVersionCheckHandler(ctx)
v.CheckLatestVersion(ctx, cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
fmt.Fprintf(os.Stdout,
"Your current version is: %s [git enabled in build: %t]\n",
"Your current version is: %s\n",
cautils.BuildNumber,
isGitEnabled(),
)
logger.L().Debug(fmt.Sprintf("git enabled in build: %t", isGitEnabled()))
return nil
},
}

View File

@@ -3,24 +3,39 @@ package cautils
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/google/uuid"
v1 "github.com/kubescape/backend/pkg/client/v1"
"github.com/kubescape/backend/pkg/servicediscovery"
servicediscoveryv1 "github.com/kubescape/backend/pkg/servicediscovery/v1"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
corev1 "k8s.io/api/core/v1"
)
const (
configFileName string = "config"
kubescapeNamespace string = "kubescape"
kubescapeConfigMapName string = "kubescape-config"
configFileName string = "config"
kubescapeNamespace string = "kubescape"
kubescapeConfigMapName string = "kubescape-config" // deprecated - for backward compatibility
kubescapeCloudConfigMapName string = "ks-cloud-config" // deprecated - for backward compatibility
cloudConfigMapLabelSelector string = "kubescape.io/infra=config"
credsLabelSelectors string = "kubescape.io/infra=credentials" //nolint:gosec
// env vars
defaultConfigMapNamespaceEnvVar string = "KS_DEFAULT_CONFIGMAP_NAMESPACE"
accountIdEnvVar string = "KS_ACCOUNT_ID"
accessKeyEnvVar string = "KS_ACCESS_KEY"
cloudApiUrlEnvVar string = "KS_CLOUD_API_URL"
cloudReportUrlEnvVar string = "KS_CLOUD_REPORT_URL"
)
func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName + ".json") }
@@ -30,16 +45,11 @@ func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName +
// ======================================================================================
type ConfigObj struct {
AccountID string `json:"accountID,omitempty"`
ClientID string `json:"clientID,omitempty"`
SecretKey string `json:"secretKey,omitempty"`
Token string `json:"invitationParam,omitempty"`
CustomerAdminEMail string `json:"adminMail,omitempty"`
ClusterName string `json:"clusterName,omitempty"`
CloudReportURL string `json:"cloudReportURL,omitempty"`
CloudAPIURL string `json:"cloudAPIURL,omitempty"`
CloudUIURL string `json:"cloudUIURL,omitempty"`
CloudAuthURL string `json:"cloudAuthURL,omitempty"`
AccountID string `json:"accountID,omitempty"`
ClusterName string `json:"clusterName,omitempty"`
CloudReportURL string `json:"cloudReportURL,omitempty"`
CloudAPIURL string `json:"cloudAPIURL,omitempty"`
AccessKey string `json:"accessKey,omitempty"`
}
// Config - convert ConfigObj to config file
@@ -47,17 +57,11 @@ func (co *ConfigObj) Config() []byte {
// remove cluster name before saving to file
clusterName := co.ClusterName
customerAdminEMail := co.CustomerAdminEMail
token := co.Token
co.ClusterName = ""
co.Token = ""
co.CustomerAdminEMail = ""
b, err := json.MarshalIndent(co, "", " ")
co.ClusterName = clusterName
co.CustomerAdminEMail = customerAdminEMail
co.Token = token
if err == nil {
return b
@@ -73,24 +77,12 @@ func (co *ConfigObj) updateEmptyFields(inCO *ConfigObj) error {
if inCO.CloudAPIURL != "" {
co.CloudAPIURL = inCO.CloudAPIURL
}
if inCO.CloudAuthURL != "" {
co.CloudAuthURL = inCO.CloudAuthURL
}
if inCO.CloudReportURL != "" {
co.CloudReportURL = inCO.CloudReportURL
}
if inCO.CloudUIURL != "" {
co.CloudUIURL = inCO.CloudUIURL
}
if inCO.ClusterName != "" {
co.ClusterName = inCO.ClusterName
}
if inCO.CustomerAdminEMail != "" {
co.CustomerAdminEMail = inCO.CustomerAdminEMail
}
if inCO.Token != "" {
co.Token = inCO.Token
}
return nil
}
@@ -99,27 +91,18 @@ func (co *ConfigObj) updateEmptyFields(inCO *ConfigObj) error {
// =============================== interface ============================================
// ======================================================================================
type ITenantConfig interface {
// set
SetTenant() error
UpdateCachedConfig() error
DeleteCachedConfig(ctx context.Context) error
GenerateAccountID() (string, error)
DeleteCredentials() error
// getters
GetContextName() string
GetAccountID() string
GetTenantEmail() string
GetToken() string
GetClientID() string
GetSecretKey() string
GetAccessKey() string
GetConfigObj() *ConfigObj
GetCloudReportURL() string
GetCloudAPIURL() string
GetCloudUIURL() string
GetCloudAuthURL() string
// GetBackendAPI() getter.IBackend
// GenerateURL()
IsConfigFound() bool
}
// ======================================================================================
@@ -130,23 +113,19 @@ type ITenantConfig interface {
var _ ITenantConfig = &LocalConfig{}
type LocalConfig struct {
backendAPI getter.IBackend
configObj *ConfigObj
configObj *ConfigObj
}
func NewLocalConfig(
backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *LocalConfig {
func NewLocalConfig(accountID, accessKey, clusterName, customClusterName string) *LocalConfig {
lc := &LocalConfig{
backendAPI: backendAPI,
configObj: &ConfigObj{},
configObj: &ConfigObj{},
}
// get from configMap
if existsConfigFile() { // get from file
loadConfigFromFile(lc.configObj)
}
updateCredentials(lc.configObj, credentials)
updateCredentials(lc.configObj, accountID, accessKey)
updateCloudURLs(lc.configObj)
// If a custom cluster name is provided then set that name, else use the cluster's original name
@@ -156,59 +135,32 @@ func NewLocalConfig(
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
}
lc.backendAPI.SetAccountID(lc.configObj.AccountID)
lc.backendAPI.SetClientID(lc.configObj.ClientID)
lc.backendAPI.SetSecretKey(lc.configObj.SecretKey)
if lc.configObj.CloudAPIURL != "" {
lc.backendAPI.SetCloudAPIURL(lc.configObj.CloudAPIURL)
} else {
lc.configObj.CloudAPIURL = lc.backendAPI.GetCloudAPIURL()
}
if lc.configObj.CloudAuthURL != "" {
lc.backendAPI.SetCloudAuthURL(lc.configObj.CloudAuthURL)
} else {
lc.configObj.CloudAuthURL = lc.backendAPI.GetCloudAuthURL()
}
if lc.configObj.CloudReportURL != "" {
lc.backendAPI.SetCloudReportURL(lc.configObj.CloudReportURL)
} else {
lc.configObj.CloudReportURL = lc.backendAPI.GetCloudReportURL()
}
if lc.configObj.CloudUIURL != "" {
lc.backendAPI.SetCloudUIURL(lc.configObj.CloudUIURL)
} else {
lc.configObj.CloudUIURL = lc.backendAPI.GetCloudUIURL()
}
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", lc.backendAPI.GetCloudAPIURL()), helpers.String("auth", lc.backendAPI.GetCloudAuthURL()), helpers.String("report", lc.backendAPI.GetCloudReportURL()), helpers.String("UI", lc.backendAPI.GetCloudUIURL()))
initializeCloudAPI(lc)
return lc
}
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
func (lc *LocalConfig) GetTenantEmail() string { return lc.configObj.CustomerAdminEMail }
func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountID }
func (lc *LocalConfig) GetClientID() string { return lc.configObj.ClientID }
func (lc *LocalConfig) GetSecretKey() string { return lc.configObj.SecretKey }
func (lc *LocalConfig) GetContextName() string { return lc.configObj.ClusterName }
func (lc *LocalConfig) GetToken() string { return lc.configObj.Token }
func (lc *LocalConfig) GetCloudReportURL() string { return lc.configObj.CloudReportURL }
func (lc *LocalConfig) GetCloudAPIURL() string { return lc.configObj.CloudAPIURL }
func (lc *LocalConfig) GetCloudUIURL() string { return lc.configObj.CloudUIURL }
func (lc *LocalConfig) GetCloudAuthURL() string { return lc.configObj.CloudAuthURL }
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
func (lc *LocalConfig) SetTenant() error {
// Kubescape Cloud tenant GUID
if err := getTenantConfigFromBE(lc.backendAPI, lc.configObj); err != nil {
return err
}
lc.UpdateCachedConfig()
return nil
func (lc *LocalConfig) GetAccessKey() string { return lc.configObj.AccessKey }
func (lc *LocalConfig) GenerateAccountID() (string, error) {
lc.configObj.AccountID = uuid.NewString()
err := lc.UpdateCachedConfig()
return lc.configObj.AccountID, err
}
func (lc *LocalConfig) DeleteCredentials() error {
lc.configObj.AccessKey = ""
lc.configObj.AccountID = ""
return lc.UpdateCachedConfig()
}
func (lc *LocalConfig) UpdateCachedConfig() error {
logger.L().Debug("updating cached config", helpers.Interface("configObj", lc.configObj))
return updateConfigFile(lc.configObj)
}
@@ -219,26 +171,6 @@ func (lc *LocalConfig) DeleteCachedConfig(ctx context.Context) error {
return nil
}
func getTenantConfigFromBE(backendAPI getter.IBackend, configObj *ConfigObj) error {
// get from Kubescape Cloud API
tenantResponse, err := backendAPI.GetTenant()
if err == nil && tenantResponse != nil {
if tenantResponse.AdminMail != "" { // registered tenant
configObj.CustomerAdminEMail = tenantResponse.AdminMail
} else { // new tenant
configObj.Token = tenantResponse.Token
configObj.AccountID = tenantResponse.TenantID
}
} else {
if err != nil && !strings.Contains(err.Error(), "already exists") {
return err
}
}
return nil
}
// ======================================================================================
// ========================== Cluster Config ============================================
// ======================================================================================
@@ -251,8 +183,6 @@ KS_DEFAULT_CONFIGMAP_NAME // name of configmap, if not set default is 'kubescap
KS_DEFAULT_CONFIGMAP_NAMESPACE // configmap namespace, if not set default is 'default'
KS_ACCOUNT_ID
KS_CLIENT_ID
KS_SECRET_KEY
TODO - support:
KS_CACHE // path to cached files
@@ -260,20 +190,15 @@ KS_CACHE // path to cached files
var _ ITenantConfig = &ClusterConfig{}
type ClusterConfig struct {
backendAPI getter.IBackend
k8s *k8sinterface.KubernetesApi
configObj *ConfigObj
configMapName string
configMapNamespace string
}
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *ClusterConfig {
// var configObj *ConfigObj
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, accessKey, clusterName, customClusterName string) *ClusterConfig {
c := &ClusterConfig{
k8s: k8s,
backendAPI: backendAPI,
configObj: &ConfigObj{},
configMapName: getConfigMapName(),
configMapNamespace: GetConfigMapNamespace(),
}
@@ -282,12 +207,13 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
loadConfigFromFile(c.configObj)
}
// second, load from configMap
if c.existsConfigMap() {
c.updateConfigEmptyFieldsFromConfigMap()
}
// second, load urls from config map
c.updateConfigEmptyFieldsFromKubescapeConfigMap()
updateCredentials(c.configObj, credentials)
// third, credentials from secret
c.updateConfigEmptyFieldsFromCredentialsSecret()
updateCredentials(c.configObj, accountID, accessKey)
updateCloudURLs(c.configObj)
// If a custom cluster name is provided then set that name, else use the cluster's original name
@@ -302,80 +228,23 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
} else { // override the cluster name if it has unwanted characters
c.configObj.ClusterName = AdoptClusterName(c.configObj.ClusterName)
}
c.backendAPI.SetAccountID(c.configObj.AccountID)
c.backendAPI.SetClientID(c.configObj.ClientID)
c.backendAPI.SetSecretKey(c.configObj.SecretKey)
if c.configObj.CloudAPIURL != "" {
c.backendAPI.SetCloudAPIURL(c.configObj.CloudAPIURL)
} else {
c.configObj.CloudAPIURL = c.backendAPI.GetCloudAPIURL()
}
if c.configObj.CloudAuthURL != "" {
c.backendAPI.SetCloudAuthURL(c.configObj.CloudAuthURL)
} else {
c.configObj.CloudAuthURL = c.backendAPI.GetCloudAuthURL()
}
if c.configObj.CloudReportURL != "" {
c.backendAPI.SetCloudReportURL(c.configObj.CloudReportURL)
} else {
c.configObj.CloudReportURL = c.backendAPI.GetCloudReportURL()
}
if c.configObj.CloudUIURL != "" {
c.backendAPI.SetCloudUIURL(c.configObj.CloudUIURL)
} else {
c.configObj.CloudUIURL = c.backendAPI.GetCloudUIURL()
}
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", c.backendAPI.GetCloudAPIURL()), helpers.String("auth", c.backendAPI.GetCloudAuthURL()), helpers.String("report", c.backendAPI.GetCloudReportURL()), helpers.String("UI", c.backendAPI.GetCloudUIURL()))
initializeCloudAPI(c)
return c
}
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
func (c *ClusterConfig) GetClientID() string { return c.configObj.ClientID }
func (c *ClusterConfig) GetSecretKey() string { return c.configObj.SecretKey }
func (c *ClusterConfig) GetTenantEmail() string { return c.configObj.CustomerAdminEMail }
func (c *ClusterConfig) GetToken() string { return c.configObj.Token }
func (c *ClusterConfig) GetCloudReportURL() string { return c.configObj.CloudReportURL }
func (c *ClusterConfig) GetCloudAPIURL() string { return c.configObj.CloudAPIURL }
func (c *ClusterConfig) GetCloudUIURL() string { return c.configObj.CloudUIURL }
func (c *ClusterConfig) GetCloudAuthURL() string { return c.configObj.CloudAuthURL }
func (c *ClusterConfig) IsConfigFound() bool { return existsConfigFile() || c.existsConfigMap() }
func (c *ClusterConfig) SetTenant() error {
// ARMO tenant GUID
if err := getTenantConfigFromBE(c.backendAPI, c.configObj); err != nil {
return err
}
c.UpdateCachedConfig()
return nil
}
func (c *ClusterConfig) GetAccessKey() string { return c.configObj.AccessKey }
func (c *ClusterConfig) UpdateCachedConfig() error {
// update/create config
if c.existsConfigMap() {
if err := c.updateConfigMap(); err != nil {
return err
}
} else {
if err := c.createConfigMap(); err != nil {
return err
}
}
logger.L().Debug("updating cached config", helpers.Interface("configObj", c.configObj))
return updateConfigFile(c.configObj)
}
func (c *ClusterConfig) DeleteCachedConfig(ctx context.Context) error {
if err := c.deleteConfigMap(); err != nil {
logger.L().Ctx(ctx).Warning(err.Error())
}
if err := DeleteConfigFile(); err != nil {
logger.L().Ctx(ctx).Warning(err.Error())
}
@@ -393,28 +262,80 @@ func (c *ClusterConfig) ToMapString() map[string]interface{} {
return m
}
func (c *ClusterConfig) updateConfigEmptyFieldsFromConfigMap() error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
func (c *ClusterConfig) updateConfigEmptyFieldsFromKubescapeConfigMap() error {
configMaps, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).List(context.Background(), metav1.ListOptions{
LabelSelector: cloudConfigMapLabelSelector,
})
if err != nil {
return err
}
tempCO := ConfigObj{}
if jsonConf, ok := configMap.Data["config.json"]; ok {
json.Unmarshal([]byte(jsonConf), &tempCO)
return c.configObj.updateEmptyFields(&tempCO)
var ksConfigMap *corev1.ConfigMap
var urlsConfigMap *corev1.ConfigMap
if len(configMaps.Items) == 0 {
// try to find configmaps by name (for backward compatibility)
ksConfigMap, _ = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), kubescapeConfigMapName, metav1.GetOptions{})
urlsConfigMap, _ = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), kubescapeCloudConfigMapName, metav1.GetOptions{})
} else {
// use the first configmap with the label
ksConfigMap = &configMaps.Items[0]
urlsConfigMap = &configMaps.Items[0]
}
return err
if ksConfigMap != nil {
if jsonConf, ok := ksConfigMap.Data["clusterData"]; ok {
tempCO := ConfigObj{}
if err = json.Unmarshal([]byte(jsonConf), &tempCO); err != nil {
return err
}
c.configObj.updateEmptyFields(&tempCO)
}
}
if urlsConfigMap != nil {
if jsonConf, ok := urlsConfigMap.Data["services"]; ok {
services, err := servicediscovery.GetServices(
servicediscoveryv1.NewServiceDiscoveryStreamV1([]byte(jsonConf)),
)
if err != nil {
return err
}
if services.GetApiServerUrl() != "" {
c.configObj.CloudAPIURL = services.GetApiServerUrl()
}
if services.GetReportReceiverHttpUrl() != "" {
c.configObj.CloudReportURL = services.GetReportReceiverHttpUrl()
}
}
}
return err
}
func (c *ClusterConfig) loadConfigFromConfigMap() error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
func (c *ClusterConfig) updateConfigEmptyFieldsFromCredentialsSecret() error {
secrets, err := c.k8s.KubernetesClient.CoreV1().Secrets(c.configMapNamespace).List(context.Background(),
metav1.ListOptions{LabelSelector: credsLabelSelectors})
if err != nil {
return err
}
return loadConfigFromData(c.configObj, configMap.Data)
if len(secrets.Items) == 0 {
return nil
}
if jsonConf, ok := secrets.Items[0].Data["account"]; ok {
if account := string(jsonConf); account != "" {
c.configObj.AccountID = account
}
}
if jsonConf, ok := secrets.Items[0].Data["accessKey"]; ok {
if accessKey := string(jsonConf); accessKey != "" {
c.configObj.AccessKey = accessKey
}
}
return nil
}
func loadConfigFromData(co *ConfigObj, data map[string]string) error {
@@ -428,107 +349,32 @@ func loadConfigFromData(co *ConfigObj, data map[string]string) error {
return e
}
func (c *ClusterConfig) existsConfigMap() bool {
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
// TODO - check if has customerGUID
return err == nil
}
func (c *ClusterConfig) GetValueByKeyFromConfigMap(key string) (string, error) {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
return "", err
}
if val, ok := configMap.Data[key]; ok {
return val, nil
} else {
return "", fmt.Errorf("value does not exist")
}
}
func GetValueFromConfigJson(key string) (string, error) {
data, err := os.ReadFile(ConfigFileFullPath())
if err != nil {
return "", err
}
var obj map[string]interface{}
if err := json.Unmarshal(data, &obj); err != nil {
return "", err
}
if val, ok := obj[key]; ok {
return fmt.Sprint(val), nil
} else {
return "", fmt.Errorf("value does not exist")
}
}
func (c *ClusterConfig) SetKeyValueInConfigmap(key string, value string) error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
configMap = &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: c.configMapName,
},
}
}
if len(configMap.Data) == 0 {
configMap.Data = make(map[string]string)
}
configMap.Data[key] = value
if err != nil {
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
} else {
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
}
return err
}
func existsConfigFile() bool {
_, err := os.ReadFile(ConfigFileFullPath())
return err == nil
}
func (c *ClusterConfig) createConfigMap() error {
if c.k8s == nil {
return nil
}
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: c.configMapName,
},
}
c.updateConfigData(configMap)
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
return err
}
func (c *ClusterConfig) updateConfigMap() error {
if c.k8s == nil {
return nil
}
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
func updateConfigFile(configObj *ConfigObj) error {
fullPath := ConfigFileFullPath()
dir := filepath.Dir(fullPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
c.updateConfigData(configMap)
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
return err
return os.WriteFile(fullPath, configObj.Config(), 0664) //nolint:gosec
}
func updateConfigFile(configObj *ConfigObj) error {
return os.WriteFile(ConfigFileFullPath(), configObj.Config(), 0664) //nolint:gosec
func (c *ClusterConfig) GenerateAccountID() (string, error) {
c.configObj.AccountID = uuid.NewString()
err := c.UpdateCachedConfig()
return c.configObj.AccountID, err
}
func (c *ClusterConfig) DeleteCredentials() error {
c.configObj.AccountID = ""
c.configObj.AccessKey = ""
return c.UpdateCachedConfig()
}
func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {
@@ -561,28 +407,6 @@ func readConfig(dat []byte, configObj *ConfigObj) error {
return nil
}
// Check if the customer is submitted
func (clusterConfig *ClusterConfig) IsSubmitted() bool {
return clusterConfig.existsConfigMap() || existsConfigFile()
}
// Check if the customer is registered
func (clusterConfig *ClusterConfig) IsRegistered() bool {
// get from armoBE
tenantResponse, err := clusterConfig.backendAPI.GetTenant()
if err == nil && tenantResponse != nil {
if tenantResponse.AdminMail != "" { // this customer already belongs to some user
return true
}
}
return false
}
func (clusterConfig *ClusterConfig) deleteConfigMap() error {
return clusterConfig.k8s.KubernetesClient.CoreV1().ConfigMaps(clusterConfig.configMapNamespace).Delete(context.Background(), clusterConfig.configMapName, metav1.DeleteOptions{})
}
func DeleteConfigFile() error {
return os.Remove(ConfigFileFullPath())
}
@@ -595,67 +419,42 @@ func AdoptClusterName(clusterName string) string {
return re.ReplaceAllString(clusterName, "-")
}
func getConfigMapName() string {
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAME"); n != "" {
return n
}
return kubescapeConfigMapName
}
// GetConfigMapNamespace returns the namespace of the cluster config, which is the same for all in-cluster components
func GetConfigMapNamespace() string {
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAMESPACE"); n != "" {
if n := os.Getenv(defaultConfigMapNamespaceEnvVar); n != "" {
return n
}
return kubescapeNamespace
}
func getAccountFromEnv(credentials *Credentials) {
// load from env
if accountID := os.Getenv("KS_ACCOUNT_ID"); credentials.Account == "" && accountID != "" {
credentials.Account = accountID
}
if clientID := os.Getenv("KS_CLIENT_ID"); credentials.ClientID == "" && clientID != "" {
credentials.ClientID = clientID
}
if secretKey := os.Getenv("KS_SECRET_KEY"); credentials.SecretKey == "" && secretKey != "" {
credentials.SecretKey = secretKey
}
}
func updateCredentials(configObj *ConfigObj, credentials *Credentials) {
if credentials == nil {
credentials = &Credentials{}
}
getAccountFromEnv(credentials)
if credentials.Account != "" {
configObj.AccountID = credentials.Account // override config Account
}
if credentials.ClientID != "" {
configObj.ClientID = credentials.ClientID // override config ClientID
}
if credentials.SecretKey != "" {
configObj.SecretKey = credentials.SecretKey // override config SecretKey
func updateCredentials(configObj *ConfigObj, accountID, accessKey string) {
if accessKey != "" {
configObj.AccessKey = accessKey
}
if envAccessKey := os.Getenv(accessKeyEnvVar); envAccessKey != "" {
configObj.AccessKey = envAccessKey
}
if accountID != "" {
configObj.AccountID = accountID
}
if envAccountID := os.Getenv(accountIdEnvVar); envAccountID != "" {
configObj.AccountID = envAccountID
}
}
func getCloudURLsFromEnv(cloudURLs *CloudURLs) {
// load from env
if cloudAPIURL := os.Getenv("KS_CLOUD_API_URL"); cloudAPIURL != "" {
if cloudAPIURL := os.Getenv(cloudApiUrlEnvVar); cloudAPIURL != "" {
logger.L().Debug("cloud API URL updated from env var", helpers.Interface(cloudApiUrlEnvVar, cloudAPIURL))
cloudURLs.CloudAPIURL = cloudAPIURL
}
if cloudAuthURL := os.Getenv("KS_CLOUD_AUTH_URL"); cloudAuthURL != "" {
cloudURLs.CloudAuthURL = cloudAuthURL
}
if cloudReportURL := os.Getenv("KS_CLOUD_REPORT_URL"); cloudReportURL != "" {
if cloudReportURL := os.Getenv(cloudReportUrlEnvVar); cloudReportURL != "" {
logger.L().Debug("cloud Report URL updated from env var", helpers.Interface(cloudReportUrlEnvVar, cloudReportURL))
cloudURLs.CloudReportURL = cloudReportURL
}
if cloudUIURL := os.Getenv("KS_CLOUD_UI_URL"); cloudUIURL != "" {
cloudURLs.CloudUIURL = cloudUIURL
}
}
func updateCloudURLs(configObj *ConfigObj) {
@@ -666,26 +465,60 @@ func updateCloudURLs(configObj *ConfigObj) {
if cloudURLs.CloudAPIURL != "" {
configObj.CloudAPIURL = cloudURLs.CloudAPIURL // override config CloudAPIURL
}
if cloudURLs.CloudAuthURL != "" {
configObj.CloudAuthURL = cloudURLs.CloudAuthURL // override config CloudAuthURL
}
if cloudURLs.CloudReportURL != "" {
configObj.CloudReportURL = cloudURLs.CloudReportURL // override config CloudReportURL
}
if cloudURLs.CloudUIURL != "" {
configObj.CloudUIURL = cloudURLs.CloudUIURL // override config CloudUIURL
}
func initializeCloudAPI(c ITenantConfig) *v1.KSCloudAPI {
if ksCloud := getter.GetKSCloudAPIConnector(); ksCloud != nil {
logger.L().Debug("KS Cloud API already initialized")
if val := c.GetCloudAPIURL(); val != "" && val != ksCloud.GetCloudAPIURL() {
logger.L().Debug("updating KS Cloud API from config", helpers.String("old", ksCloud.GetCloudAPIURL()), helpers.String("new", val))
ksCloud.SetCloudAPIURL(val)
}
if val := c.GetCloudReportURL(); val != "" && val != ksCloud.GetCloudReportURL() {
logger.L().Debug("updating KS Cloud Report from config", helpers.String("old", ksCloud.GetCloudReportURL()), helpers.String("new", val))
ksCloud.SetCloudReportURL(val)
}
if val := c.GetAccountID(); val != "" && val != ksCloud.GetAccountID() {
logger.L().Debug("updating Account ID from config", helpers.String("old", ksCloud.GetAccountID()), helpers.String("new", val))
ksCloud.SetAccountID(val)
}
if val := c.GetAccessKey(); val != "" && val != ksCloud.GetAccessKey() {
logger.L().Debug("updating Access Key from config", helpers.Int("old (len)", len(ksCloud.GetAccessKey())), helpers.Int("new (len)", len(val)))
ksCloud.SetAccessKey(val)
}
getter.SetKSCloudAPIConnector(ksCloud)
} else {
logger.L().Debug("initializing KS Cloud API from config", helpers.String("accountID", c.GetAccountID()), helpers.String("cloudAPIURL", c.GetCloudAPIURL()), helpers.String("cloudReportURL", c.GetCloudReportURL()))
cloud, err := v1.NewKSCloudAPI(
c.GetCloudAPIURL(),
c.GetCloudReportURL(),
c.GetAccountID(),
c.GetAccessKey())
if err != nil {
logger.L().Fatal("failed to create KS Cloud client", helpers.Error(err))
}
getter.SetKSCloudAPIConnector(cloud)
}
return getter.GetKSCloudAPIConnector()
}
func initializeCloudAPI(c ITenantConfig) {
cloud := getter.GetKSCloudAPIConnector()
cloud.SetAccountID(c.GetAccountID())
cloud.SetClientID(c.GetClientID())
cloud.SetSecretKey(c.GetSecretKey())
cloud.SetCloudAuthURL(c.GetCloudAuthURL())
cloud.SetCloudReportURL(c.GetCloudReportURL())
cloud.SetCloudUIURL(c.GetCloudUIURL())
cloud.SetCloudAPIURL(c.GetCloudAPIURL())
getter.SetKSCloudAPIConnector(cloud)
func GetTenantConfig(accountID, accessKey, clusterName, customClusterName string, k8s *k8sinterface.KubernetesApi) ITenantConfig {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return NewLocalConfig(accountID, accessKey, clusterName, customClusterName)
}
return NewClusterConfig(k8s, accountID, accessKey, clusterName, customClusterName)
}
// firstNonEmpty returns the first non-empty string
func firstNonEmpty(s1, s2 string) string {
if s1 != "" {
return s1
}
return s2
}

View File

@@ -5,36 +5,28 @@ import (
"os"
"testing"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
)
func mockConfigObj() *ConfigObj {
return &ConfigObj{
AccountID: "aaa",
ClientID: "bbb",
SecretKey: "ccc",
ClusterName: "ddd",
CustomerAdminEMail: "ab@cd",
Token: "eee",
CloudReportURL: "report.armo.cloud",
CloudAPIURL: "api.armosec.io",
CloudUIURL: "cloud.armosec.io",
CloudAuthURL: "auth.armosec.io",
AccountID: "aaa",
ClusterName: "ddd",
CloudReportURL: "report.domain.com",
CloudAPIURL: "api.domain.com",
}
}
func mockLocalConfig() *LocalConfig {
return &LocalConfig{
backendAPI: nil,
configObj: mockConfigObj(),
configObj: mockConfigObj(),
}
}
func mockClusterConfig() *ClusterConfig {
return &ClusterConfig{
backendAPI: nil,
configObj: mockConfigObj(),
configObj: mockConfigObj(),
}
}
func TestConfig(t *testing.T) {
@@ -43,15 +35,9 @@ func TestConfig(t *testing.T) {
assert.NoError(t, json.Unmarshal(co.Config(), &cop))
assert.Equal(t, co.AccountID, cop.AccountID)
assert.Equal(t, co.ClientID, cop.ClientID)
assert.Equal(t, co.SecretKey, cop.SecretKey)
assert.Equal(t, co.CloudReportURL, cop.CloudReportURL)
assert.Equal(t, co.CloudAPIURL, cop.CloudAPIURL)
assert.Equal(t, co.CloudUIURL, cop.CloudUIURL)
assert.Equal(t, co.CloudAuthURL, cop.CloudAuthURL)
assert.Equal(t, "", cop.ClusterName) // Not copied to bytes
assert.Equal(t, "", cop.CustomerAdminEMail) // Not copied to bytes
assert.Equal(t, "", cop.Token) // Not copied to bytes
assert.Equal(t, "", cop.ClusterName) // Not copied to bytes
}
@@ -65,27 +51,15 @@ func TestITenantConfig(t *testing.T) {
// test LocalConfig methods
assert.Equal(t, co.AccountID, lc.GetAccountID())
assert.Equal(t, co.ClientID, lc.GetClientID())
assert.Equal(t, co.SecretKey, lc.GetSecretKey())
assert.Equal(t, co.ClusterName, lc.GetContextName())
assert.Equal(t, co.CustomerAdminEMail, lc.GetTenantEmail())
assert.Equal(t, co.Token, lc.GetToken())
assert.Equal(t, co.CloudReportURL, lc.GetCloudReportURL())
assert.Equal(t, co.CloudAPIURL, lc.GetCloudAPIURL())
assert.Equal(t, co.CloudUIURL, lc.GetCloudUIURL())
assert.Equal(t, co.CloudAuthURL, lc.GetCloudAuthURL())
// test ClusterConfig methods
assert.Equal(t, co.AccountID, c.GetAccountID())
assert.Equal(t, co.ClientID, c.GetClientID())
assert.Equal(t, co.SecretKey, c.GetSecretKey())
assert.Equal(t, co.ClusterName, c.GetContextName())
assert.Equal(t, co.CustomerAdminEMail, c.GetTenantEmail())
assert.Equal(t, co.Token, c.GetToken())
assert.Equal(t, co.CloudReportURL, c.GetCloudReportURL())
assert.Equal(t, co.CloudAPIURL, c.GetCloudAPIURL())
assert.Equal(t, co.CloudUIURL, c.GetCloudUIURL())
assert.Equal(t, co.CloudAuthURL, c.GetCloudAuthURL())
}
func TestUpdateConfigData(t *testing.T) {
@@ -96,12 +70,8 @@ func TestUpdateConfigData(t *testing.T) {
c.updateConfigData(configMap)
assert.Equal(t, c.GetAccountID(), configMap.Data["accountID"])
assert.Equal(t, c.GetClientID(), configMap.Data["clientID"])
assert.Equal(t, c.GetSecretKey(), configMap.Data["secretKey"])
assert.Equal(t, c.GetCloudReportURL(), configMap.Data["cloudReportURL"])
assert.Equal(t, c.GetCloudAPIURL(), configMap.Data["cloudAPIURL"])
assert.Equal(t, c.GetCloudUIURL(), configMap.Data["cloudUIURL"])
assert.Equal(t, c.GetCloudAuthURL(), configMap.Data["cloudAuthURL"])
}
func TestReadConfig(t *testing.T) {
@@ -114,15 +84,9 @@ func TestReadConfig(t *testing.T) {
readConfig(b, co)
assert.Equal(t, com.AccountID, co.AccountID)
assert.Equal(t, com.ClientID, co.ClientID)
assert.Equal(t, com.SecretKey, co.SecretKey)
assert.Equal(t, com.ClusterName, co.ClusterName)
assert.Equal(t, com.CustomerAdminEMail, co.CustomerAdminEMail)
assert.Equal(t, com.Token, co.Token)
assert.Equal(t, com.CloudReportURL, co.CloudReportURL)
assert.Equal(t, com.CloudAPIURL, co.CloudAPIURL)
assert.Equal(t, com.CloudUIURL, co.CloudUIURL)
assert.Equal(t, com.CloudAuthURL, co.CloudAuthURL)
}
func TestLoadConfigFromData(t *testing.T) {
@@ -141,15 +105,9 @@ func TestLoadConfigFromData(t *testing.T) {
loadConfigFromData(c.configObj, configMap.Data)
assert.Equal(t, c.GetAccountID(), co.AccountID)
assert.Equal(t, c.GetClientID(), co.ClientID)
assert.Equal(t, c.GetSecretKey(), co.SecretKey)
assert.Equal(t, c.GetContextName(), co.ClusterName)
assert.Equal(t, c.GetTenantEmail(), co.CustomerAdminEMail)
assert.Equal(t, c.GetToken(), co.Token)
assert.Equal(t, c.GetCloudReportURL(), co.CloudReportURL)
assert.Equal(t, c.GetCloudAPIURL(), co.CloudAPIURL)
assert.Equal(t, c.GetCloudUIURL(), co.CloudUIURL)
assert.Equal(t, c.GetCloudAuthURL(), co.CloudAuthURL)
}
// use case: all data is in config.json
@@ -167,12 +125,8 @@ func TestLoadConfigFromData(t *testing.T) {
loadConfigFromData(c.configObj, configMap.Data)
assert.Equal(t, c.GetAccountID(), co.AccountID)
assert.Equal(t, c.GetClientID(), co.ClientID)
assert.Equal(t, c.GetSecretKey(), co.SecretKey)
assert.Equal(t, c.GetCloudReportURL(), co.CloudReportURL)
assert.Equal(t, c.GetCloudAPIURL(), co.CloudAPIURL)
assert.Equal(t, c.GetCloudUIURL(), co.CloudUIURL)
assert.Equal(t, c.GetCloudAuthURL(), co.CloudAuthURL)
}
// use case: some data is in config.json
@@ -183,21 +137,15 @@ func TestLoadConfigFromData(t *testing.T) {
}
// add to map
configMap.Data["clientID"] = c.configObj.ClientID
configMap.Data["secretKey"] = c.configObj.SecretKey
configMap.Data["cloudReportURL"] = c.configObj.CloudReportURL
// delete the content
c.configObj.ClientID = ""
c.configObj.SecretKey = ""
c.configObj.CloudReportURL = ""
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
loadConfigFromData(c.configObj, configMap.Data)
assert.NotEmpty(t, c.GetAccountID())
assert.NotEmpty(t, c.GetClientID())
assert.NotEmpty(t, c.GetSecretKey())
assert.NotEmpty(t, c.GetCloudReportURL())
}
@@ -212,19 +160,11 @@ func TestLoadConfigFromData(t *testing.T) {
// add to map
configMap.Data["accountID"] = mockConfigObj().AccountID
configMap.Data["clientID"] = c.configObj.ClientID
configMap.Data["secretKey"] = c.configObj.SecretKey
// delete the content
c.configObj.ClientID = ""
c.configObj.SecretKey = ""
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
loadConfigFromData(c.configObj, configMap.Data)
assert.Equal(t, mockConfigObj().AccountID, c.GetAccountID())
assert.NotEmpty(t, c.GetClientID())
assert.NotEmpty(t, c.GetSecretKey())
}
}
@@ -289,13 +229,9 @@ func Test_initializeCloudAPI(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
initializeCloudAPI(tt.args.c)
cloud := getter.GetKSCloudAPIConnector()
assert.Equal(t, tt.args.c.GetCloudAPIURL(), cloud.GetCloudAPIURL())
assert.Equal(t, tt.args.c.GetCloudAuthURL(), cloud.GetCloudAuthURL())
assert.Equal(t, tt.args.c.GetCloudUIURL(), cloud.GetCloudUIURL())
assert.Equal(t, tt.args.c.GetCloudReportURL(), cloud.GetCloudReportURL())
assert.Equal(t, "https://api.domain.com", cloud.GetCloudAPIURL())
assert.Equal(t, "https://report.domain.com", cloud.GetCloudReportURL())
assert.Equal(t, tt.args.c.GetAccountID(), cloud.GetAccountID())
assert.Equal(t, tt.args.c.GetClientID(), cloud.GetClientID())
assert.Equal(t, tt.args.c.GetSecretKey(), cloud.GetSecretKey())
})
}
}
@@ -354,90 +290,58 @@ func TestUpdateEmptyFields(t *testing.T) {
}{
{
outCo: &ConfigObj{
AccountID: "",
Token: "",
CustomerAdminEMail: "",
ClusterName: "",
CloudReportURL: "",
CloudAPIURL: "",
CloudUIURL: "",
CloudAuthURL: "",
AccountID: "",
ClusterName: "",
CloudReportURL: "",
CloudAPIURL: "",
},
inCo: &ConfigObj{
AccountID: shouldUpdate,
Token: shouldUpdate,
CustomerAdminEMail: shouldUpdate,
ClusterName: shouldUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldUpdate,
CloudUIURL: shouldUpdate,
CloudAuthURL: shouldUpdate,
AccountID: shouldUpdate,
ClusterName: shouldUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldUpdate,
},
},
{
outCo: &ConfigObj{
AccountID: anyString,
Token: anyString,
CustomerAdminEMail: "",
ClusterName: "",
CloudReportURL: "",
CloudAPIURL: "",
CloudUIURL: "",
CloudAuthURL: "",
AccountID: anyString,
ClusterName: "",
CloudReportURL: "",
CloudAPIURL: "",
},
inCo: &ConfigObj{
AccountID: shouldNotUpdate,
Token: shouldNotUpdate,
CustomerAdminEMail: shouldUpdate,
ClusterName: shouldUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldUpdate,
CloudUIURL: shouldUpdate,
CloudAuthURL: shouldUpdate,
AccountID: shouldNotUpdate,
ClusterName: shouldUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldUpdate,
},
},
{
outCo: &ConfigObj{
AccountID: "",
Token: "",
CustomerAdminEMail: anyString,
ClusterName: anyString,
CloudReportURL: anyString,
CloudAPIURL: anyString,
CloudUIURL: anyString,
CloudAuthURL: anyString,
AccountID: "",
ClusterName: anyString,
CloudReportURL: anyString,
CloudAPIURL: anyString,
},
inCo: &ConfigObj{
AccountID: shouldUpdate,
Token: shouldUpdate,
CustomerAdminEMail: shouldNotUpdate,
ClusterName: shouldNotUpdate,
CloudReportURL: shouldNotUpdate,
CloudAPIURL: shouldNotUpdate,
CloudUIURL: shouldNotUpdate,
CloudAuthURL: shouldNotUpdate,
AccountID: shouldUpdate,
ClusterName: shouldNotUpdate,
CloudReportURL: shouldNotUpdate,
CloudAPIURL: shouldNotUpdate,
},
},
{
outCo: &ConfigObj{
AccountID: anyString,
Token: anyString,
CustomerAdminEMail: "",
ClusterName: anyString,
CloudReportURL: "",
CloudAPIURL: anyString,
CloudUIURL: "",
CloudAuthURL: anyString,
AccountID: anyString,
ClusterName: anyString,
CloudReportURL: "",
CloudAPIURL: anyString,
},
inCo: &ConfigObj{
AccountID: shouldNotUpdate,
Token: shouldNotUpdate,
CustomerAdminEMail: shouldUpdate,
ClusterName: shouldNotUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldNotUpdate,
CloudUIURL: shouldUpdate,
CloudAuthURL: shouldNotUpdate,
AccountID: shouldNotUpdate,
ClusterName: shouldNotUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldNotUpdate,
},
},
}
@@ -447,11 +351,7 @@ func TestUpdateEmptyFields(t *testing.T) {
tests[i].outCo.updateEmptyFields(tests[i].inCo)
checkIsUpdateCorrectly(t, beforeChangesOutCO.AccountID, tests[i].outCo.AccountID)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudAPIURL, tests[i].outCo.CloudAPIURL)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudAuthURL, tests[i].outCo.CloudAuthURL)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudReportURL, tests[i].outCo.CloudReportURL)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudUIURL, tests[i].outCo.CloudUIURL)
checkIsUpdateCorrectly(t, beforeChangesOutCO.ClusterName, tests[i].outCo.ClusterName)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CustomerAdminEMail, tests[i].outCo.CustomerAdminEMail)
checkIsUpdateCorrectly(t, beforeChangesOutCO.Token, tests[i].outCo.Token)
}
}

View File

@@ -27,12 +27,13 @@ type ImageScanData struct {
type ScanTypes string
const (
TopWorkloadsNumber = 5
TopWorkloadsNumber = 3
ScanTypeCluster ScanTypes = "cluster"
ScanTypeRepo ScanTypes = "repo"
ScanTypeImage ScanTypes = "image"
ScanTypeWorkload ScanTypes = "workload"
ScanTypeFramework ScanTypes = "framework"
ScanTypeControl ScanTypes = "control"
)
type OPASessionObj struct {
@@ -56,6 +57,7 @@ type OPASessionObj struct {
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
OmitRawResources bool // omit raw resources from output
SingleResourceScan workloadinterface.IWorkload // single resource scan
TopWorkloadsByScore []reporthandling.IResource
}
func NewOPASessionObj(ctx context.Context, frameworks []reporthandling.Framework, k8sResources K8SResources, scanInfo *ScanInfo) *OPASessionObj {
@@ -109,7 +111,7 @@ func (sessionObj *OPASessionObj) SetTopWorkloads() {
Source: &source,
}
sessionObj.Report.SummaryDetails.TopWorkloadsByScore = append(sessionObj.Report.SummaryDetails.TopWorkloadsByScore, wlObj)
sessionObj.TopWorkloadsByScore = append(sessionObj.TopWorkloadsByScore, wlObj)
count++
}
}

View File

@@ -4,8 +4,6 @@ import (
"golang.org/x/mod/semver"
"github.com/armosec/utils-go/boolutils"
cloudsupport "github.com/kubescape/k8s-interface/cloudsupport/v1"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/apis"
)
@@ -89,58 +87,24 @@ func isRuleKubescapeVersionCompatible(attributes map[string]interface{}, version
return true
}
func getCloudType(scanInfo *ScanInfo) (bool, reporthandling.ScanningScopeType) {
if cloudsupport.IsAKS() {
return true, reporthandling.ScopeCloudAKS
}
if cloudsupport.IsEKS(k8sinterface.GetConfig()) {
return true, reporthandling.ScopeCloudEKS
}
if cloudsupport.IsGKE(k8sinterface.GetConfig()) {
return true, reporthandling.ScopeCloudGKE
}
return false, ""
}
func GetScanningScope(scanInfo *ScanInfo) reporthandling.ScanningScopeType {
var result reporthandling.ScanningScopeType
switch scanInfo.GetScanningContext() {
case ContextCluster:
isCloud, cloudType := getCloudType(scanInfo)
if isCloud {
result = cloudType
} else {
result = reporthandling.ScopeCluster
}
default:
result = reporthandling.ScopeFile
}
return result
}
func isScanningScopeMatchToControlScope(scanScope reporthandling.ScanningScopeType, controlScope reporthandling.ScanningScopeType) bool {
result := false
switch controlScope {
case reporthandling.ScopeFile:
result = (reporthandling.ScopeFile == scanScope)
return reporthandling.ScopeFile == scanScope
case reporthandling.ScopeCluster:
result = (reporthandling.ScopeCluster == scanScope) || (reporthandling.ScopeCloud == scanScope) || (reporthandling.ScopeCloudAKS == scanScope) || (reporthandling.ScopeCloudEKS == scanScope) || (reporthandling.ScopeCloudGKE == scanScope)
return reporthandling.ScopeCluster == scanScope || reporthandling.ScopeCloud == scanScope || reporthandling.ScopeCloudAKS == scanScope || reporthandling.ScopeCloudEKS == scanScope || reporthandling.ScopeCloudGKE == scanScope
case reporthandling.ScopeCloud:
result = (reporthandling.ScopeCloud == scanScope) || (reporthandling.ScopeCloudAKS == scanScope) || (reporthandling.ScopeCloudEKS == scanScope) || (reporthandling.ScopeCloudGKE == scanScope)
return reporthandling.ScopeCloud == scanScope || reporthandling.ScopeCloudAKS == scanScope || reporthandling.ScopeCloudEKS == scanScope || reporthandling.ScopeCloudGKE == scanScope
case reporthandling.ScopeCloudAKS:
result = (reporthandling.ScopeCloudAKS == scanScope)
return reporthandling.ScopeCloudAKS == scanScope
case reporthandling.ScopeCloudEKS:
result = (reporthandling.ScopeCloudEKS == scanScope)
return reporthandling.ScopeCloudEKS == scanScope
case reporthandling.ScopeCloudGKE:
result = (reporthandling.ScopeCloudGKE == scanScope)
return reporthandling.ScopeCloudGKE == scanScope
default:
result = true
return true
}
return result
}
func isControlFitToScanScope(control reporthandling.Control, scanScopeMatches reporthandling.ScanningScopeType) bool {

View File

@@ -4,101 +4,238 @@ import (
"fmt"
"testing"
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/stretchr/testify/assert"
)
func TestIsControlFitToScanScope(t *testing.T) {
func TestIsScanningScopeMatchToControlScope(t *testing.T) {
tests := []struct {
scanInfo *ScanInfo
Control reporthandling.Control
expected_res bool
scanScope reporthandling.ScanningScopeType
controlScope reporthandling.ScanningScopeType
expected bool
}{
{
scanInfo: &ScanInfo{
InputPatterns: []string{
"./testdata/any_file_for_test.json",
},
},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeFile,
},
},
},
expected_res: true,
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeFile,
expected: true,
},
{
scanInfo: &ScanInfo{
InputPatterns: []string{
"./testdata/any_file_for_test.json",
},
},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
scanScope: ScopeCluster,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: reporthandling.ScopeCloud,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: reporthandling.ScopeCloudAKS,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: reporthandling.ScopeCloudEKS,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: reporthandling.ScopeCloudGKE,
expected: true,
},
{
scanScope: ScopeCluster,
controlScope: reporthandling.ScopeCloud,
expected: false,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: reporthandling.ScopeCloud,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: reporthandling.ScopeCloud,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: reporthandling.ScopeCloud,
expected: true,
},
{
scanScope: ScopeCluster,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: ScopeCluster,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
{
scanScope: ScopeCluster,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: ScopeCluster,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeCloud,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
}
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCluster,
reporthandling.ScopeFile,
},
},
},
expected_res: true,
},
{
scanInfo: &ScanInfo{},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCluster,
},
},
},
expected_res: true,
},
{
scanInfo: &ScanInfo{
InputPatterns: []string{
"./testdata/any_file_for_test.json",
},
},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCloudGKE,
},
},
},
expected_res: false,
},
{
scanInfo: &ScanInfo{},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCloudEKS,
},
},
},
expected_res: false,
},
{
scanInfo: &ScanInfo{},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCloud,
},
},
},
expected_res: false,
}}
for i := range tests {
assert.Equal(t, isControlFitToScanScope(tests[i].Control, GetScanningScope(tests[i].scanInfo)), tests[i].expected_res, fmt.Sprintf("tests_true index %d", i))
for _, test := range tests {
result := isScanningScopeMatchToControlScope(test.scanScope, test.controlScope)
assert.Equal(t, test.expected, result, fmt.Sprintf("scanScope: %v, controlScope: %v", test.scanScope, test.controlScope))
}
}
func TestIsFrameworkFitToScanScope(t *testing.T) {
tests := []struct {
name string
framework reporthandling.Framework
scanScopeMatch reporthandling.ScanningScopeType
want bool
}{
{
name: "Framework with nil ScanningScope should return true",
framework: reporthandling.Framework{
PortalBase: armotypes.PortalBase{
Name: "test-framework",
},
},
scanScopeMatch: reporthandling.ScopeFile,
want: true,
},
{
name: "Framework with empty ScanningScope.Matches should return true",
framework: reporthandling.Framework{
PortalBase: armotypes.PortalBase{
Name: "test-framework",
}, ScanningScope: &reporthandling.ScanningScope{},
},
scanScopeMatch: reporthandling.ScopeFile,
want: true,
},
{
name: "Framework with matching ScanningScope.Matches should return true",
framework: reporthandling.Framework{
PortalBase: armotypes.PortalBase{
Name: "test-framework",
}, ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{reporthandling.ScopeFile},
},
},
scanScopeMatch: reporthandling.ScopeFile,
want: true,
},
{
name: "Framework with non-matching ScanningScope.Matches should return false",
framework: reporthandling.Framework{
PortalBase: armotypes.PortalBase{
Name: "test-framework",
}, ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{reporthandling.ScopeCluster},
},
},
scanScopeMatch: reporthandling.ScopeFile,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isFrameworkFitToScanScope(tt.framework, tt.scanScopeMatch); got != tt.want {
t.Errorf("isFrameworkFitToScanScope() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,7 +0,0 @@
package cautils
// Kubescape Cloud environment vars
var (
CustomerGUID = ""
ClusterName = ""
)

View File

@@ -1,61 +1,4 @@
package getter
import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
)
// NativeFrameworks identifies all pre-built, native frameworks.
var NativeFrameworks = []string{"allcontrols", "nsa", "mitre"}
type (
// TenantResponse holds the credentials for a tenant.
TenantResponse struct {
TenantID string `json:"tenantId"`
Token string `json:"token"`
Expires string `json:"expires"`
AdminMail string `json:"adminMail,omitempty"`
}
// AttackTrack is an alias to the API type definition for attack tracks.
AttackTrack = v1alpha1.AttackTrack
// Framework is an alias to the API type definition for a framework.
Framework = reporthandling.Framework
// Control is an alias to the API type definition for a control.
Control = reporthandling.Control
// PostureExceptionPolicy is an alias to the API type definition for posture exception policy.
PostureExceptionPolicy = armotypes.PostureExceptionPolicy
// CustomerConfig is an alias to the API type definition for a customer configuration.
CustomerConfig = armotypes.CustomerConfig
// PostureReport is an alias to the API type definition for a posture report.
PostureReport = reporthandlingv2.PostureReport
)
type (
// internal data descriptors
// feLoginData describes the input to a login challenge.
feLoginData struct {
Secret string `json:"secret"`
ClientId string `json:"clientId"`
}
// feLoginResponse describes the response to a login challenge.
feLoginResponse struct {
Token string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
Expires string `json:"expires"`
ExpiresIn int32 `json:"expiresIn"`
}
ksCloudSelectCustomer struct {
SelectedCustomerGuid string `json:"selectedCustomer"`
}
)

View File

@@ -5,6 +5,7 @@ import (
"strings"
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"

View File

@@ -9,11 +9,19 @@ import (
"strings"
"testing"
"github.com/kubescape/kubescape/v2/internal/testutils"
jsoniter "github.com/json-iterator/go"
"github.com/kubescape/kubescape/v3/internal/testutils"
"github.com/stretchr/testify/require"
)
func min(a, b int64) int64 {
if a < b {
return a
}
return b
}
func TestReleasedPolicy(t *testing.T) {
t.Parallel()

View File

@@ -1,42 +0,0 @@
package getter
import (
"context"
"os"
containeranalysis "cloud.google.com/go/containeranalysis/apiv1"
)
type GCPCloudAPI struct {
credentialsPath string
context context.Context
client *containeranalysis.Client
projectID string
credentialsCheck bool
}
func GetGlobalGCPCloudAPIConnector() *GCPCloudAPI {
if os.Getenv("KS_GCP_CREDENTIALS_PATH") == "" || os.Getenv("KS_GCP_PROJECT_ID") == "" {
return &GCPCloudAPI{
credentialsCheck: false,
}
} else {
return &GCPCloudAPI{
context: context.Background(),
credentialsPath: os.Getenv("KS_GCP_CREDENTIALS_PATH"),
projectID: os.Getenv("KS_GCP_PROJECT_ID"),
credentialsCheck: true,
}
}
}
func (api *GCPCloudAPI) SetClient(client *containeranalysis.Client) {
api.client = client
}
func (api *GCPCloudAPI) GetCredentialsPath() string { return api.credentialsPath }
func (api *GCPCloudAPI) GetClient() *containeranalysis.Client { return api.client }
func (api *GCPCloudAPI) GetProjectID() string { return api.projectID }
func (api *GCPCloudAPI) GetCredentialsCheck() bool { return api.credentialsCheck }
func (api *GCPCloudAPI) GetContext() context.Context { return api.context }

View File

@@ -6,6 +6,7 @@ import (
"path/filepath"
"testing"
beClient "github.com/kubescape/backend/pkg/client/v1"
"github.com/stretchr/testify/require"
)
@@ -72,7 +73,7 @@ func TestHttpMethods(t *testing.T) {
client := http.DefaultClient
hdrs := map[string]string{"key": "value"}
srv := mockAPIServer(t)
srv := beClient.MockAPIServer(t)
t.Cleanup(srv.Close)
t.Run("HttpGetter should GET", func(t *testing.T) {

View File

@@ -31,25 +31,4 @@ type (
IAttackTracksGetter interface {
GetAttackTracks() ([]v1alpha1.AttackTrack, error)
}
// IBackend knows how to configure a KS Cloud client
IBackend interface {
GetAccountID() string
GetClientID() string
GetSecretKey() string
GetCloudReportURL() string
GetCloudAPIURL() string
GetCloudUIURL() string
GetCloudAuthURL() string
SetAccountID(accountID string)
SetClientID(clientID string)
SetSecretKey(secretKey string)
SetCloudReportURL(cloudReportURL string)
SetCloudAPIURL(cloudAPIURL string)
SetCloudUIURL(cloudUIURL string)
SetCloudAuthURL(cloudAuthURL string)
GetTenant() (*TenantResponse, error)
}
)

View File

@@ -1,833 +1,49 @@
package getter
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
"strings"
)
const (
// Kubescape API endpoints
// production
ksCloudERURL = "report.armo.cloud" // API reports URL
ksCloudBEURL = "api.armosec.io" // API backend URL
ksCloudFEURL = "cloud.armosec.io" // API frontend (UI) URIL
ksCloudAUTHURL = "auth.armosec.io" // API login URL
// staging
ksCloudStageERURL = "report-ks.eustage2.cyberarmorsoft.com"
ksCloudStageBEURL = "api-stage.armosec.io"
ksCloudStageFEURL = "armoui-stage.armosec.io"
ksCloudStageAUTHURL = "eggauth-stage.armosec.io"
// dev
ksCloudDevERURL = "report.eudev3.cyberarmorsoft.com"
ksCloudDevBEURL = "api-dev.armosec.io"
ksCloudDevFEURL = "cloud-dev.armosec.io"
ksCloudDevAUTHURL = "eggauth-dev.armosec.io"
// Kubescape API routes
pathAttackTracks = "/api/v1/attackTracks"
pathFrameworks = "/api/v1/armoFrameworks"
pathExceptions = "/api/v1/armoPostureExceptions"
pathTenant = "/api/v1/tenants/createTenant"
pathExceptionPolicy = "/api/v1/postureExceptionPolicy"
pathCustomerConfig = "/api/v1/armoCustomerConfiguration"
pathLogin = "/identity/resources/auth/v1/api-token"
pathToken = "/api/v1/openid_customers" //nolint:gosec
// reports upload route
pathReport = "/k8s/v2/postureReport"
// Kubescape UI routes
pathUIScan = "/compliance/%s"
pathUIRBAC = "/rbac-visualizer"
pathUIRepository = "/repository-scanning/%s"
pathUIDashboard = "/dashboard/"
pathUISign = "/account/sign-up"
)
const (
// default dummy GUID when not defined
fallbackGUID = "11111111-1111-1111-1111-111111111111"
// URL query parameters
queryParamGUID = "customerGUID"
queryParamScope = "scope"
queryParamFrameworkName = "frameworkName"
queryParamPolicyName = "policyName"
queryParamClusterName = "clusterName"
queryParamContextName = "contextName"
queryParamUTMSource = "utm_source"
queryParamUTMMedium = "utm_medium"
// queryParamUTMCampaign = "utm_campaign"
queryParamReport = "reportGUID"
queryParamInvitationToken = "invitationToken"
authenticationCookie = "auth"
)
var (
// Errors returned by the API
ErrLoginMissingAccountID = errors.New("failed to login, missing accountID")
ErrLoginMissingClientID = errors.New("failed to login, missing clientID")
ErrLoginMissingSecretKey = errors.New("failed to login, missing secretKey")
ErrAPINotPublic = errors.New("control api is not public")
v1 "github.com/kubescape/backend/pkg/client/v1"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
)
var (
// globalKSCloudAPIConnector is a static global instance of the KS Cloud client,
// to be initialized with SetKSCloudAPIConnector.
globalKSCloudAPIConnector *KSCloudAPI
globalKSCloudAPIConnector *v1.KSCloudAPI
_ IPolicyGetter = &KSCloudAPI{}
_ IExceptionsGetter = &KSCloudAPI{}
_ IAttackTracksGetter = &KSCloudAPI{}
_ IControlsInputsGetter = &KSCloudAPI{}
_ IPolicyGetter = &v1.KSCloudAPI{}
_ IExceptionsGetter = &v1.KSCloudAPI{}
_ IAttackTracksGetter = &v1.KSCloudAPI{}
_ IControlsInputsGetter = &v1.KSCloudAPI{}
)
// KSCloudAPI allows to access the API of the Kubescape Cloud offering.
type KSCloudAPI struct {
authCookie *http.Cookie
*ksCloudOptions
authhost string
cloudAPIURL string
secretKey string
accountID string
cloudAuthURL string
invitationToken string
reporthost string
scheme string
host string
authscheme string
clientID string
uischeme string
uihost string
reportscheme string
feToken feLoginResponse
loggedIn bool
}
// SetKSCloudAPIConnector registers a global instance of the KS Cloud client.
//
// NOTE: cannot be used concurrently.
func SetKSCloudAPIConnector(ksCloudAPI *KSCloudAPI) {
func SetKSCloudAPIConnector(ksCloudAPI *v1.KSCloudAPI) {
if ksCloudAPI != nil {
logger.L().Debug("setting global KS Cloud API connector",
helpers.String("accountID", ksCloudAPI.GetAccountID()),
helpers.String("cloudAPIURL", ksCloudAPI.GetCloudAPIURL()),
helpers.String("cloudReportURL", ksCloudAPI.GetCloudReportURL()))
} else {
logger.L().Debug("setting global KS Cloud API connector (nil)")
}
globalKSCloudAPIConnector = ksCloudAPI
}
// GetKSCloudAPIConnector returns a shallow clone of the KS Cloud client registered for this package.
//
// NOTE: cannot be used concurrently with SetKSCloudAPIConnector.
func GetKSCloudAPIConnector() *KSCloudAPI {
func GetKSCloudAPIConnector() *v1.KSCloudAPI {
if globalKSCloudAPIConnector == nil {
SetKSCloudAPIConnector(NewKSCloudAPIProd())
SetKSCloudAPIConnector(v1.NewEmptyKSCloudAPI())
}
// we return a shallow clone that may be freely modified by the caller.
client := *globalKSCloudAPIConnector
options := *globalKSCloudAPIConnector.ksCloudOptions
client.ksCloudOptions = &options
options := *globalKSCloudAPIConnector.KsCloudOptions
client.KsCloudOptions = &options
return &client
}
// NewKSCloudAPIDev returns a KS Cloud client pointing to a development environment.
func NewKSCloudAPIDev(opts ...KSCloudOption) *KSCloudAPI {
devOpts := []KSCloudOption{
WithFrontendURL(ksCloudDevFEURL),
WithReportURL(ksCloudDevERURL),
}
devOpts = append(devOpts, opts...)
apiObj := newKSCloudAPI(
ksCloudDevBEURL,
ksCloudDevAUTHURL,
devOpts...,
)
return apiObj
}
// NewKSCloudAPIDProd returns a KS Cloud client pointing to a production environment.
func NewKSCloudAPIProd(opts ...KSCloudOption) *KSCloudAPI {
prodOpts := []KSCloudOption{
WithFrontendURL(ksCloudFEURL),
WithReportURL(ksCloudERURL),
}
prodOpts = append(prodOpts, opts...)
return newKSCloudAPI(
ksCloudBEURL,
ksCloudAUTHURL,
prodOpts...,
)
}
// NewKSCloudAPIStaging returns a KS Cloud client pointing to a testing environment.
func NewKSCloudAPIStaging(opts ...KSCloudOption) *KSCloudAPI {
stagingOpts := []KSCloudOption{
WithFrontendURL(ksCloudStageFEURL),
WithReportURL(ksCloudStageERURL),
}
stagingOpts = append(stagingOpts, opts...)
return newKSCloudAPI(
ksCloudStageBEURL,
ksCloudStageAUTHURL,
stagingOpts...,
)
}
// NewKSCloudAPICustomed returns a KS Cloud client with configurable API and authentication endpoints.
func NewKSCloudAPICustomized(ksCloudAPIURL, ksCloudAuthURL string, opts ...KSCloudOption) *KSCloudAPI {
return newKSCloudAPI(
ksCloudAPIURL,
ksCloudAuthURL,
opts...,
)
}
func newKSCloudAPI(apiURL, authURL string, opts ...KSCloudOption) *KSCloudAPI {
api := &KSCloudAPI{
cloudAPIURL: apiURL,
cloudAuthURL: authURL,
ksCloudOptions: ksCloudOptionsWithDefaults(opts),
}
api.SetCloudAPIURL(apiURL)
api.SetCloudAuthURL(authURL)
api.SetCloudUIURL(api.cloudUIURL)
api.SetCloudReportURL(api.cloudReportURL)
return api
}
// Get retrieves an API resource.
//
// The response is serialized as a string.
//
// The caller may specify extra headers.
//
// By default, all authentication headers are added.
func (api *KSCloudAPI) Get(fullURL string, headers map[string]string) (string, error) {
rdr, size, err := api.get(fullURL, withExtraHeaders(headers))
if err != nil {
return "", err
}
defer rdr.Close()
return readString(rdr, size)
}
// Post creates an API resource.
//
// The response is serialized as a string.
//
// The caller may specify extra headers.
//
// By default, the body content type is set to JSON and all authentication headers are added.
func (api *KSCloudAPI) Post(fullURL string, headers map[string]string, body []byte) (string, error) {
rdr, size, err := api.post(fullURL, body, withContentJSON(true), withExtraHeaders(headers))
if err != nil {
return "", err
}
defer rdr.Close()
return readString(rdr, size)
}
// Delete an API resource.
//
// The response is serialized as a string.
//
// The caller may specify extra headers.
//
// By default, all authentication headers are added.
func (api *KSCloudAPI) Delete(fullURL string, headers map[string]string) (string, error) {
rdr, size, err := api.delete(fullURL, withExtraHeaders(headers))
if err != nil {
return "", err
}
defer rdr.Close()
return readString(rdr, size)
}
// GetAccountID returns the customer account's GUID.
func (api *KSCloudAPI) GetAccountID() string { return api.accountID }
// IsLoggedIn indicates if the client has sucessfully authenticated.
func (api *KSCloudAPI) IsLoggedIn() bool { return api.loggedIn }
func (api *KSCloudAPI) GetClientID() string { return api.clientID }
func (api *KSCloudAPI) GetSecretKey() string { return api.secretKey }
func (api *KSCloudAPI) GetCloudReportURL() string { return api.cloudReportURL }
func (api *KSCloudAPI) GetCloudAPIURL() string { return api.cloudAPIURL }
func (api *KSCloudAPI) GetCloudUIURL() string { return api.cloudUIURL }
func (api *KSCloudAPI) GetCloudAuthURL() string { return api.cloudAuthURL }
func (api *KSCloudAPI) GetInvitationToken() string { return api.invitationToken }
func (api *KSCloudAPI) SetAccountID(accountID string) { api.accountID = accountID }
func (api *KSCloudAPI) SetClientID(clientID string) { api.clientID = clientID }
func (api *KSCloudAPI) SetSecretKey(secretKey string) { api.secretKey = secretKey }
func (api *KSCloudAPI) SetInvitationToken(token string) { api.invitationToken = token }
func (api *KSCloudAPI) SetCloudAPIURL(cloudAPIURL string) {
api.cloudAPIURL = cloudAPIURL
api.scheme, api.host = parseHost(cloudAPIURL)
}
func (api *KSCloudAPI) SetCloudUIURL(cloudUIURL string) {
api.cloudUIURL = cloudUIURL
api.uischeme, api.uihost = parseHost(cloudUIURL)
}
func (api *KSCloudAPI) SetCloudAuthURL(cloudAuthURL string) {
api.cloudAuthURL = cloudAuthURL
api.authscheme, api.authhost = parseHost(cloudAuthURL)
}
func (api *KSCloudAPI) SetCloudReportURL(cloudReportURL string) {
api.cloudReportURL = cloudReportURL
api.reportscheme, api.reporthost = parseHost(cloudReportURL)
}
func (api *KSCloudAPI) GetAttackTracks() ([]AttackTrack, error) {
rdr, _, err := api.get(api.getAttackTracksURL())
if err != nil {
return nil, err
}
defer rdr.Close()
attackTracks, err := decode[[]AttackTrack](rdr)
if err != nil {
return nil, err
}
return attackTracks, nil
}
func (api *KSCloudAPI) getAttackTracksURL() string {
return api.buildAPIURL(
pathAttackTracks,
api.paramsWithGUID()...,
)
}
// GetFramework retrieves a framework by name.
func (api *KSCloudAPI) GetFramework(frameworkName string) (*Framework, error) {
rdr, _, err := api.get(api.getFrameworkURL(frameworkName))
if err != nil {
return nil, err
}
defer rdr.Close()
framework, err := decode[Framework](rdr)
if err != nil {
return nil, err
}
return &framework, err
}
func (api *KSCloudAPI) getFrameworkURL(frameworkName string) string {
if isNativeFramework(frameworkName) {
// Native framework name is normalized as upper case, but for a custom framework the name remains unaltered
frameworkName = strings.ToUpper(frameworkName)
}
return api.buildAPIURL(
pathFrameworks,
append(
api.paramsWithGUID(),
queryParamFrameworkName, frameworkName,
)...,
)
}
// GetFrameworks returns all registered frameworks.
func (api *KSCloudAPI) GetFrameworks() ([]Framework, error) {
rdr, _, err := api.get(api.getListFrameworkURL())
if err != nil {
return nil, err
}
defer rdr.Close()
frameworks, err := decode[[]Framework](rdr)
if err != nil {
return nil, err
}
return frameworks, err
}
func (api *KSCloudAPI) getListFrameworkURL() string {
return api.buildAPIURL(
pathFrameworks,
api.paramsWithGUID()...,
)
}
// ListCustomFrameworks lists the names of all non-native frameworks that have been registered for this account.
func (api *KSCloudAPI) ListCustomFrameworks() ([]string, error) {
frameworks, err := api.GetFrameworks()
if err != nil {
return nil, err
}
frameworkList := make([]string, 0, len(frameworks))
for _, framework := range frameworks {
if isNativeFramework(framework.Name) {
continue
}
frameworkList = append(frameworkList, framework.Name)
}
return frameworkList, nil
}
// ListFrameworks list the names of all registered frameworks.
func (api *KSCloudAPI) ListFrameworks() ([]string, error) {
frameworks, err := api.GetFrameworks()
if err != nil {
return nil, err
}
frameworkList := make([]string, 0, len(frameworks))
for _, framework := range frameworks {
name := framework.Name
if isNativeFramework(framework.Name) {
name = strings.ToLower(framework.Name)
}
frameworkList = append(frameworkList, name)
}
return frameworkList, nil
}
// GetExceptions returns exception policies.
func (api *KSCloudAPI) GetExceptions(clusterName string) ([]PostureExceptionPolicy, error) {
rdr, _, err := api.get(api.getExceptionsURL(clusterName))
if err != nil {
return nil, err
}
defer rdr.Close()
exceptions, err := decode[[]PostureExceptionPolicy](rdr)
if err != nil {
return nil, err
}
return exceptions, nil
}
func (api *KSCloudAPI) getExceptionsURL(clusterName string) string {
return api.buildAPIURL(
pathExceptions,
api.paramsWithGUID()...,
)
// queryParamClusterName, clusterName, // TODO - fix customer name support in Armo BE
}
// GetTenant retrieves the credentials for the calling tenant.
//
// The tenant ID overides any already provided account ID.
func (api *KSCloudAPI) GetTenant() (*TenantResponse, error) {
rdr, _, err := api.get(api.getTenantURL())
if err != nil {
return nil, err
}
defer rdr.Close()
tenant, err := decode[TenantResponse](rdr)
if err != nil {
return nil, err
}
if tenant.TenantID != "" {
api.accountID = tenant.TenantID
}
return &tenant, nil
}
func (api *KSCloudAPI) getTenantURL() string {
var params []string
if api.accountID != "" {
params = []string{
queryParamGUID, api.accountID, // NOTE: no fallback in this case
}
}
return api.buildAPIURL(
pathTenant,
params...,
)
}
// GetAccountConfig yields the account configuration.
func (api *KSCloudAPI) GetAccountConfig(clusterName string) (*CustomerConfig, error) {
if api.accountID == "" {
return &CustomerConfig{}, nil
}
rdr, _, err := api.get(api.getAccountConfig(clusterName))
if err != nil {
return nil, err
}
defer rdr.Close()
accountConfig, err := decode[CustomerConfig](rdr)
if err != nil {
// retry with default scope
rdr, _, err = api.get(api.getAccountConfigDefault(clusterName))
if err != nil {
return nil, err
}
defer rdr.Close()
accountConfig, err = decode[CustomerConfig](rdr)
if err != nil {
return nil, err
}
}
return &accountConfig, nil
}
func (api *KSCloudAPI) getAccountConfig(clusterName string) string {
params := api.paramsWithGUID()
if clusterName != "" { // TODO - fix customer name support in Armo BE
params = append(params, queryParamClusterName, clusterName)
}
return api.buildAPIURL(
pathCustomerConfig,
params...,
)
}
func (api *KSCloudAPI) getAccountConfigDefault(clusterName string) string {
params := append(
api.paramsWithGUID(),
queryParamScope, "customer",
)
if clusterName != "" { // TODO - fix customer name support in Armo BE
params = append(params, queryParamClusterName, clusterName)
}
return api.buildAPIURL(
pathCustomerConfig,
params...,
)
}
// GetControlsInputs returns the controls inputs configured in the account configuration.
func (api *KSCloudAPI) GetControlsInputs(clusterName string) (map[string][]string, error) {
accountConfig, err := api.GetAccountConfig(clusterName)
if err != nil {
return nil, err
}
return accountConfig.Settings.PostureControlInputs, nil
}
// GetControl is currently not exposed as a public API endpoint.
func (api *KSCloudAPI) GetControl(ID string) (*Control, error) {
return nil, ErrAPINotPublic
}
// ListControls is currently not exposed as a public API endpoint.
func (api *KSCloudAPI) ListControls() ([]string, error) {
return nil, ErrAPINotPublic
}
// PostExceptions registers a list of exceptions.
func (api *KSCloudAPI) PostExceptions(exceptions []PostureExceptionPolicy) error {
target := api.exceptionsURL("")
for i := range exceptions {
jazon, err := json.Marshal(exceptions[i])
if err != nil {
return err
}
_, _, err = api.post(target, jazon, withContentJSON(true))
if err != nil {
return err
}
}
return nil
}
// Delete exception removes a registered exception rule.
func (api *KSCloudAPI) DeleteException(exceptionName string) error {
_, _, err := api.delete(api.exceptionsURL(exceptionName))
return err
}
func (api *KSCloudAPI) exceptionsURL(exceptionsPolicyName string) string {
params := api.paramsWithGUID()
if exceptionsPolicyName != "" { // for delete
params = append(params, queryParamPolicyName, exceptionsPolicyName)
}
return api.buildAPIURL(
pathExceptionPolicy,
params...,
)
}
// SubmitReport uploads a posture report.
func (api *KSCloudAPI) SubmitReport(report *PostureReport) error {
jazon, err := json.Marshal(report)
if err != nil {
return err
}
_, _, err = api.post(api.postReportURL(report.ClusterName, report.ReportID), jazon, withContentJSON(true), withToken(api.invitationToken))
return err
}
func (api *KSCloudAPI) postReportURL(cluster, reportID string) string {
return api.buildReportURL(pathReport,
append(
api.paramsWithGUID(),
queryParamContextName, cluster,
queryParamClusterName, cluster, // deprecated
queryParamReport, reportID,
)...,
)
}
// ViewReportURL yields the frontend URL to view a posture report (e.g. from a repository scan).
func (api *KSCloudAPI) ViewReportURL(reportID string) string {
return api.buildUIURL(
fmt.Sprintf(pathUIRepository, reportID),
)
}
// ViewDashboardURL yields the frontend URL for the dashboard.
func (api *KSCloudAPI) ViewDashboardURL() string {
return api.buildUIURL(
pathUIDashboard,
)
}
// ViewRBACURL yields the frontend URL to visualize RBAC.
func (api *KSCloudAPI) ViewRBACURL() string {
return api.buildUIURL(
pathUIRBAC,
)
}
// ViewRBACURL yields the frontend URL to check the compliance of a scanned cluster.
func (api *KSCloudAPI) ViewScanURL(cluster string) string {
return api.buildUIURL(
fmt.Sprintf(pathUIScan, cluster),
)
}
// ViewSignURL yields the frontend login page.
func (api *KSCloudAPI) ViewSignURL() string {
params := api.paramsWithGUID()
params = append(params, api.paramsWithUTM()...)
params = append(params, queryParamInvitationToken, api.invitationToken)
return api.buildUIURL(
pathUISign,
params...,
)
}
// Login to the KS Cloud using the caller's accountID, clientID and secret key.
func (api *KSCloudAPI) Login() error {
if err := api.loginRequirements(); err != nil {
return err
}
// 1. acquire auth token
body, err := json.Marshal(feLoginData{ClientId: api.clientID, Secret: api.secretKey})
if err != nil {
return err
}
rdr, _, err := api.post(api.authTokenURL(), body, withContentJSON(true))
if err != nil {
return err
}
defer rdr.Close()
resp, err := decode[feLoginResponse](rdr)
if err != nil {
return err
}
api.feToken = resp
// 2. acquire auth cookie
// Now that we have the JWT token, acquire a cookie from the API
api.authCookie, err = api.getAuthCookie()
if err != nil {
return err
}
api.loggedIn = true
return nil
}
func (api *KSCloudAPI) authTokenURL() string {
return api.buildAuthURL(pathLogin)
}
func (api *KSCloudAPI) getOpenidURL() string {
return api.buildAPIURL(pathToken)
}
func (api *KSCloudAPI) getAuthCookie() (*http.Cookie, error) {
selectCustomer := ksCloudSelectCustomer{SelectedCustomerGuid: api.accountID}
body, err := json.Marshal(selectCustomer)
if err != nil {
return nil, err
}
target := api.getOpenidURL()
o := api.defaultRequestOptions([]requestOption{withContentJSON(true), withCookie(nil)})
req, err := http.NewRequestWithContext(o.reqContext, http.MethodPost, target, bytes.NewBuffer(body))
if err != nil {
return nil, err
}
o.setHeaders(req)
o.traceReq(req)
resp, err := api.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
o.traceResp(resp)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get cookie from %s: status %d", target, resp.StatusCode)
}
for _, cookie := range resp.Cookies() {
if cookie.Name == authenticationCookie {
return cookie, nil
}
}
return nil, fmt.Errorf("no auth cookie in response from %s", target)
}
func (api *KSCloudAPI) loginRequirements() error {
if api.accountID == "" {
return ErrLoginMissingAccountID
}
if api.clientID == "" {
return ErrLoginMissingClientID
}
if api.secretKey == "" {
return ErrLoginMissingSecretKey
}
return nil
}
// defaultRequestOptions adds standard authentication headers to all requests
func (api *KSCloudAPI) defaultRequestOptions(opts []requestOption) *requestOptions {
optionsWithDefaults := append(make([]requestOption, 0, 4),
withToken(api.feToken.Token),
withCookie(api.authCookie),
withTrace(api.withTrace),
)
optionsWithDefaults = append(optionsWithDefaults, opts...)
return requestOptionsWithDefaults(optionsWithDefaults)
}
func (api *KSCloudAPI) get(fullURL string, opts ...requestOption) (io.ReadCloser, int64, error) {
o := api.defaultRequestOptions(opts)
req, err := http.NewRequestWithContext(o.reqContext, http.MethodGet, fullURL, nil)
if err != nil {
return nil, 0, err
}
return api.do(req, o)
}
func (api *KSCloudAPI) post(fullURL string, body []byte, opts ...requestOption) (io.ReadCloser, int64, error) {
o := api.defaultRequestOptions(opts)
req, err := http.NewRequestWithContext(o.reqContext, http.MethodPost, fullURL, bytes.NewBuffer(body))
if err != nil {
return nil, 0, err
}
return api.do(req, o)
}
func (api *KSCloudAPI) delete(fullURL string, opts ...requestOption) (io.ReadCloser, int64, error) {
o := api.defaultRequestOptions(opts)
req, err := http.NewRequestWithContext(o.reqContext, http.MethodDelete, fullURL, nil)
if err != nil {
return nil, 0, err
}
return api.do(req, o)
}
func (api *KSCloudAPI) do(req *http.Request, o *requestOptions) (io.ReadCloser, int64, error) {
o.setHeaders(req)
o.traceReq(req)
resp, err := api.httpClient.Do(req)
if err != nil {
return nil, 0, err
}
o.traceResp(resp)
if resp.StatusCode >= 400 {
if req.URL.Path == pathLogin {
return nil, 0, errAuth(resp)
}
return nil, 0, errAPI(resp)
}
return resp.Body, resp.ContentLength, err
}
func (api *KSCloudAPI) paramsWithGUID() []string {
return append(make([]string, 0, 6),
queryParamGUID, api.getCustomerGUIDFallBack(),
)
}
func (api *KSCloudAPI) paramsWithUTM() []string {
return append(make([]string, 0, 6),
queryParamUTMSource, "ARMOgithub",
queryParamUTMMedium, "createaccount",
)
}
func (api *KSCloudAPI) getCustomerGUIDFallBack() string {
if api.accountID != "" {
return api.accountID
}
return fallbackGUID
}

View File

@@ -1,295 +0,0 @@
package getter
import (
"os"
"path/filepath"
"testing"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/armoapi-go/identifiers"
jsoniter "github.com/json-iterator/go"
"github.com/kubescape/kubescape/v2/internal/testutils"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
"github.com/stretchr/testify/require"
)
func mockAttackTracks() []v1alpha1.AttackTrack {
return []v1alpha1.AttackTrack{
{
ApiVersion: "v1",
Kind: "track",
Metadata: map[string]interface{}{"label": "name"},
Spec: v1alpha1.AttackTrackSpecification{
Version: "v2",
Description: "a mock",
Data: v1alpha1.AttackTrackStep{
Name: "track1",
Description: "mock-step",
SubSteps: []v1alpha1.AttackTrackStep{
{
Name: "track1",
Description: "mock-step",
Controls: []v1alpha1.IAttackTrackControl{
mockControlPtr("control-1"),
},
},
},
Controls: []v1alpha1.IAttackTrackControl{
mockControlPtr("control-2"),
mockControlPtr("control-3"),
},
},
},
},
{
ApiVersion: "v1",
Kind: "track",
Metadata: map[string]interface{}{"label": "stuff"},
Spec: v1alpha1.AttackTrackSpecification{
Version: "v1",
Description: "another mock",
Data: v1alpha1.AttackTrackStep{
Name: "track2",
Description: "mock-step2",
SubSteps: []v1alpha1.AttackTrackStep{
{
Name: "track3",
Description: "mock-step",
Controls: []v1alpha1.IAttackTrackControl{
mockControlPtr("control-4"),
},
},
},
Controls: []v1alpha1.IAttackTrackControl{
mockControlPtr("control-5"),
mockControlPtr("control-6"),
},
},
},
},
}
}
func mockFrameworks() []reporthandling.Framework {
id1s := []string{"control-1", "control-2"}
id2s := []string{"control-3", "control-4"}
id3s := []string{"control-5", "control-6"}
return []reporthandling.Framework{
{
PortalBase: armotypes.PortalBase{
Name: "mock-1",
},
CreationTime: "now",
Description: "mock-1",
Controls: []reporthandling.Control{
mockControl("control-1"),
mockControl("control-2"),
},
ControlsIDs: &id1s,
SubSections: map[string]*reporthandling.FrameworkSubSection{
"section1": {
ID: "section-id",
ControlIDs: id1s,
},
},
},
{
PortalBase: armotypes.PortalBase{
Name: "mock-2",
},
CreationTime: "then",
Description: "mock-2",
Controls: []reporthandling.Control{
mockControl("control-3"),
mockControl("control-4"),
},
ControlsIDs: &id2s,
SubSections: map[string]*reporthandling.FrameworkSubSection{
"section2": {
ID: "section-id",
ControlIDs: id2s,
},
},
},
{
PortalBase: armotypes.PortalBase{
Name: "nsa",
},
CreationTime: "tomorrow",
Description: "nsa mock",
Controls: []reporthandling.Control{
mockControl("control-5"),
mockControl("control-6"),
},
ControlsIDs: &id3s,
SubSections: map[string]*reporthandling.FrameworkSubSection{
"section2": {
ID: "section-id",
ControlIDs: id3s,
},
},
},
}
}
func mockControl(controlID string) reporthandling.Control {
return reporthandling.Control{
ControlID: controlID,
}
}
func mockControlPtr(controlID string) *reporthandling.Control {
val := mockControl(controlID)
return &val
}
func mockExceptions() []armotypes.PostureExceptionPolicy {
return []armotypes.PostureExceptionPolicy{
{
PolicyType: "postureExceptionPolicy",
CreationTime: "now",
Actions: []armotypes.PostureExceptionPolicyActions{
"alertOnly",
},
Resources: []identifiers.PortalDesignator{
{
DesignatorType: "Attributes",
Attributes: map[string]string{
"kind": "Pod",
"name": "coredns-[A-Za-z0-9]+-[A-Za-z0-9]+",
"namespace": "kube-system",
},
},
{
DesignatorType: "Attributes",
Attributes: map[string]string{
"kind": "Pod",
"name": "etcd-.*",
"namespace": "kube-system",
},
},
},
PosturePolicies: []armotypes.PosturePolicy{
{
FrameworkName: "MITRE",
ControlID: "C-.*",
},
{
FrameworkName: "another-framework",
ControlID: "a regexp",
},
},
},
{
PolicyType: "postureExceptionPolicy",
CreationTime: "then",
Actions: []armotypes.PostureExceptionPolicyActions{
"alertOnly",
},
Resources: []identifiers.PortalDesignator{
{
DesignatorType: "Attributes",
Attributes: map[string]string{
"kind": "Deployment",
"name": "my-regexp",
},
},
{
DesignatorType: "Attributes",
Attributes: map[string]string{
"kind": "Secret",
"name": "another-regexp",
},
},
},
PosturePolicies: []armotypes.PosturePolicy{
{
FrameworkName: "yet-another-framework",
ControlID: "a regexp",
},
},
},
}
}
func mockTenantResponse() *TenantResponse {
return &TenantResponse{
TenantID: "id",
Token: "token",
Expires: "expiry-time",
AdminMail: "admin@example.com",
}
}
func mockCustomerConfig(cluster, scope string) func() *armotypes.CustomerConfig {
if cluster == "" {
cluster = "my-cluster"
}
if scope == "" {
scope = "default"
}
return func() *armotypes.CustomerConfig {
return &armotypes.CustomerConfig{
Name: "user",
Attributes: map[string]interface{}{
"label": "value",
},
Scope: identifiers.PortalDesignator{
DesignatorType: "Attributes",
Attributes: map[string]string{
"kind": "Cluster",
"name": cluster,
"scope": scope,
},
},
Settings: armotypes.Settings{
PostureControlInputs: map[string][]string{
"inputs-1": {"x1", "y2"},
"inputs-2": {"x2", "y2"},
},
PostureScanConfig: armotypes.PostureScanConfig{
ScanFrequency: armotypes.ScanFrequency("weekly"),
},
VulnerabilityScanConfig: armotypes.VulnerabilityScanConfig{
ScanFrequency: armotypes.ScanFrequency("daily"),
CriticalPriorityThreshold: 1,
HighPriorityThreshold: 2,
MediumPriorityThreshold: 3,
ScanNewDeployment: true,
AllowlistRegistries: []string{"a", "b"},
BlocklistRegistries: []string{"c", "d"},
},
SlackConfigurations: armotypes.SlackSettings{
Token: "slack-token",
},
},
}
}
}
func mockLoginResponse() *feLoginResponse {
return &feLoginResponse{
Token: "access-token",
RefreshToken: "refresh-token",
Expires: "expiry-time",
ExpiresIn: 123,
}
}
func mockPostureReport(t testing.TB, reportID, cluster string) *PostureReport {
fixture := filepath.Join(testutils.CurrentDir(), "testdata", "mock_posture_report.json")
buf, err := os.ReadFile(fixture)
require.NoError(t, err)
var report PostureReport
require.NoError(t,
jsoniter.Unmarshal(buf, &report),
)
return &report
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,202 +0,0 @@
package getter
import (
"context"
"fmt"
"log"
"net/http"
"net/http/httputil"
"time"
)
type (
// KSCloudOption allows to configure the behavior of the KS Cloud client.
KSCloudOption func(*ksCloudOptions)
// ksCloudOptions holds all the configurable parts of the KS Cloud client.
ksCloudOptions struct {
httpClient *http.Client
cloudReportURL string
cloudUIURL string
timeout *time.Duration
withTrace bool
}
// request option instructs post/get/delete to alter the outgoing request
requestOption func(*requestOptions)
// requestOptions knows how to enrich a request with headers
requestOptions struct {
withJSON bool
withToken string
withCookie *http.Cookie
withTrace bool
headers map[string]string
reqContext context.Context
}
)
// KS Cloud client options
// WithHTTPClient overrides the default http.Client used by the KS Cloud client.
func WithHTTPClient(client *http.Client) KSCloudOption {
return func(o *ksCloudOptions) {
o.httpClient = client
}
}
// WithTimeout sets a global timeout on a operations performed by the KS Cloud client.
//
// A value of 0 means no timeout.
//
// The default is 61s.
func WithTimeout(timeout time.Duration) KSCloudOption {
duration := timeout
return func(o *ksCloudOptions) {
o.timeout = &duration
}
}
// WithReportURL specifies the URL to post reports.
func WithReportURL(u string) KSCloudOption {
return func(o *ksCloudOptions) {
o.cloudReportURL = u
}
}
// WithFrontendURL specifies the URL to access the KS Cloud UI.
func WithFrontendURL(u string) KSCloudOption {
return func(o *ksCloudOptions) {
o.cloudUIURL = u
}
}
// WithTrace toggles requests dump for inspection & debugging.
func WithTrace(enabled bool) KSCloudOption {
return func(o *ksCloudOptions) {
o.withTrace = enabled
}
}
var defaultClient = &http.Client{
Timeout: 61 * time.Second,
}
// ksCloudOptionsWithDefaults sets defaults for the KS client and applies overrides.
func ksCloudOptionsWithDefaults(opts []KSCloudOption) *ksCloudOptions {
options := &ksCloudOptions{
httpClient: defaultClient,
}
for _, apply := range opts {
apply(options)
}
if options.timeout != nil {
// non-default timeout (0 means no timeout)
// clone the client and override the timeout
client := *options.httpClient
client.Timeout = *options.timeout
options.httpClient = &client
}
return options
}
// http request options
// withContentJSON sets JSON content type for a request
func withContentJSON(enabled bool) requestOption {
return func(o *requestOptions) {
o.withJSON = enabled
}
}
// withToken sets an Authorization header for a request
func withToken(token string) requestOption {
return func(o *requestOptions) {
o.withToken = token
}
}
// withCookie sets an authentication cookie for a request
func withCookie(cookie *http.Cookie) requestOption {
return func(o *requestOptions) {
o.withCookie = cookie
}
}
// withExtraHeaders adds extra headers to a request
func withExtraHeaders(headers map[string]string) requestOption {
return func(o *requestOptions) {
o.headers = headers
}
}
/* not used yet
// withContext sets the context of a request.
//
// By default, context.Background() is used.
func withContext(ctx context.Context) requestOption {
return func(o *requestOptions) {
o.reqContext = ctx
}
}
*/
// withTrace dumps requests for debugging
func withTrace(enabled bool) requestOption {
return func(o *requestOptions) {
o.withTrace = enabled
}
}
func (o *requestOptions) setHeaders(req *http.Request) {
if o.withJSON {
req.Header.Set("Content-Type", "application/json")
}
if len(o.withToken) > 0 {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", o.withToken))
}
if o.withCookie != nil {
req.AddCookie(o.withCookie)
}
for k, v := range o.headers {
req.Header.Set(k, v)
}
}
// traceReq dumps the content of an outgoing request for inspecting or debugging the client.
func (o *requestOptions) traceReq(req *http.Request) {
if !o.withTrace {
return
}
dump, _ := httputil.DumpRequestOut(req, true)
log.Printf("%s\n", dump)
}
// traceResp dumps the content of an API response for inspecting or debugging the client.
func (o *requestOptions) traceResp(resp *http.Response) {
if !o.withTrace {
return
}
dump, _ := httputil.DumpResponse(resp, true)
log.Printf("%s\n", dump)
}
func requestOptionsWithDefaults(opts []requestOption) *requestOptions {
o := &requestOptions{
reqContext: context.Background(),
}
for _, apply := range opts {
apply(o)
}
return o
}

View File

@@ -6,7 +6,7 @@ import (
"path/filepath"
"testing"
"github.com/kubescape/kubescape/v2/internal/testutils"
"github.com/kubescape/kubescape/v3/internal/testutils"
"github.com/stretchr/testify/require"
)

View File

@@ -1,65 +0,0 @@
package getter
import (
"net/url"
"path"
)
// buildAPIURL builds an URL pointing to the API backend.
func (api *KSCloudAPI) buildAPIURL(pth string, pairs ...string) string {
return buildQuery(url.URL{
Scheme: api.scheme,
Host: api.host,
Path: pth,
}, pairs...)
}
// buildUIURL builds an URL pointing to the UI frontend.
func (api *KSCloudAPI) buildUIURL(pth string, pairs ...string) string {
return buildQuery(url.URL{
Scheme: api.uischeme,
Host: api.uihost,
Path: pth,
}, pairs...)
}
// buildAuthURL builds an URL pointing to the authentication endpoint.
func (api *KSCloudAPI) buildAuthURL(pth string, pairs ...string) string {
return buildQuery(url.URL{
Scheme: api.authscheme,
Host: api.authhost,
Path: pth,
}, pairs...)
}
// buildReportURL builds an URL pointing to the reporting endpoint.
func (api *KSCloudAPI) buildReportURL(pth string, pairs ...string) string {
return buildQuery(url.URL{
Scheme: api.reportscheme,
Host: api.reporthost,
Path: pth,
}, pairs...)
}
// buildQuery builds an URL with query params.
//
// Params are provided in pairs (param name, value).
func buildQuery(u url.URL, pairs ...string) string {
if len(pairs)%2 != 0 {
panic("dev error: buildURL accepts query params in (name, value) pairs")
}
q := u.Query()
for i := 0; i < len(pairs)-1; i += 2 {
param := pairs[i]
value := pairs[i+1]
q.Add(param, value)
}
u.RawQuery = q.Encode()
u.Path = path.Clean(u.Path)
return u.String()
}

View File

@@ -1,86 +0,0 @@
package getter
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestBuildURL(t *testing.T) {
t.Parallel()
ks := NewKSCloudAPICustomized(
"api.example.com", "auth.example.com", // required
WithFrontendURL("ui.example.com"), // optional
WithReportURL("report.example.com"), // optional
)
t.Run("should build API URL with query params on https host", func(t *testing.T) {
require.Equal(t,
"https://api.example.com/path?q1=v1&q2=v2",
ks.buildAPIURL("/path", "q1", "v1", "q2", "v2"),
)
})
t.Run("should build API URL with query params on http host", func(t *testing.T) {
ku := NewKSCloudAPICustomized("http://api.example.com", "auth.example.com")
require.Equal(t,
"http://api.example.com/path?q1=v1&q2=v2",
ku.buildAPIURL("/path", "q1", "v1", "q2", "v2"),
)
})
t.Run("should panic when params are not provided in pairs", func(t *testing.T) {
require.Panics(t, func() {
// notice how the linter detects wrong args
_ = ks.buildAPIURL("/path", "q1", "v1", "q2") //nolint:staticcheck
})
})
t.Run("should build UI URL with query params on https host", func(t *testing.T) {
require.Equal(t,
"https://ui.example.com/path?q1=v1&q2=v2",
ks.buildUIURL("/path", "q1", "v1", "q2", "v2"),
)
})
t.Run("should build report URL with query params on https host", func(t *testing.T) {
require.Equal(t,
"https://report.example.com/path?q1=v1&q2=v2",
ks.buildReportURL("/path", "q1", "v1", "q2", "v2"),
)
})
}
func TestViewURL(t *testing.T) {
t.Parallel()
ks := NewKSCloudAPICustomized(
"api.example.com", "auth.example.com", // required
WithFrontendURL("ui.example.com"), // optional
WithReportURL("report.example.com"), // optional
)
ks.SetAccountID("me")
ks.SetInvitationToken("invite")
t.Run("should render UI report URL", func(t *testing.T) {
require.Equal(t, "https://ui.example.com/repository-scanning/xyz", ks.ViewReportURL("xyz"))
})
t.Run("should render UI dashboard URL", func(t *testing.T) {
require.Equal(t, "https://ui.example.com/dashboard", ks.ViewDashboardURL())
})
t.Run("should render UI RBAC URL", func(t *testing.T) {
require.Equal(t, "https://ui.example.com/rbac-visualizer", ks.ViewRBACURL())
})
t.Run("should render UI scan URL", func(t *testing.T) {
require.Equal(t, "https://ui.example.com/compliance/cluster", ks.ViewScanURL("cluster"))
})
t.Run("should render UI sign URL", func(t *testing.T) {
require.Equal(t, "https://ui.example.com/account/sign-up?customerGUID=me&invitationToken=invite&utm_medium=createaccount&utm_source=ARMOgithub", ks.ViewSignURL())
})
}

View File

@@ -1,28 +1,9 @@
package getter
import (
"fmt"
"io"
"net/http"
"strings"
)
// parseHost picks a host from a hostname or an URL and detects the scheme.
//
// The default scheme is https. This may be altered by specifying an explicit http://hostname URL.
func parseHost(host string) (string, string) {
if strings.HasPrefix(host, "http://") {
return "http", strings.Replace(host, "http://", "", 1) // cut... index ...
}
// default scheme
return "https", strings.Replace(host, "https://", "", 1)
}
func isNativeFramework(framework string) bool {
return contains(NativeFrameworks, framework)
}
func contains(s []string, str string) bool {
for _, v := range s {
if strings.EqualFold(v, str) {
@@ -32,51 +13,3 @@ func contains(s []string, str string) bool {
return false
}
func min(a, b int64) int64 {
if a < b {
return a
}
return b
}
// errAPI reports an API error, with a cap on the length of the error message.
func errAPI(resp *http.Response) error {
const maxSize = 1024
reason := new(strings.Builder)
if resp.Body != nil {
size := min(resp.ContentLength, maxSize)
if size > 0 {
reason.Grow(int(size))
}
_, _ = io.CopyN(reason, resp.Body, size)
defer resp.Body.Close()
}
return fmt.Errorf("http-error: '%s', reason: '%s'", resp.Status, reason.String())
}
// errAuth returns an authentication error.
//
// Authentication errors upon login croak a less detailed message.
func errAuth(resp *http.Response) error {
return fmt.Errorf("error authenticating: %d", resp.StatusCode)
}
func readString(rdr io.Reader, sizeHint int64) (string, error) {
// if the response is empty, return an empty string
if sizeHint < 0 {
return "", nil
}
var b strings.Builder
b.Grow(int(sizeHint))
_, err := io.Copy(&b, rdr)
return b.String(), err
}

View File

@@ -1,99 +0,0 @@
package getter
import (
"io"
"testing"
"github.com/stretchr/testify/require"
)
func TestParseHost(t *testing.T) {
t.Parallel()
t.Run("should recognize http scheme", func(t *testing.T) {
t.Parallel()
const input = "http://localhost:7555"
scheme, host := parseHost(input)
require.Equal(t, "http", scheme)
require.Equal(t, "localhost:7555", host)
})
t.Run("should recognize https scheme", func(t *testing.T) {
t.Parallel()
const input = "https://localhost:7555"
scheme, host := parseHost(input)
require.Equal(t, "https", scheme)
require.Equal(t, "localhost:7555", host)
})
t.Run("should adopt https scheme by default", func(t *testing.T) {
t.Parallel()
const input = "portal-dev.armo.cloud"
scheme, host := parseHost(input)
require.Equal(t, "https", scheme)
require.Equal(t, "portal-dev.armo.cloud", host)
})
}
func TestIsNativeFramework(t *testing.T) {
t.Parallel()
require.Truef(t, isNativeFramework("nSa"), "expected nsa to be native (case insensitive)")
require.Falsef(t, isNativeFramework("foo"), "expected framework to be custom")
}
func Test_readString(t *testing.T) {
type args struct {
rdr io.Reader
sizeHint int64
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "should return empty string if sizeHint is negative",
args: args{
rdr: nil,
sizeHint: -1,
},
want: "",
wantErr: false,
},
{
name: "should return empty string if sizeHint is zero",
args: args{
rdr: &io.LimitedReader{},
sizeHint: 0,
},
want: "",
wantErr: false,
},
{
name: "should return empty string if sizeHint is positive",
args: args{
rdr: &io.LimitedReader{},
sizeHint: 1,
},
want: "",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := readString(tt.args.rdr, tt.args.sizeHint)
if (err != nil) != tt.wantErr {
t.Errorf("readString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("readString() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -3,20 +3,87 @@
package cautils
import (
"errors"
"fmt"
"path/filepath"
"time"
"github.com/kubescape/go-git-url/apis"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/matthyx/go-gitlog"
)
var ErrFatalNotSupportedByBuild = errors.New(`git scan not supported by this build. Build with tag "gitenabled" to enable the git scan feature`)
type gitRepository struct {
gitLogDisabled bool
gitRepo gitlog.GitLog
fileToLastCommit map[string]*gitlog.Commit
}
func newGitRepository(root string) (*gitRepository, error) {
return &gitRepository{}, ErrWarnNotSupportedByBuild
gitRepo := gitlog.New(&gitlog.Config{Path: root})
return &gitRepository{
gitRepo: gitRepo,
}, nil
}
func (g *gitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) {
return nil, ErrFatalNotSupportedByBuild
if len(g.fileToLastCommit) == 0 && !g.gitLogDisabled {
g.buildCommitMap()
}
if relevantCommit, exists := g.fileToLastCommit[filepath.ToSlash(filePath)]; exists {
return g.getCommit(relevantCommit), nil
}
return nil, fmt.Errorf("failed to get commit information for file: %s", filePath)
}
func (g *gitRepository) buildCommitMap() {
filePathToCommitTime := map[string]time.Time{}
filePathToCommit := map[string]*gitlog.Commit{}
allCommits, err := g.gitRepo.Log(nil, &gitlog.Params{IgnoreMerges: true})
if err != nil {
logger.L().Warning("git not found in PATH: git metadata (author and hash) will not be available", helpers.Error(err))
g.gitLogDisabled = true
return
}
// builds a map of all files to their last commit
for _, commit := range allCommits {
for _, file := range commit.Files {
commitTime := commit.Author.Date
// In case we have the commit information for the file which is not the latest - we override it
if currentCommitTime, exists := filePathToCommitTime[file]; exists {
if currentCommitTime.Before(commitTime) {
filePathToCommitTime[file] = commitTime
filePathToCommit[file] = commit
}
} else {
filePathToCommitTime[file] = commitTime
filePathToCommit[file] = commit
}
}
}
g.fileToLastCommit = filePathToCommit
}
func (g *gitRepository) getCommit(commit *gitlog.Commit) *apis.Commit {
return &apis.Commit{
SHA: commit.Hash.Long,
Author: apis.Committer{
Name: commit.Author.Name,
Email: commit.Author.Email,
Date: commit.Author.Date,
},
Message: commit.Subject + "\n" + commit.Body,
Committer: apis.Committer{
Name: commit.Committer.Name,
Email: commit.Committer.Email,
Date: commit.Committer.Date,
},
Files: []apis.Files{},
}
}

View File

@@ -1,11 +0,0 @@
//go:build !gitenabled
package cautils
func (s *LocalGitRepositoryTestSuite) TestGetLastCommit() {
s.T().Log("warn: skipped testing native git functionality [GetLastCommit]")
}
func (s *LocalGitRepositoryTestSuite) TestGetFileLastCommit() {
s.T().Log("warn: skipped testing native git functionality [GetFileLastCommit]")
}

View File

@@ -1,4 +1,5 @@
//go:build gitenabled
package cautils
import (
@@ -27,64 +28,7 @@ func newGitRepository(root string) (*gitRepository, error) {
func (g *gitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) {
if len(g.fileToLastCommit) == 0 {
filePathToCommitTime := map[string]time.Time{}
filePathToCommit := map[string]*git2go.Commit{}
allCommits, _ := g.getAllCommits()
// builds a map of all files to their last commit
for _, commit := range allCommits {
// Ignore merge commits (2+ parents)
if commit.ParentCount() <= 1 {
tree, err := commit.Tree()
if err != nil {
continue
}
// ParentCount can be either 1 or 0 (initial commit)
// In case it's the initial commit, prevTree is nil
var prevTree *git2go.Tree
if commit.ParentCount() == 1 {
prevCommit := commit.Parent(0)
prevTree, err = prevCommit.Tree()
if err != nil {
continue
}
}
diff, err := g.git2GoRepo.DiffTreeToTree(prevTree, tree, nil)
if err != nil {
continue
}
numDeltas, err := diff.NumDeltas()
if err != nil {
continue
}
for i := 0; i < numDeltas; i++ {
delta, err := diff.Delta(i)
if err != nil {
continue
}
deltaFilePath := delta.NewFile.Path
commitTime := commit.Author().When
// In case we have the commit information for the file which is not the latest - we override it
if currentCommitTime, exists := filePathToCommitTime[deltaFilePath]; exists {
if currentCommitTime.Before(commitTime) {
filePathToCommitTime[deltaFilePath] = commitTime
filePathToCommit[deltaFilePath] = commit
}
} else {
filePathToCommitTime[deltaFilePath] = commitTime
filePathToCommit[deltaFilePath] = commit
}
}
}
}
g.fileToLastCommit = filePathToCommit
g.buildCommitMap()
}
if relevantCommit, exists := g.fileToLastCommit[filePath]; exists {
@@ -94,6 +38,67 @@ func (g *gitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error)
return nil, fmt.Errorf("failed to get commit information for file: %s", filePath)
}
func (g *gitRepository) buildCommitMap() {
filePathToCommitTime := map[string]time.Time{}
filePathToCommit := map[string]*git2go.Commit{}
allCommits, _ := g.getAllCommits()
// builds a map of all files to their last commit
for _, commit := range allCommits {
// Ignore merge commits (2+ parents)
if commit.ParentCount() <= 1 {
tree, err := commit.Tree()
if err != nil {
continue
}
// ParentCount can be either 1 or 0 (initial commit)
// In case it's the initial commit, prevTree is nil
var prevTree *git2go.Tree
if commit.ParentCount() == 1 {
prevCommit := commit.Parent(0)
prevTree, err = prevCommit.Tree()
if err != nil {
continue
}
}
diff, err := g.git2GoRepo.DiffTreeToTree(prevTree, tree, nil)
if err != nil {
continue
}
numDeltas, err := diff.NumDeltas()
if err != nil {
continue
}
for i := 0; i < numDeltas; i++ {
delta, err := diff.Delta(i)
if err != nil {
continue
}
deltaFilePath := delta.NewFile.Path
commitTime := commit.Author().When
// In case we have the commit information for the file which is not the latest - we override it
if currentCommitTime, exists := filePathToCommitTime[deltaFilePath]; exists {
if currentCommitTime.Before(commitTime) {
filePathToCommitTime[deltaFilePath] = commitTime
filePathToCommit[deltaFilePath] = commit
}
} else {
filePathToCommitTime[deltaFilePath] = commitTime
filePathToCommit[deltaFilePath] = commit
}
}
}
}
g.fileToLastCommit = filePathToCommit
}
func (g *gitRepository) getAllCommits() ([]*git2go.Commit, error) {
logItr, itrErr := g.git2GoRepo.Walk()
if itrErr != nil {

View File

@@ -1,13 +1,18 @@
//go:build gitenabled
package cautils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func (s *LocalGitRepositoryTestSuite) TestGetLastCommit() {
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
if commit, err := localRepo.GetLastCommit(); s.NoError(err) {
s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
s.Equal(int64(1653235917), commit.Author.Date.Unix())
s.Equal("added file B\n", commit.Message)
}
}
@@ -21,7 +26,7 @@ func (s *LocalGitRepositoryTestSuite) TestGetFileLastCommit() {
s.Equal("9fae4be19624297947d2b605cefbff516628612d", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal("2022-05-22 18:55:48 +0300 +0300", commit.Author.Date.String())
s.Equal(int64(1653234948), commit.Author.Date.Unix())
s.Equal("added file A\n", commit.Message)
}
@@ -35,10 +40,19 @@ func (s *LocalGitRepositoryTestSuite) TestGetFileLastCommit() {
s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
s.Equal(int64(1653235917), commit.Author.Date.Unix())
s.Equal("added file B\n", commit.Message)
}
}
})
}
func BenchmarkBuildCommitMap(b *testing.B) {
localRepo, err := NewLocalGitRepository("testdata/temp/localrepo")
assert.NoError(b, err)
for i := 0; i < b.N; i++ {
localRepo.buildCommitMap()
}
b.ReportAllocs()
}

View File

@@ -1,10 +1,10 @@
package opaprocessor
package cautils
import (
"github.com/docker/distribution/reference"
)
func normalize_image_name(img string) (string, error) {
func NormalizeImageName(img string) (string, error) {
name, err := reference.ParseNormalizedNamed(img)
if err != nil {
return "", err

View File

@@ -1,4 +1,4 @@
package opaprocessor
package cautils
import (
"testing"
@@ -21,7 +21,7 @@ func Test_normalize_name(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
name, _ := normalize_image_name(tt.img)
name, _ := NormalizeImageName(tt.img)
assert.Equal(t, tt.want, name, tt.name)
})
}

View File

@@ -0,0 +1,144 @@
package cautils
import (
"errors"
"testing"
"github.com/armosec/armoapi-go/apis"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
utilsmetav1 "github.com/kubescape/opa-utils/httpserver/meta/v1"
"github.com/stretchr/testify/assert"
)
func newFalse() *bool {
f := false
return &f
}
func Test_GetRequestPayload(t *testing.T) {
testCases := []struct {
name string
clusterName string
OperatorScanInfo
result *apis.Commands
}{
{
name: "scan kubescape config",
OperatorScanInfo: &ConfigScanInfo{
ExcludedNamespaces: []string{"1111"},
IncludedNamespaces: []string{"2222"},
HostScanner: false,
Frameworks: []string{"any", "many"},
},
result: &apis.Commands{
Commands: []apis.Command{
{
CommandName: apis.TypeRunKubescape,
Args: map[string]interface{}{
KubescapeScanV1: utilsmetav1.PostScanRequest{
ExcludedNamespaces: []string{"1111"},
IncludeNamespaces: []string{"2222"},
TargetType: apisv1.KindFramework,
TargetNames: []string{"any", "many"},
HostScanner: newFalse(),
},
},
},
},
},
},
{
name: "scan kubescape vulns",
OperatorScanInfo: &VulnerabilitiesScanInfo{
ClusterName: "any",
IncludeNamespaces: []string{""},
},
result: &apis.Commands{
Commands: []apis.Command{
{
CommandName: apis.TypeScanImages,
WildWlid: "wlid://cluster-any",
},
},
},
},
{
name: "scan kubescape vulns with namespace",
OperatorScanInfo: &VulnerabilitiesScanInfo{
ClusterName: "any",
IncludeNamespaces: []string{"123"},
},
result: &apis.Commands{
Commands: []apis.Command{
{
CommandName: apis.TypeScanImages,
WildWlid: "wlid://cluster-any/namespace-123",
},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := tc.OperatorScanInfo.GetRequestPayload()
assert.Equal(t, tc.result, result)
})
}
}
func Test_ValidatePayload(t *testing.T) {
testCases := []struct {
name string
clusterName string
OperatorScanInfo
result error
}{
{
name: "ConfigScanInfo first happy case",
OperatorScanInfo: &ConfigScanInfo{
ExcludedNamespaces: []string{"1111"},
IncludedNamespaces: []string{},
HostScanner: false,
Frameworks: []string{"any", "many"},
},
result: nil,
},
{
name: "ConfigScanInfo second happy case",
OperatorScanInfo: &ConfigScanInfo{
ExcludedNamespaces: []string{},
IncludedNamespaces: []string{"1111"},
HostScanner: false,
Frameworks: []string{"any", "many"},
},
result: nil,
},
{
name: "ConfigScanInfo returned error",
OperatorScanInfo: &ConfigScanInfo{
ExcludedNamespaces: []string{"1111"},
IncludedNamespaces: []string{"2222"},
HostScanner: false,
Frameworks: []string{"any", "many"},
},
result: errors.New("invalid arguments: include-namespaces and exclude-namespaces can't pass together to the CLI"),
},
{
name: "VulnerabilitiesScanInfo happy case",
OperatorScanInfo: &VulnerabilitiesScanInfo{
ClusterName: "any",
IncludeNamespaces: []string{""},
},
result: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
payload := tc.OperatorScanInfo.GetRequestPayload()
result := tc.OperatorScanInfo.ValidatePayload(payload)
assert.Equal(t, tc.result, result)
})
}
}

View File

@@ -0,0 +1,107 @@
package cautils
import (
"errors"
"github.com/armosec/armoapi-go/apis"
"github.com/armosec/utils-k8s-go/wlid"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
utilsmetav1 "github.com/kubescape/opa-utils/httpserver/meta/v1"
)
type OperatorSubCommand string
const (
ScanCommand OperatorSubCommand = "scan"
ScanConfigCommand OperatorSubCommand = "config"
ScanVulnerabilitiesCommand OperatorSubCommand = "vulnerabilities"
KubescapeScanV1 string = "scanV1"
)
type VulnerabilitiesScanInfo struct {
IncludeNamespaces []string
ClusterName string
}
type ConfigScanInfo struct {
ExcludedNamespaces []string
IncludedNamespaces []string
HostScanner bool
Frameworks []string // Load frameworks for config scan
}
type OperatorInfo struct {
Subcommands []OperatorSubCommand
OperatorScanInfo
}
type OperatorConnector interface {
StartPortForwarder() error
StopPortForwarder()
GetPortForwardLocalhost() string
}
type OperatorScanInfo interface {
GetRequestPayload() *apis.Commands
ValidatePayload(*apis.Commands) error
}
func (v *VulnerabilitiesScanInfo) ValidatePayload(commands *apis.Commands) error {
return nil
}
func (v *VulnerabilitiesScanInfo) GetRequestPayload() *apis.Commands {
var commands []apis.Command
clusterName := v.ClusterName
if len(v.IncludeNamespaces) == 0 {
wildWlid := wlid.GetWLID(clusterName, "", "", "")
command := apis.Command{
CommandName: apis.TypeScanImages,
WildWlid: wildWlid,
}
commands = append(commands, command)
} else {
for i := range v.IncludeNamespaces {
wildWlid := wlid.GetWLID(clusterName, v.IncludeNamespaces[i], "", "")
command := apis.Command{
CommandName: apis.TypeScanImages,
WildWlid: wildWlid,
}
commands = append(commands, command)
}
}
return &apis.Commands{
Commands: commands,
}
}
func (c *ConfigScanInfo) ValidatePayload(commands *apis.Commands) error {
if len(c.IncludedNamespaces) != 0 && len(c.ExcludedNamespaces) != 0 {
return errors.New("invalid arguments: include-namespaces and exclude-namespaces can't pass together to the CLI")
}
return nil
}
func (c *ConfigScanInfo) GetRequestPayload() *apis.Commands {
if len(c.Frameworks) == 0 {
c.Frameworks = append(c.Frameworks, "all")
}
return &apis.Commands{
Commands: []apis.Command{
{
CommandName: apis.TypeRunKubescape,
Args: map[string]interface{}{
KubescapeScanV1: utilsmetav1.PostScanRequest{
ExcludedNamespaces: c.ExcludedNamespaces,
IncludeNamespaces: c.IncludedNamespaces,
TargetType: apisv1.KindFramework,
TargetNames: c.Frameworks,
HostScanner: &c.HostScanner,
},
},
},
},
}
}

View File

@@ -0,0 +1,86 @@
package cautils
import (
"bytes"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"github.com/kubescape/k8s-interface/k8sinterface"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
)
const (
DefaultPortForwardPortEnv string = "DEFAULT_PORT_FORWARDER_PORT"
DefaultPortForwardPortValue string = "4444"
)
type portForward struct {
*portforward.PortForwarder
localPort string
stopChan chan struct{}
readyChan chan struct{}
out *bytes.Buffer
errOut *bytes.Buffer
}
func getPortForwardingPort() string {
if port, exist := os.LookupEnv(DefaultPortForwardPortEnv); exist {
return port
}
return DefaultPortForwardPortValue
}
func CreatePortForwarder(k8sClient *k8sinterface.KubernetesApi, pod *v1.Pod, forwardingPort, namespace string) (OperatorConnector, error) {
path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", namespace, pod.Name)
hostIP := strings.TrimLeft(k8sClient.K8SConfig.Host, "htps:/")
serverURL := &url.URL{Scheme: "https", Path: path, Host: hostIP}
roundTripper, upgrader, err := spdy.RoundTripperFor(k8sClient.K8SConfig)
if err != nil {
return nil, err
}
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, serverURL)
stopChan, readyChan := make(chan struct{}, 1), make(chan struct{})
out, errOut := new(bytes.Buffer), new(bytes.Buffer)
forwarder, err := portforward.NewOnAddresses(dialer, []string{"localhost"}, []string{fmt.Sprintf("%s:%s", getPortForwardingPort(), forwardingPort)}, stopChan, readyChan, out, errOut)
if err != nil {
return nil, err
}
return &portForward{
PortForwarder: forwarder,
localPort: getPortForwardingPort(),
stopChan: stopChan,
readyChan: readyChan,
out: out,
errOut: errOut,
}, nil
}
func (p *portForward) waitForPortForwardReadiness() struct{} {
return <-p.readyChan
}
func (p *portForward) GetPortForwardLocalhost() string {
return "localhost:" + getPortForwardingPort()
}
func (p *portForward) StopPortForwarder() {
p.stopChan <- struct{}{}
}
func (p *portForward) StartPortForwarder() error {
go func() {
p.PortForwarder.ForwardPorts()
}()
p.waitForPortForwardReadiness()
return nil
}

View File

@@ -0,0 +1,134 @@
package cautils
import (
"context"
"testing"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/rest"
)
type FakeCachedDiscoveryClient struct {
discovery.DiscoveryInterface
Groups []*metav1.APIGroup
Resources []*metav1.APIResourceList
PreferredResources []*metav1.APIResourceList
Invalidations int
}
func Test_getPortForwardingPort(t *testing.T) {
testCases := []struct {
name string
createNewPort bool
port string
expectedPort string
}{
{
name: "test default port",
port: "",
expectedPort: DefaultPortForwardPortValue,
},
{
name: "test set port",
createNewPort: true,
port: "1234",
expectedPort: "1234",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.createNewPort {
t.Setenv(DefaultPortForwardPortEnv, tc.port)
}
assert.Equal(t, tc.expectedPort, getPortForwardingPort())
})
}
}
func Test_CreatePortForwarder(t *testing.T) {
testCases := []struct {
name string
expectedError error
}{
{
name: "test creation",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
k8sClient := k8sinterface.KubernetesApi{
KubernetesClient: fake.NewSimpleClientset(),
K8SConfig: &rest.Config{
Host: "any",
},
Context: context.TODO(),
}
operatorPod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "first",
Labels: map[string]string{
"app": "operator",
},
},
}
createdOperatorPod, err := k8sClient.KubernetesClient.CoreV1().Pods(kubescapeNamespace).Create(k8sClient.Context, &operatorPod, metav1.CreateOptions{})
assert.Equal(t, nil, err)
_, err = CreatePortForwarder(&k8sClient, createdOperatorPod, "1234", "any")
assert.Equal(t, nil, err)
})
}
}
func Test_GetPortForwardLocalhost(t *testing.T) {
testCases := []struct {
name string
port string
result string
}{
{
name: "test creation",
port: "1234",
result: "localhost",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
k8sClient := k8sinterface.KubernetesApi{
KubernetesClient: fake.NewSimpleClientset(),
K8SConfig: &rest.Config{
Host: "any",
},
Context: context.TODO(),
}
operatorPod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "first",
Labels: map[string]string{
"app": "operator",
},
},
}
createdOperatorPod, err := k8sClient.KubernetesClient.CoreV1().Pods(kubescapeNamespace).Create(k8sClient.Context, &operatorPod, metav1.CreateOptions{})
assert.Equal(t, nil, err)
t.Setenv(DefaultPortForwardPortEnv, tc.port)
pf, err := CreatePortForwarder(&k8sClient, createdOperatorPod, "1234", "any")
assert.Equal(t, nil, err)
result := pf.GetPortForwardLocalhost()
assert.Equal(t, tc.result+":"+getPortForwardingPort(), result)
})
}
}

View File

@@ -7,43 +7,24 @@ import (
)
type RootInfo struct {
Logger string // logger level
LoggerName string // logger name ("pretty"/"zap"/"none")
CacheDir string // cached dir
DisableColor bool // Disable Color
EnableColor bool // Force enable Color
KSCloudBEURLs string // Kubescape Cloud URL
KSCloudBEURLsDep string // Kubescape Cloud URL
Logger string // logger level
LoggerName string // logger name ("pretty"/"zap"/"none")
CacheDir string // cached dir
DisableColor bool // Disable Color
EnableColor bool // Force enable Color
DiscoveryServerURL string // Discovery Server URL (See https://github.com/kubescape/backend/tree/main/pkg/servicediscovery)
KubeContext string // context name
}
type CloudURLs struct {
CloudReportURL string
CloudAPIURL string
CloudUIURL string
CloudAuthURL string
}
type Credentials struct {
Account string
ClientID string
SecretKey string
}
// To check if the user's credentials: accountID / clientID / secretKey are valid.
func (credentials *Credentials) Validate() error {
// To check if the provided account ID is valid
func ValidateAccountID(accountID string) error {
// Check if the Account-ID is valid
if _, err := uuid.Parse(credentials.Account); credentials.Account != "" && err != nil {
return fmt.Errorf("bad argument: account must be a valid UUID")
}
// Check if the Client-ID is valid
if _, err := uuid.Parse(credentials.ClientID); credentials.ClientID != "" && err != nil {
return fmt.Errorf("bad argument: account must be a valid UUID")
}
// Check if the Secret-Key is valid
if _, err := uuid.Parse(credentials.SecretKey); credentials.SecretKey != "" && err != nil {
return fmt.Errorf("bad argument: account must be a valid UUID")
if _, err := uuid.Parse(accountID); accountID != "" && err != nil {
return fmt.Errorf("bad argument: accound ID must be a valid UUID")
}
return nil

View File

@@ -2,11 +2,9 @@ package cautils
import "testing"
func TestCredentials_Validate(t *testing.T) {
func TestValidateAccountID(t *testing.T) {
type fields struct {
Account string
ClientID string
SecretKey string
Account string
}
tests := []struct {
name string
@@ -27,44 +25,11 @@ func TestCredentials_Validate(t *testing.T) {
},
wantErr: true,
},
{
name: "valid client ID",
fields: fields{
ClientID: "22019933-feac-4012-a8eb-e81461ba6655",
},
wantErr: false,
},
{
name: "invalid client ID",
fields: fields{
ClientID: "22019933-feac-4012-a8eb-e81461ba665",
},
wantErr: true,
},
{
name: "valid secret key",
fields: fields{
SecretKey: "22019933-feac-4012-a8eb-e81461ba6655",
},
wantErr: false,
},
{
name: "invalid secret key",
fields: fields{
SecretKey: "22019933-feac-4012-a8eb-e81461ba665",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
credentials := &Credentials{
Account: tt.fields.Account,
ClientID: tt.fields.ClientID,
SecretKey: tt.fields.SecretKey,
}
if err := credentials.Validate(); (err != nil) != tt.wantErr {
t.Errorf("Credentials.Validate() error = %v, wantErr %v", err, tt.wantErr)
if err := ValidateAccountID(tt.fields.Account); (err != nil) != tt.wantErr {
t.Errorf("ValidateAccountID() error = %v, wantErr %v", err, tt.wantErr)
}
})
}

View File

@@ -12,7 +12,7 @@ import (
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/opa-utils/objectsenvelopes"
"github.com/kubescape/opa-utils/reporthandling"
@@ -124,18 +124,18 @@ type ScanInfo struct {
ComplianceThreshold float32 // Compliance score threshold
FailThresholdSeverity string // Severity at and above which the command should fail
Submit bool // Submit results to Kubescape Cloud BE
CreateAccount bool // Create account in Kubescape Cloud BE if no account found in local cache
ScanID string // Report id of the current scan
HostSensorEnabled BoolPtrFlag // Deploy Kubescape K8s host scanner to collect data from certain controls
HostSensorYamlPath string // Path to hostsensor file
Local bool // Do not submit results
Credentials Credentials // account ID
KubeContext string // context name
AccountID string // account ID
AccessKey string // access key
FrameworkScan bool // false if scanning control
ScanAll bool // true if scan all frameworks
OmitRawResources bool // true if omit raw resources from the output
PrintAttackTree bool // true if print attack tree
ScanObject *objectsenvelopes.ScanObject // identifies a single resource (k8s object) to be scanned
IsDeletedScanObject bool // indicates whether the ScanObject is a deleted K8S resource
ScanType ScanTypes
ScanImages bool
ChartPath string
@@ -296,11 +296,10 @@ func scanInfoToScanMetadata(ctx context.Context, scanInfo *ScanInfo) *reporthand
}
func (scanInfo *ScanInfo) GetScanningContext() ScanningContext {
input := ""
if len(scanInfo.InputPatterns) > 0 {
input = scanInfo.InputPatterns[0]
return GetScanningContext(scanInfo.InputPatterns[0])
}
return GetScanningContext(input)
return GetScanningContext("")
}
// GetScanningContext get scanning context from the input param

View File

@@ -2,6 +2,8 @@ package cautils
import (
"context"
"os"
"path/filepath"
"testing"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
@@ -23,13 +25,11 @@ func TestSetContextMetadata(t *testing.T) {
/*{
ctx := reporthandlingv2.ContextMetadata{}
setContextMetadata(&ctx, "https://github.com/kubescape/kubescape")
assert.Nil(t, ctx.ClusterContextMetadata)
assert.Nil(t, ctx.DirectoryContextMetadata)
assert.Nil(t, ctx.FileContextMetadata)
assert.Nil(t, ctx.HelmContextMetadata)
assert.NotNil(t, ctx.RepoContextMetadata)
assert.Equal(t, "kubescape", ctx.RepoContextMetadata.Repo)
assert.Equal(t, "kubescape", ctx.RepoContextMetadata.Owner)
assert.Equal(t, "master", ctx.RepoContextMetadata.Branch)
@@ -37,12 +37,18 @@ func TestSetContextMetadata(t *testing.T) {
}
func TestGetHostname(t *testing.T) {
// Test that the hostname is not empty
assert.NotEqual(t, "", getHostname())
}
func TestGetScanningContext(t *testing.T) {
// Test with empty input
assert.Equal(t, ContextCluster, GetScanningContext(""))
// Test with Git URL input
assert.Equal(t, ContextGitURL, GetScanningContext("https://github.com/kubescape/kubescape"))
// TODO: Add more tests with other input types
}
func TestScanInfoFormats(t *testing.T) {
@@ -71,3 +77,30 @@ func TestScanInfoFormats(t *testing.T) {
})
}
}
func TestGetScanningContextWithFile(t *testing.T) {
// Test with a file
dir, err := os.MkdirTemp("", "example")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
filePath := filepath.Join(dir, "file.txt")
if _, err := os.Create(filePath); err != nil {
t.Fatal(err)
}
assert.Equal(t, ContextFile, GetScanningContext(filePath))
}
func TestGetScanningContextWithDir(t *testing.T) {
// Test with a directory
dir, err := os.MkdirTemp("", "example")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
assert.Equal(t, ContextDir, GetScanningContext(dir))
}

View File

@@ -63,3 +63,17 @@ func ParseIntEnvVar(varName string, defaultValue int) (int, error) {
return intValue, nil
}
func ParseBoolEnvVar(varName string, defaultValue bool) (bool, error) {
varValue, exists := os.LookupEnv(varName)
if !exists {
return defaultValue, nil
}
boolValue, err := strconv.ParseBool(varValue)
if err != nil {
return defaultValue, fmt.Errorf("failed to parse %s env var as bool: %w", varName, err)
}
return boolValue, nil
}

View File

@@ -2,7 +2,6 @@ package cautils
import (
"fmt"
"os"
"strings"
"testing"
@@ -75,9 +74,7 @@ func TestParseIntEnvVar(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.varValue != "" {
os.Setenv(tc.varName, tc.varValue)
} else {
os.Unsetenv(tc.varName)
t.Setenv(tc.varName, tc.varValue)
}
actual, err := ParseIntEnvVar(tc.varName, tc.defaultValue)
@@ -135,3 +132,57 @@ func TestStringSlicesAreEqual(t *testing.T) {
})
}
}
func TestParseBoolEnvVar(t *testing.T) {
testCases := []struct {
expectedErr string
name string
varName string
varValue string
defaultValue bool
expected bool
}{
{
name: "Variable does not exist",
varName: "DOES_NOT_EXIST",
varValue: "",
defaultValue: true,
expected: true,
expectedErr: "",
},
{
name: "Variable exists and is a valid bool",
varName: "MY_VAR",
varValue: "true",
defaultValue: false,
expected: true,
expectedErr: "",
},
{
name: "Variable exists but is not a valid bool",
varName: "MY_VAR",
varValue: "not_a_boolean",
defaultValue: false,
expected: false,
expectedErr: "failed to parse MY_VAR env var as bool",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.varValue != "" {
t.Setenv(tc.varName, tc.varValue)
}
actual, err := ParseBoolEnvVar(tc.varName, tc.defaultValue)
if tc.expectedErr != "" {
assert.NotNil(t, err)
assert.ErrorContains(t, err, tc.expectedErr)
} else {
assert.Nil(t, err)
}
assert.Equalf(t, tc.expected, actual, "unexpected result")
})
}
}

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