Compare commits

...

127 Commits

Author SHA1 Message Date
Matthias Bertschy
60d7276de3 Merge pull request #1901 from kubescape/copilot/fix-cis-framework-metrics-export
Fix CIS framework metrics not exported to Prometheus /v1/metrics endpoint
2025-12-05 09:45:06 +01:00
copilot-swe-agent[bot]
c05427ff38 Remove KS_METRICS_FRAMEWORKS environment variable support
Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-12-05 09:43:49 +01:00
Matthias Bertschy
3e245da02b Merge pull request #1902 from kubescape/copilot/fix-air-gapped-configuration
Fix air-gapped mode network access attempts
2025-12-05 08:44:50 +01:00
copilot-swe-agent[bot]
cc7aae470f Fix typo in comment: fom -> from
Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-04 13:47:52 +00:00
copilot-swe-agent[bot]
8d59a6074e Add frameworks query parameter to /v1/metrics endpoint
Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-04 13:45:55 +00:00
copilot-swe-agent[bot]
1f7dd6e5f5 Fix incorrect comment about default output format
Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-04 13:31:52 +00:00
copilot-swe-agent[bot]
bf5ca3c1f0 Add KS_METRICS_FRAMEWORKS env var to allow selecting specific frameworks
Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-04 13:30:11 +00:00
copilot-swe-agent[bot]
a8574c61ea Fix: properly handle nil downloadReleasedPolicy in getters
Ensure getter functions handle nil downloadReleasedPolicy correctly by creating a new instance when needed, maintaining backward compatibility with existing code while supporting air-gapped mode.

Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-03 21:19:39 +00:00
copilot-swe-agent[bot]
6f9c0ae85f Address code review feedback
- Fix string field checks in isAirGappedMode (use != "" instead of len() > 0)
- Use centralized isAirGappedMode function in getResourceHandler
- Improve comment clarity to reflect all air-gapped conditions

Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-03 21:14:04 +00:00
copilot-swe-agent[bot]
be2c74e48a Add test for isAirGappedMode function
Add comprehensive tests to verify air-gapped mode detection logic.

Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-03 21:11:18 +00:00
copilot-swe-agent[bot]
68da73855f Refactor: Extract isAirGappedMode helper function
Extract complex boolean condition into a dedicated helper function for better readability and maintainability.

Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-03 21:06:52 +00:00
copilot-swe-agent[bot]
5b3f2d0ff9 Fix air-gapped mode to prevent network access
- Skip version check when --keep-local flag is set
- Skip DownloadReleasedPolicy initialization when in air-gapped mode
- Skip KSCloudAPIConnector initialization when --keep-local is set

Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-03 21:03:01 +00:00
copilot-swe-agent[bot]
02637c7a8e Initial plan 2025-12-03 20:50:40 +00:00
Matthias Bertschy
7d5b374f9d Merge pull request #1900 from kubescape/copilot/fix-severity-missing-json
Add severity field to controls in JSON output
2025-12-03 21:44:49 +01:00
copilot-swe-agent[bot]
1dd6d7a1b3 Address code review feedback: nil check and trailing whitespace
Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-03 16:46:11 +00:00
copilot-swe-agent[bot]
6b80b85555 Add tests for results enrichment with severity
Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-03 16:42:03 +00:00
copilot-swe-agent[bot]
d88bc067e2 Add severity to controls in results section as well
Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-03 16:40:48 +00:00
copilot-swe-agent[bot]
ba78527c80 Enable ScanAll for prometheus metrics to include all frameworks including CIS
Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-03 16:32:43 +00:00
copilot-swe-agent[bot]
4c8692bf8c Remove test output files and update gitignore 2025-12-03 16:32:29 +00:00
copilot-swe-agent[bot]
742e3bb67f Add severity field to controls in JSON output
Co-authored-by: matthyx <20683409+matthyx@users.noreply.github.com>
2025-12-03 16:31:57 +00:00
copilot-swe-agent[bot]
a39f36c9fb Initial plan 2025-12-03 16:18:31 +00:00
copilot-swe-agent[bot]
9bc29032e1 Initial plan 2025-12-03 16:12:04 +00:00
Matthias Bertschy
a4a290a3ce Merge pull request #1898 from kubescape/dependabot/go_modules/github.com/cilium/cilium-1.16.17
Bump github.com/cilium/cilium from 1.16.9 to 1.16.17
2025-12-02 17:12:06 +01:00
dependabot[bot]
379a3fbc27 Bump github.com/cilium/cilium from 1.16.9 to 1.16.17
Bumps [github.com/cilium/cilium](https://github.com/cilium/cilium) from 1.16.9 to 1.16.17.
- [Release notes](https://github.com/cilium/cilium/releases)
- [Changelog](https://github.com/cilium/cilium/blob/1.16.17/CHANGELOG.md)
- [Commits](https://github.com/cilium/cilium/compare/1.16.9...1.16.17)

---
updated-dependencies:
- dependency-name: github.com/cilium/cilium
  dependency-version: 1.16.17
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-12-02 12:34:17 +01:00
Matthias Bertschy
a46098c034 Merge pull request #1896 from kubescape/summaries
always save WorkloadConfigurationScanResultSummaries
2025-12-01 08:05:57 +01:00
Matthias Bertschy
440f39ba3e Merge pull request #1897 from kubescape/docs
Revamp documentation
2025-11-30 11:47:23 +01:00
Matthias Bertschy
b6a4e282f9 Revamp documentation and reduce host sensor workers
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-11-30 11:47:00 +01:00
Matthias Bertschy
8deff34d12 always save WorkloadConfigurationScanResultSummaries
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-11-28 16:45:23 +01:00
Matthias Bertschy
acc9b54b2b Merge pull request #1895 from pfarikrispy/pfarikrispy-patch-1
update from Debian 12 to 13 when building container images
2025-11-26 14:52:53 +01:00
Christian Peper
1ffa29fbaa feat(security): update golang to debian trixie
build in the same env as the final image

Signed-off-by: Christian Peper <dreamszz@gmail.com>
2025-11-26 14:34:49 +01:00
Christian Peper
2ae30a8162 feat(security): update to Debian 13
Signed-off-by: Christian Peper <dreamszz@gmail.com>
2025-11-26 14:32:41 +01:00
Christian Peper
0ca5378c6b feat(security): update to Debian 13
use debian 13 as a base image

Signed-off-by: Christian Peper <dreamszz@gmail.com>
2025-11-26 14:30:46 +01:00
Matthias Bertschy
f51a1281f7 Merge pull request #1892 from kubescape/dependabot/go_modules/httphandler/golang.org/x/crypto-0.45.0
Bump golang.org/x/crypto from 0.41.0 to 0.45.0 in /httphandler
2025-11-21 20:59:38 +01:00
dependabot[bot]
5469d8bc04 Bump golang.org/x/crypto from 0.41.0 to 0.45.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.41.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.41.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-21 17:10:28 +01:00
Matthias Bertschy
bd7c0c580e fix go mod tidy
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-11-11 17:52:55 +01:00
Matthias Bertschy
154fec1385 Allow artifact-metadata read in release workflow
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-11-11 16:14:32 +01:00
Matthias Bertschy
5c2275e32a Allow artifact-metadata read in release workflow
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-11-11 16:13:16 +01:00
Matthias Bertschy
2da4736201 fix workflow permissions
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-11-11 16:10:15 +01:00
Matthias Bertschy
aefafeae6f Merge pull request #1890 from kubescape/dependabot/go_modules/github.com/opencontainers/selinux-1.13.0
Bump github.com/opencontainers/selinux from 1.12.0 to 1.13.0
2025-11-11 14:10:59 +01:00
dependabot[bot]
1772b38b8c Bump github.com/opencontainers/selinux from 1.12.0 to 1.13.0
Bumps [github.com/opencontainers/selinux](https://github.com/opencontainers/selinux) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/opencontainers/selinux/releases)
- [Commits](https://github.com/opencontainers/selinux/compare/v1.12.0...v1.13.0)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/selinux
  dependency-version: 1.13.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-11 12:44:20 +00:00
Matthias Bertschy
c844f42208 Merge pull request #1889 from kubescape/dependabot/go_modules/github.com/containerd/containerd-1.7.29
Bump github.com/containerd/containerd from 1.7.28 to 1.7.29
2025-11-07 08:44:37 +01:00
dependabot[bot]
b86d051998 Bump github.com/containerd/containerd from 1.7.28 to 1.7.29
Bumps [github.com/containerd/containerd](https://github.com/containerd/containerd) from 1.7.28 to 1.7.29.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v1.7.28...v1.7.29)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd
  dependency-version: 1.7.29
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-07 08:32:59 +01:00
Matthias Bertschy
aaa8d1ed35 Merge pull request #1888 from kubescape/dependabot/go_modules/github.com/containerd/containerd/v2-2.0.7
Bump github.com/containerd/containerd/v2 from 2.0.5 to 2.0.7
2025-11-07 08:16:43 +01:00
dependabot[bot]
441d16aa08 Bump github.com/containerd/containerd/v2 from 2.0.5 to 2.0.7
Bumps [github.com/containerd/containerd/v2](https://github.com/containerd/containerd) from 2.0.5 to 2.0.7.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v2.0.5...v2.0.7)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd/v2
  dependency-version: 2.0.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-07 08:04:26 +01:00
Matthias Bertschy
b33f1c8cc7 Merge pull request #1887 from Mujib-Ahasan/fix-print-sarif
fix: --format sarif logs as expected
2025-11-05 18:17:31 +01:00
Mujib Ahasan
4929af510e fix: --format sarif logs as expected
Signed-off-by: Mujib Ahasan <ahasanmujib8@gmail.com>
2025-11-04 20:30:10 +05:30
mandronic
f28bb11c55 removed 'procMount: Unmasked' from host-scanner daemonset definition (refs kubescape/helm-charts#711) (#1886)
Signed-off-by: Mihail Andronic <104365774+mandronic@users.noreply.github.com>
2025-11-03 13:40:14 +02:00
Matthias Bertschy
8bff4a02e1 Merge pull request #1884 from Mujib-Ahasan/fix-url
fixed "404" URL issue for command $kubescape scan.
2025-11-03 11:11:55 +01:00
Matthias Bertschy
33d1e018ec fix: update documentation links to include 'controls' path
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-11-03 07:47:37 +01:00
Mujib Ahasan
0c74599314 Test file updated
Signed-off-by: Mujib Ahasan <ahasanmujib8@gmail.com>
2025-10-30 01:14:25 +05:30
Mujib Ahasan
c23b85cc84 fixed 404 url issue in kubescape scan
Signed-off-by: Mujib Ahasan <ahasanmujib8@gmail.com>
2025-10-25 17:39:28 +05:30
Matthias Bertschy
aab10d14a2 Merge pull request #1881 from kubescape/dependabot/go_modules/github.com/nwaples/rardecode/v2-2.2.0
Bump github.com/nwaples/rardecode/v2 from 2.1.1 to 2.2.0
2025-10-21 09:45:29 +02:00
dependabot[bot]
2fcbe54e4e Bump github.com/nwaples/rardecode/v2 from 2.1.1 to 2.2.0
Bumps [github.com/nwaples/rardecode/v2](https://github.com/nwaples/rardecode) from 2.1.1 to 2.2.0.
- [Commits](https://github.com/nwaples/rardecode/compare/v2.1.1...v2.2.0)

---
updated-dependencies:
- dependency-name: github.com/nwaples/rardecode/v2
  dependency-version: 2.2.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-21 09:31:33 +02:00
Matthias Bertschy
078d154ab8 chore: update runners to use ubuntu-large for improved performance
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-10-15 10:12:24 +02:00
Matthias Bertschy
cc9dcf827e Revert "use ubuntu-latest-16-cores runner for docker build"
This reverts commit 76943d05fb.
2025-10-15 10:01:39 +02:00
Matthias Bertschy
76943d05fb use ubuntu-latest-16-cores runner for docker build
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-10-13 10:23:07 +02:00
Matthias Bertschy
621ac111cb Merge pull request #1880 from kubescape/hostagent-block
fix: improve error handling in hostscanner pod validation
2025-10-12 11:52:16 +02:00
Matthias Bertschy
3f80bce811 fix: improve error handling in hostscanner pod validation
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-10-10 16:13:43 +02:00
Matthias Bertschy
cc6895fc50 chore: update syft action to use latest version
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-10-10 15:00:35 +02:00
Matthias Bertschy
7d9d8e4b59 Merge pull request #1879 from kubescape/fix-post-release
fix: post release action does not take tag from GITHUB_REF env var
2025-10-08 15:57:44 +02:00
Amir Malka
f8d4bf515d fix: post release action does not take tag from GITHUB_REF env var
Signed-off-by: Amir Malka <amirm@armosec.io>
2025-10-08 16:18:35 +03:00
Matthias Bertschy
774ebe4a5f bump python to 3.9
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-09-29 17:44:13 +02:00
Matthias Bertschy
45a07a8046 Merge pull request #1877 from kubescape/grype
migrate to grype v0.99.1
2025-09-17 11:35:30 +02:00
Matthias Bertschy
ff96edae4d use grype v0.99.1
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-09-17 10:11:19 +02:00
Matthias Bertschy
34b82cad27 Merge pull request #1875 from kkrypt0nn/master
fix: Don't run scan in installation script
2025-09-16 15:13:31 +02:00
Matthias Bertschy
1a4c979ab8 update golangci-lint config
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-09-16 13:54:23 +02:00
Matthias Bertschy
3481af4a5b bump golangci-lint action
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-09-16 11:48:02 +02:00
Ben Hirschberg
71dc7a702c Merge pull request #1876 from cx-anjali-deore/fix/policy-binding-param
Fixed issue #1800 : Added parameterNotFoundAction in spec.paramRef while creating policy binding
2025-09-15 10:17:52 +03:00
anjali-deore
6d92389285 Fixed issue 1800
Signed-off-by: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com>
2025-09-13 18:06:37 +05:30
Krypton
bd0be45c0b fix: Don't run scan in installation script
Signed-off-by: Krypton <root@krypton.ninja>
2025-09-12 19:54:31 +02:00
Amir Malka
7ebf078d0c fix post-release workflow (#1873)
Signed-off-by: Amir Malka <amirm@armosec.io>
2025-09-02 19:51:20 +03:00
Matthias Bertschy
1bd729cf83 fix go tidy
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-09-02 14:45:12 +02:00
Matthias Bertschy
88b9b22bca Merge pull request #1857 from aadarsh-nagrath/default-matchers
feat: add default matchers option to image scanning
2025-09-02 14:25:29 +02:00
Matthias Bertschy
182162d521 gofmt
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-09-02 13:51:45 +02:00
Matthias Bertschy
1c02191bb1 Merge pull request #1866 from kubescape/tablewriter
replace olekukonko/tablewriter with jedib0t/go-pretty
2025-09-01 17:38:18 +02:00
Matthias Bertschy
ca66ccb33d replace olekukonko/tablewriter with jedib0t/go-pretty
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-09-01 16:38:42 +02:00
Matthias Bertschy
07eda20b88 Merge pull request #1869 from htsr/fix-imagescan-use-all-targets-exceptions
fix(imagescan): use all targets in exceptions
2025-09-01 16:29:04 +02:00
Matthias Bertschy
108c84d97d Merge pull request #1867 from cx-anjali-deore/feature/ListcontainerName
Issue 1817 fix: Show container name in Assisted remediation
2025-09-01 16:28:51 +02:00
Matthias Bertschy
35e7fa2b94 fix imports
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-09-01 15:49:31 +02:00
Hugo Thiessard
abb7917b29 fix(imagescan): use all targets in exceptions
Previously, kubescape only used the first target in scan image exceptions.
Added a test to verify the fix.

Signed-off-by: Hugo Thiessard <htsr@pm.me>
2025-09-01 15:05:00 +02:00
Matthias Bertschy
31ba56a0cf Merge pull request #1868 from kubescape/dependabot/go_modules/github.com/ulikunitz/xz-0.5.14
Bump github.com/ulikunitz/xz from 0.5.12 to 0.5.14
2025-09-01 09:43:12 +02:00
dependabot[bot]
b3efe4d003 Bump github.com/ulikunitz/xz from 0.5.12 to 0.5.14
Bumps [github.com/ulikunitz/xz](https://github.com/ulikunitz/xz) from 0.5.12 to 0.5.14.
- [Commits](https://github.com/ulikunitz/xz/compare/v0.5.12...v0.5.14)

---
updated-dependencies:
- dependency-name: github.com/ulikunitz/xz
  dependency-version: 0.5.14
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-01 05:35:18 +00:00
anjali-deore
5faade2b66 Fixed test cases
Signed-off-by: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com>
2025-08-31 17:37:32 +05:30
Matthias Bertschy
79207f66be don't read services from configmap, use file
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-08-29 15:21:25 +02:00
anjali-deore
af39f9a7ef fix removed space
Signed-off-by: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com>
2025-08-29 15:31:35 +05:30
anjali-deore
482b7c1f67 fix 1817 ,code cleanup
Signed-off-by: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com>
2025-08-29 15:20:00 +05:30
anjali-deore
82e2fd0be2 fix issue 1817,Added Container Name in control scan output
Signed-off-by: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com>
2025-08-29 15:20:00 +05:30
Matthias Bertschy
6eec751027 Merge pull request #1864 from kubescape/dependabot/go_modules/httphandler/github.com/hashicorp/go-getter-1.7.9
Bump github.com/hashicorp/go-getter from 1.7.8 to 1.7.9 in /httphandler
2025-08-21 17:48:44 +02:00
dependabot[bot]
4a6480c8b4 Bump github.com/hashicorp/go-getter from 1.7.8 to 1.7.9
Bumps [github.com/hashicorp/go-getter](https://github.com/hashicorp/go-getter) from 1.7.8 to 1.7.9.
- [Release notes](https://github.com/hashicorp/go-getter/releases)
- [Changelog](https://github.com/hashicorp/go-getter/blob/main/.goreleaser.yml)
- [Commits](https://github.com/hashicorp/go-getter/compare/v1.7.8...v1.7.9)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-getter
  dependency-version: 1.7.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-21 17:14:35 +02:00
Matthias Bertschy
a51bfa4c3e Merge pull request #1863 from kubescape/new_fix/issue_1284
Issue 1284 fix: new approach implemented
2025-08-19 16:02:53 +02:00
Yehudah Tor
2a48af3c17 new approach of fix implemented
Signed-off-by: Yehudah Tor <yehudahtor@gmail.com>
2025-08-19 12:22:15 +03:00
Matthias Bertschy
ffeb4577e3 refactor output formatting in prettyprinter and related files
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-08-18 12:15:54 +02:00
Matthias Bertschy
b5c7422355 Merge pull request #1862 from kubescape/threshold
bump helm.sh/helm/v3 to 3.18.5
2025-08-18 11:07:42 +02:00
Matthias Bertschy
e41b5d77a0 bump helm.sh/helm/v3 to 3.18.5
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-08-18 10:28:34 +02:00
Matthias Bertschy
5afaae8847 Merge pull request #1860 from kubescape/threshold
return error on image when severity threshold exceeded
2025-08-18 10:22:19 +02:00
Matthias Bertschy
011fc0689d return error on image when severity threshold exceeded
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-08-14 14:38:59 +02:00
aadarsh-nagrath
db30020c95 feat: add default matchers option to image scanning
hey! added the default matchers option for image scanning as requested in #1838. now you can choose between stock matchers and CPE matchers when scanning images.

what's new:
- added --use-default-matchers flag to scan/image/patch commands
- true = stock matchers (default behavior)
- false = CPE matchers (more precise)

usage:
# use CPE matchers for more precise detection
kubescape scan image nginx:latest --use-default-matchers=false

# or in scan command
kubescape scan --scan-images --use-default-matchers=false

everything's backward compatible - existing code works exactly the same. just added the new option for folks who want more control over their vulnerability detection.

fixes #1838

Signed-off-by: aadarsh-nagrath <anagrath1@gmail.com>
2025-08-06 21:48:40 +05:30
Matthias Bertschy
c5341a356b fix prettyprinter test results
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-07-28 15:06:09 +02:00
Matthias Bertschy
85a7f57373 Merge pull request #1854 from kubescape/fixurls
fix docs URL in tests
2025-07-28 14:55:14 +02:00
Matthias Bertschy
cd9ebdf08f fix workflow permissions
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-07-28 14:44:36 +02:00
Matthias Bertschy
bc602a78ab fix docs URL in tests
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-07-28 14:41:25 +02:00
Matthias Bertschy
a2361fd155 Merge pull request #1853 from kubescape/dependabot/go_modules/github.com/containerd/containerd/v2-2.0.5
Bump github.com/containerd/containerd/v2 from 2.0.4 to 2.0.5
2025-07-28 14:26:07 +02:00
Matthias Bertschy
aa8d41fc2e fix workflow permissions
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-07-28 14:18:31 +02:00
dependabot[bot]
5bd4beb41f Bump github.com/containerd/containerd/v2 from 2.0.4 to 2.0.5
Bumps [github.com/containerd/containerd/v2](https://github.com/containerd/containerd) from 2.0.4 to 2.0.5.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v2.0.4...v2.0.5)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd/v2
  dependency-version: 2.0.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 14:14:59 +02:00
Matthias Bertschy
dbf21dee37 Merge pull request #1852 from kubescape/dependabot/go_modules/helm.sh/helm/v3-3.17.4
Bump helm.sh/helm/v3 from 3.17.3 to 3.17.4
2025-07-28 14:13:32 +02:00
dependabot[bot]
be49d9b7be Bump helm.sh/helm/v3 from 3.17.3 to 3.17.4
Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.17.3 to 3.17.4.
- [Release notes](https://github.com/helm/helm/releases)
- [Commits](https://github.com/helm/helm/compare/v3.17.3...v3.17.4)

---
updated-dependencies:
- dependency-name: helm.sh/helm/v3
  dependency-version: 3.17.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 14:02:05 +02:00
Matthias Bertschy
7a5699fba3 Merge pull request #1851 from kubescape/dependabot/go_modules/github.com/cloudflare/circl-1.6.1
Bump github.com/cloudflare/circl from 1.5.0 to 1.6.1
2025-07-28 13:48:28 +02:00
dependabot[bot]
1f8afecea8 Bump github.com/cloudflare/circl from 1.5.0 to 1.6.1
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.5.0 to 1.6.1.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.5.0...v1.6.1)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-version: 1.6.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 13:33:52 +02:00
Matthias Bertschy
3ebb1d749e Merge pull request #1850 from kubescape/dependabot/go_modules/httphandler/github.com/go-viper/mapstructure/v2-2.3.0
Bump github.com/go-viper/mapstructure/v2 from 2.2.1 to 2.3.0 in /httphandler
2025-07-28 13:32:17 +02:00
dependabot[bot]
f80c9d947d Bump github.com/go-viper/mapstructure/v2 in /httphandler
Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.2.1 to 2.3.0.
- [Release notes](https://github.com/go-viper/mapstructure/releases)
- [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-viper/mapstructure/compare/v2.2.1...v2.3.0)

---
updated-dependencies:
- dependency-name: github.com/go-viper/mapstructure/v2
  dependency-version: 2.3.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 13:18:52 +02:00
Matthias Bertschy
03b76ff4aa Merge pull request #1833 from kubescape/dependabot/go_modules/github.com/open-policy-agent/opa-1.4.0
Bump github.com/open-policy-agent/opa from 1.3.0 to 1.4.0
2025-07-28 13:06:52 +02:00
dependabot[bot]
01531b6276 Bump github.com/open-policy-agent/opa from 1.3.0 to 1.4.0
Bumps [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) from 1.3.0 to 1.4.0.
- [Release notes](https://github.com/open-policy-agent/opa/releases)
- [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-policy-agent/opa/compare/v1.3.0...v1.4.0)

---
updated-dependencies:
- dependency-name: github.com/open-policy-agent/opa
  dependency-version: 1.4.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 12:20:18 +02:00
Ben Hirschberg
aedfe1c4c0 Merge pull request #1849 from kubescape/fix/addon-urls
added urls hub.armo --> kubescape.io
2025-07-27 14:19:34 +03:00
Yehudah Tor
d2bedc1d2b added urls
Signed-off-by: Yehudah Tor <yehudahtor@gmail.com>
2025-07-27 13:50:47 +03:00
Ben Hirschberg
35288e7b85 Merge pull request #1846 from kubescape/fix/update-links
Fix/update links
2025-07-27 10:01:27 +03:00
Matthias Bertschy
cd046fa695 fix buildnumber test
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-07-26 11:08:43 +02:00
Matthias Bertschy
407b8be08f Merge pull request #1848 from kubescape/fixgrype
close grype DB at the very end of processing
2025-07-25 11:23:56 +02:00
Matthias Bertschy
b211fe9148 Merge pull request #1847 from kubescape/fixversion
do not fail version if update info cannot be fetched
2025-07-25 11:23:24 +02:00
Matthias Bertschy
525e51d68e close grype DB at the very end of processing
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-07-25 09:50:10 +02:00
Matthias Bertschy
daabd6c81a do not fail version if update info cannot be fetched
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-07-25 08:48:27 +02:00
Yehudah Tor
5b351d5eec Done URL ref's before problematic ones
Signed-off-by: Yehudah Tor <yehudahtor@gmail.com>
2025-07-24 12:11:31 +03:00
Yehudah Tor
a5b607ae2e All controls updated
Signed-off-by: Yehudah Tor <yehudahtor@gmail.com>
2025-07-24 11:12:18 +03:00
Yehudah Tor
fec51b00ba getting started kubescape.io control links updated
Signed-off-by: Yehudah Tor <yehudahtor@gmail.com>
2025-07-23 15:16:41 +03:00
Amir Malka
4f9809eec1 fix: control-plane node taints check (#1843)
Signed-off-by: Amir Malka <amirm@armosec.io>
2025-07-15 11:06:09 +03:00
Matthias Bertschy
c0c25c3430 Merge pull request #1841 from kubescape/submit
check scanInfo.Submit in HandleResults to not submit by default
2025-06-30 09:09:05 +02:00
Matthias Bertschy
6ed3e408be check scanInfo.Submit in HandleResults to not submit by default
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-06-30 08:16:41 +02:00
Matthias Bertschy
6042818a71 use go 1.24
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2025-06-24 16:31:45 +02:00
105 changed files with 9309 additions and 3501 deletions

View File

@@ -27,6 +27,7 @@ jobs:
deployments: read
id-token: write
issues: read
models: read
discussions: read
packages: read
pages: read
@@ -54,6 +55,7 @@ jobs:
discussions: read
id-token: write
issues: read
models: read
packages: write
pages: read
pull-requests: read
@@ -66,7 +68,7 @@ jobs:
COMPONENT_NAME: kubescape
CGO_ENABLED: 0
GO111MODULE: ""
GO_VERSION: "1.23"
GO_VERSION: "1.25"
RELEASE: "latest"
CLIENT: test
secrets: inherit

View File

@@ -3,12 +3,12 @@ permissions: read-all
on:
push:
tags:
- 'v*.*.*-rc.*'
- "v*.*.*-rc.*"
jobs:
retag:
outputs:
NEW_TAG: ${{ steps.tag-calculator.outputs.NEW_TAG }}
runs-on: ubuntu22-core4-mem16-ssd150
runs-on: ubuntu-large
steps:
- uses: actions/checkout@v4
- id: tag-calculator
@@ -32,13 +32,14 @@ jobs:
statuses: read
contents: write
attestations: write
artifact-metadata: read
needs: [retag]
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
with:
COMPONENT_NAME: kubescape
CGO_ENABLED: 0
GO111MODULE: ""
GO_VERSION: "1.23"
GO_VERSION: "1.25"
RELEASE: ${{ needs.retag.outputs.NEW_TAG }}
CLIENT: release
secrets: inherit
@@ -59,6 +60,7 @@ jobs:
statuses: read
security-events: read
attestations: read
artifact-metadata: read
needs: [retag, binary-build]
uses: ./.github/workflows/c-create-release.yaml
with:
@@ -109,8 +111,9 @@ jobs:
statuses: read
attestations: read
contents: write
artifact-metadata: read
uses: ./.github/workflows/e-post-release.yaml
needs: [publish-image]
needs: [retag, publish-image]
with:
TAG: ${{ needs.retag.outputs.NEW_TAG }}
secrets: inherit

View File

@@ -27,7 +27,7 @@ jobs:
name: Create cross-platform build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: ubuntu22-core4-mem16-ssd150
runs-on: ubuntu-large
steps:
- uses: actions/checkout@v4
@@ -48,7 +48,7 @@ jobs:
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test -v ./...'
if: startsWith(github.ref, 'refs/tags')
- uses: anchore/sbom-action/download-syft@v0.15.2
- uses: anchore/sbom-action/download-syft@v0
name: Setup Syft
- uses: goreleaser/goreleaser-action@v6
@@ -70,20 +70,18 @@ jobs:
- name: golangci-lint
continue-on-error: false
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v8
with:
version: latest
version: v2.1
args: --timeout 10m
only-new-issues: true
skip-pkg-cache: true
skip-build-cache: true
scanners:
env:
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
name: PR Scanner
runs-on: ubuntu22-core4-mem16-ssd150
runs-on: ubuntu-large
steps:
- uses: actions/checkout@v4
with:
@@ -92,7 +90,7 @@ jobs:
- uses: actions/setup-go@v4
name: Installing go
with:
go-version: "1.23"
go-version: "1.25"
- name: Scanning - Forbidden Licenses (go-licenses)
id: licenses-scan
continue-on-error: true

View File

@@ -18,7 +18,7 @@ on:
GO_VERSION:
required: false
type: string
default: "1.23"
default: "1.25"
GO111MODULE:
required: false
type: string
@@ -70,7 +70,7 @@ on:
type: string
GO_VERSION:
type: string
default: "1.23"
default: "1.25"
GO111MODULE:
required: true
type: string
@@ -248,7 +248,7 @@ jobs:
CGO_ENABLED: 0
GO111MODULE: "on"
BUILD_PLATFORM: linux/amd64,linux/arm64
GO_VERSION: "1.23"
GO_VERSION: "1.25"
REQUIRED_TESTS: '[
"ks_microservice_create_2_cronjob_mitre_and_nsa_proxy",
"ks_microservice_triggering_with_cron_job",
@@ -308,7 +308,7 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: '3.8.13'
python-version: '3.9'
cache: 'pip'
- name: create env

View File

@@ -33,7 +33,7 @@ jobs:
CGO_ENABLED: 0
GO111MODULE: "on"
BUILD_PLATFORM: ${{ inputs.PLATFORMS && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
GO_VERSION: "1.23"
GO_VERSION: "1.25"
REQUIRED_TESTS: '[]'
COSIGN: ${{ inputs.CO_SIGN }}
HELM_E2E_TEST: false

View File

@@ -7,6 +7,7 @@ permissions:
discussions: read
id-token: write
issues: read
models: read
packages: read
pages: read
pull-requests: read

View File

@@ -19,7 +19,7 @@ jobs:
uses: rajatjindal/krew-release-bot@v0.0.47
if: github.repository_owner == 'kubescape'
env:
GITHUB_REF: ${{ inputs.TAG }}
GITHUB_REF: refs/tags/${{ inputs.TAG }}
- name: Invoke workflow to update packaging
uses: benc-uk/workflow-dispatch@v1
if: github.repository_owner == 'kubescape'

4
.gitignore vendored
View File

@@ -11,3 +11,7 @@ ca.srl
ks
dist/
# Test output files
customFilename.pdf
customFilename.xml

View File

@@ -1,51 +1,57 @@
linters-settings:
govet:
shadow: true
dupl:
threshold: 200
goconst:
min-len: 3
min-occurrences: 2
gocognit:
min-complexity: 65
version: "2"
linters:
enable:
- gosec
- staticcheck
- nolintlint
- gofmt
- unused
- govet
- bodyclose
- typecheck
- goimports
- ineffassign
- gosimple
- gosec
- nolintlint
disable:
# temporarily disabled
- errcheck
- dupl
- gocritic
- errcheck
- gochecknoglobals
- gochecknoinits
- gocognit
- gocritic
- lll
- nakedret
- revive
- stylecheck
- unconvert
- unparam
#- forbidigo # <- see later
# should remain disabled
- lll
- gochecknoinits
- gochecknoglobals
issues:
exclude-rules:
- linters:
- revive
text: "var-naming"
- linters:
- revive
text: "type name will be used as (.+?) by other packages, and that stutters"
- linters:
- stylecheck
text: "ST1003"
settings:
dupl:
threshold: 200
gocognit:
min-complexity: 65
goconst:
min-len: 3
min-occurrences: 2
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- revive
text: var-naming
- linters:
- revive
text: type name will be used as (.+?) by other packages, and that stutters
- linters:
- staticcheck
text: ST1003
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

471
README.md
View File

@@ -8,6 +8,7 @@
[![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)
[![Docs](https://img.shields.io/badge/docs-latest-brightgreen?logo=gitbook)](https://kubescape.io/docs/)
[![Stars](https://img.shields.io/github/stars/kubescape/kubescape?style=social)](https://github.com/kubescape/kubescape/stargazers)
[![Twitter Follow](https://img.shields.io/twitter/follow/kubescape?style=social)](https://twitter.com/kubescape)
[![Slack](https://img.shields.io/badge/slack-kubescape-blueviolet?logo=slack)](https://cloud-native.slack.com/archives/C04EY3ZF9GE)
@@ -22,100 +23,478 @@
_Comprehensive Kubernetes Security from Development to Runtime_
Kubescape is an open-source Kubernetes security platform that provides comprehensive security coverage, from left to right across the entire development and deployment lifecycle. It offers hardening, posture management, and runtime security capabilities to ensure robust protection for Kubernetes environments. It saves Kubernetes users and admins precious time, effort, and resources.
Kubescape scans clusters, YAML files, and Helm charts. It detects misconfigurations according to multiple frameworks (including [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo/?utm_source=github&utm_medium=repository), [MITRE ATT&CK®](https://www.armosec.io/glossary/mitre-attck-framework/?utm_source=github&utm_medium=repository) and the [CIS Benchmark](https://www.armosec.io/blog/cis-kubernetes-benchmark-framework-scanning-tools-comparison/?utm_source=github&utm_medium=repository)).
Kubescape is an open-source Kubernetes security platform that provides comprehensive security coverage, from left to right across the entire development and deployment lifecycle. It offers hardening, posture management, and runtime security capabilities to ensure robust protection for Kubernetes environments.
Kubescape was created by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository) and is a [Cloud Native Computing Foundation (CNCF) incubating project](https://www.cncf.io/projects/).
_Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape! 😀_
_Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape!_
## Demo
---
Kubescape has a command line tool that you can use to quickly get a report on the security posture of a Kubernetes cluster:
## 📑 Table of Contents
<img src="docs/img/demo-v3.gif">
- [Features](#-features)
- [Demo](#-demo)
- [Quick Start](#-quick-start)
- [Installation](#-installation)
- [CLI Commands](#-cli-commands)
- [Usage Examples](#-usage-examples)
- [Architecture](#-architecture)
- [In-Cluster Operator](#-in-cluster-operator)
- [Integrations](#-integrations)
- [Community](#-community)
- [Changelog](#changelog)
- [License](#license)
## Getting started
---
Experimenting with Kubescape is as easy as:
## ✨ Features
| Feature | Description |
|---------|-------------|
| 🔍 **Misconfiguration Scanning** | Scan clusters, YAML files, and Helm charts against NSA-CISA, MITRE ATT&CK®, and CIS Benchmarks |
| 🐳 **Image Vulnerability Scanning** | Detect CVEs in container images using [Grype](https://github.com/anchore/grype) |
| 🩹 **Image Patching** | Automatically patch vulnerable images using [Copacetic](https://github.com/project-copacetic/copacetic) |
| 🔧 **Auto-Remediation** | Automatically fix misconfigurations in Kubernetes manifests |
| 🛡️ **Admission Control** | Enforce security policies with Validating Admission Policies (VAP) |
| 📊 **Runtime Security** | eBPF-based runtime monitoring via [Inspektor Gadget](https://github.com/inspektor-gadget) |
| 🤖 **AI Integration** | MCP server for AI assistant integration |
---
## 🎬 Demo
<img src="docs/img/demo-v3.gif" alt="Kubescape CLI demo">
---
## 🚀 Quick Start
### 1. Install Kubescape
```sh
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
```
This script will automatically download the latest Kubescape CLI release and scan the Kubernetes cluster in your current kubectl context.
> 💡 See [Installation](#-installation) for more options (Homebrew, Krew, Windows, etc.)
Learn more about:
### 2. Run Your First Scan
* [Installing the Kubescape CLI](https://kubescape.io/docs/install-cli/)
* [Running your first scan](https://kubescape.io/docs/scanning/)
* [Accepting risk with exceptions](https://kubescape.io/docs/accepting-risk/)
```sh
# Scan your current cluster
kubescape scan
_Did you know you can use Kubescape in all these places?_
# Scan a specific YAML file or directory
kubescape scan /path/to/manifests/
# Scan a container image for vulnerabilities
kubescape scan image nginx:latest
```
### 3. Explore the Results
Kubescape provides a detailed security posture overview including:
- Control plane security status
- Access control risks
- Workload misconfigurations
- Network policy gaps
- Compliance scores (MITRE, NSA)
---
## 📦 Installation
### One-Line Install (Linux/macOS)
```bash
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
```
### Package Managers
| Platform | Command |
|----------|---------|
| **Homebrew** | `brew install kubescape` |
| **Krew** | `kubectl krew install kubescape` |
| **Arch Linux** | `yay -S kubescape` |
| **Ubuntu** | `sudo add-apt-repository ppa:kubescape/kubescape && sudo apt install kubescape` |
| **NixOS** | `nix-shell -p kubescape` |
| **Chocolatey** | `choco install kubescape` |
| **Scoop** | `scoop install kubescape` |
### Windows (PowerShell)
```powershell
iwr -useb https://raw.githubusercontent.com/kubescape/kubescape/master/install.ps1 | iex
```
📖 **[Full Installation Guide →](docs/installation.md)**
---
## 🛠️ CLI Commands
Kubescape provides a comprehensive CLI with the following commands:
| Command | Description |
|---------|-------------|
| [`kubescape scan`](#scanning) | Scan cluster, files, or images for security issues |
| [`kubescape scan image`](#image-scanning) | Scan container images for vulnerabilities |
| [`kubescape fix`](#auto-fix) | Auto-fix misconfigurations in manifest files |
| [`kubescape patch`](#image-patching) | Patch container images to fix vulnerabilities |
| [`kubescape list`](#list-frameworks-and-controls) | List available frameworks and controls |
| [`kubescape download`](#offline-support) | Download artifacts for offline/air-gapped use |
| [`kubescape config`](#configuration) | Manage cached configurations |
| [`kubescape operator`](#operator-commands) | Interact with in-cluster Kubescape operator |
| [`kubescape vap`](#validating-admission-policies) | Manage Validating Admission Policies |
| [`kubescape mcpserver`](#mcp-server) | Start MCP server for AI assistant integration |
| `kubescape completion` | Generate shell completion scripts |
| `kubescape version` | Display version information |
---
## 📖 Usage Examples
### Scanning
#### Scan a Running Cluster
```bash
# Default scan (all frameworks)
kubescape scan
# Scan with a specific framework
kubescape scan framework nsa
kubescape scan framework mitre
kubescape scan framework cis-v1.23-t1.0.1
# Scan a specific control
kubescape scan control C-0005 -v
```
#### Scan Files and Repositories
```bash
# Scan local YAML files
kubescape scan /path/to/manifests/
# Scan a Helm chart
kubescape scan /path/to/helm/chart/
# Scan a Git repository
kubescape scan https://github.com/kubescape/kubescape
# Scan with Kustomize
kubescape scan /path/to/kustomize/directory/
```
#### Scan Options
```bash
# Include/exclude namespaces
kubescape scan --include-namespaces production,staging
kubescape scan --exclude-namespaces kube-system,kube-public
# Use alternative kubeconfig
kubescape scan --kubeconfig /path/to/kubeconfig
# Set compliance threshold (exit code 1 if below threshold)
kubescape scan --compliance-threshold 80
# Set severity threshold
kubescape scan --severity-threshold high
```
#### Output Formats
```bash
# JSON output
kubescape scan --format json --output results.json
# JUnit XML (for CI/CD)
kubescape scan --format junit --output results.xml
# SARIF (for GitHub Code Scanning)
kubescape scan --format sarif --output results.sarif
# HTML report
kubescape scan --format html --output report.html
# PDF report
kubescape scan --format pdf --output report.pdf
```
### Image Scanning
```bash
# Scan a public image
kubescape scan image nginx:1.21
# Scan with verbose output
kubescape scan image nginx:1.21 -v
# Scan a private registry image
kubescape scan image myregistry/myimage:tag --username user --password pass
```
### Auto-Fix
Automatically fix misconfigurations in your manifest files:
```bash
# First, scan and save results to JSON
kubescape scan /path/to/manifests --format json --output results.json
# Then apply fixes
kubescape fix results.json
# Dry run (preview changes without applying)
kubescape fix results.json --dry-run
# Apply fixes without confirmation prompts
kubescape fix results.json --no-confirm
```
### Image Patching
Patch container images to fix OS-level vulnerabilities:
```bash
# Start buildkitd (required)
sudo buildkitd &
# Patch an image
sudo kubescape patch --image docker.io/library/nginx:1.22
# Specify custom output tag
sudo kubescape patch --image nginx:1.22 --tag nginx:1.22-patched
# See detailed vulnerability report
sudo kubescape patch --image nginx:1.22 -v
```
📖 **[Full Patch Command Documentation →](cmd/patch/README.md)**
### List Frameworks and Controls
```bash
# List available frameworks
kubescape list frameworks
# List all controls
kubescape list controls
# Output as JSON
kubescape list controls --format json
```
### Offline Support
Download artifacts for air-gapped environments:
```bash
# Download all artifacts
kubescape download artifacts --output /path/to/offline/dir
# Download a specific framework
kubescape download framework nsa --output /path/to/nsa.json
# Scan using downloaded artifacts
kubescape scan --use-artifacts-from /path/to/offline/dir
```
### Configuration
```bash
# View current configuration
kubescape config view
# Set account ID
kubescape config set accountID <your-account-id>
# Delete cached configuration
kubescape config delete
```
### Operator Commands
Interact with the in-cluster Kubescape operator:
```bash
# Trigger a configuration scan
kubescape operator scan configurations
# Trigger a vulnerability scan
kubescape operator scan vulnerabilities
```
### Validating Admission Policies
Manage Kubernetes Validating Admission Policies:
```bash
# Deploy the Kubescape CEL admission policy library
kubescape vap deploy-library | kubectl apply -f -
# Create a policy binding
kubescape vap create-policy-binding \
--name my-policy-binding \
--policy c-0016 \
--namespace my-namespace | kubectl apply -f -
```
### MCP Server
Start an MCP (Model Context Protocol) server for AI assistant integration:
```bash
kubescape mcpserver
```
The MCP server exposes Kubescape's vulnerability and configuration scan data to AI assistants, enabling natural language queries about your cluster's security posture.
**Available MCP Tools:**
- `list_vulnerability_manifests` - Discover vulnerability manifests
- `list_vulnerabilities_in_manifest` - List CVEs in a manifest
- `list_vulnerability_matches_for_cve` - Get details for a specific CVE
- `list_configuration_security_scan_manifests` - List configuration scan results
- `get_configuration_security_scan_manifest` - Get configuration scan details
---
## 🏗️ Architecture
Kubescape can run in two modes:
### CLI Mode
The CLI is a standalone tool that scans clusters, files, and images on-demand.
<div align="center">
<img src="docs/img/ksfromcodetodeploy.png" alt="Places you can use Kubescape: in your IDE, CI, CD, or against a running cluster.">
<img src="docs/img/ks-cli-arch.png" width="600" alt="CLI Architecture">
</div>
### Continuous security monitoring with the Kubescape Operator
**Key Components:**
- **[Open Policy Agent (OPA)](https://github.com/open-policy-agent/opa)** - Policy evaluation engine
- **[Regolibrary](https://github.com/kubescape/regolibrary)** - Library of security controls
- **[Grype](https://github.com/anchore/grype)** - Image vulnerability scanning
- **[Copacetic](https://github.com/project-copacetic/copacetic)** - Image patching
As well as a CLI, Kubescape provides an in-cluster mode, which is installed via a Helm chart. Kubescape in-cluster provides extensive features such as continuous scanning, image vulnerability scanning, runtime analysis, network policy generation, and more. [Learn more about the Kubescape operator](https://kubescape.io/docs/operator/).
### Operator Mode (In-Cluster)
### Using Kubescape as a GitHub Action
For continuous monitoring, deploy the Kubescape operator via Helm.
Kubescape can be used as a GitHub Action. This is a great way to integrate Kubescape into your CI/CD pipeline. You can find the Kubescape GitHub Action in the [GitHub Action marketplace](https://github.com/marketplace/actions/kubescape).
<div align="center">
<img src="docs/img/ks-operator-arch.png" width="600" alt="Operator Architecture">
</div>
## Under the hood
**Additional Capabilities:**
- Continuous configuration scanning
- Image vulnerability scanning
- Runtime analysis with eBPF
- Network policy generation
Kubescape uses [Open Policy Agent](https://github.com/open-policy-agent/opa) to verify Kubernetes objects against [a library of posture controls](https://github.com/kubescape/regolibrary).
For image scanning, it uses [Grype](https://github.com/anchore/grype).
For image patching, it uses [Copacetic](https://github.com/project-copacetic/copacetic).
For eBPF, it uses [Inspektor Gadget](https://github.com/inspektor-gadget)
📖 **[Full Architecture Documentation →](docs/architecture.md)**
By default, CLI scan results are printed in a console-friendly manner, but they can be:
---
* exported to JSON, junit XML or SARIF
* rendered to HTML or PDF
* submitted to a [cloud service](docs/providers.md)
## ☸️ In-Cluster Operator
### In-cluster architecture
The Kubescape operator provides continuous security monitoring in your cluster:
![Architecture diagram](docs/img/architecture-diagram.png)
```bash
# Add the Kubescape Helm repository
helm repo add kubescape https://kubescape.github.io/helm-charts/
## Community
# Install the operator
helm upgrade --install kubescape kubescape/kubescape-operator \
--namespace kubescape \
--create-namespace
```
Kubescape is an open source project. We welcome your feedback and ideas for improvement. We are part of the CNCF community and are evolving Kubescape in sync with the security needs of Kubernetes users. To learn more about where Kubescape is heading, please check out our [ROADMAP](https://github.com/kubescape/project-governance/blob/main/ROADMAP.md).
**Operator Features:**
- 🔄 Continuous misconfiguration scanning
- 🐳 Image vulnerability scanning for all workloads
- 🔍 Runtime threat detection (eBPF-based)
- 🌐 Network policy generation
- 📈 Prometheus metrics integration
If you feel inspired to contribute to Kubescape, check out our [CONTRIBUTING](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md) file to learn how. You can find the issues we are working on (triage to development) on the [Kubescaping board](https://github.com/orgs/kubescape/projects/4/views/1)
📖 **[Operator Installation Guide →](https://kubescape.io/docs/operator/)**
* Feel free to pick a task from the [board](https://github.com/orgs/kubescape/projects/4) or suggest a feature of your own.
* Open an issue on the board. We aim to respond to all issues within 48 hours.
* [Join the CNCF Slack](https://slack.cncf.io/) and then our [users](https://cloud-native.slack.com/archives/C04EY3ZF9GE) or [developers](https://cloud-native.slack.com/archives/C04GY6H082K) channel.
---
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
## 🔌 Integrations
For more information about the Kubescape community, please visit [COMMUNITY](https://github.com/kubescape/project-governance/blob/main/COMMUNITY.md).
### CI/CD
| Platform | Integration |
|----------|-------------|
| **GitHub Actions** | [kubescape/github-action](https://github.com/marketplace/actions/kubescape) |
| **GitLab CI** | [Documentation](https://kubescape.io/docs/integrations/gitlab/) |
| **Jenkins** | [Documentation](https://kubescape.io/docs/integrations/jenkins/) |
We would like to take this opportunity to thank all our contibutors to date.
### IDE Extensions
<br>
| IDE | Extension |
|-----|-----------|
| **VS Code** | [Kubescape Extension](https://marketplace.visualstudio.com/items?itemName=kubescape.kubescape) |
| **Lens** | [Kubescape Lens Extension](https://github.com/armosec/lens-kubescape) |
<a href = "https://github.com/kubescape/kubescape/graphs/contributors">
<img src = "https://contrib.rocks/image?repo=kubescape/kubescape"/>
### Where You Can Use Kubescape
<div align="center">
<img src="docs/img/ksfromcodetodeploy.png" alt="Kubescape integration points: IDE, CI, CD, Runtime">
</div>
---
## 👥 Community
Kubescape is a CNCF incubating project with an active community.
### Get Involved
- 💬 **[Slack - Users Channel](https://cloud-native.slack.com/archives/C04EY3ZF9GE)** - Ask questions, get help
- 💬 **[Slack - Developers Channel](https://cloud-native.slack.com/archives/C04GY6H082K)** - Contribute to development
- 🐛 **[GitHub Issues](https://github.com/kubescape/kubescape/issues)** - Report bugs and request features
- 📋 **[Project Board](https://github.com/orgs/kubescape/projects/4)** - See what we're working on
- 🗺️ **[Roadmap](https://github.com/kubescape/project-governance/blob/main/ROADMAP.md)** - Future plans
### Contributing
We welcome contributions! Please see our:
- **[Contributing Guide](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md)**
- **[Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md)**
### Community Resources
- **[Community Info](https://github.com/kubescape/project-governance/blob/main/COMMUNITY.md)**
- **[Governance](https://github.com/kubescape/project-governance/blob/main/GOVERNANCE.md)**
- **[Security Policy](https://github.com/kubescape/project-governance/blob/main/SECURITY.md)**
- **[Maintainers](https://github.com/kubescape/project-governance/blob/main/MAINTAINERS.md)**
### Contributors
<a href="https://github.com/kubescape/kubescape/graphs/contributors">
<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.
Kubescape changes are tracked on the [releases page](https://github.com/kubescape/kubescape/releases).
---
## License
Copyright 2021-2025, the Kubescape Authors. All rights reserved. Kubescape is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details.
Copyright 2021-2025, the Kubescape Authors. All rights reserved.
Kubescape is released under the [Apache 2.0 license](LICENSE).
Kubescape is a [Cloud Native Computing Foundation (CNCF) incubating project](https://www.cncf.io/projects/kubescape/) and was contributed by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository).
<div align="center">
<img src="https://raw.githubusercontent.com/cncf/artwork/refs/heads/main/other/cncf-member/incubating/color/cncf-incubating-color.svg" width="300" alt="CNCF Incubating Project">
</div>
</div>

View File

@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM golang:1.23-bookworm AS builder
FROM --platform=$BUILDPLATFORM golang:1.25-trixie AS builder
ENV GO111MODULE=on CGO_ENABLED=0
WORKDIR /work
@@ -13,7 +13,7 @@ RUN --mount=target=. \
--mount=type=cache,target=/go/pkg \
go run downloader/main.go
FROM gcr.io/distroless/static-debian12:nonroot
FROM gcr.io/distroless/static-debian13:nonroot
USER nonroot
WORKDIR /home/nonroot/

View File

@@ -1,19 +1,264 @@
## Docker Build
# Building Kubescape
### Build your own Docker image
This guide covers how to build Kubescape from source.
1. Clone Project
```
git clone https://github.com/kubescape/kubescape.git kubescape && cd "$_"
## Table of Contents
- [Prerequisites](#prerequisites)
- [Building the CLI](#building-the-cli)
- [Building Docker Images](#building-docker-images)
- [Build Options](#build-options)
- [Development Setup](#development-setup)
- [Troubleshooting](#troubleshooting)
---
## Prerequisites
### Required
- **Go 1.23+** - [Installation Guide](https://golang.org/doc/install)
- **Git** - For cloning the repository
- **Make** - For running build commands
### Optional (for Docker builds)
- **Docker** - [Installation Guide](https://docs.docker.com/get-docker/)
- **Docker Buildx** - For multi-platform builds (included with Docker Desktop)
### Verify Prerequisites
```bash
go version # Should be 1.21 or higher
git --version
make --version
docker --version # Optional
```
2. Build kubescape CLI Docker image
---
## Building the CLI
### Clone the Repository
```bash
git clone https://github.com/kubescape/kubescape.git
cd kubescape
```
### Build with Make
```bash
# Build for your current platform
make build
# The binary will be at ./kubescape
./kubescape version
```
### Build Directly with Go
```bash
go build -o kubescape .
```
### Cross-Compilation
Build for different platforms:
```bash
# Linux (amd64)
GOOS=linux GOARCH=amd64 go build -o kubescape-linux-amd64 .
# Linux (arm64)
GOOS=linux GOARCH=arm64 go build -o kubescape-linux-arm64 .
# macOS (amd64)
GOOS=darwin GOARCH=amd64 go build -o kubescape-darwin-amd64 .
# macOS (arm64 / Apple Silicon)
GOOS=darwin GOARCH=arm64 go build -o kubescape-darwin-arm64 .
# Windows (amd64)
GOOS=windows GOARCH=amd64 go build -o kubescape-windows-amd64.exe .
```
---
## Building Docker Images
### Build All Images
```bash
make all
docker buildx build -t kubescape-cli -f build/kubescape-cli.Dockerfile --build-arg="ks_binary=kubescape" --load .
```
3. Build kubescape Docker image
### Build CLI Docker Image
Build a Docker image containing only the Kubescape CLI:
```bash
# First build the binary
make build
# Then build the Docker image
docker buildx build \
-t kubescape-cli:latest \
-f build/kubescape-cli.Dockerfile \
--build-arg="ks_binary=kubescape" \
--load .
```
docker buildx build -t kubescape -f build/Dockerfile --load .
### Build Full Kubescape Image
Build the complete Kubescape image (includes HTTP handler):
```bash
docker buildx build \
-t kubescape:latest \
-f build/Dockerfile \
--load .
```
### Multi-Platform Build
Build for multiple architectures:
```bash
docker buildx build \
-t kubescape:latest \
-f build/Dockerfile \
--platform linux/amd64,linux/arm64 \
--push .
```
---
## Build Options
### Make Targets
| Target | Description |
|--------|-------------|
| `make build` | Build the Kubescape binary |
| `make test` | Run unit tests |
| `make all` | Build everything |
| `make clean` | Remove build artifacts |
### Build Tags
You can use Go build tags to customize the build:
```bash
# Example with build tags
go build -tags "netgo" -o kubescape .
```
### Version Information
To embed version information in the build:
```bash
VERSION=$(git describe --tags --always)
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
COMMIT=$(git rev-parse HEAD)
go build -ldflags "-X main.version=$VERSION -X main.buildDate=$BUILD_DATE -X main.commit=$COMMIT" -o kubescape .
```
---
## Development Setup
### Install Development Dependencies
```bash
# Install golangci-lint for linting
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# Install other tools as needed
go mod download
```
### Run Tests
```bash
# Run all tests
make test
# Run tests with coverage
go test -cover ./...
# Run specific package tests
go test ./core/...
```
### Run Linter
```bash
golangci-lint run
```
### Code Formatting
```bash
go fmt ./...
```
---
## Troubleshooting
### Build Fails with "module not found"
```bash
# Update dependencies
go mod tidy
go mod download
```
### CGO-related Errors
If you encounter CGO errors, try building with CGO disabled:
```bash
CGO_ENABLED=0 go build -o kubescape .
```
### Docker Build Fails
Ensure Docker daemon is running and you have sufficient permissions:
```bash
# Check Docker status
docker info
# Run with sudo if needed (Linux)
sudo docker buildx build ...
```
### Out of Memory During Build
For systems with limited memory:
```bash
# Limit Go's memory usage
GOGC=50 go build -o kubescape .
```
---
## Dockerfiles
| File | Description |
|------|-------------|
| `build/Dockerfile` | Full Kubescape image with HTTP handler |
| `build/kubescape-cli.Dockerfile` | Minimal CLI-only image |
---
## Related Documentation
- [Contributing Guide](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md)
- [Architecture](../docs/architecture.md)
- [Getting Started](../docs/getting-started.md)

View File

@@ -1,4 +1,4 @@
FROM gcr.io/distroless/static-debian12:debug-nonroot
FROM gcr.io/distroless/static-debian13:debug-nonroot
USER nonroot
WORKDIR /home/nonroot/

View File

@@ -26,7 +26,7 @@ var (
%[1]s list controls
Control documentation:
https://hub.armosec.io/docs/controls
https://kubescape.io/docs/controls/
`, cautils.ExecName())
)

View File

@@ -6,13 +6,12 @@ import (
"strings"
"time"
"github.com/docker/distribution/reference"
"github.com/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"
)
@@ -28,6 +27,7 @@ var patchCmdExamples = fmt.Sprintf(`
func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
var patchInfo metav1.PatchInfo
var scanInfo cautils.ScanInfo
var useDefaultMatchers bool
patchCmd := &cobra.Command{
Use: "patch --image <image>:<tag> [flags]",
@@ -49,12 +49,15 @@ func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
return err
}
results, err := ks.Patch(&patchInfo, &scanInfo)
// Set the UseDefaultMatchers field in scanInfo
scanInfo.UseDefaultMatchers = useDefaultMatchers
exceedsSeverityThreshold, err := ks.Patch(&patchInfo, &scanInfo)
if err != nil {
return err
}
if imagescan.ExceedsSeverityThreshold(results, imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)) {
if exceedsSeverityThreshold {
shared.TerminateOnExceedingSeverity(&scanInfo, logger.L())
}
@@ -76,6 +79,7 @@ func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
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")
patchCmd.PersistentFlags().BoolVarP(&useDefaultMatchers, "use-default-matchers", "", true, "Use default matchers (true) or CPE matchers (false) for image scanning")
return patchCmd
}

View File

@@ -53,7 +53,7 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd := &cobra.Command{
Use: "kubescape",
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://hub.armosec.io/docs",
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://kubescape.io/docs/",
Example: ksExamples,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
k8sinterface.SetClusterContextName(rootInfo.KubeContext)

View File

@@ -29,7 +29,7 @@ var (
Run '%[1]s list controls' for the list of supported controls
Control documentation:
https://hub.armosec.io/docs/controls
https://kubescape.io/docs/controls/
`, cautils.ExecName())
)
@@ -99,7 +99,7 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
if err != nil {
logger.L().Fatal(err.Error())
}
if err := results.HandleResults(ks.Context()); err != nil {
if err := results.HandleResults(ks.Context(), scanInfo); err != nil {
logger.L().Fatal(err.Error())
}
if !scanInfo.VerboseMode {

View File

@@ -117,7 +117,7 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
logger.L().Fatal(err.Error())
}
if err = results.HandleResults(ks.Context()); err != nil {
if err = results.HandleResults(ks.Context(), scanInfo); err != nil {
logger.L().Fatal(err.Error())
}

View File

@@ -8,7 +8,6 @@ import (
"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"
)
@@ -33,6 +32,7 @@ var (
func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
var imgCredentials shared.ImageCredentials
var exceptions string
var useDefaultMatchers bool
cmd := &cobra.Command{
Use: "image <image>:<tag> [flags]",
@@ -54,18 +54,19 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command
}
imgScanInfo := &metav1.ImageScanInfo{
Image: args[0],
Username: imgCredentials.Username,
Password: imgCredentials.Password,
Exceptions: exceptions,
Image: args[0],
Username: imgCredentials.Username,
Password: imgCredentials.Password,
Exceptions: exceptions,
UseDefaultMatchers: useDefaultMatchers,
}
results, err := ks.ScanImage(imgScanInfo, scanInfo)
exceedsSeverityThreshold, err := ks.ScanImage(imgScanInfo, scanInfo)
if err != nil {
return err
}
if imagescan.ExceedsSeverityThreshold(results, imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)) {
if exceedsSeverityThreshold {
shared.TerminateOnExceedingSeverity(scanInfo, logger.L())
}
@@ -77,6 +78,7 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command
cmd.PersistentFlags().StringVarP(&exceptions, "exceptions", "", "", "Path to the exceptions file")
cmd.PersistentFlags().StringVarP(&imgCredentials.Username, "username", "u", "", "Username for registry login")
cmd.PersistentFlags().StringVarP(&imgCredentials.Password, "password", "p", "", "Password for registry login")
cmd.PersistentFlags().BoolVarP(&useDefaultMatchers, "use-default-matchers", "", true, "Use default matchers (true) or CPE matchers (false)")
return cmd
}

View File

@@ -92,6 +92,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
scanCmd.PersistentFlags().BoolVarP(&scanInfo.PrintAttackTree, "print-attack-tree", "", false, "Print attack tree")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.EnableRegoPrint, "enable-rego-prints", "", false, "Enable sending to rego prints to the logs (use with debug log level: -l debug)")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.ScanImages, "scan-images", "", false, "Scan resources images")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.UseDefaultMatchers, "use-default-matchers", "", true, "Use default matchers (true) or CPE matchers (false) for image scanning")
scanCmd.PersistentFlags().MarkDeprecated("fail-threshold", "use '--compliance-threshold' flag instead. Flag will be removed at 1.Dec.2023")
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.")
@@ -139,7 +140,7 @@ func securityScan(scanInfo cautils.ScanInfo, ks meta.IKubescape) error {
return err
}
if err = results.HandleResults(ks.Context()); err != nil {
if err = results.HandleResults(ks.Context(), &scanInfo); err != nil {
return err
}

View File

@@ -5,6 +5,7 @@ import (
"os"
"reflect"
"testing"
"time"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/cmd/shared"
@@ -186,20 +187,23 @@ 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) {}
var _ helpers.ILogger = &spyLogger{}
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) TimedWrapper(funcName string, timeout time.Duration, task func()) {}
func (l *spyLogger) Fatal(msg string, details ...helpers.IDetails) {
firstDetail := details[0]

View File

@@ -70,7 +70,7 @@ func getWorkloadCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comma
logger.L().Fatal(err.Error())
}
if err = results.HandleResults(ks.Context()); err != nil {
if err = results.HandleResults(ks.Context(), scanInfo); err != nil {
logger.L().Fatal(err.Error())
}

View File

@@ -5,6 +5,7 @@ import (
"os"
"reflect"
"testing"
"time"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
@@ -20,20 +21,23 @@ 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) {}
var _ helpers.ILogger = &spyLogger{}
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) TimedWrapper(funcName string, timeout time.Duration, task func()) {}
func (l *spyLogger) Fatal(msg string, details ...helpers.IDetails) {
firstDetail := details[0]

View File

@@ -220,9 +220,11 @@ func createPolicyBinding(bindingName string, policyName string, action string, p
}
policyBinding.Spec.ValidationActions = []admissionv1.ValidationAction{admissionv1.ValidationAction(action)}
paramAction := admissionv1.DenyAction
if paramRefName != "" {
policyBinding.Spec.ParamRef = &admissionv1.ParamRef{
Name: paramRefName,
Name: paramRefName,
ParameterNotFoundAction: &paramAction,
}
}
// Marshal the policy binding to YAML

View File

@@ -16,14 +16,11 @@ func GetVersionCmd(ks meta.IKubescape) *cobra.Command {
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
v := versioncheck.NewIVersionCheckHandler(ks.Context())
versionCheckRequest := versioncheck.NewVersionCheckRequest("", versioncheck.BuildNumber, "", "", "version", nil)
if err := v.CheckLatestVersion(ks.Context(), versionCheckRequest); err != nil {
return err
}
_ = v.CheckLatestVersion(ks.Context(), versioncheck.NewVersionCheckRequest("", versioncheck.BuildNumber, "", "", "version", nil))
fmt.Fprintf(cmd.OutOrStdout(),
_, _ = fmt.Fprintf(cmd.OutOrStdout(),
"Your current version is: %s\n",
versionCheckRequest.ClientVersion,
versioncheck.BuildNumber,
)
return nil
},

View File

@@ -20,7 +20,7 @@ func TestGetVersionCmd(t *testing.T) {
}{
{
name: "Undefined Build Number",
buildNumber: "",
buildNumber: "unknown",
want: "Your current version is: unknown\n",
},
{

View File

@@ -1,14 +1,248 @@
# Kubescape core package
# Kubescape Core Package
The `core` package provides the main Kubescape scanning engine as a Go library, allowing you to integrate Kubescape security scanning directly into your applications.
## Table of Contents
- [Installation](#installation)
- [Quick Start](#quick-start)
- [API Reference](#api-reference)
- [Examples](#examples)
- [Configuration Options](#configuration-options)
---
## Installation
```bash
go get github.com/kubescape/kubescape/v3/core
```
---
## Quick Start
```go
package main
// initialize kubescape
ks := core.NewKubescape()
import (
"context"
"fmt"
"log"
// scan cluster
results, err := ks.Scan(&cautils.ScanInfo{})
"github.com/kubescape/kubescape/v3/core"
"github.com/kubescape/kubescape/v3/core/cautils"
)
// convert scan results to json
jsonRes, err := results.ToJson()
func main() {
ctx := context.Background()
```
// Initialize Kubescape
ks := core.NewKubescape(ctx)
// Configure scan
scanInfo := &cautils.ScanInfo{
// Scan the current cluster
ScanAll: true,
}
// Run scan
results, err := ks.Scan(scanInfo)
if err != nil {
log.Fatalf("Scan failed: %v", err)
}
// Convert results to JSON
jsonRes, err := results.ToJson()
if err != nil {
log.Fatalf("Failed to convert results: %v", err)
}
fmt.Println(string(jsonRes))
}
```
---
## API Reference
### Creating a Kubescape Instance
```go
// Create with context
ks := core.NewKubescape(ctx)
```
### Scanning
```go
// Scan with configuration
results, err := ks.Scan(scanInfo)
```
### Listing Frameworks and Controls
```go
// List available policies
err := ks.List(listPolicies)
```
### Downloading Artifacts
```go
// Download for offline use
err := ks.Download(downloadInfo)
```
### Image Scanning
```go
// Scan container image
exceedsSeverity, err := ks.ScanImage(imgScanInfo, scanInfo)
```
### Fixing Misconfigurations
```go
// Apply fixes to manifests
err := ks.Fix(fixInfo)
```
---
## Examples
### Scan a Specific Framework
```go
scanInfo := &cautils.ScanInfo{}
scanInfo.SetPolicyIdentifiers([]string{"nsa"}, "framework")
results, err := ks.Scan(scanInfo)
```
### Scan Specific Namespaces
```go
scanInfo := &cautils.ScanInfo{
IncludeNamespaces: "production,staging",
}
results, err := ks.Scan(scanInfo)
```
### Scan Local YAML Files
```go
scanInfo := &cautils.ScanInfo{
InputPatterns: []string{"/path/to/manifests"},
}
scanInfo.SetScanType(cautils.ScanTypeRepo)
results, err := ks.Scan(scanInfo)
```
### Export Results to Different Formats
```go
results, _ := ks.Scan(scanInfo)
// JSON
jsonData, _ := results.ToJson()
// Get summary
summary := results.GetData().Report.SummaryDetails
fmt.Printf("Compliance Score: %.2f%%\n", summary.ComplianceScore)
```
### Scan with Compliance Threshold
```go
scanInfo := &cautils.ScanInfo{
ComplianceThreshold: 80.0, // Fail if below 80%
}
results, err := ks.Scan(scanInfo)
if err != nil {
// Handle scan failure
}
// Check if threshold was exceeded
if results.GetData().Report.SummaryDetails.ComplianceScore < scanInfo.ComplianceThreshold {
log.Fatal("Compliance score below threshold")
}
```
---
## Configuration Options
### ScanInfo Fields
| Field | Type | Description |
|-------|------|-------------|
| `AccountID` | string | Kubescape SaaS account ID |
| `AccessKey` | string | Kubescape SaaS access key |
| `InputPatterns` | []string | Paths to scan (files, directories, URLs) |
| `ExcludedNamespaces` | string | Comma-separated namespaces to exclude |
| `IncludeNamespaces` | string | Comma-separated namespaces to include |
| `Format` | string | Output format (json, junit, sarif, etc.) |
| `Output` | string | Output file path |
| `VerboseMode` | bool | Show all resources in output |
| `FailThreshold` | float32 | Fail threshold percentage |
| `ComplianceThreshold` | float32 | Compliance threshold percentage |
| `UseExceptions` | string | Path to exceptions file |
| `UseArtifactsFrom` | string | Path to offline artifacts |
| `Submit` | bool | Submit results to SaaS |
| `Local` | bool | Keep results local (don't submit) |
---
## Error Handling
```go
results, err := ks.Scan(scanInfo)
if err != nil {
switch {
case errors.Is(err, context.DeadlineExceeded):
log.Fatal("Scan timed out")
case errors.Is(err, context.Canceled):
log.Fatal("Scan was canceled")
default:
log.Fatalf("Scan error: %v", err)
}
}
```
---
## Thread Safety
The Kubescape instance is safe for concurrent use. You can run multiple scans in parallel:
```go
var wg sync.WaitGroup
for _, ns := range namespaces {
wg.Add(1)
go func(namespace string) {
defer wg.Done()
scanInfo := &cautils.ScanInfo{
IncludeNamespaces: namespace,
}
results, _ := ks.Scan(scanInfo)
// Process results...
}(ns)
}
wg.Wait()
```
---
## Related Documentation
- [CLI Reference](../docs/cli-reference.md)
- [Getting Started Guide](../docs/getting-started.md)
- [Architecture](../docs/architecture.md)

View File

@@ -24,8 +24,7 @@ const (
configFileName string = "config"
kubescapeNamespace string = "kubescape"
kubescapeConfigMapName string = "kubescape-config" // deprecated - for backward compatibility
kubescapeCloudConfigMapName string = "ks-cloud-config" // deprecated - for backward compatibility
kubescapeConfigMapName string = "kubescape-config" // deprecated - for backward compatibility
cloudConfigMapLabelSelector string = "kubescape.io/infra=config"
credsLabelSelectors string = "kubescape.io/infra=credentials" //nolint:gosec
@@ -207,6 +206,8 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, accessKey, clu
loadConfigFromFile(c.configObj)
}
loadUrlsFromFile(c.configObj)
// second, load urls from config map
c.updateConfigEmptyFieldsFromKubescapeConfigMap()
@@ -270,15 +271,12 @@ func (c *ClusterConfig) updateConfigEmptyFieldsFromKubescapeConfigMap() error {
return err
}
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]
}
if ksConfigMap != nil {
@@ -291,30 +289,6 @@ func (c *ClusterConfig) updateConfigEmptyFieldsFromKubescapeConfigMap() error {
}
}
if urlsConfigMap != nil {
if jsonConf, ok := urlsConfigMap.Data["services"]; ok {
services, err := servicediscovery.GetServices(
servicediscoveryv2.NewServiceDiscoveryStreamV2([]byte(jsonConf)),
)
if err != nil {
// try to parse as v1
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
}
@@ -397,7 +371,7 @@ func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {
func loadConfigFromFile(configObj *ConfigObj) error {
dat, err := os.ReadFile(ConfigFileFullPath())
if err != nil {
return err
return nil // no config file
}
return readConfig(dat, configObj)
}
@@ -413,6 +387,32 @@ func readConfig(dat []byte, configObj *ConfigObj) error {
return nil
}
func loadUrlsFromFile(obj *ConfigObj) error {
dat, err := os.ReadFile("/etc/config/services.json")
if err != nil {
return nil // no config file
}
services, err := servicediscovery.GetServices(
servicediscoveryv2.NewServiceDiscoveryStreamV2(dat),
)
if err != nil {
// try to parse as v1
services, err = servicediscovery.GetServices(
servicediscoveryv1.NewServiceDiscoveryStreamV1(dat),
)
if err != nil {
return err
}
}
if services.GetApiServerUrl() != "" {
obj.CloudAPIURL = services.GetApiServerUrl()
}
if services.GetReportReceiverHttpUrl() != "" {
obj.CloudReportURL = services.GetReportReceiverHttpUrl()
}
return nil
}
func DeleteConfigFile() error {
return os.Remove(ConfigFileFullPath())
}

View File

@@ -4,7 +4,10 @@ import (
"context"
"sort"
"github.com/anchore/grype/grype/presenter/models"
"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/pkg"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/syft/syft/sbom"
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/opa-utils/reporthandling"
@@ -20,8 +23,14 @@ type K8SResources map[string][]string
type ExternalResources map[string][]string
type ImageScanData struct {
PresenterConfig *models.PresenterConfig
Image string
Context pkg.Context
IgnoredMatches []match.IgnoredMatch
Image string
Matches match.Matches
Packages []pkg.Package
RemainingMatches *match.Matches
SBOM *sbom.SBOM
VulnerabilityProvider vulnerability.Provider
}
type ScanTypes string

View File

@@ -1,6 +1,9 @@
package cautils
import (
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
@@ -12,7 +15,12 @@ import (
helmchart "helm.sh/helm/v3/pkg/chart"
helmloader "helm.sh/helm/v3/pkg/chart/loader"
helmchartutil "helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli"
helmdownloader "helm.sh/helm/v3/pkg/downloader"
helmengine "helm.sh/helm/v3/pkg/engine"
helmgetter "helm.sh/helm/v3/pkg/getter"
helmregistry "helm.sh/helm/v3/pkg/registry"
"k8s.io/client-go/util/homedir"
)
type HelmChart struct {
@@ -24,7 +32,51 @@ func IsHelmDirectory(path string) (bool, error) {
return helmchartutil.IsChartDir(path)
}
// newRegistryClient creates a Helm registry client for chart authentication
func newRegistryClient(certFile, keyFile, caFile string, insecureSkipTLS, plainHTTP bool, username, password string) (*helmregistry.Client, error) {
// Basic client options with debug disabled
opts := []helmregistry.ClientOption{
helmregistry.ClientOptDebug(false),
helmregistry.ClientOptWriter(io.Discard),
}
// Add TLS certificates if provided
if certFile != "" && keyFile != "" {
opts = append(opts, helmregistry.ClientOptCredentialsFile(certFile))
}
// Add CA certificate if provided
if caFile != "" {
opts = append(opts, helmregistry.ClientOptCredentialsFile(caFile))
}
// Enable plain HTTP if needed
if insecureSkipTLS {
opts = append(opts, helmregistry.ClientOptPlainHTTP())
}
registryClient, err := helmregistry.NewClient(opts...)
if err != nil {
return nil, err
}
return registryClient, nil
}
// defaultKeyring returns the default GPG keyring path for chart verification
func defaultKeyring() string {
if v, ok := os.LookupEnv("GNUPGHOME"); ok {
return filepath.Join(v, "pubring.gpg")
}
return filepath.Join(homedir.HomeDir(), ".gnupg", "pubring.gpg")
}
func NewHelmChart(path string) (*HelmChart, error) {
// Build chart dependencies before loading if Chart.lock exists
if err := buildDependencies(path); err != nil {
logger.L().Warning("Failed to build chart dependencies", helpers.String("path", path), helpers.Error(err))
}
chart, err := helmloader.Load(path)
if err != nil {
return nil, err
@@ -36,6 +88,35 @@ func NewHelmChart(path string) (*HelmChart, error) {
}, nil
}
// buildDependencies builds chart dependencies using the downloader manager
func buildDependencies(chartPath string) error {
// Create registry client for authentication
registryClient, err := newRegistryClient("", "", "", false, false, "", "")
if err != nil {
return fmt.Errorf("failed to create registry client: %w", err)
}
// Create downloader manager with required configuration
settings := cli.New()
manager := &helmdownloader.Manager{
Out: io.Discard, // Suppress output during scanning
ChartPath: chartPath,
Keyring: defaultKeyring(),
SkipUpdate: false, // Allow updates to get latest dependencies
Getters: helmgetter.All(settings),
RegistryClient: registryClient,
Debug: false,
}
// Build dependencies from Chart.lock file
err = manager.Build()
if e, ok := err.(helmdownloader.ErrRepoNotFound); ok {
return fmt.Errorf("%s. Please add missing repos via 'helm repo add'", e.Error())
}
return err
}
func (hc *HelmChart) GetName() string {
return hc.chart.Name()
}

View File

@@ -137,6 +137,7 @@ type ScanInfo struct {
TriggeredByCLI bool // indicates whether the scan was triggered by the CLI
ScanType ScanTypes
ScanImages bool
UseDefaultMatchers bool
ChartPath string
FilePath string
scanningContext *ScanningContext

View File

@@ -7,7 +7,6 @@ import (
"regexp"
"strings"
"github.com/anchore/grype/grype/presenter/models"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/core/cautils"
ksmetav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
@@ -111,7 +110,9 @@ func regexStringMatch(pattern, target string) bool {
// exception policy.
func isTargetImage(targets []Target, attributes Attributes) bool {
for _, target := range targets {
return regexStringMatch(target.Attributes.Registry, attributes.Registry) && regexStringMatch(target.Attributes.Organization, attributes.Organization) && regexStringMatch(target.Attributes.ImageName, attributes.ImageName) && regexStringMatch(target.Attributes.ImageTag, attributes.ImageTag)
if regexStringMatch(target.Attributes.Registry, attributes.Registry) && regexStringMatch(target.Attributes.Organization, attributes.Organization) && regexStringMatch(target.Attributes.ImageName, attributes.ImageName) && regexStringMatch(target.Attributes.ImageTag, attributes.ImageTag) {
return true
}
}
return false
@@ -161,11 +162,16 @@ func getUniqueVulnerabilitiesAndSeverities(policies []VulnerabilitiesIgnorePolic
return uniqueVulnsList, uniqueSeversList
}
func (ks *Kubescape) ScanImage(imgScanInfo *ksmetav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
func (ks *Kubescape) ScanImage(imgScanInfo *ksmetav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (bool, error) {
logger.L().Start(fmt.Sprintf("Scanning image %s...", imgScanInfo.Image))
dbCfg, _ := imagescan.NewDefaultDBConfig()
svc := imagescan.NewScanService(dbCfg)
distCfg, installCfg, _ := imagescan.NewDefaultDBConfig()
svc, err := imagescan.NewScanServiceWithMatchers(distCfg, installCfg, imgScanInfo.UseDefaultMatchers)
if err != nil {
logger.L().StopError(fmt.Sprintf("Failed to initialize image scanner: %s", err))
return false, err
}
defer svc.Close()
creds := imagescan.RegistryCredentials{
Username: imgScanInfo.Username,
@@ -178,16 +184,16 @@ func (ks *Kubescape) ScanImage(imgScanInfo *ksmetav1.ImageScanInfo, scanInfo *ca
exceptionPolicies, err := GetImageExceptionsFromFile(imgScanInfo.Exceptions)
if err != nil {
logger.L().StopError(fmt.Sprintf("Failed to load exceptions from file: %s", imgScanInfo.Exceptions))
return nil, err
return false, err
}
vulnerabilityExceptions, severityExceptions = getUniqueVulnerabilitiesAndSeverities(exceptionPolicies, imgScanInfo.Image)
}
scanResults, err := svc.Scan(ks.Context(), imgScanInfo.Image, creds, vulnerabilityExceptions, severityExceptions)
imageScanData, err := svc.Scan(ks.Context(), imgScanInfo.Image, creds, vulnerabilityExceptions, severityExceptions)
if err != nil {
logger.L().StopError(fmt.Sprintf("Failed to scan image: %s", imgScanInfo.Image))
return nil, err
return false, err
}
logger.L().StopSuccess(fmt.Sprintf("Successfully scanned image: %s", imgScanInfo.Image))
@@ -200,12 +206,7 @@ func (ks *Kubescape) ScanImage(imgScanInfo *ksmetav1.ImageScanInfo, scanInfo *ca
resultsHandler := resultshandling.NewResultsHandler(nil, outputPrinters, uiPrinter)
resultsHandler.ImageScanData = []cautils.ImageScanData{
{
PresenterConfig: scanResults,
Image: imgScanInfo.Image,
},
}
resultsHandler.ImageScanData = []cautils.ImageScanData{*imageScanData}
return scanResults, resultsHandler.HandleResults(ks.Context())
return svc.ExceedsSeverityThreshold(imagescan.ParseSeverity(scanInfo.FailThresholdSeverity), imageScanData.Matches), resultsHandler.HandleResults(ks.Context(), scanInfo)
}

View File

@@ -241,6 +241,33 @@ func TestIsTargetImage(t *testing.T) {
},
expected: true,
},
{
targets: []Target{
{
Attributes: Attributes{
Registry: "quay.io",
Organization: "kubescape",
ImageName: "kubescape*",
ImageTag: "",
},
},
{
Attributes: Attributes{
Registry: "docker.io",
Organization: "library",
ImageName: "alpine",
ImageTag: "",
},
},
},
attributes: Attributes{
Registry: "docker.io",
Organization: "library",
ImageName: "alpine",
ImageTag: "latest",
},
expected: true,
},
}
for _, tt := range tests {

View File

@@ -90,7 +90,11 @@ func getResourceHandler(ctx context.Context, scanInfo *cautils.ScanInfo, tenantC
return resourcehandler.NewFileResourceHandler()
}
getter.GetKSCloudAPIConnector()
// Only initialize cloud connector if not in air-gapped mode
// This call initializes the global cloud API connector for later use
if !isAirGappedMode(scanInfo) {
_ = getter.GetKSCloudAPIConnector()
}
rbacObjects := getRBACHandler(tenantConfig, k8s, scanInfo.Submit)
return resourcehandler.NewK8sResourceHandler(k8s, hostSensorHandler, rbacObjects, tenantConfig.GetContextName())
}

View File

@@ -7,14 +7,13 @@ import (
"sort"
"strings"
"github.com/jwalton/gchalk"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/kubescape/kubescape/v3/core/cautils"
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer"
v2 "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/maruel/natural"
"github.com/olekukonko/tablewriter"
)
var listFunc = map[string]func(context.Context, *metav1.ListPolicies) ([]string, error){
@@ -100,30 +99,19 @@ func prettyPrintListFormat(ctx context.Context, targetPolicy string, policies []
return
}
policyTable := tablewriter.NewWriter(printer.GetWriter(ctx, ""))
policyTable := table.NewWriter()
policyTable.SetOutputMirror(printer.GetWriter(ctx, ""))
policyTable.SetAutoWrapText(true)
header := fmt.Sprintf("Supported %s", targetPolicy)
policyTable.SetHeader([]string{header})
policyTable.SetHeaderLine(true)
policyTable.SetRowLine(true)
policyTable.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
policyTable.SetAutoFormatHeaders(false)
policyTable.SetAlignment(tablewriter.ALIGN_CENTER)
policyTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
data := v2.Matrix{}
policyTable.AppendHeader(table.Row{header})
policyTable.Style().Options.SeparateHeader = true
policyTable.Style().Options.SeparateRows = true
policyTable.Style().Format.HeaderAlign = text.AlignLeft
policyTable.Style().Format.Header = text.FormatDefault
policyTable.Style().Format.RowAlign = text.AlignCenter
policyTable.Style().Box = table.StyleBoxRounded
controlRows := generatePolicyRows(policies)
var headerColors []tablewriter.Colors
for range controlRows[0] {
headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor})
}
policyTable.SetHeaderColor(headerColors...)
data = append(data, controlRows...)
policyTable.AppendBulk(data)
policyTable.AppendRows(generatePolicyRows(policies))
policyTable.Render()
}
@@ -134,40 +122,32 @@ func jsonListFormat(_ context.Context, _ string, policies []string) {
}
func prettyPrintControls(ctx context.Context, policies []string) {
controlsTable := tablewriter.NewWriter(printer.GetWriter(ctx, ""))
controlsTable := table.NewWriter()
controlsTable.SetOutputMirror(printer.GetWriter(ctx, ""))
controlsTable.SetAutoWrapText(false)
controlsTable.SetHeaderLine(true)
controlsTable.SetRowLine(true)
controlsTable.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
controlsTable.SetAutoFormatHeaders(false)
controlsTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
controlsTable.Style().Options.SeparateHeader = true
controlsTable.Style().Options.SeparateRows = true
controlsTable.Style().Format.HeaderAlign = text.AlignLeft
controlsTable.Style().Format.Header = text.FormatDefault
controlsTable.Style().Box = table.StyleBoxRounded
controlsTable.SetColumnConfigs([]table.ColumnConfig{{Number: 1, Align: text.AlignRight}})
controlRows := generateControlRows(policies)
short := utils.CheckShortTerminalWidth(controlRows, []string{"Control ID", "Control name", "Docs", "Frameworks"})
short := utils.CheckShortTerminalWidth(controlRows, table.Row{"Control ID", "Control name", "Docs", "Frameworks"})
if short {
controlsTable.SetAutoWrapText(false)
controlsTable.SetHeader([]string{"Controls"})
controlsTable.AppendHeader(table.Row{"Controls"})
controlRows = shortFormatControlRows(controlRows)
} else {
controlsTable.SetHeader([]string{"Control ID", "Control name", "Docs", "Frameworks"})
controlsTable.AppendHeader(table.Row{"Control ID", "Control name", "Docs", "Frameworks"})
}
var headerColors []tablewriter.Colors
for range controlRows[0] {
headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor})
}
controlsTable.SetHeaderColor(headerColors...)
data := v2.Matrix{}
data = append(data, controlRows...)
controlsTable.AppendBulk(data)
controlsTable.AppendRows(controlRows)
controlsTable.Render()
}
func generateControlRows(policies []string) [][]string {
rows := [][]string{}
func generateControlRows(policies []string) []table.Row {
rows := make([]table.Row, 0, len(policies))
for _, control := range policies {
@@ -188,7 +168,7 @@ func generateControlRows(policies []string) [][]string {
docs := cautils.GetControlLink(id)
currentRow := []string{id, control, docs, strings.Replace(framework, " ", "\n", -1)}
currentRow := table.Row{id, control, docs, strings.Replace(framework, " ", "\n", -1)}
rows = append(rows, currentRow)
}
@@ -196,20 +176,19 @@ func generateControlRows(policies []string) [][]string {
return rows
}
func generatePolicyRows(policies []string) [][]string {
rows := [][]string{}
func generatePolicyRows(policies []string) []table.Row {
rows := make([]table.Row, 0, len(policies))
for _, policy := range policies {
currentRow := []string{policy}
rows = append(rows, currentRow)
rows = append(rows, table.Row{policy})
}
return rows
}
func shortFormatControlRows(controlRows [][]string) [][]string {
rows := [][]string{}
func shortFormatControlRows(controlRows []table.Row) []table.Row {
rows := make([]table.Row, 0, len(controlRows))
for _, controlRow := range controlRows {
rows = append(rows, []string{fmt.Sprintf("Control ID"+strings.Repeat(" ", 3)+": %+v\nControl Name"+strings.Repeat(" ", 1)+": %+v\nDocs"+strings.Repeat(" ", 9)+": %+v\nFrameworks"+strings.Repeat(" ", 3)+": %+v", controlRow[0], controlRow[1], controlRow[2], strings.Replace(controlRow[3], "\n", " ", -1))})
rows = append(rows, table.Row{fmt.Sprintf("Control ID"+strings.Repeat(" ", 3)+": %+v\nControl Name"+strings.Repeat(" ", 1)+": %+v\nDocs"+strings.Repeat(" ", 9)+": %+v\nFrameworks"+strings.Repeat(" ", 3)+": %+v", controlRow[0], controlRow[1], controlRow[2], strings.Replace(controlRow[3].(string), "\n", " ", -1))})
}
return rows
}

View File

@@ -9,6 +9,7 @@ import (
"sort"
"testing"
"github.com/jedib0t/go-pretty/v6/table"
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/stretchr/testify/assert"
)
@@ -105,7 +106,7 @@ func TestGeneratePolicyRows_NonEmptyPolicyList(t *testing.T) {
result := generatePolicyRows(policies)
// Assert
assert.Equal(t, [][]string{{"policy1"}, {"policy2"}, {"policy3"}}, result)
assert.Equal(t, []table.Row{{"policy1"}, {"policy2"}, {"policy3"}}, result)
}
// Returns an empty 2D slice for an empty list of policies.
@@ -122,12 +123,12 @@ func TestGeneratePolicyRows_EmptyPolicyList(t *testing.T) {
// The function returns a list of rows, each containing a formatted string with control ID, control name, docs, and frameworks.
func TestShortFormatControlRows_ReturnsListOfRowsWithFormattedString(t *testing.T) {
controlRows := [][]string{
controlRows := []table.Row{
{"ID1", "Control 1", "Docs 1", "Framework 1"},
{"ID2", "Control 2", "Docs 2", "Framework 2"},
}
want := [][]string{
want := []table.Row{
{"Control ID : ID1\nControl Name : Control 1\nDocs : Docs 1\nFrameworks : Framework 1"},
{"Control ID : ID2\nControl Name : Control 2\nDocs : Docs 2\nFrameworks : Framework 2"},
}
@@ -139,12 +140,12 @@ func TestShortFormatControlRows_ReturnsListOfRowsWithFormattedString(t *testing.
// The function formats the control rows correctly, replacing newlines in the frameworks column with line breaks.
func TestShortFormatControlRows_FormatsControlRowsCorrectly(t *testing.T) {
controlRows := [][]string{
controlRows := []table.Row{
{"ID1", "Control 1", "Docs 1", "Framework\n1"},
{"ID2", "Control 2", "Docs 2", "Framework\n2"},
}
want := [][]string{
want := []table.Row{
{"Control ID : ID1\nControl Name : Control 1\nDocs : Docs 1\nFrameworks : Framework 1"},
{"Control ID : ID2\nControl Name : Control 2\nDocs : Docs 2\nFrameworks : Framework 2"},
}
@@ -156,11 +157,11 @@ func TestShortFormatControlRows_FormatsControlRowsCorrectly(t *testing.T) {
// The function handles a control row with an empty control ID.
func TestShortFormatControlRows_HandlesControlRowWithEmptyControlID(t *testing.T) {
controlRows := [][]string{
controlRows := []table.Row{
{"", "Control 1", "Docs 1", "Framework 1"},
}
want := [][]string{
want := []table.Row{
{"Control ID : \nControl Name : Control 1\nDocs : Docs 1\nFrameworks : Framework 1"},
}
@@ -171,11 +172,11 @@ func TestShortFormatControlRows_HandlesControlRowWithEmptyControlID(t *testing.T
// The function handles a control row with an empty control name.
func TestShortFormatControlRows_HandlesControlRowWithEmptyControlName(t *testing.T) {
controlRows := [][]string{
controlRows := []table.Row{
{"ID1", "", "Docs 1", "Framework 1"},
}
want := [][]string{
want := []table.Row{
{"Control ID : ID1\nControl Name : \nDocs : Docs 1\nFrameworks : Framework 1"},
}
@@ -192,7 +193,7 @@ func TestGenerateControlRowsWithAllFields(t *testing.T) {
"3|Control 3|Framework 3",
}
want := [][]string{
want := []table.Row{
{"1", "Control 1", "https://hub.armosec.io/docs/1", "Framework\n1"},
{"2", "Control 2", "https://hub.armosec.io/docs/2", "Framework\n2"},
{"3", "Control 3", "https://hub.armosec.io/docs/3", "Framework\n3"},
@@ -215,7 +216,7 @@ func TestGenerateControlRowsHandlesPoliciesWithEmptyStringOrNoPipesOrOnePipeMiss
"5|Control 5||Extra 5",
}
expectedRows := [][]string{
expectedRows := []table.Row{
{"", "", "https://hub.armosec.io/docs/", ""},
{"1", "", "https://hub.armosec.io/docs/1", ""},
{"2", "Control 2", "https://hub.armosec.io/docs/2", "Framework\n2"},
@@ -252,18 +253,18 @@ func TestGenerateTableWithCorrectHeadersAndRows(t *testing.T) {
os.Stdout = rescueStdout
// got := buf.String()
want := `────────────┬──────────────┬───────────────────────────────┬────────────
want := `────────────┬──────────────┬───────────────────────────────┬────────────
│ Control ID │ Control name │ Docs │ Frameworks │
├────────────┼──────────────┼───────────────────────────────┼────────────┤
│ 1 │ Control 1 │ https://hub.armosec.io/docs/1 │ Framework │
│ │ │ │ 1
│ │ │ │ 1
├────────────┼──────────────┼───────────────────────────────┼────────────┤
│ 2 │ Control 2 │ https://hub.armosec.io/docs/2 │ Framework │
│ │ │ │ 2
│ │ │ │ 2
├────────────┼──────────────┼───────────────────────────────┼────────────┤
│ 3 │ Control 3 │ https://hub.armosec.io/docs/3 │ Framework │
│ │ │ │ 3
────────────┴──────────────┴───────────────────────────────┴────────────
│ │ │ │ 3
────────────┴──────────────┴───────────────────────────────┴────────────
`
assert.Equal(t, want, string(got))
@@ -294,7 +295,7 @@ func TestGenerateTableWithMalformedPoliciesAndPrettyPrintHeadersAndRows(t *testi
os.Stdout = rescueStdout
want := `────────────┬──────────────┬───────────────────────────────┬────────────
want := `────────────┬──────────────┬───────────────────────────────┬────────────
│ Control ID │ Control name │ Docs │ Frameworks │
├────────────┼──────────────┼───────────────────────────────┼────────────┤
│ │ │ https://hub.armosec.io/docs/ │ │
@@ -302,18 +303,18 @@ func TestGenerateTableWithMalformedPoliciesAndPrettyPrintHeadersAndRows(t *testi
│ 1 │ │ https://hub.armosec.io/docs/1 │ │
├────────────┼──────────────┼───────────────────────────────┼────────────┤
│ 2 │ Control 2 │ https://hub.armosec.io/docs/2 │ Framework │
│ │ │ │ 2
│ │ │ │ 2
├────────────┼──────────────┼───────────────────────────────┼────────────┤
│ 3 │ Control 3 │ https://hub.armosec.io/docs/3 │ Framework │
│ │ │ │ 3
│ │ │ │ 3
├────────────┼──────────────┼───────────────────────────────┼────────────┤
│ 4 │ │ https://hub.armosec.io/docs/4 │ Framework │
│ │ │ │ 4
│ │ │ │ 4
├────────────┼──────────────┼───────────────────────────────┼────────────┤
│ │ │ https://hub.armosec.io/docs/ │ │
├────────────┼──────────────┼───────────────────────────────┼────────────┤
│ 5 │ Control 5 │ https://hub.armosec.io/docs/5 │ │
────────────┴──────────────┴───────────────────────────────┴────────────
────────────┴──────────────┴───────────────────────────────┴────────────
`
assert.Equal(t, want, string(got))

View File

@@ -1,15 +1,22 @@
package core
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"slices"
"strings"
"time"
"github.com/anchore/grype/grype/presenter"
"github.com/anchore/clio"
grypejson "github.com/anchore/grype/grype/presenter/json"
"github.com/anchore/grype/grype/presenter/models"
copaGrype "github.com/anubhav06/copa-grype/grype"
"github.com/containerd/platforms"
"github.com/docker/buildx/build"
"github.com/docker/cli/cli/config"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
@@ -17,21 +24,37 @@ import (
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer"
"github.com/kubescape/kubescape/v3/pkg/imagescan"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/project-copacetic/copacetic/pkg/buildkit"
"github.com/project-copacetic/copacetic/pkg/pkgmgr"
"github.com/project-copacetic/copacetic/pkg/types/unversioned"
"github.com/project-copacetic/copacetic/pkg/utils"
"github.com/quay/claircore/osrelease"
log "github.com/sirupsen/logrus"
)
func (ks *Kubescape) Patch(patchInfo *ksmetav1.PatchInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
const (
copaProduct = "copa"
)
func (ks *Kubescape) Patch(patchInfo *ksmetav1.PatchInfo, scanInfo *cautils.ScanInfo) (bool, error) {
// ===================== Scan the image =====================
logger.L().Start(fmt.Sprintf("Scanning image: %s", patchInfo.Image))
// Setup the scan service
dbCfg, _ := imagescan.NewDefaultDBConfig()
svc := imagescan.NewScanService(dbCfg)
distCfg, installCfg, _ := imagescan.NewDefaultDBConfig()
svc, err := imagescan.NewScanServiceWithMatchers(distCfg, installCfg, scanInfo.UseDefaultMatchers)
if err != nil {
logger.L().StopError(fmt.Sprintf("Failed to initialize image scanner: %s", err))
return false, err
}
defer svc.Close()
creds := imagescan.RegistryCredentials{
Username: patchInfo.Username,
Password: patchInfo.Password,
@@ -39,15 +62,21 @@ func (ks *Kubescape) Patch(patchInfo *ksmetav1.PatchInfo, scanInfo *cautils.Scan
// Scan the image
scanResults, err := svc.Scan(ks.Context(), patchInfo.Image, creds, nil, nil)
if err != nil {
return nil, err
return false, err
}
model, err := models.NewDocument(clio.Identification{}, scanResults.Packages, scanResults.Context,
*scanResults.RemainingMatches, scanResults.IgnoredMatches, scanResults.VulnerabilityProvider, nil, nil, models.DefaultSortStrategy, false)
if err != nil {
return false, fmt.Errorf("failed to create document: %w", err)
}
// If the scan results ID is empty, set it to "grype"
if scanResults.ID.Name == "" {
scanResults.ID.Name = "grype"
if model.Descriptor.Name == "" {
model.Descriptor.Name = "grype"
}
// Save the scan results to a file in json format
pres := presenter.GetPresenter("json", "", false, *scanResults)
pres := grypejson.NewPresenter(models.PresenterConfig{Document: model, SBOM: scanResults.SBOM})
fileName := fmt.Sprintf("%s:%s.json", patchInfo.ImageName, patchInfo.ImageTag)
fileName = strings.ReplaceAll(fileName, "/", "-")
@@ -55,7 +84,7 @@ func (ks *Kubescape) Patch(patchInfo *ksmetav1.PatchInfo, scanInfo *cautils.Scan
writer := printer.GetWriter(ks.Context(), fileName)
if err = pres.Present(writer); err != nil {
return nil, err
return false, err
}
logger.L().StopSuccess(fmt.Sprintf("Successfully scanned image: %s", patchInfo.Image))
@@ -69,7 +98,7 @@ func (ks *Kubescape) Patch(patchInfo *ksmetav1.PatchInfo, scanInfo *cautils.Scan
}
if err = copaPatch(ks.Context(), patchInfo.Timeout, patchInfo.BuildkitAddress, patchInfo.Image, fileName, patchedImageName, "", patchInfo.IgnoreError, patchInfo.BuildKitOpts); err != nil {
return nil, err
return false, err
}
// Restore the output streams
@@ -83,7 +112,7 @@ func (ks *Kubescape) Patch(patchInfo *ksmetav1.PatchInfo, scanInfo *cautils.Scan
scanResultsPatched, err := svc.Scan(ks.Context(), patchedImageName, creds, nil, nil)
if err != nil {
return nil, err
return false, err
}
logger.L().StopSuccess(fmt.Sprintf("Successfully re-scanned image: %s", patchedImageName))
@@ -99,14 +128,9 @@ func (ks *Kubescape) Patch(patchInfo *ksmetav1.PatchInfo, scanInfo *cautils.Scan
outputPrinters := GetOutputPrinters(scanInfo, ks.Context(), "")
uiPrinter := GetUIPrinter(ks.Context(), scanInfo, "")
resultsHandler := resultshandling.NewResultsHandler(nil, outputPrinters, uiPrinter)
resultsHandler.ImageScanData = []cautils.ImageScanData{
{
PresenterConfig: scanResultsPatched,
Image: patchedImageName,
},
}
resultsHandler.ImageScanData = []cautils.ImageScanData{*scanResultsPatched}
return scanResultsPatched, resultsHandler.HandleResults(ks.Context())
return svc.ExceedsSeverityThreshold(imagescan.ParseSeverity(scanInfo.FailThresholdSeverity), scanResultsPatched.Matches), resultsHandler.HandleResults(ks.Context(), scanInfo)
}
func disableCopaLogger() {
@@ -160,43 +184,185 @@ func patchWithContext(ctx context.Context, buildkitAddr, image, reportFile, patc
}
}
var updates *unversioned.UpdateManifest
// Parse report for update packages
updates, err := tryParseScanReport(reportFile)
if err != nil {
return err
}
client, err := buildkit.NewClient(ctx, bkOpts)
bkClient, err := buildkit.NewClient(ctx, bkOpts)
if err != nil {
return err
return fmt.Errorf("copa: error creating buildkit client :: %w", err)
}
defer client.Close()
defer bkClient.Close()
// Configure buildctl/client for use by package manager
config, err := buildkit.InitializeBuildkitConfig(ctx, client, image, updates)
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
cfg := authprovider.DockerAuthProviderConfig{ConfigFile: dockerConfig}
attachable := []session.Attachable{authprovider.NewDockerAuthProvider(cfg)}
solveOpt := client.SolveOpt{
Exports: []client.ExportEntry{
{
Type: client.ExporterImage,
Attrs: map[string]string{
"name": patchedImageName,
"push": "true",
},
},
},
Frontend: "", // i.e. we are passing in the llb.Definition directly
Session: attachable, // used for authprovider, sshagentprovider and secretprovider
}
solveOpt.SourcePolicy, err = build.ReadSourcePolicy()
if err != nil {
return err
return fmt.Errorf("copa: error reading source policy :: %w", err)
}
// Create package manager helper
pkgmgr, err := pkgmgr.GetPackageManager(updates.Metadata.OS.Type, config, workingFolder)
if err != nil {
return err
}
buildChannel := make(chan *client.SolveStatus)
_, err = bkClient.Build(ctx, solveOpt, copaProduct, func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
// Configure buildctl/client for use by package manager
config, err := buildkit.InitializeBuildkitConfig(ctx, c, image)
if err != nil {
return nil, fmt.Errorf("copa: error initializing buildkit config for image %s :: %w", image, err)
}
// Export the patched image state to Docker
patchedImageState, _, err := pkgmgr.InstallUpdates(ctx, updates, ignoreError)
if err != nil {
return err
}
// Create package manager helper
var manager pkgmgr.PackageManager
if reportFile == "" {
// determine OS family
fileBytes, err := buildkit.ExtractFileFromState(ctx, c, &config.ImageState, "/etc/os-release")
if err != nil {
return nil, fmt.Errorf("unable to extract /etc/os-release file from state %w", err)
}
if err = buildkit.SolveToDocker(ctx, config.Client, patchedImageState, config.ConfigData, patchedImageName); err != nil {
return err
}
osType, err := getOSType(ctx, fileBytes)
if err != nil {
return nil, fmt.Errorf("copa: error getting os type :: %w", err)
}
osVersion, err := getOSVersion(ctx, fileBytes)
if err != nil {
return nil, fmt.Errorf("copa: error getting os version :: %w", err)
}
// get package manager based on os family type
manager, err = pkgmgr.GetPackageManager(osType, osVersion, config, workingFolder)
if err != nil {
return nil, fmt.Errorf("copa: error getting package manager for ostype=%s, version=%s :: %w", osType, osVersion, err)
}
// do not specify updates, will update all
updates = nil
} else {
// get package manager based on os family type
manager, err = pkgmgr.GetPackageManager(updates.Metadata.OS.Type, updates.Metadata.OS.Version, config, workingFolder)
if err != nil {
return nil, fmt.Errorf("copa: error getting package manager by family type: ostype=%s, osversion=%s :: %w", updates.Metadata.OS.Type, updates.Metadata.OS.Version, err)
}
}
// Export the patched image state to Docker
// TODO: Add support for other output modes as buildctl does.
log.Infof("Patching %d vulnerabilities", len(updates.Updates))
patchedImageState, errPkgs, err := manager.InstallUpdates(ctx, updates, ignoreError)
log.Infof("Error is: %v", err)
if err != nil {
return nil, nil
}
platform := platforms.Normalize(platforms.DefaultSpec())
if platform.OS != "linux" {
platform.OS = "linux"
}
def, err := patchedImageState.Marshal(ctx, llb.Platform(platform))
if err != nil {
return nil, err
}
res, err := c.Solve(ctx, gwclient.SolveRequest{
Definition: def.ToPB(),
Evaluate: true,
})
if err != nil {
return nil, err
}
res.AddMeta(exptypes.ExporterImageConfigKey, config.ConfigData)
// Currently can only validate updates if updating via scanner
if reportFile != "" {
// create a new manifest with the successfully patched packages
validatedManifest := &unversioned.UpdateManifest{
Metadata: unversioned.Metadata{
OS: unversioned.OS{
Type: updates.Metadata.OS.Type,
Version: updates.Metadata.OS.Version,
},
Config: unversioned.Config{
Arch: updates.Metadata.Config.Arch,
},
},
Updates: []unversioned.UpdatePackage{},
}
for _, update := range updates.Updates {
if !slices.Contains(errPkgs, update.Name) {
validatedManifest.Updates = append(validatedManifest.Updates, update)
}
}
}
return res, nil
}, buildChannel)
return nil
}
func getOSType(ctx context.Context, osreleaseBytes []byte) (string, error) {
r := bytes.NewReader(osreleaseBytes)
osData, err := osrelease.Parse(ctx, r)
if err != nil {
return "", fmt.Errorf("unable to parse os-release data %w", err)
}
osType := strings.ToLower(osData["NAME"])
switch {
case strings.Contains(osType, "alpine"):
return "alpine", nil
case strings.Contains(osType, "debian"):
return "debian", nil
case strings.Contains(osType, "ubuntu"):
return "ubuntu", nil
case strings.Contains(osType, "amazon"):
return "amazon", nil
case strings.Contains(osType, "centos"):
return "centos", nil
case strings.Contains(osType, "mariner"):
return "cbl-mariner", nil
case strings.Contains(osType, "azure linux"):
return "azurelinux", nil
case strings.Contains(osType, "red hat"):
return "redhat", nil
case strings.Contains(osType, "rocky"):
return "rocky", nil
case strings.Contains(osType, "oracle"):
return "oracle", nil
case strings.Contains(osType, "alma"):
return "alma", nil
default:
log.Error("unsupported osType ", osType)
return "", errors.ErrUnsupported
}
}
func getOSVersion(ctx context.Context, osreleaseBytes []byte) (string, error) {
r := bytes.NewReader(osreleaseBytes)
osData, err := osrelease.Parse(ctx, r)
if err != nil {
return "", fmt.Errorf("unable to parse os-release data %w", err)
}
return osData["VERSION_ID"], nil
}
// This function adds support to copa for patching Kubescape produced results
func tryParseScanReport(file string) (*unversioned.UpdateManifest, error) {

View File

@@ -66,9 +66,11 @@ func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInt
}
// ================== version testing ======================================
v := versioncheck.NewIVersionCheckHandler(ctx)
_ = v.CheckLatestVersion(ctx, versioncheck.NewVersionCheckRequest(scanInfo.AccountID, versioncheck.BuildNumber, policyIdentifierIdentities(scanInfo.PolicyIdentifier), "", string(scanInfo.GetScanningContext()), k8sClient))
// Skip version check in air-gapped mode (when keep-local flag is set)
if !scanInfo.Local {
v := versioncheck.NewIVersionCheckHandler(ctx)
_ = v.CheckLatestVersion(ctx, versioncheck.NewVersionCheckRequest(scanInfo.AccountID, versioncheck.BuildNumber, policyIdentifierIdentities(scanInfo.PolicyIdentifier), "", string(scanInfo.GetScanningContext()), k8sClient))
}
// ================== setup host scanner object ======================================
ctxHostScanner, spanHostScanner := otel.Tracer("").Start(ctx, "setup host scanner")
@@ -132,7 +134,15 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
interfaces := getInterfaces(ctxInit, scanInfo)
interfaces.report.SetTenantConfig(interfaces.tenantConfig)
downloadReleasedPolicy := getter.NewDownloadReleasedPolicy() // download config inputs from github release
// Only create DownloadReleasedPolicy if not in air-gapped mode
var downloadReleasedPolicy *getter.DownloadReleasedPolicy
if isAirGappedMode(scanInfo) {
// In air-gapped mode (--keep-local or using local files via --use-from, --controls-config, --exceptions, or attack tracks),
// don't initialize the downloader to prevent network access
downloadReleasedPolicy = nil
} else {
downloadReleasedPolicy = getter.NewDownloadReleasedPolicy() // download config inputs from github release
}
// set policy getter only after setting the customerGUID
scanInfo.Getters.PolicyGetter = getPolicyGetter(ctxInit, scanInfo.UseFrom, interfaces.tenantConfig.GetAccountID(), scanInfo.FrameworkScan, downloadReleasedPolicy)
@@ -202,7 +212,7 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
}
if scanInfo.ScanImages {
scanImages(scanInfo.ScanType, scanData, ks.Context(), resultsHandling)
scanImages(scanInfo.ScanType, scanData, ks.Context(), resultsHandling, scanInfo)
}
// ========================= results handling =====================
resultsHandling.SetData(scanData)
@@ -214,7 +224,7 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
return resultsHandling, nil
}
func scanImages(scanType cautils.ScanTypes, scanData *cautils.OPASessionObj, ctx context.Context, resultsHandling *resultshandling.ResultsHandler) {
func scanImages(scanType cautils.ScanTypes, scanData *cautils.OPASessionObj, ctx context.Context, resultsHandling *resultshandling.ResultsHandler, scanInfo *cautils.ScanInfo) {
var imagesToScan []string
if scanType == cautils.ScanTypeWorkload {
@@ -243,8 +253,13 @@ func scanImages(scanType cautils.ScanTypes, scanData *cautils.OPASessionObj, ctx
}
}
dbCfg, _ := imagescan.NewDefaultDBConfig()
svc := imagescan.NewScanService(dbCfg)
distCfg, installCfg, _ := imagescan.NewDefaultDBConfig()
svc, err := imagescan.NewScanServiceWithMatchers(distCfg, installCfg, scanInfo.UseDefaultMatchers)
if err != nil {
logger.L().StopError(fmt.Sprintf("Failed to initialize image scanner: %s", err))
return
}
defer svc.Close()
for _, img := range imagesToScan {
logger.L().Start("Scanning", helpers.String("image", img))
@@ -255,20 +270,27 @@ func scanImages(scanType cautils.ScanTypes, scanData *cautils.OPASessionObj, ctx
}
}
func scanSingleImage(ctx context.Context, img string, svc imagescan.Service, resultsHandling *resultshandling.ResultsHandler) error {
func scanSingleImage(ctx context.Context, img string, svc *imagescan.Service, resultsHandling *resultshandling.ResultsHandler) error {
scanResults, err := svc.Scan(ctx, img, imagescan.RegistryCredentials{}, nil, nil)
if err != nil {
return err
}
resultsHandling.ImageScanData = append(resultsHandling.ImageScanData, cautils.ImageScanData{
Image: img,
PresenterConfig: scanResults,
})
resultsHandling.ImageScanData = append(resultsHandling.ImageScanData, *scanResults)
return nil
}
func isPrioritizationScanType(scanType cautils.ScanTypes) bool {
return scanType == cautils.ScanTypeCluster || scanType == cautils.ScanTypeRepo
}
// isAirGappedMode returns true if the scan is configured to run in air-gapped mode
// (i.e., without any network access to download policies, exceptions, or other artifacts)
func isAirGappedMode(scanInfo *cautils.ScanInfo) bool {
return scanInfo.Local ||
len(scanInfo.UseFrom) > 0 ||
scanInfo.ControlsInputs != "" ||
scanInfo.UseExceptions != "" ||
scanInfo.AttackTracks != ""
}

View File

@@ -58,3 +58,66 @@ func TestIsPrioritizationScanType(t *testing.T) {
})
}
}
func TestIsAirGappedMode(t *testing.T) {
tests := []struct {
name string
scanInfo *cautils.ScanInfo
want bool
}{
{
name: "air-gapped with Local flag",
scanInfo: &cautils.ScanInfo{
Local: true,
},
want: true,
},
{
name: "air-gapped with UseFrom",
scanInfo: &cautils.ScanInfo{
UseFrom: []string{"/path/to/policy"},
},
want: true,
},
{
name: "air-gapped with ControlsInputs",
scanInfo: &cautils.ScanInfo{
ControlsInputs: "/path/to/controls",
},
want: true,
},
{
name: "air-gapped with UseExceptions",
scanInfo: &cautils.ScanInfo{
UseExceptions: "/path/to/exceptions",
},
want: true,
},
{
name: "air-gapped with AttackTracks",
scanInfo: &cautils.ScanInfo{
AttackTracks: "/path/to/attack-tracks",
},
want: true,
},
{
name: "not air-gapped - all empty",
scanInfo: &cautils.ScanInfo{},
want: false,
},
{
name: "air-gapped with multiple flags",
scanInfo: &cautils.ScanInfo{
Local: true,
UseFrom: []string{"/path/to/policy"},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, isAirGappedMode(tt.scanInfo))
})
}
}

View File

@@ -1,8 +1,9 @@
package v1
type ImageScanInfo struct {
Username string
Password string
Image string
Exceptions string
Username string
Password string
Image string
Exceptions string
UseDefaultMatchers bool
}

View File

@@ -3,7 +3,6 @@ package meta
import (
"context"
"github.com/anchore/grype/grype/presenter/models"
"github.com/kubescape/kubescape/v3/core/cautils"
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling"
@@ -27,8 +26,8 @@ type IKubescape interface {
Fix(fixInfo *metav1.FixInfo) error
// patch
Patch(patchInfo *metav1.PatchInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error)
Patch(patchInfo *metav1.PatchInfo, scanInfo *cautils.ScanInfo) (bool, error)
// scan image
ScanImage(imgScanInfo *metav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error)
ScanImage(imgScanInfo *metav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (bool, error)
}

View File

@@ -3,7 +3,6 @@ package mocks
import (
"context"
"github.com/anchore/grype/grype/presenter/models"
"github.com/kubescape/kubescape/v3/core/cautils"
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling"
@@ -15,38 +14,38 @@ func (m *MockIKubescape) Context() context.Context {
return context.TODO()
}
func (m *MockIKubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
func (m *MockIKubescape) Scan(_ *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
return nil, nil
}
func (m *MockIKubescape) List(listPolicies *metav1.ListPolicies) error {
func (m *MockIKubescape) List(_ *metav1.ListPolicies) error {
return nil
}
func (m *MockIKubescape) Download(downloadInfo *metav1.DownloadInfo) error {
func (m *MockIKubescape) Download(_ *metav1.DownloadInfo) error {
return nil
}
func (m *MockIKubescape) SetCachedConfig(setConfig *metav1.SetConfig) error {
func (m *MockIKubescape) SetCachedConfig(_ *metav1.SetConfig) error {
return nil
}
func (m *MockIKubescape) ViewCachedConfig(viewConfig *metav1.ViewConfig) error {
func (m *MockIKubescape) ViewCachedConfig(_ *metav1.ViewConfig) error {
return nil
}
func (m *MockIKubescape) DeleteCachedConfig(deleteConfig *metav1.DeleteConfig) error {
func (m *MockIKubescape) DeleteCachedConfig(_ *metav1.DeleteConfig) error {
return nil
}
func (m *MockIKubescape) Fix(fixInfo *metav1.FixInfo) error {
func (m *MockIKubescape) Fix(_ *metav1.FixInfo) error {
return nil
}
func (m *MockIKubescape) Patch(patchInfo *metav1.PatchInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
return nil, nil
func (m *MockIKubescape) Patch(_ *metav1.PatchInfo, _ *cautils.ScanInfo) (bool, error) {
return false, nil
}
func (m *MockIKubescape) ScanImage(imgScanInfo *metav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
return nil, nil
func (m *MockIKubescape) ScanImage(_ *metav1.ImageScanInfo, _ *cautils.ScanInfo) (bool, error) {
return false, nil
}

View File

@@ -37,7 +37,6 @@ spec:
allowPrivilegeEscalation: true
privileged: true
readOnlyRootFilesystem: true
procMount: Unmasked
ports:
- name: scanner # Do not change port name
containerPort: 7888

View File

@@ -96,7 +96,7 @@ func (hsh *HostSensorHandler) Init(ctx context.Context) error {
hsh.populatePodNamesToNodeNames(ctx, log)
if err := hsh.checkPodForEachNode(); err != nil {
logger.L().Ctx(ctx).Warning(failedToValidateHostSensorPodStatus, helpers.Error(err))
return fmt.Errorf("%s: %v", failedToValidateHostSensorPodStatus, err)
}
return nil

View File

@@ -37,7 +37,6 @@ spec:
allowPrivilegeEscalation: true
privileged: true
readOnlyRootFilesystem: true
procMount: Unmasked
ports:
- name: scanner # Do not change port name
containerPort: 7888

View File

@@ -232,7 +232,7 @@ func (k8sHandler *K8sResourceHandler) collectCloudResources(ctx context.Context,
if !strings.Contains(err.Error(), cloudv1.NotSupportedMsg) {
// Return error with useful info on how to configure credentials for getting cloud provider info
logger.L().Debug("failed to get cloud data", helpers.String("resourceKind", resourceKind), helpers.Error(err))
err = fmt.Errorf("failed to get %s descriptive information. Read more: https://hub.armosec.io/docs/kubescape-integration-with-cloud-providers", strings.ToUpper(k8sHandler.cloudProvider))
err = fmt.Errorf("failed to get %s descriptive information. Read more: https://kubescape.io/docs/integrations/kubescape-integration-with-cloud-providers/", strings.ToUpper(k8sHandler.cloudProvider))
cautils.SetInfoMapForResources(err.Error(), cloudResources, sessionObj.InfoMap)
}
@@ -478,8 +478,15 @@ func (k8sHandler *K8sResourceHandler) setCloudProvider() error {
// NoSchedule taint with empty value is usually applied to controlplane
func isMasterNodeTaints(taints []v1.Taint) bool {
for _, taint := range taints {
if taint.Effect == v1.TaintEffectNoSchedule && taint.Value == "" {
return true
if taint.Effect == v1.TaintEffectNoSchedule {
// NoSchedule taint with empty value is usually applied to controlplane
if taint.Value == "" {
return true
}
if taint.Key == "node-role.kubernetes.io/control-plane" && taint.Value == "true" {
return true
}
}
}
return false

View File

@@ -14,264 +14,264 @@ import (
)
func TestIsMasterNodeTaints(t *testing.T) {
noTaintNode := `
{
"apiVersion": "v1",
"kind": "Node",
"metadata": {
"annotations": {
"kubeadm.alpha.kubernetes.io/cri-socket": "/var/run/dockershim.sock",
"node.alpha.kubernetes.io/ttl": "0",
"volumes.kubernetes.io/controller-managed-attach-detach": "true"
},
"creationTimestamp": "2022-05-16T10:52:32Z",
"labels": {
"beta.kubernetes.io/arch": "amd64",
"beta.kubernetes.io/os": "linux",
"kubernetes.io/arch": "amd64",
"kubernetes.io/hostname": "danielg-minikube",
"kubernetes.io/os": "linux",
"minikube.k8s.io/commit": "3e64b11ed75e56e4898ea85f96b2e4af0301f43d",
"minikube.k8s.io/name": "danielg-minikube",
"minikube.k8s.io/updated_at": "2022_05_16T13_52_35_0700",
"minikube.k8s.io/version": "v1.25.1",
"node-role.kubernetes.io/control-plane": "",
"node-role.kubernetes.io/master": "",
"node.kubernetes.io/exclude-from-external-load-balancers": ""
},
"name": "danielg-minikube",
"resourceVersion": "9432",
"uid": "fc4afcb6-4ca4-4038-ba54-5e16065a614a"
},
"spec": {
"podCIDR": "10.244.0.0/24",
"podCIDRs": [
"10.244.0.0/24"
]
},
"status": {
"addresses": [
noTaintNodeJson := `
{
"address": "192.168.49.2",
"type": "InternalIP"
"apiVersion": "v1",
"kind": "Node",
"metadata": {
"annotations": {
"kubeadm.alpha.kubernetes.io/cri-socket": "/var/run/dockershim.sock",
"node.alpha.kubernetes.io/ttl": "0",
"volumes.kubernetes.io/controller-managed-attach-detach": "true"
},
"creationTimestamp": "2022-05-16T10:52:32Z",
"labels": {
"beta.kubernetes.io/arch": "amd64",
"beta.kubernetes.io/os": "linux",
"kubernetes.io/arch": "amd64",
"kubernetes.io/hostname": "danielg-minikube",
"kubernetes.io/os": "linux",
"minikube.k8s.io/commit": "3e64b11ed75e56e4898ea85f96b2e4af0301f43d",
"minikube.k8s.io/name": "danielg-minikube",
"minikube.k8s.io/updated_at": "2022_05_16T13_52_35_0700",
"minikube.k8s.io/version": "v1.25.1",
"node-role.kubernetes.io/control-plane": "",
"node-role.kubernetes.io/master": "",
"node.kubernetes.io/exclude-from-external-load-balancers": ""
},
"name": "danielg-minikube",
"resourceVersion": "9432",
"uid": "fc4afcb6-4ca4-4038-ba54-5e16065a614a"
},
{
"address": "danielg-minikube",
"type": "Hostname"
"spec": {
"podCIDR": "10.244.0.0/24",
"podCIDRs": [
"10.244.0.0/24"
]
},
"status": {
"addresses": [
{
"address": "192.168.49.2",
"type": "InternalIP"
},
{
"address": "danielg-minikube",
"type": "Hostname"
}
],
"allocatable": {
"cpu": "4",
"ephemeral-storage": "94850516Ki",
"hugepages-2Mi": "0",
"memory": "10432976Ki",
"pods": "110"
},
"capacity": {
"cpu": "4",
"ephemeral-storage": "94850516Ki",
"hugepages-2Mi": "0",
"memory": "10432976Ki",
"pods": "110"
},
"conditions": [
{
"lastHeartbeatTime": "2022-05-16T14:14:31Z",
"lastTransitionTime": "2022-05-16T10:52:29Z",
"message": "kubelet has sufficient memory available",
"reason": "KubeletHasSufficientMemory",
"status": "False",
"type": "MemoryPressure"
},
{
"lastHeartbeatTime": "2022-05-16T14:14:31Z",
"lastTransitionTime": "2022-05-16T10:52:29Z",
"message": "kubelet has no disk pressure",
"reason": "KubeletHasNoDiskPressure",
"status": "False",
"type": "DiskPressure"
},
{
"lastHeartbeatTime": "2022-05-16T14:14:31Z",
"lastTransitionTime": "2022-05-16T10:52:29Z",
"message": "kubelet has sufficient PID available",
"reason": "KubeletHasSufficientPID",
"status": "False",
"type": "PIDPressure"
},
{
"lastHeartbeatTime": "2022-05-16T14:14:31Z",
"lastTransitionTime": "2022-05-16T10:52:45Z",
"message": "kubelet is posting ready status",
"reason": "KubeletReady",
"status": "True",
"type": "Ready"
}
],
"daemonEndpoints": {
"kubeletEndpoint": {
"Port": 10250
}
},
"images": [
{
"names": [
"requarks/wiki@sha256:dd83fff15e77843ff934b25c28c865ac000edf7653e5d11adad1dd51df87439d"
],
"sizeBytes": 441083858
},
{
"names": [
"mariadb@sha256:821d0411208eaa88f9e1f0daccd1d534f88d19baf724eb9a2777cbedb10b6c66"
],
"sizeBytes": 400782682
},
{
"names": [
"k8s.gcr.io/etcd@sha256:64b9ea357325d5db9f8a723dcf503b5a449177b17ac87d69481e126bb724c263",
"k8s.gcr.io/etcd:3.5.1-0"
],
"sizeBytes": 292558922
},
{
"names": [
"kubernetesui/dashboard@sha256:ec27f462cf1946220f5a9ace416a84a57c18f98c777876a8054405d1428cc92e",
"kubernetesui/dashboard:v2.3.1"
],
"sizeBytes": 220033604
},
{
"names": [
"k8s.gcr.io/kube-apiserver@sha256:f54681a71cce62cbc1b13ebb3dbf1d880f849112789811f98b6aebd2caa2f255",
"k8s.gcr.io/kube-apiserver:v1.23.1"
],
"sizeBytes": 135162256
},
{
"names": [
"k8s.gcr.io/kube-controller-manager@sha256:a7ed87380108a2d811f0d392a3fe87546c85bc366e0d1e024dfa74eb14468604",
"k8s.gcr.io/kube-controller-manager:v1.23.1"
],
"sizeBytes": 124971684
},
{
"names": [
"k8s.gcr.io/kube-proxy@sha256:e40f3a28721588affcf187f3f246d1e078157dabe274003eaa2957a83f7170c8",
"k8s.gcr.io/kube-proxy:v1.23.1"
],
"sizeBytes": 112327826
},
{
"names": [
"quay.io/kubescape/kubescape@sha256:6196f766be50d94b45d903a911f5ee95ac99bc392a1324c3e063bec41efd98ba",
"quay.io/kubescape/kubescape:v2.0.153"
],
"sizeBytes": 110345054
},
{
"names": [
"nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d"
],
"sizeBytes": 109129446
},
{
"names": [
"quay.io/armosec/action-trigger@sha256:b93707d10ff86aac8dfa42ad37192d6bcf9aceeb4321b21756e438389c26e07c",
"quay.io/armosec/action-trigger:v0.0.5"
],
"sizeBytes": 65127067
},
{
"names": [
"quay.io/armosec/images-vulnerabilities-scan@sha256:a5f9ddc04a7fdce6d52ef85a21f0de567d8e04d418c2bc5bf5d72b151c997625",
"quay.io/armosec/images-vulnerabilities-scan:v0.0.7"
],
"sizeBytes": 61446712
},
{
"names": [
"quay.io/armosec/images-vulnerabilities-scan@sha256:2f879858da89f6542e3223fb18d6d793810cc2ad6e398b66776475e4218b6af5",
"quay.io/armosec/images-vulnerabilities-scan:v0.0.8"
],
"sizeBytes": 61446528
},
{
"names": [
"quay.io/armosec/cluster-collector@sha256:2c4f733d09f7f4090ace04585230bdfacbbc29a3ade38a2e1233d2c0f730d9b6",
"quay.io/armosec/cluster-collector:v0.0.9"
],
"sizeBytes": 53699576
},
{
"names": [
"k8s.gcr.io/kube-scheduler@sha256:8be4eb1593cf9ff2d91b44596633b7815a3753696031a1eb4273d1b39427fa8c",
"k8s.gcr.io/kube-scheduler:v1.23.1"
],
"sizeBytes": 53488305
},
{
"names": [
"k8s.gcr.io/coredns/coredns@sha256:5b6ec0d6de9baaf3e92d0f66cd96a25b9edbce8716f5f15dcd1a616b3abd590e",
"k8s.gcr.io/coredns/coredns:v1.8.6"
],
"sizeBytes": 46829283
},
{
"names": [
"kubernetesui/metrics-scraper@sha256:36d5b3f60e1a144cc5ada820910535074bdf5cf73fb70d1ff1681537eef4e172",
"kubernetesui/metrics-scraper:v1.0.7"
],
"sizeBytes": 34446077
},
{
"names": [
"gcr.io/k8s-minikube/storage-provisioner@sha256:18eb69d1418e854ad5a19e399310e52808a8321e4c441c1dddad8977a0d7a944",
"gcr.io/k8s-minikube/storage-provisioner:v5"
],
"sizeBytes": 31465472
},
{
"names": [
"quay.io/armosec/notification-server@sha256:b6e9b296cd53bd3b2b42c516d8ab43db998acff1124a57aff8d66b3dd7881979",
"quay.io/armosec/notification-server:v0.0.3"
],
"sizeBytes": 20209940
},
{
"names": [
"quay.io/kubescape/host-scanner@sha256:82139d2561039726be060df2878ef023c59df7c536fbd7f6d766af5a99569fee",
"quay.io/kubescape/host-scanner:latest"
],
"sizeBytes": 11796788
},
{
"names": [
"k8s.gcr.io/pause@sha256:3d380ca8864549e74af4b29c10f9cb0956236dfb01c40ca076fb6c37253234db",
"k8s.gcr.io/pause:3.6"
],
"sizeBytes": 682696
}
],
"nodeInfo": {
"architecture": "amd64",
"bootID": "828cbe73-120b-43cf-aae0-9e2d15b8c873",
"containerRuntimeVersion": "docker://20.10.12",
"kernelVersion": "5.13.0-40-generic",
"kubeProxyVersion": "v1.23.1",
"kubeletVersion": "v1.23.1",
"machineID": "8de776e053e140d6a14c2d2def3d6bb8",
"operatingSystem": "linux",
"osImage": "Ubuntu 20.04.2 LTS",
"systemUUID": "da12dc19-10bf-4033-a440-2d9aa33d6fe3"
}
}
],
"allocatable": {
"cpu": "4",
"ephemeral-storage": "94850516Ki",
"hugepages-2Mi": "0",
"memory": "10432976Ki",
"pods": "110"
},
"capacity": {
"cpu": "4",
"ephemeral-storage": "94850516Ki",
"hugepages-2Mi": "0",
"memory": "10432976Ki",
"pods": "110"
},
"conditions": [
{
"lastHeartbeatTime": "2022-05-16T14:14:31Z",
"lastTransitionTime": "2022-05-16T10:52:29Z",
"message": "kubelet has sufficient memory available",
"reason": "KubeletHasSufficientMemory",
"status": "False",
"type": "MemoryPressure"
},
{
"lastHeartbeatTime": "2022-05-16T14:14:31Z",
"lastTransitionTime": "2022-05-16T10:52:29Z",
"message": "kubelet has no disk pressure",
"reason": "KubeletHasNoDiskPressure",
"status": "False",
"type": "DiskPressure"
},
{
"lastHeartbeatTime": "2022-05-16T14:14:31Z",
"lastTransitionTime": "2022-05-16T10:52:29Z",
"message": "kubelet has sufficient PID available",
"reason": "KubeletHasSufficientPID",
"status": "False",
"type": "PIDPressure"
},
{
"lastHeartbeatTime": "2022-05-16T14:14:31Z",
"lastTransitionTime": "2022-05-16T10:52:45Z",
"message": "kubelet is posting ready status",
"reason": "KubeletReady",
"status": "True",
"type": "Ready"
}
],
"daemonEndpoints": {
"kubeletEndpoint": {
"Port": 10250
}
},
"images": [
{
"names": [
"requarks/wiki@sha256:dd83fff15e77843ff934b25c28c865ac000edf7653e5d11adad1dd51df87439d"
],
"sizeBytes": 441083858
},
{
"names": [
"mariadb@sha256:821d0411208eaa88f9e1f0daccd1d534f88d19baf724eb9a2777cbedb10b6c66"
],
"sizeBytes": 400782682
},
{
"names": [
"k8s.gcr.io/etcd@sha256:64b9ea357325d5db9f8a723dcf503b5a449177b17ac87d69481e126bb724c263",
"k8s.gcr.io/etcd:3.5.1-0"
],
"sizeBytes": 292558922
},
{
"names": [
"kubernetesui/dashboard@sha256:ec27f462cf1946220f5a9ace416a84a57c18f98c777876a8054405d1428cc92e",
"kubernetesui/dashboard:v2.3.1"
],
"sizeBytes": 220033604
},
{
"names": [
"k8s.gcr.io/kube-apiserver@sha256:f54681a71cce62cbc1b13ebb3dbf1d880f849112789811f98b6aebd2caa2f255",
"k8s.gcr.io/kube-apiserver:v1.23.1"
],
"sizeBytes": 135162256
},
{
"names": [
"k8s.gcr.io/kube-controller-manager@sha256:a7ed87380108a2d811f0d392a3fe87546c85bc366e0d1e024dfa74eb14468604",
"k8s.gcr.io/kube-controller-manager:v1.23.1"
],
"sizeBytes": 124971684
},
{
"names": [
"k8s.gcr.io/kube-proxy@sha256:e40f3a28721588affcf187f3f246d1e078157dabe274003eaa2957a83f7170c8",
"k8s.gcr.io/kube-proxy:v1.23.1"
],
"sizeBytes": 112327826
},
{
"names": [
"quay.io/kubescape/kubescape@sha256:6196f766be50d94b45d903a911f5ee95ac99bc392a1324c3e063bec41efd98ba",
"quay.io/kubescape/kubescape:v2.0.153"
],
"sizeBytes": 110345054
},
{
"names": [
"nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d"
],
"sizeBytes": 109129446
},
{
"names": [
"quay.io/armosec/action-trigger@sha256:b93707d10ff86aac8dfa42ad37192d6bcf9aceeb4321b21756e438389c26e07c",
"quay.io/armosec/action-trigger:v0.0.5"
],
"sizeBytes": 65127067
},
{
"names": [
"quay.io/armosec/images-vulnerabilities-scan@sha256:a5f9ddc04a7fdce6d52ef85a21f0de567d8e04d418c2bc5bf5d72b151c997625",
"quay.io/armosec/images-vulnerabilities-scan:v0.0.7"
],
"sizeBytes": 61446712
},
{
"names": [
"quay.io/armosec/images-vulnerabilities-scan@sha256:2f879858da89f6542e3223fb18d6d793810cc2ad6e398b66776475e4218b6af5",
"quay.io/armosec/images-vulnerabilities-scan:v0.0.8"
],
"sizeBytes": 61446528
},
{
"names": [
"quay.io/armosec/cluster-collector@sha256:2c4f733d09f7f4090ace04585230bdfacbbc29a3ade38a2e1233d2c0f730d9b6",
"quay.io/armosec/cluster-collector:v0.0.9"
],
"sizeBytes": 53699576
},
{
"names": [
"k8s.gcr.io/kube-scheduler@sha256:8be4eb1593cf9ff2d91b44596633b7815a3753696031a1eb4273d1b39427fa8c",
"k8s.gcr.io/kube-scheduler:v1.23.1"
],
"sizeBytes": 53488305
},
{
"names": [
"k8s.gcr.io/coredns/coredns@sha256:5b6ec0d6de9baaf3e92d0f66cd96a25b9edbce8716f5f15dcd1a616b3abd590e",
"k8s.gcr.io/coredns/coredns:v1.8.6"
],
"sizeBytes": 46829283
},
{
"names": [
"kubernetesui/metrics-scraper@sha256:36d5b3f60e1a144cc5ada820910535074bdf5cf73fb70d1ff1681537eef4e172",
"kubernetesui/metrics-scraper:v1.0.7"
],
"sizeBytes": 34446077
},
{
"names": [
"gcr.io/k8s-minikube/storage-provisioner@sha256:18eb69d1418e854ad5a19e399310e52808a8321e4c441c1dddad8977a0d7a944",
"gcr.io/k8s-minikube/storage-provisioner:v5"
],
"sizeBytes": 31465472
},
{
"names": [
"quay.io/armosec/notification-server@sha256:b6e9b296cd53bd3b2b42c516d8ab43db998acff1124a57aff8d66b3dd7881979",
"quay.io/armosec/notification-server:v0.0.3"
],
"sizeBytes": 20209940
},
{
"names": [
"quay.io/kubescape/host-scanner@sha256:82139d2561039726be060df2878ef023c59df7c536fbd7f6d766af5a99569fee",
"quay.io/kubescape/host-scanner:latest"
],
"sizeBytes": 11796788
},
{
"names": [
"k8s.gcr.io/pause@sha256:3d380ca8864549e74af4b29c10f9cb0956236dfb01c40ca076fb6c37253234db",
"k8s.gcr.io/pause:3.6"
],
"sizeBytes": 682696
}
],
"nodeInfo": {
"architecture": "amd64",
"bootID": "828cbe73-120b-43cf-aae0-9e2d15b8c873",
"containerRuntimeVersion": "docker://20.10.12",
"kernelVersion": "5.13.0-40-generic",
"kubeProxyVersion": "v1.23.1",
"kubeletVersion": "v1.23.1",
"machineID": "8de776e053e140d6a14c2d2def3d6bb8",
"operatingSystem": "linux",
"osImage": "Ubuntu 20.04.2 LTS",
"systemUUID": "da12dc19-10bf-4033-a440-2d9aa33d6fe3"
}
}
}
`
var l v1.Node
_ = json.Unmarshal([]byte(noTaintNode), &l)
assert.False(t, isMasterNodeTaints(l.Spec.Taints))
`
var noTaintNode v1.Node
_ = json.Unmarshal([]byte(noTaintNodeJson), &noTaintNode)
assert.False(t, isMasterNodeTaints(noTaintNode.Spec.Taints))
taintNode :=
taintNodeJson :=
`
{
"apiVersion": "v1",
@@ -532,8 +532,60 @@ func TestIsMasterNodeTaints(t *testing.T) {
}
}
`
_ = json.Unmarshal([]byte(taintNode), &l)
assert.True(t, isMasterNodeTaints(l.Spec.Taints))
var taintNode v1.Node
_ = json.Unmarshal([]byte(taintNodeJson), &taintNode)
assert.True(t, isMasterNodeTaints(taintNode.Spec.Taints))
taintNodeJson1 :=
`
{
"apiVersion": "v1",
"kind": "Node",
"metadata": {
"annotations": {
"kubeadm.alpha.kubernetes.io/cri-socket": "/var/run/dockershim.sock",
"node.alpha.kubernetes.io/ttl": "0",
"volumes.kubernetes.io/controller-managed-attach-detach": "true"
},
"creationTimestamp": "2022-05-16T10:52:32Z",
"labels": {
"beta.kubernetes.io/arch": "amd64",
"beta.kubernetes.io/os": "linux",
"kubernetes.io/arch": "amd64",
"kubernetes.io/hostname": "danielg-minikube",
"kubernetes.io/os": "linux",
"minikube.k8s.io/commit": "3e64b11ed75e56e4898ea85f96b2e4af0301f43d",
"minikube.k8s.io/name": "danielg-minikube",
"minikube.k8s.io/updated_at": "2022_05_16T13_52_35_0700",
"minikube.k8s.io/version": "v1.25.1",
"node-role.kubernetes.io/control-plane": "",
"node-role.kubernetes.io/master": "",
"node.kubernetes.io/exclude-from-external-load-balancers": ""
},
"name": "danielg-minikube",
"resourceVersion": "9871",
"uid": "fc4afcb6-4ca4-4038-ba54-5e16065a614a"
},
"spec": {
"podCIDR": "10.244.0.0/24",
"podCIDRs": [
"10.244.0.0/24"
],
"taints": [
{
"effect": "NoSchedule",
"key": "node-role.kubernetes.io/control-plane",
"value": "true"
}
]
},
"status": {}
}
`
var taintNode1 v1.Node
_ = json.Unmarshal([]byte(taintNodeJson1), &taintNode1)
assert.True(t, isMasterNodeTaints(taintNode1.Spec.Taints))
}
func TestSetMapNamespaceToNumOfResources(t *testing.T) {

View File

@@ -9,7 +9,7 @@ const (
emptySpace = " "
middleItem = "├── "
continueItem = "│ "
lastItem = "── "
lastItem = "── "
)
type (
@@ -66,7 +66,7 @@ func (t *tree) Items() []Tree {
return t.items
}
// Print returns an visual representation of the tree
// Print returns a visual representation of the tree
func (t *tree) Print() string {
return newPrinter().Print(t)
}

View File

@@ -31,7 +31,7 @@ func TestTreePrint(t *testing.T) {
tree: SimpleTreeMock(),
want: "root\n" +
"├── child1\n" +
"── child2\n",
"── child2\n",
},
{
name: "SimpleTreeWithLinesMock",
@@ -42,36 +42,36 @@ func TestTreePrint(t *testing.T) {
"├── child3\n" +
"│ Line2\n" +
"│ Line3\n" +
"── child4\n",
"── child4\n",
},
{
name: "SubTreeMock1",
tree: SubTreeMock1(),
want: "root\n" +
"── child1\n" +
" ── child1.1\n",
"── child1\n" +
" ── child1.1\n",
},
{
name: "SubTreeMock2",
tree: SubTreeMock2(),
want: "root\n" +
"├── child1\n" +
"│ ── child1.1\n" +
"│ ── child1.1\n" +
"├── child2\n" +
"── child3\n" +
" ── child3.1\n",
"── child3\n" +
" ── child3.1\n",
},
{
name: "SubTreeWithLinesMock",
tree: SubTreeWithLinesMock(),
want: "root\n" +
"├── child1\n" +
"│ ── child1.1\n" +
"│ ── child1.1\n" +
"│ Line2\n" +
"│ Line3\n" +
"├── child2\n" +
"── child3\n" +
" ── child3.1\n" +
"── child3\n" +
" ── child3.1\n" +
" Line2\n" +
" Line3\n",
},
@@ -85,8 +85,8 @@ func TestTreePrint(t *testing.T) {
}
func TestPrintText_LastTree(t *testing.T) {
inputText := "Root\n├── Child1\n── Child2"
expectedOutput := "── Root\n ├── Child1\n ── Child2\n"
inputText := "Root\n├── Child1\n── Child2"
expectedOutput := "── Root\n ├── Child1\n ── Child2\n"
result := p.printText(inputText, []bool{}, true)
@@ -94,8 +94,8 @@ func TestPrintText_LastTree(t *testing.T) {
}
func TestPrintText_NotLastTree(t *testing.T) {
inputText := "Root\n├── Child1\n── Child2"
expectedOutput := "├── Root\n│ ├── Child1\n│ ── Child2\n"
inputText := "Root\n├── Child1\n── Child2"
expectedOutput := "├── Root\n│ ├── Child1\n│ ── Child2\n"
result := p.printText(inputText, []bool{}, false)
@@ -122,7 +122,7 @@ func Test_printer_printItems(t *testing.T) {
name: "SimpleTreeMock",
tree: SimpleTreeMock(),
want: "├── child1\n" +
"── child2\n",
"── child2\n",
},
{
name: "SimpleTreeWithLinesMock",
@@ -132,33 +132,33 @@ func Test_printer_printItems(t *testing.T) {
"├── child3\n" +
"│ Line2\n" +
"│ Line3\n" +
"── child4\n",
"── child4\n",
},
{
name: "SubTreeMock1",
tree: SubTreeMock1(),
want: "── child1\n" +
" ── child1.1\n",
want: "── child1\n" +
" ── child1.1\n",
},
{
name: "SubTreeMock2",
tree: SubTreeMock2(),
want: "├── child1\n" +
"│ ── child1.1\n" +
"│ ── child1.1\n" +
"├── child2\n" +
"── child3\n" +
" ── child3.1\n",
"── child3\n" +
" ── child3.1\n",
},
{
name: "SubTreeWithLinesMock",
tree: SubTreeWithLinesMock(),
want: "├── child1\n" +
"│ ── child1.1\n" +
"│ ── child1.1\n" +
"│ Line2\n" +
"│ Line3\n" +
"├── child2\n" +
"── child3\n" +
" ── child3.1\n" +
"── child3\n" +
" ── child3.1\n" +
" Line2\n" +
" Line3\n",
},

View File

@@ -95,7 +95,7 @@ func (prettyPrinter *PrettyPrinter) printAttackTracks(opaSessionObj *cautils.OPA
})
for i := 0; i < topResourceCount && i < len(resources); i++ {
fmt.Fprintf(prettyPrinter.writer, "\n"+getSeparator("^")+"\n")
fmt.Fprintf(prettyPrinter.writer, "\n%s\n", getSeparator("^"))
resource := resources[i]
resourceObj := opaSessionObj.AllResources[resource.ResourceID]

View File

@@ -6,10 +6,11 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"github.com/anchore/clio"
"github.com/anchore/grype/grype/presenter"
grypejson "github.com/anchore/grype/grype/presenter/json"
"github.com/anchore/grype/grype/presenter/models"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
@@ -17,7 +18,6 @@ import (
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"k8s.io/utils/strings/slices"
)
const (
@@ -70,17 +70,10 @@ func (jp *JsonPrinter) convertToImageScanSummary(imageScanData []cautils.ImageSc
imageScanSummary.Images = append(imageScanSummary.Images, imageScanData[i].Image)
}
presenterConfig := imageScanData[i].PresenterConfig
doc, err := models.NewDocument(clio.Identification{}, presenterConfig.Packages, presenterConfig.Context, presenterConfig.Matches, presenterConfig.IgnoredMatches, presenterConfig.MetadataProvider, nil, presenterConfig.DBStatus)
if err != nil {
logger.L().Error(fmt.Sprintf("failed to create document for image: %v", imageScanData[i].Image), helpers.Error(err))
continue
}
CVEs := extractCVEs(doc.Matches)
CVEs := extractCVEs(imageScanData[i].Matches)
imageScanSummary.CVEs = append(imageScanSummary.CVEs, CVEs...)
setPkgNameToScoreMap(doc.Matches, imageScanSummary.PackageScores)
setPkgNameToScoreMap(imageScanData[i].Matches, imageScanSummary.PackageScores)
setSeverityToSummaryMap(CVEs, imageScanSummary.MapsSeverityToSummary)
}
@@ -92,9 +85,15 @@ func (jp *JsonPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.O
var err error
if opaSessionObj != nil {
err = printConfigurationsScanning(opaSessionObj, ctx, imageScanData, jp)
err = printConfigurationsScanning(opaSessionObj, imageScanData, jp)
} else if imageScanData != nil {
err = jp.PrintImageScan(ctx, imageScanData[0].PresenterConfig)
model, err2 := models.NewDocument(clio.Identification{}, imageScanData[0].Packages, imageScanData[0].Context,
*imageScanData[0].RemainingMatches, imageScanData[0].IgnoredMatches, imageScanData[0].VulnerabilityProvider, nil, nil, models.DefaultSortStrategy, false)
if err2 != nil {
logger.L().Ctx(ctx).Error("failed to create document: %w", helpers.Error(err))
return
}
err = grypejson.NewPresenter(models.PresenterConfig{Document: model, SBOM: imageScanData[0].SBOM}).Present(jp.writer)
} else {
err = fmt.Errorf("no data provided")
}
@@ -107,7 +106,7 @@ func (jp *JsonPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.O
printer.LogOutputFile(jp.writer.Name())
}
func printConfigurationsScanning(opaSessionObj *cautils.OPASessionObj, ctx context.Context, imageScanData []cautils.ImageScanData, jp *JsonPrinter) error {
func printConfigurationsScanning(opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData, jp *JsonPrinter) error {
if imageScanData != nil {
imageScanSummary, err := jp.convertToImageScanSummary(imageScanData)
@@ -121,7 +120,11 @@ func printConfigurationsScanning(opaSessionObj *cautils.OPASessionObj, ctx conte
opaSessionObj.Report.SummaryDetails.Vulnerabilities.Images = imageScanSummary.Images
}
r, err := json.Marshal(FinalizeResults(opaSessionObj))
// Convert to PostureReportWithSeverity to add severity field to controls
finalizedReport := FinalizeResults(opaSessionObj)
reportWithSeverity := ConvertToPostureReportWithSeverity(finalizedReport)
r, err := json.Marshal(reportWithSeverity)
_, err = jp.writer.Write(r)
return err
@@ -168,14 +171,6 @@ func convertToReportSummary(input map[string]*imageprinter.SeveritySummary) map[
return output
}
func (jp *JsonPrinter) PrintImageScan(ctx context.Context, scanResults *models.PresenterConfig) error {
if scanResults == nil {
return fmt.Errorf("no image vulnerability data provided")
}
pres := presenter.GetPresenter("json", "", false, *scanResults)
return pres.Present(jp.writer)
}
func (jp *JsonPrinter) PrintNextSteps() {
}

View File

@@ -7,6 +7,8 @@ import (
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
"github.com/stretchr/testify/assert"
)
@@ -192,3 +194,154 @@ func TestConvertToReportSummary(t *testing.T) {
assert.Equal(t, want, got)
}
func TestEnrichControlsWithSeverity(t *testing.T) {
tests := []struct {
name string
scoreFactor float32
wantSeverity string
}{
{
name: "Critical severity",
scoreFactor: 9.0,
wantSeverity: "Critical",
},
{
name: "High severity",
scoreFactor: 8.0,
wantSeverity: "High",
},
{
name: "Medium severity",
scoreFactor: 6.0,
wantSeverity: "Medium",
},
{
name: "Low severity",
scoreFactor: 3.0,
wantSeverity: "Low",
},
{
name: "Unknown severity",
scoreFactor: 0.0,
wantSeverity: "Unknown",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
controls := reportsummary.ControlSummaries{
"C-0001": reportsummary.ControlSummary{
ControlID: "C-0001",
Name: "Test Control",
ScoreFactor: tt.scoreFactor,
},
}
enrichedControls := enrichControlsWithSeverity(controls)
assert.Equal(t, 1, len(enrichedControls))
assert.Equal(t, tt.wantSeverity, enrichedControls["C-0001"].Severity)
assert.Equal(t, "Test Control", enrichedControls["C-0001"].Name)
assert.Equal(t, tt.scoreFactor, enrichedControls["C-0001"].ScoreFactor)
})
}
}
func TestConvertToPostureReportWithSeverity(t *testing.T) {
// Create a mock PostureReport with controls having different severity levels
mockReport := reportsummary.MockSummaryDetails()
// Get the controls from mock data
controls := mockReport.Controls
// Create a minimal PostureReport
report := &reporthandlingv2.PostureReport{
SummaryDetails: *mockReport,
}
// Convert to PostureReportWithSeverity
reportWithSeverity := ConvertToPostureReportWithSeverity(report)
// Verify controls have severity field
assert.NotNil(t, reportWithSeverity)
assert.NotNil(t, reportWithSeverity.SummaryDetails.Controls)
// Verify each control in the original report has a corresponding enriched control with severity
for controlID, control := range controls {
enrichedControl, exists := reportWithSeverity.SummaryDetails.Controls[controlID]
assert.True(t, exists, "Control %s should exist in enriched controls", controlID)
assert.NotEmpty(t, enrichedControl.Severity, "Severity should not be empty for control %s", controlID)
assert.Equal(t, control.ControlID, enrichedControl.ControlID, "Control ID should match")
assert.Equal(t, control.ScoreFactor, enrichedControl.ScoreFactor, "ScoreFactor should match")
}
}
func TestConvertToPostureReportWithSeverityNilCheck(t *testing.T) {
// Test that nil report returns nil
result := ConvertToPostureReportWithSeverity(nil)
assert.Nil(t, result, "Converting nil report should return nil")
}
func TestEnrichResultsWithSeverity(t *testing.T) {
// Create mock control summaries
controlSummaries := reportsummary.ControlSummaries{
"C-0001": reportsummary.ControlSummary{
ControlID: "C-0001",
Name: "Test Control High",
ScoreFactor: 8.0,
},
"C-0002": reportsummary.ControlSummary{
ControlID: "C-0002",
Name: "Test Control Medium",
ScoreFactor: 6.0,
},
}
// Create mock results with associated controls
results := []resourcesresults.Result{
{
ResourceID: "test-resource-1",
AssociatedControls: []resourcesresults.ResourceAssociatedControl{
{
ControlID: "C-0001",
Name: "Test Control High",
},
},
},
{
ResourceID: "test-resource-2",
AssociatedControls: []resourcesresults.ResourceAssociatedControl{
{
ControlID: "C-0002",
Name: "Test Control Medium",
},
{
ControlID: "C-0003", // Not in control summaries
Name: "Unknown Control",
},
},
},
}
// Enrich results with severity
enrichedResults := enrichResultsWithSeverity(results, controlSummaries)
// Verify results structure
assert.Equal(t, 2, len(enrichedResults))
// Verify first result
assert.Equal(t, "test-resource-1", enrichedResults[0].ResourceID)
assert.Equal(t, 1, len(enrichedResults[0].AssociatedControls))
assert.Equal(t, "High", enrichedResults[0].AssociatedControls[0].Severity)
assert.Equal(t, "C-0001", enrichedResults[0].AssociatedControls[0].ControlID)
// Verify second result
assert.Equal(t, "test-resource-2", enrichedResults[1].ResourceID)
assert.Equal(t, 2, len(enrichedResults[1].AssociatedControls))
assert.Equal(t, "Medium", enrichedResults[1].AssociatedControls[0].Severity)
assert.Equal(t, "C-0002", enrichedResults[1].AssociatedControls[0].ControlID)
// Verify unknown control gets "Unknown" severity
assert.Equal(t, "Unknown", enrichedResults[1].AssociatedControls[1].Severity)
assert.Equal(t, "C-0003", enrichedResults[1].AssociatedControls[1].ControlID)
}

View File

@@ -7,9 +7,9 @@ import (
"sort"
"strings"
"github.com/anchore/clio"
"github.com/anchore/grype/grype/presenter/models"
"github.com/enescakir/emoji"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/jwalton/gchalk"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
@@ -21,7 +21,6 @@ import (
"github.com/kubescape/opa-utils/objectsenvelopes"
"github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/olekukonko/tablewriter"
"k8s.io/utils/strings/slices"
)
@@ -90,17 +89,10 @@ func (pp *PrettyPrinter) convertToImageScanSummary(imageScanData []cautils.Image
imageScanSummary.Images = append(imageScanSummary.Images, imageScanData[i].Image)
}
presenterConfig := imageScanData[i].PresenterConfig
doc, err := models.NewDocument(clio.Identification{}, presenterConfig.Packages, presenterConfig.Context, presenterConfig.Matches, presenterConfig.IgnoredMatches, presenterConfig.MetadataProvider, nil, presenterConfig.DBStatus)
if err != nil {
logger.L().Error(fmt.Sprintf("failed to create document for image: %v", imageScanData[i].Image), helpers.Error(err))
continue
}
CVEs := extractCVEs(doc.Matches)
CVEs := extractCVEs(imageScanData[i].Matches)
imageScanSummary.CVEs = append(imageScanSummary.CVEs, CVEs...)
setPkgNameToScoreMap(doc.Matches, imageScanSummary.PackageScores)
setPkgNameToScoreMap(imageScanData[i].Matches, imageScanSummary.PackageScores)
setSeverityToSummaryMap(CVEs, imageScanSummary.MapsSeverityToSummary)
}
@@ -121,9 +113,8 @@ func (pp *PrettyPrinter) ActionPrint(_ context.Context, opaSessionObj *cautils.O
if opaSessionObj != nil {
// TODO line is currently printed on framework scan only
if isPrintSeparatorType(pp.scanType) {
fmt.Fprintf(pp.writer, "\n"+
gchalk.WithAnsi256(238).Bold(fmt.Sprintf("%s\n", strings.Repeat("─", 50)))+
"\n")
fmt.Fprintf(pp.writer, "\n%s\n\n",
gchalk.WithAnsi256(238).Bold(strings.Repeat("─", 50)))
} else {
fmt.Fprintf(pp.writer, "\n")
}
@@ -174,20 +165,20 @@ func (pp *PrettyPrinter) printHeader(opaSessionObj *cautils.OPASessionObj) {
} else if pp.scanType == cautils.ScanTypeWorkload {
cautils.InfoDisplay(pp.writer, "Workload security posture overview for:\n")
ns := opaSessionObj.SingleResourceScan.GetNamespace()
rows := [][]string{}
var rows []table.Row
if ns != "" {
rows = append(rows, []string{"Namespace", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetNamespace())})
rows = append(rows, table.Row{"Namespace", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetNamespace())})
}
rows = append(rows, []string{"Kind", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetKind())})
rows = append(rows, []string{"Name", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetName())})
rows = append(rows, table.Row{"Kind", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetKind())})
rows = append(rows, table.Row{"Name", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetName())})
table := tablewriter.NewWriter(pp.writer)
tableWriter := table.NewWriter()
tableWriter.SetOutputMirror(pp.writer)
table.SetColumnAlignment([]int{tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_LEFT})
table.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
table.AppendBulk(rows)
tableWriter.SetColumnConfigs([]table.ColumnConfig{{Number: 1, Align: text.AlignRight}, {Number: 2, Align: text.AlignLeft}})
tableWriter.AppendRows(rows)
table.Render()
tableWriter.Render()
cautils.SimpleDisplay(pp.writer, "\nIn this overview, Kubescape shows you a summary of the security posture of a workload, including key controls that apply to its configuration, and the vulnerability status of the container image.\n\n\n")
}
@@ -209,7 +200,7 @@ func (pp *PrettyPrinter) SetWriter(ctx context.Context, outputFile string) {
pp.SetMainPrinter()
}
func (pp *PrettyPrinter) Score(score float32) {
func (pp *PrettyPrinter) Score(_ float32) {
}
func (pp *PrettyPrinter) printResults(controls *reportsummary.ControlSummaries, allResources map[string]workloadinterface.IMetadata, sortedControlIDs [][]string) {
@@ -218,12 +209,12 @@ func (pp *PrettyPrinter) printResults(controls *reportsummary.ControlSummaries,
controlSummary := controls.GetControl(reportsummary.EControlCriteriaID, c) // summaryDetails.Controls ListControls().All() Controls.GetControl(ca)
pp.printTitle(controlSummary)
pp.printResources(controlSummary, allResources)
pp.printSummary(c, controlSummary)
pp.printSummary(controlSummary)
}
}
}
func (prettyPrinter *PrettyPrinter) printSummary(controlName string, controlSummary reportsummary.IControlSummary) {
func (prettyPrinter *PrettyPrinter) printSummary(controlSummary reportsummary.IControlSummary) {
cautils.SimpleDisplay(prettyPrinter.writer, "Summary - ")
cautils.SuccessDisplay(prettyPrinter.writer, "Passed:%v ", controlSummary.NumberOfResources().Passed())
cautils.WarningDisplay(prettyPrinter.writer, "Action Required:%v ", controlSummary.NumberOfResources().Skipped())

View File

@@ -3,15 +3,15 @@ package configurationprinter
import (
"io"
"github.com/jwalton/gchalk"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/olekukonko/tablewriter"
)
const (
docsPrefix = "https://hub.armosec.io/docs"
docsPrefix = "https://kubescape.io/docs/controls"
scanControlPrefix = "$ kubescape scan control"
controlNameHeader = "Control name"
statusHeader = ""
@@ -21,15 +21,15 @@ const (
)
// initializes the table headers and column alignments based on the category type
func initCategoryTableData(categoryType CategoryType) ([]string, []int) {
func initCategoryTableData(categoryType CategoryType) (table.Row, []table.ColumnConfig) {
if categoryType == TypeCounting {
return getCategoryCountingTypeHeaders(), getCountingTypeAlignments()
}
return getCategoryStatusTypeHeaders(), getStatusTypeAlignments()
}
func getCategoryStatusTypeHeaders() []string {
headers := make([]string, 3)
func getCategoryStatusTypeHeaders() table.Row {
headers := make(table.Row, 3)
headers[0] = statusHeader
headers[1] = controlNameHeader
headers[2] = docsHeader
@@ -37,8 +37,8 @@ func getCategoryStatusTypeHeaders() []string {
return headers
}
func getCategoryCountingTypeHeaders() []string {
headers := make([]string, 3)
func getCategoryCountingTypeHeaders() table.Row {
headers := make(table.Row, 3)
headers[0] = controlNameHeader
headers[1] = resourcesHeader
headers[2] = runHeader
@@ -46,16 +46,16 @@ func getCategoryCountingTypeHeaders() []string {
return headers
}
func getStatusTypeAlignments() []int {
return []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER}
func getStatusTypeAlignments() []table.ColumnConfig {
return []table.ColumnConfig{{Number: 1, Align: text.AlignCenter}, {Number: 2, Align: text.AlignLeft}, {Number: 3, Align: text.AlignCenter}}
}
func getCountingTypeAlignments() []int {
return []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT}
func getCountingTypeAlignments() []table.ColumnConfig {
return []table.ColumnConfig{{Number: 1, Align: text.AlignLeft}, {Number: 2, Align: text.AlignCenter}, {Number: 3, Align: text.AlignLeft}}
}
// returns a row for status type table based on the control summary
func generateCategoryStatusRow(controlSummary reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) []string {
func generateCategoryStatusRow(controlSummary reportsummary.IControlSummary) table.Row {
// show only passed, failed and action required controls
status := controlSummary.GetStatus()
@@ -63,7 +63,7 @@ func generateCategoryStatusRow(controlSummary reportsummary.IControlSummary, inf
return nil
}
rows := make([]string, 3)
rows := make(table.Row, 3)
rows[0] = utils.GetStatusIcon(controlSummary.GetStatus().Status())
@@ -80,31 +80,26 @@ func generateCategoryStatusRow(controlSummary reportsummary.IControlSummary, inf
}
func getCategoryTableWriter(writer io.Writer, headers []string, columnAligments []int) *tablewriter.Table {
table := tablewriter.NewWriter(writer)
table.SetHeader(headers)
table.SetHeaderLine(true)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAutoFormatHeaders(false)
table.SetColumnAlignment(columnAligments)
table.SetAutoWrapText(false)
table.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
var headerColors []tablewriter.Colors
for range headers {
headerColors = append(headerColors, tablewriter.Colors{tablewriter.FgHiYellowColor})
}
table.SetHeaderColor(headerColors...)
return table
func getCategoryTableWriter(writer io.Writer, headers table.Row, columnAlignments []table.ColumnConfig) table.Writer {
tableWriter := table.NewWriter()
tableWriter.SetOutputMirror(writer)
tableWriter.AppendHeader(headers)
tableWriter.Style().Options.SeparateHeader = true
tableWriter.Style().Format.HeaderAlign = text.AlignLeft
tableWriter.Style().Format.Header = text.FormatDefault
tableWriter.SetColumnConfigs(columnAlignments)
tableWriter.Style().Box = table.StyleBoxRounded
return tableWriter
}
func renderSingleCategory(writer io.Writer, categoryName string, table *tablewriter.Table, rows [][]string, infoToPrintInfo []utils.InfoStars) {
func renderSingleCategory(writer io.Writer, categoryName string, tableWriter table.Writer, rows []table.Row, infoToPrintInfo []utils.InfoStars) {
cautils.InfoDisplay(writer, categoryName+"\n")
table.ClearRows()
table.AppendBulk(rows)
tableWriter.ResetRows()
tableWriter.AppendRows(rows)
table.Render()
tableWriter.Render()
if len(infoToPrintInfo) > 0 {
printCategoryInfo(writer, infoToPrintInfo)

View File

@@ -3,13 +3,13 @@ package configurationprinter
import (
"io"
"os"
"reflect"
"testing"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/olekukonko/tablewriter"
"github.com/stretchr/testify/assert"
)
@@ -17,20 +17,20 @@ func TestInitCategoryTableData(t *testing.T) {
tests := []struct {
name string
categoryType CategoryType
expectedHeaders []string
expectedAlignments []int
expectedHeaders table.Row
expectedAlignments []table.ColumnConfig
}{
{
name: "Test1",
categoryType: TypeCounting,
expectedHeaders: []string{"Control name", "Resources", "View details"},
expectedAlignments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT},
expectedHeaders: table.Row{"Control name", "Resources", "View details"},
expectedAlignments: []table.ColumnConfig{{Number: 1, Align: text.AlignLeft}, {Number: 2, Align: text.AlignCenter}, {Number: 3, Align: text.AlignLeft}},
},
{
name: "Test2",
categoryType: TypeStatus,
expectedHeaders: []string{"", "Control name", "Docs"},
expectedAlignments: []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER},
expectedHeaders: table.Row{"", "Control name", "Docs"},
expectedAlignments: []table.ColumnConfig{{Number: 1, Align: text.AlignCenter}, {Number: 2, Align: text.AlignLeft}, {Number: 3, Align: text.AlignCenter}},
},
}
for _, tt := range tests {
@@ -42,8 +42,8 @@ func TestInitCategoryTableData(t *testing.T) {
if len(alignments) != len(tt.expectedAlignments) {
t.Errorf("initCategoryTableData() alignments = %v, want %v", alignments, tt.expectedAlignments)
}
assert.True(t, reflect.DeepEqual(headers, tt.expectedHeaders))
assert.True(t, reflect.DeepEqual(alignments, tt.expectedAlignments))
assert.Equal(t, headers, tt.expectedHeaders)
assert.Equal(t, alignments, tt.expectedAlignments)
})
}
}
@@ -88,52 +88,12 @@ func TestGetCategoryCountingTypeHeaders(t *testing.T) {
}
}
func TestGetStatusTypeAlignments(t *testing.T) {
alignments := getStatusTypeAlignments()
if len(alignments) != 3 {
t.Errorf("Expected 3 alignments, got %d", len(alignments))
}
if alignments[0] != tablewriter.ALIGN_CENTER {
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_CENTER, alignments[0])
}
if alignments[1] != tablewriter.ALIGN_LEFT {
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_LEFT, alignments[1])
}
if alignments[2] != tablewriter.ALIGN_CENTER {
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_CENTER, alignments[2])
}
}
func TestGetCountingTypeAlignments(t *testing.T) {
alignments := getCountingTypeAlignments()
if len(alignments) != 3 {
t.Errorf("Expected 3 alignments, got %d", len(alignments))
}
if alignments[0] != tablewriter.ALIGN_LEFT {
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_LEFT, alignments[0])
}
if alignments[1] != tablewriter.ALIGN_CENTER {
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_CENTER, alignments[1])
}
if alignments[2] != tablewriter.ALIGN_LEFT {
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_LEFT, alignments[2])
}
}
func TestGenerateCategoryStatusRow(t *testing.T) {
tests := []struct {
name string
controlSummary reportsummary.IControlSummary
infoToPrintInfo []utils.InfoStars
expectedRows []string
expectedRows table.Row
}{
{
name: "failed control",
@@ -142,7 +102,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) {
Status: apis.StatusFailed,
ControlID: "ctrlID",
},
expectedRows: []string{"❌", "test", "https://hub.armosec.io/docs/ctrlid"},
expectedRows: table.Row{"❌", "test", "https://kubescape.io/docs/controls/ctrlid"},
},
{
name: "skipped control",
@@ -154,7 +114,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) {
},
ControlID: "ctrlID",
},
expectedRows: []string{"⚠️", "test", "https://hub.armosec.io/docs/ctrlid"},
expectedRows: table.Row{"⚠️", "test", "https://kubescape.io/docs/controls/ctrlid"},
infoToPrintInfo: []utils.InfoStars{
{
Info: "testInfo",
@@ -169,7 +129,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) {
Status: apis.StatusPassed,
ControlID: "ctrlID",
},
expectedRows: []string{"✅", "test", "https://hub.armosec.io/docs/ctrlid"},
expectedRows: table.Row{"✅", "test", "https://kubescape.io/docs/controls/ctrlid"},
},
{
name: "big name",
@@ -178,36 +138,36 @@ func TestGenerateCategoryStatusRow(t *testing.T) {
Status: apis.StatusFailed,
ControlID: "ctrlID",
},
expectedRows: []string{"❌", "testtesttesttesttesttesttesttesttesttesttesttestte...", "https://hub.armosec.io/docs/ctrlid"},
expectedRows: table.Row{"❌", "testtesttesttesttesttesttesttesttesttesttesttestte...", "https://kubescape.io/docs/controls/ctrlid"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
row := generateCategoryStatusRow(tt.controlSummary, tt.infoToPrintInfo)
assert.True(t, reflect.DeepEqual(row, tt.expectedRows))
row := generateCategoryStatusRow(tt.controlSummary)
assert.Equal(t, tt.expectedRows, row)
})
}
}
func TestGetCategoryTableWriter(t *testing.T) {
tests := []struct {
name string
headers []string
columnAligments []int
want string
name string
headers table.Row
columnAlignments []table.ColumnConfig
want string
}{
{
name: "Test1",
headers: []string{"Control name", "Resources", "View details"},
columnAligments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT},
want: "──────────────┬───────────┬──────────────\n│ Control name │ Resources │ View details │\n├──────────────┼───────────┼──────────────┤\n──────────────┴───────────┴──────────────\n",
name: "Test1",
headers: table.Row{"Control name", "Resources", "View details"},
columnAlignments: []table.ColumnConfig{{Number: 1, Align: text.AlignLeft}, {Number: 2, Align: text.AlignCenter}, {Number: 3, Align: text.AlignLeft}},
want: "──────────────┬───────────┬──────────────\n│ Control name │ Resources │ View details │\n├──────────────┼───────────┼──────────────┤\n──────────────┴───────────┴──────────────\n",
},
{
name: "Test2",
headers: []string{"", "Control name", "Docs"},
columnAligments: []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER},
want: "──┬──────────────┬──────\n│ │ Control name │ Docs │\n├──┼──────────────┼──────┤\n──┴──────────────┴──────\n",
name: "Test2",
headers: table.Row{"", "Control name", "Docs"},
columnAlignments: []table.ColumnConfig{{Number: 1, Align: text.AlignCenter}, {Number: 2, Align: text.AlignLeft}, {Number: 3, Align: text.AlignCenter}},
want: "──┬──────────────┬──────\n│ │ Control name │ Docs │\n├──┼──────────────┼──────┤\n──┴──────────────┴──────\n",
},
}
for _, tt := range tests {
@@ -219,7 +179,7 @@ func TestGetCategoryTableWriter(t *testing.T) {
}
defer f.Close()
tableWriter := getCategoryTableWriter(f, tt.headers, tt.columnAligments)
tableWriter := getCategoryTableWriter(f, tt.headers, tt.columnAlignments)
// Redirect stderr to the temporary file
oldStderr := os.Stderr
@@ -245,61 +205,61 @@ func TestGetCategoryTableWriter(t *testing.T) {
func TestRenderSingleCategory(t *testing.T) {
tests := []struct {
name string
categoryName string
rows [][]string
infoToPrintInfo []utils.InfoStars
headers []string
columnAligments []int
want string
name string
categoryName string
rows []table.Row
infoToPrintInfo []utils.InfoStars
headers table.Row
columnAlignments []table.ColumnConfig
want string
}{
{
name: "Test1",
categoryName: "Resources",
rows: [][]string{
rows: []table.Row{
{"Regular", "regular line", "1"},
{"Thick", "particularly thick line", "2"},
{"Double", "double line", "3"},
},
infoToPrintInfo: []utils.InfoStars{
utils.InfoStars{
{
Stars: "1",
Info: "Low severity",
},
utils.InfoStars{
{
Stars: "5",
Info: "Critical severity",
},
},
headers: []string{"Control name", "Resources", "View details"},
columnAligments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT},
want: "Resources\n──────────────┬─────────────────────────┬──────────────\n│ Control name │ Resources │ View details │\n├──────────────┼─────────────────────────┼──────────────┤\n│ Regular │ regular line │ 1 │\n│ Thick │ particularly thick line │ 2 │\n│ Double │ double line │ 3 │\n──────────────┴─────────────────────────┴──────────────\n1 Low severity\n5 Critical severity\n\n",
headers: table.Row{"Control name", "Resources", "View details"},
columnAlignments: []table.ColumnConfig{{Number: 1, Align: text.AlignLeft}, {Number: 2, Align: text.AlignCenter}, {Number: 3, Align: text.AlignLeft}},
want: "Resources\n──────────────┬─────────────────────────┬──────────────\n│ Control name │ Resources │ View details │\n├──────────────┼─────────────────────────┼──────────────┤\n│ Regular │ regular line │ 1 │\n│ Thick │ particularly thick line │ 2 │\n│ Double │ double line │ 3 │\n──────────────┴─────────────────────────┴──────────────\n1 Low severity\n5 Critical severity\n\n",
},
{
name: "Test2",
categoryName: "Control name",
rows: [][]string{
rows: []table.Row{
{"Regular", "regular line", "1"},
{"Thick", "particularly thick line", "2"},
{"Double", "double line", "3"},
},
infoToPrintInfo: []utils.InfoStars{
utils.InfoStars{
{
Stars: "1",
Info: "Low severity",
},
utils.InfoStars{
{
Stars: "5",
Info: "Critical severity",
},
utils.InfoStars{
{
Stars: "4",
Info: "High severity",
},
},
headers: []string{"Control name", "Resources", "View details"},
columnAligments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT},
want: "Control name\n──────────────┬─────────────────────────┬──────────────\n│ Control name │ Resources │ View details │\n├──────────────┼─────────────────────────┼──────────────┤\n│ Regular │ regular line │ 1 │\n│ Thick │ particularly thick line │ 2 │\n│ Double │ double line │ 3 │\n──────────────┴─────────────────────────┴──────────────\n1 Low severity\n5 Critical severity\n4 High severity\n\n",
headers: table.Row{"Control name", "Resources", "View details"},
columnAlignments: []table.ColumnConfig{{Number: 1, Align: text.AlignLeft}, {Number: 2, Align: text.AlignCenter}, {Number: 3, Align: text.AlignLeft}},
want: "Control name\n──────────────┬─────────────────────────┬──────────────\n│ Control name │ Resources │ View details │\n├──────────────┼─────────────────────────┼──────────────┤\n│ Regular │ regular line │ 1 │\n│ Thick │ particularly thick line │ 2 │\n│ Double │ double line │ 3 │\n──────────────┴─────────────────────────┴──────────────\n1 Low severity\n5 Critical severity\n4 High severity\n\n",
},
}
for _, tt := range tests {
@@ -311,7 +271,7 @@ func TestRenderSingleCategory(t *testing.T) {
}
defer f.Close()
tableWriter := getCategoryTableWriter(f, tt.headers, tt.columnAligments)
tableWriter := getCategoryTableWriter(f, tt.headers, tt.columnAlignments)
// Redirect stderr to the temporary file
oldStderr := os.Stderr

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"io"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jwalton/gchalk"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
@@ -17,11 +18,11 @@ func NewClusterPrinter() *ClusterPrinter {
var _ TablePrinter = &ClusterPrinter{}
func (cp *ClusterPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
func (cp *ClusterPrinter) PrintSummaryTable(_ io.Writer, _ *reportsummary.SummaryDetails, _ [][]string) {
}
func (cp *ClusterPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
func (cp *ClusterPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, _ [][]string) {
categoriesToCategoryControls := mapCategoryToSummary(summaryDetails.ListControls(), mapClusterControlsToCategories)
@@ -38,17 +39,17 @@ func (cp *ClusterPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails
func (cp *ClusterPrinter) renderSingleCategoryTable(categoryName string, categoryType CategoryType, writer io.Writer, controlSummaries []reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) {
sortControlSummaries(controlSummaries)
headers, columnAligments := initCategoryTableData(categoryType)
headers, columnAlignments := initCategoryTableData(categoryType)
table := getCategoryTableWriter(writer, headers, columnAligments)
tableWriter := getCategoryTableWriter(writer, headers, columnAlignments)
var rows [][]string
var rows []table.Row
for _, ctrls := range controlSummaries {
var row []string
var row table.Row
if categoryType == TypeCounting {
row = cp.generateCountingCategoryRow(ctrls)
} else {
row = generateCategoryStatusRow(ctrls, infoToPrintInfo)
row = generateCategoryStatusRow(ctrls)
}
if len(row) > 0 {
rows = append(rows, row)
@@ -59,19 +60,19 @@ func (cp *ClusterPrinter) renderSingleCategoryTable(categoryName string, categor
return
}
renderSingleCategory(writer, categoryName, table, rows, infoToPrintInfo)
renderSingleCategory(writer, categoryName, tableWriter, rows, infoToPrintInfo)
}
func (cp *ClusterPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary) []string {
func (cp *ClusterPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary) table.Row {
row := make([]string, 3)
row := make(table.Row, 3)
row[0] = controlSummary.GetName()
failedResources := controlSummary.NumberOfResources().Failed()
if failedResources > 0 {
row[1] = string(gchalk.WithYellow().Bold(fmt.Sprintf("%d", failedResources)))
row[1] = gchalk.WithYellow().Bold(fmt.Sprintf("%d", failedResources))
} else {
row[1] = fmt.Sprintf("%d", failedResources)
}

View File

@@ -6,11 +6,11 @@ import (
"strconv"
"strings"
"github.com/jwalton/gchalk"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/olekukonko/tablewriter"
)
type FrameworkPrinter struct {
@@ -38,19 +38,21 @@ func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *
// When scanning controls the framework list will be empty
cautils.SimpleDisplay(writer, utils.FrameworksScoresToString(summaryDetails.ListFrameworks())+"\n")
controlCountersTable := tablewriter.NewWriter(writer)
controlCountersTable := table.NewWriter()
controlCountersTable.SetOutputMirror(writer)
controlCountersTable.SetColumnAlignment([]int{tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_LEFT})
controlCountersTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
controlCountersTable.AppendBulk(ControlCountersForSummary(summaryDetails.NumberOfControls()))
controlCountersTable.SetColumnConfigs([]table.ColumnConfig{{Number: 1, Align: text.AlignRight}, {Number: 2, Align: text.AlignLeft}})
controlCountersTable.Style().Box = table.StyleBoxRounded
controlCountersTable.AppendRows(ControlCountersForSummary(summaryDetails.NumberOfControls()))
controlCountersTable.Render()
cautils.SimpleDisplay(writer, "\nFailed resources by severity:\n\n")
severityCountersTable := tablewriter.NewWriter(writer)
severityCountersTable.SetColumnAlignment([]int{tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_LEFT})
severityCountersTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
severityCountersTable.AppendBulk(renderSeverityCountersSummary(summaryDetails.GetResourcesSeverityCounters()))
severityCountersTable := table.NewWriter()
severityCountersTable.SetOutputMirror(writer)
severityCountersTable.SetColumnConfigs([]table.ColumnConfig{{Number: 1, Align: text.AlignRight}, {Number: 2, Align: text.AlignLeft}})
severityCountersTable.Style().Box = table.StyleBoxRounded
severityCountersTable.AppendRows(renderSeverityCountersSummary(summaryDetails.GetResourcesSeverityCounters()))
severityCountersTable.Render()
cautils.SimpleDisplay(writer, "\n")
@@ -59,14 +61,15 @@ func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *
cautils.SimpleDisplay(writer, "Run with '--verbose'/'-v' to see control failures for each resource.\n\n")
}
summaryTable := tablewriter.NewWriter(writer)
summaryTable := table.NewWriter()
summaryTable.SetOutputMirror(writer)
summaryTable.SetAutoWrapText(false)
summaryTable.SetHeaderLine(true)
summaryTable.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
summaryTable.SetAutoFormatHeaders(false)
summaryTable.SetColumnAlignment(GetColumnsAlignments())
summaryTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
summaryTable.Style().Options.SeparateHeader = true
summaryTable.Style().Format.HeaderAlign = text.AlignLeft
summaryTable.Style().Format.Header = text.FormatDefault
summaryTable.Style().Format.Footer = text.FormatDefault
summaryTable.SetColumnConfigs(GetColumnsAlignments())
summaryTable.Style().Box = table.StyleBoxRounded
printAll := fp.getVerboseMode()
if summaryDetails.NumberOfResources().Failed() == 0 {
@@ -74,7 +77,7 @@ func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *
printAll = true
}
dataRows := [][]string{}
var dataRows []table.Row
infoToPrintInfo := utils.MapInfoToPrintInfo(summaryDetails.Controls)
for i := len(sortedControlIDs) - 1; i >= 0; i-- {
@@ -88,28 +91,23 @@ func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *
short := utils.CheckShortTerminalWidth(dataRows, GetControlTableHeaders(false))
if short {
summaryTable.SetRowLine(true)
summaryTable.Style().Options.SeparateRows = true
dataRows = shortFormatRow(dataRows)
} else {
summaryTable.SetColumnAlignment(GetColumnsAlignments())
summaryTable.SetColumnConfigs(GetColumnsAlignments())
summaryTable.Style().Format.FooterAlign = text.AlignCenter
}
summaryTable.SetHeader(GetControlTableHeaders(short))
summaryTable.SetFooter(GenerateFooter(summaryDetails, short))
summaryTable.AppendHeader(GetControlTableHeaders(short))
summaryTable.AppendFooter(GenerateFooter(summaryDetails, short))
var headerColors []tablewriter.Colors
for range dataRows[0] {
headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor})
}
summaryTable.SetHeaderColor(headerColors...)
summaryTable.AppendBulk(dataRows)
summaryTable.AppendRows(dataRows)
summaryTable.Render()
utils.PrintInfo(writer, infoToPrintInfo)
}
func shortFormatRow(dataRows [][]string) [][]string {
rows := [][]string{}
func shortFormatRow(dataRows []table.Row) []table.Row {
rows := make([]table.Row, 0, len(dataRows))
for _, dataRow := range dataRows {
// Define the row content using a formatted string
rowContent := fmt.Sprintf("Severity%s: %+v\nControl Name%s: %+v\nFailed Resources%s: %+v\nAll Resources%s: %+v\n%% Compliance-Score%s: %+v",
@@ -125,22 +123,22 @@ func shortFormatRow(dataRows [][]string) [][]string {
dataRow[summaryColumnComplianceScore])
// Append the formatted row content to the rows slice
rows = append(rows, []string{rowContent})
rows = append(rows, table.Row{rowContent})
}
return rows
}
func (fp *FrameworkPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
func (fp *FrameworkPrinter) PrintCategoriesTables(_ io.Writer, _ *reportsummary.SummaryDetails, _ [][]string) {
}
func renderSeverityCountersSummary(counters reportsummary.ISeverityCounters) [][]string {
func renderSeverityCountersSummary(counters reportsummary.ISeverityCounters) []table.Row {
rows := [][]string{}
rows = append(rows, []string{"Critical", utils.GetColorForVulnerabilitySeverity("Critical")(strconv.Itoa(counters.NumberOfCriticalSeverity()))})
rows = append(rows, []string{"High", utils.GetColorForVulnerabilitySeverity("High")(strconv.Itoa(counters.NumberOfHighSeverity()))})
rows = append(rows, []string{"Medium", utils.GetColorForVulnerabilitySeverity("Medium")(strconv.Itoa(counters.NumberOfMediumSeverity()))})
rows = append(rows, []string{"Low", utils.GetColorForVulnerabilitySeverity("Low")(strconv.Itoa(counters.NumberOfLowSeverity()))})
rows := make([]table.Row, 0, 4)
rows = append(rows, table.Row{"Critical", utils.GetColorForVulnerabilitySeverity("Critical")(strconv.Itoa(counters.NumberOfCriticalSeverity()))})
rows = append(rows, table.Row{"High", utils.GetColorForVulnerabilitySeverity("High")(strconv.Itoa(counters.NumberOfHighSeverity()))})
rows = append(rows, table.Row{"Medium", utils.GetColorForVulnerabilitySeverity("Medium")(strconv.Itoa(counters.NumberOfMediumSeverity()))})
rows = append(rows, table.Row{"Low", utils.GetColorForVulnerabilitySeverity("Low")(strconv.Itoa(counters.NumberOfLowSeverity()))})
return rows
}

View File

@@ -5,6 +5,7 @@ import (
"os"
"testing"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/stretchr/testify/assert"
)
@@ -32,7 +33,7 @@ func (m *MockISeverityCounters) NumberOfLowSeverity() int {
return m.LowCount
}
func (m *MockISeverityCounters) Increase(severity string, amount int) {
func (m *MockISeverityCounters) Increase(_ string, _ int) {
}
func TestNewFrameworkPrinter(t *testing.T) {
@@ -60,28 +61,28 @@ func TestGetVerboseMode(t *testing.T) {
func TestShortRowFormat(t *testing.T) {
tests := []struct {
name string
rows [][]string
expectedRows [][]string
rows []table.Row
expectedRows []table.Row
}{
{
name: "Test Empty rows",
rows: [][]string{},
expectedRows: [][]string{},
rows: []table.Row{},
expectedRows: []table.Row{},
},
{
name: "Test Non empty row",
rows: [][]string{
rows: []table.Row{
{"Medium", "Control 1", "2", "20", "0.8"},
},
expectedRows: [][]string{[]string{"Severity : Medium\nControl Name : Control 1\nFailed Resources : 2\nAll Resources : 20\n% Compliance-Score : 0.8"}},
expectedRows: []table.Row{{"Severity : Medium\nControl Name : Control 1\nFailed Resources : 2\nAll Resources : 20\n% Compliance-Score : 0.8"}},
},
{
name: "Test Non empty rows",
rows: [][]string{
rows: []table.Row{
{"Medium", "Control 1", "2", "20", "0.8"},
{"Low", "Control 2", "0", "30", "1.0"},
},
expectedRows: [][]string{[]string{"Severity : Medium\nControl Name : Control 1\nFailed Resources : 2\nAll Resources : 20\n% Compliance-Score : 0.8"}, []string{"Severity : Low\nControl Name : Control 2\nFailed Resources : 0\nAll Resources : 30\n% Compliance-Score : 1.0"}},
expectedRows: []table.Row{{"Severity : Medium\nControl Name : Control 1\nFailed Resources : 2\nAll Resources : 20\n% Compliance-Score : 0.8"}, {"Severity : Low\nControl Name : Control 2\nFailed Resources : 0\nAll Resources : 30\n% Compliance-Score : 1.0"}},
},
}
@@ -96,12 +97,12 @@ func TestRenderSeverityCountersSummary(t *testing.T) {
tests := []struct {
name string
counters MockISeverityCounters
expected [][]string
expected []table.Row
}{
{
name: "All empty",
counters: MockISeverityCounters{},
expected: [][]string{[]string{"Critical", "0"}, []string{"High", "0"}, []string{"Medium", "0"}, []string{"Low", "0"}},
expected: []table.Row{{"Critical", "0"}, {"High", "0"}, {"Medium", "0"}, {"Low", "0"}},
},
{
name: "All different",
@@ -111,7 +112,7 @@ func TestRenderSeverityCountersSummary(t *testing.T) {
MediumCount: 27,
LowCount: 37,
},
expected: [][]string{[]string{"Critical", "7"}, []string{"High", "17"}, []string{"Medium", "27"}, []string{"Low", "37"}},
expected: []table.Row{{"Critical", "7"}, {"High", "17"}, {"Medium", "27"}, {"Low", "37"}},
},
{
name: "All equal",
@@ -121,7 +122,7 @@ func TestRenderSeverityCountersSummary(t *testing.T) {
MediumCount: 7,
LowCount: 7,
},
expected: [][]string{[]string{"Critical", "7"}, []string{"High", "7"}, []string{"Medium", "7"}, []string{"Low", "7"}},
expected: []table.Row{{"Critical", "7"}, {"High", "7"}, {"Medium", "7"}, {"Low", "7"}},
},
}

View File

@@ -5,6 +5,7 @@ import (
"io"
"strings"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jwalton/gchalk"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/kubescape/opa-utils/reporthandling"
@@ -24,15 +25,15 @@ func NewRepoPrinter(inputPatterns []string) *RepoPrinter {
var _ TablePrinter = &RepoPrinter{}
func (rp *RepoPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
func (rp *RepoPrinter) PrintSummaryTable(_ io.Writer, _ *reportsummary.SummaryDetails, _ [][]string) {
}
func (rp *RepoPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
func (rp *RepoPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, _ [][]string) {
categoriesToCategoryControls := mapCategoryToSummary(summaryDetails.ListControls(), mapRepoControlsToCategories)
tableRended := false
tableRendered := false
for _, id := range repoCategoriesDisplayOrder {
categoryControl, ok := categoriesToCategoryControls[id]
if !ok {
@@ -43,10 +44,10 @@ func (rp *RepoPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *r
continue
}
tableRended = tableRended || rp.renderSingleCategoryTable(categoryControl.CategoryName, mapCategoryToType[id], writer, categoryControl.controlSummaries, utils.MapInfoToPrintInfoFromIface(categoryControl.controlSummaries))
tableRendered = tableRendered || rp.renderSingleCategoryTable(categoryControl.CategoryName, mapCategoryToType[id], writer, categoryControl.controlSummaries, utils.MapInfoToPrintInfoFromIface(categoryControl.controlSummaries))
}
if !tableRended {
if !tableRendered {
fmt.Fprintln(writer, gchalk.WithGreen().Bold("All controls passed. No issues found"))
}
@@ -55,21 +56,21 @@ func (rp *RepoPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *r
func (rp *RepoPrinter) renderSingleCategoryTable(categoryName string, categoryType CategoryType, writer io.Writer, controlSummaries []reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) bool {
sortControlSummaries(controlSummaries)
headers, columnAligments := initCategoryTableData(categoryType)
headers, columnAlignments := initCategoryTableData(categoryType)
table := getCategoryTableWriter(writer, headers, columnAligments)
tableWriter := getCategoryTableWriter(writer, headers, columnAlignments)
var rows [][]string
var rows []table.Row
for _, ctrls := range controlSummaries {
if ctrls.NumberOfResources().Failed() == 0 {
continue
}
var row []string
var row table.Row
if categoryType == TypeCounting {
row = rp.generateCountingCategoryRow(ctrls, rp.inputPatterns)
} else {
row = generateCategoryStatusRow(ctrls, infoToPrintInfo)
row = generateCategoryStatusRow(ctrls)
}
if len(row) > 0 {
rows = append(rows, row)
@@ -80,18 +81,18 @@ func (rp *RepoPrinter) renderSingleCategoryTable(categoryName string, categoryTy
return false
}
renderSingleCategory(writer, categoryName, table, rows, infoToPrintInfo)
renderSingleCategory(writer, categoryName, tableWriter, rows, infoToPrintInfo)
return true
}
func (rp *RepoPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary, inputPatterns []string) []string {
rows := make([]string, 3)
func (rp *RepoPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary, inputPatterns []string) table.Row {
rows := make(table.Row, 3)
rows[0] = controlSummary.GetName()
failedResources := controlSummary.NumberOfResources().Failed()
if failedResources > 0 {
rows[1] = string(gchalk.WithYellow().Bold(fmt.Sprintf("%d", failedResources)))
rows[1] = gchalk.WithYellow().Bold(fmt.Sprintf("%d", failedResources))
} else {
rows[1] = fmt.Sprintf("%d", failedResources)
}

View File

@@ -5,11 +5,12 @@ import (
"strconv"
"strings"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/olekukonko/tablewriter"
)
const (
@@ -21,12 +22,12 @@ const (
_summaryRowLen = iota
)
func ControlCountersForSummary(counters reportsummary.ICounters) [][]string {
rows := [][]string{}
rows = append(rows, []string{"Controls", strconv.Itoa(counters.All())})
rows = append(rows, []string{"Passed", strconv.Itoa(counters.Passed())})
rows = append(rows, []string{"Failed", strconv.Itoa(counters.Failed())})
rows = append(rows, []string{"Action Required", strconv.Itoa(counters.Skipped())})
func ControlCountersForSummary(counters reportsummary.ICounters) []table.Row {
rows := make([]table.Row, 0, 4)
rows = append(rows, table.Row{"Controls", strconv.Itoa(counters.All())})
rows = append(rows, table.Row{"Passed", strconv.Itoa(counters.Passed())})
rows = append(rows, table.Row{"Failed", strconv.Itoa(counters.Failed())})
rows = append(rows, table.Row{"Action Required", strconv.Itoa(counters.Skipped())})
return rows
}
@@ -35,13 +36,13 @@ func GetSeverityColumn(controlSummary reportsummary.IControlSummary) string {
return utils.GetColor(apis.ControlSeverityToInt(controlSummary.GetScoreFactor()))(apis.ControlSeverityToString(controlSummary.GetScoreFactor()))
}
func GetControlTableHeaders(short bool) []string {
var headers []string
func GetControlTableHeaders(short bool) table.Row {
var headers table.Row
if short {
headers = make([]string, 1)
headers = make(table.Row, 1)
headers[0] = "Controls"
} else {
headers = make([]string, _summaryRowLen)
headers = make(table.Row, _summaryRowLen)
headers[summaryColumnName] = "Control name"
headers[summaryColumnCounterFailed] = "Failed resources"
headers[summaryColumnCounterAll] = "All Resources"
@@ -51,22 +52,22 @@ func GetControlTableHeaders(short bool) []string {
return headers
}
func GetColumnsAlignments() []int {
alignments := make([]int, _summaryRowLen)
alignments[summaryColumnSeverity] = tablewriter.ALIGN_CENTER
alignments[summaryColumnName] = tablewriter.ALIGN_LEFT
alignments[summaryColumnCounterFailed] = tablewriter.ALIGN_CENTER
alignments[summaryColumnCounterAll] = tablewriter.ALIGN_CENTER
alignments[summaryColumnComplianceScore] = tablewriter.ALIGN_CENTER
return alignments
func GetColumnsAlignments() []table.ColumnConfig {
return []table.ColumnConfig{
{Number: summaryColumnSeverity + 1, Align: text.AlignCenter},
{Number: summaryColumnName + 1, Align: text.AlignLeft},
{Number: summaryColumnCounterFailed + 1, Align: text.AlignCenter},
{Number: summaryColumnCounterAll + 1, Align: text.AlignCenter},
{Number: summaryColumnComplianceScore + 1, Align: text.AlignCenter},
}
}
func GenerateRow(controlSummary reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars, verbose bool) []string {
row := make([]string, _summaryRowLen)
func GenerateRow(controlSummary reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars, verbose bool) table.Row {
row := make(table.Row, _summaryRowLen)
// ignore passed results
if !verbose && (controlSummary.GetStatus().IsPassed()) {
return []string{}
return table.Row{}
}
row[summaryColumnSeverity] = GetSeverityColumn(controlSummary)
@@ -98,14 +99,14 @@ func GetInfoColumn(controlSummary reportsummary.IControlSummary, infoToPrintInfo
return ""
}
func GenerateFooter(summaryDetails *reportsummary.SummaryDetails, short bool) []string {
var row []string
func GenerateFooter(summaryDetails *reportsummary.SummaryDetails, short bool) table.Row {
var row table.Row
if short {
row = make([]string, 1)
row = make(table.Row, 1)
row[0] = fmt.Sprintf("Resource Summary"+strings.Repeat(" ", 0)+"\n\nFailed Resources"+strings.Repeat(" ", 1)+": %d\nAll Resources"+strings.Repeat(" ", 4)+": %d\n%% Compliance-Score"+strings.Repeat(" ", 4)+": %.2f%%", summaryDetails.NumberOfResources().Failed(), summaryDetails.NumberOfResources().All(), summaryDetails.ComplianceScore)
} else {
// Severity | Control name | failed resources | all resources | % success
row = make([]string, _summaryRowLen)
row = make(table.Row, _summaryRowLen)
row[summaryColumnName] = "Resource Summary"
row[summaryColumnCounterFailed] = fmt.Sprintf("%d", summaryDetails.NumberOfResources().Failed())
row[summaryColumnCounterAll] = fmt.Sprintf("%d", summaryDetails.NumberOfResources().All())

View File

@@ -542,14 +542,14 @@ func TestGetDocsForControl(t *testing.T) {
controlSummary: &reportsummary.ControlSummary{
ControlID: "ctrlID1",
},
expectedDocsLink: "https://hub.armosec.io/docs/ctrlid1",
expectedDocsLink: "https://kubescape.io/docs/controls/ctrlid1",
},
{
name: "control with lowercase ID",
controlSummary: &reportsummary.ControlSummary{
ControlID: "ctrlid1",
},
expectedDocsLink: "https://hub.armosec.io/docs/ctrlid1",
expectedDocsLink: "https://kubescape.io/docs/controls/ctrlid1",
},
}

View File

@@ -3,6 +3,7 @@ package configurationprinter
import (
"io"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
)
@@ -16,11 +17,11 @@ func NewWorkloadPrinter() *WorkloadPrinter {
return &WorkloadPrinter{}
}
func (wp *WorkloadPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
func (wp *WorkloadPrinter) PrintSummaryTable(_ io.Writer, _ *reportsummary.SummaryDetails, _ [][]string) {
}
func (wp *WorkloadPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
func (wp *WorkloadPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, _ [][]string) {
categoriesToCategoryControls := mapCategoryToSummary(summaryDetails.ListControls(), mapWorkloadControlsToCategories)
@@ -30,21 +31,20 @@ func (wp *WorkloadPrinter) PrintCategoriesTables(writer io.Writer, summaryDetail
continue
}
wp.renderSingleCategoryTable(categoryControl.CategoryName, mapCategoryToType[id], writer, categoryControl.controlSummaries, utils.MapInfoToPrintInfoFromIface(categoryControl.controlSummaries))
wp.renderSingleCategoryTable(categoryControl.CategoryName, writer, categoryControl.controlSummaries, utils.MapInfoToPrintInfoFromIface(categoryControl.controlSummaries))
}
}
func (wp *WorkloadPrinter) renderSingleCategoryTable(categoryName string, categoryType CategoryType, writer io.Writer, controlSummaries []reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) {
func (wp *WorkloadPrinter) renderSingleCategoryTable(categoryName string, writer io.Writer, controlSummaries []reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) {
sortControlSummaries(controlSummaries)
headers, columnAligments := wp.initCategoryTableData()
headers, columnAlignments := wp.initCategoryTableData()
table := getCategoryTableWriter(writer, headers, columnAligments)
tableWriter := getCategoryTableWriter(writer, headers, columnAlignments)
var rows [][]string
var rows []table.Row
for _, ctrls := range controlSummaries {
var row []string
row = generateCategoryStatusRow(ctrls, infoToPrintInfo)
row := generateCategoryStatusRow(ctrls)
if len(row) > 0 {
rows = append(rows, row)
}
@@ -54,9 +54,9 @@ func (wp *WorkloadPrinter) renderSingleCategoryTable(categoryName string, catego
return
}
renderSingleCategory(writer, categoryName, table, rows, infoToPrintInfo)
renderSingleCategory(writer, categoryName, tableWriter, rows, infoToPrintInfo)
}
func (wp *WorkloadPrinter) initCategoryTableData() ([]string, []int) {
func (wp *WorkloadPrinter) initCategoryTableData() (table.Row, []table.ColumnConfig) {
return getCategoryStatusTypeHeaders(), getStatusTypeAlignments()
}

View File

@@ -3,17 +3,19 @@ package configurationprinter
import (
"testing"
"github.com/olekukonko/tablewriter"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/stretchr/testify/assert"
)
func TestWorkloadScan_InitCategoryTableData(t *testing.T) {
expectedHeader := []string{"", "Control name", "Docs"}
expectedAlign := []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER}
expectedAlign := []table.ColumnConfig{{Number: 1, Align: text.AlignCenter}, {Number: 2, Align: text.AlignLeft}, {Number: 3, Align: text.AlignCenter}}
workloadPrinter := NewWorkloadPrinter()
headers, columnAligments := workloadPrinter.initCategoryTableData()
headers, columnAlignments := workloadPrinter.initCategoryTableData()
for i := range headers {
if headers[i] != expectedHeader[i] {
@@ -21,10 +23,8 @@ func TestWorkloadScan_InitCategoryTableData(t *testing.T) {
}
}
for i := range columnAligments {
if columnAligments[i] != expectedAlign[i] {
t.Errorf("Expected column alignment %d, got %d", expectedAlign[i], columnAligments[i])
}
for i := range columnAlignments {
assert.Equal(t, expectedAlign[i], columnAlignments[i])
}
}

View File

@@ -42,7 +42,7 @@ func TestPrintImageScanningTable(t *testing.T) {
},
},
},
want: "──────────┬───────────────┬───────────┬─────────┬──────────\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ │\n│ Medium │ CVE-2020-0003 │ package3 │ 1.0.0 │ │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n──────────┴───────────────┴───────────┴─────────┴──────────\n",
want: "──────────┬───────────────┬───────────┬─────────┬──────────\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ │\n│ Medium │ CVE-2020-0003 │ package3 │ 1.0.0 │ │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n──────────┴───────────────┴───────────┴─────────┴──────────\n",
},
{
name: "check fixed CVEs show versions",
@@ -65,7 +65,7 @@ func TestPrintImageScanningTable(t *testing.T) {
},
},
},
want: "──────────┬───────────────┬───────────┬─────────┬──────────\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ v1,v2 │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n──────────┴───────────────┴───────────┴─────────┴──────────\n",
want: "──────────┬───────────────┬───────────┬─────────┬──────────\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ v1,v2 │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n──────────┴───────────────┴───────────┴─────────┴──────────\n",
},
}

View File

@@ -6,33 +6,28 @@ import (
"strings"
v5 "github.com/anchore/grype/grype/db/v5"
"github.com/jwalton/gchalk"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/olekukonko/tablewriter"
)
func renderTable(writer io.Writer, headers []string, columnAlignments []int, rows [][]string) {
table := tablewriter.NewWriter(writer)
table.SetHeader(headers)
table.SetHeaderLine(true)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAutoFormatHeaders(false)
table.SetColumnAlignment(columnAlignments)
table.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
func renderTable(writer io.Writer, headers table.Row, columnAlignments []table.ColumnConfig, rows []table.Row) {
tableWriter := table.NewWriter()
tableWriter.SetOutputMirror(writer)
tableWriter.AppendHeader(headers)
tableWriter.Style().Options.SeparateHeader = true
tableWriter.Style().Format.HeaderAlign = text.AlignLeft
tableWriter.Style().Format.Header = text.FormatDefault
tableWriter.SetColumnConfigs(columnAlignments)
tableWriter.Style().Box = table.StyleBoxRounded
var headerColors []tablewriter.Colors
for range rows[0] {
headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor})
}
table.SetHeaderColor(headerColors...)
tableWriter.AppendRows(rows)
table.AppendBulk(rows)
table.Render()
tableWriter.Render()
}
func generateRows(summary ImageScanSummary) [][]string {
rows := make([][]string, 0, len(summary.CVEs))
func generateRows(summary ImageScanSummary) []table.Row {
rows := make([]table.Row, 0, len(summary.CVEs))
// sort CVEs by severity
sort.Slice(summary.CVEs, func(i, j int) bool {
@@ -46,8 +41,8 @@ func generateRows(summary ImageScanSummary) [][]string {
return rows
}
func generateRow(cve CVE) []string {
row := make([]string, 5)
func generateRow(cve CVE) table.Row {
row := make(table.Row, 5)
row[imageColumnSeverity] = utils.GetColorForVulnerabilitySeverity(cve.Severity)(cve.Severity)
row[imageColumnName] = cve.ID
row[imageColumnComponent] = cve.Package
@@ -59,13 +54,15 @@ func generateRow(cve CVE) []string {
// if the CVE is not fixed, show the state
} else if cve.FixedState == string(v5.WontFixState) {
row[imageColumnFixedIn] = cve.FixedState
} else {
row[imageColumnFixedIn] = ""
}
return row
}
func getImageScanningHeaders() []string {
headers := make([]string, 5)
func getImageScanningHeaders() table.Row {
headers := make(table.Row, 5)
headers[imageColumnSeverity] = "Severity"
headers[imageColumnName] = "Vulnerability"
headers[imageColumnComponent] = "Component"
@@ -74,6 +71,12 @@ func getImageScanningHeaders() []string {
return headers
}
func getImageScanningColumnsAlignments() []int {
return []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}
func getImageScanningColumnsAlignments() []table.ColumnConfig {
return []table.ColumnConfig{
{Number: 1, Align: text.AlignCenter},
{Number: 2, Align: text.AlignLeft},
{Number: 3, Align: text.AlignLeft},
{Number: 4, Align: text.AlignLeft},
{Number: 5, Align: text.AlignLeft},
}
}

View File

@@ -6,7 +6,6 @@ import (
"testing"
v5 "github.com/anchore/grype/grype/db/v5"
"github.com/olekukonko/tablewriter"
"github.com/stretchr/testify/assert"
)
@@ -46,7 +45,7 @@ func TestRenderTable(t *testing.T) {
},
},
},
want: "──────────┬───────────────┬───────────┬─────────┬──────────\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ │\n│ Medium │ CVE-2020-0003 │ package3 │ 1.0.0 │ │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n──────────┴───────────────┴───────────┴─────────┴──────────\n",
want: "──────────┬───────────────┬───────────┬─────────┬──────────\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ │\n│ Medium │ CVE-2020-0003 │ package3 │ 1.0.0 │ │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n──────────┴───────────────┴───────────┴─────────┴──────────\n",
},
{
name: "check fixed CVEs show versions",
@@ -69,7 +68,7 @@ func TestRenderTable(t *testing.T) {
},
},
},
want: "──────────┬───────────────┬───────────┬─────────┬──────────\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ v1,v2 │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n──────────┴───────────────┴───────────┴─────────┴──────────\n",
want: "──────────┬───────────────┬───────────┬─────────┬──────────\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ v1,v2 │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n──────────┴───────────────┴───────────┴─────────┴──────────\n",
},
}
@@ -247,15 +246,3 @@ func TestGetImageScanningHeaders(t *testing.T) {
}
}
}
func TestGetImageScanningColumnsAlignments(t *testing.T) {
alignments := getImageScanningColumnsAlignments()
expectedAlignments := []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}
for i := range alignments {
if alignments[i] != expectedAlignments[i] {
t.Errorf("expected %d, got %d", expectedAlignments[i], alignments[i])
}
}
}

View File

@@ -6,6 +6,7 @@ import (
"os"
"github.com/enescakir/emoji"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jwalton/gchalk"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/opa-utils/reporthandling/apis"
@@ -138,19 +139,19 @@ func GetStatusIcon(status apis.ScanningStatus) string {
}
}
func CheckShortTerminalWidth(rows [][]string, headers []string) bool {
func CheckShortTerminalWidth(rows []table.Row, headers table.Row) bool {
maxWidth := 0
for _, row := range rows {
rowWidth := 0
for idx, cell := range row {
cellLen := len(cell)
cellLen := len(cell.(string))
if cellLen > 50 { // Take only 50 characters of each sentence for counting size
cellLen = 50
}
if cellLen > len(headers[idx]) {
if cellLen > len(headers[idx].(string)) {
rowWidth += cellLen
} else {
rowWidth += len(headers[idx])
rowWidth += len(headers[idx].(string))
}
rowWidth += 2
}

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"os"
"github.com/anchore/grype/grype/presenter/models"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/workloadinterface"
@@ -59,9 +58,6 @@ func (pp *PrometheusPrinter) generatePrometheusFormat(
return m
}
func (pp *PrometheusPrinter) PrintImageScan(context.Context, *models.PresenterConfig) {
}
func (pp *PrometheusPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
if opaSessionObj == nil {
logger.L().Ctx(ctx).Error("failed to print results, missing data")

View File

@@ -2,16 +2,18 @@ package printer
import (
"fmt"
"sort"
"regexp"
"strconv"
"strings"
"github.com/jwalton/gchalk"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults"
"github.com/olekukonko/tablewriter"
)
const (
@@ -43,61 +45,46 @@ func (prettyPrinter *PrettyPrinter) resourceTable(opaSessionObj *cautils.OPASess
if resource.GetNamespace() != "" {
fmt.Fprintf(prettyPrinter.writer, "Namespace: %s\n", resource.GetNamespace())
}
fmt.Fprintf(prettyPrinter.writer, "\n"+prettyprinter.ControlCountersForResource(result.ListControlsIDs(nil))+"\n\n")
fmt.Fprintf(prettyPrinter.writer, "\n%s\n\n", prettyprinter.ControlCountersForResource(result.ListControlsIDs(nil)))
summaryTable := tablewriter.NewWriter(prettyPrinter.writer)
summaryTable := table.NewWriter()
summaryTable.SetOutputMirror(prettyPrinter.writer)
summaryTable.SetAutoWrapText(true)
summaryTable.SetAutoMergeCells(true)
summaryTable.SetHeaderLine(true)
summaryTable.SetRowLine(true)
summaryTable.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
summaryTable.SetAutoFormatHeaders(false)
summaryTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
summaryTable.Style().Options.SeparateHeader = true
summaryTable.Style().Options.SeparateRows = true
summaryTable.Style().Format.HeaderAlign = text.AlignLeft
summaryTable.Style().Format.Header = text.FormatDefault
summaryTable.Style().Box = table.StyleBoxRounded
resourceRows := [][]string{}
if raw := generateResourceRows(result.ListControls(), &opaSessionObj.Report.SummaryDetails); len(raw) > 0 {
resourceRows = append(resourceRows, raw...)
}
resourceRows := generateResourceRows(result.ListControls(), &opaSessionObj.Report.SummaryDetails, resource)
short := utils.CheckShortTerminalWidth(resourceRows, generateResourceHeader(false))
if short {
summaryTable.SetAutoWrapText(false)
summaryTable.SetAutoMergeCells(false)
resourceRows = shortFormatResource(resourceRows)
}
summaryTable.SetHeader(generateResourceHeader(short))
summaryTable.AppendHeader(generateResourceHeader(short))
var headerColors []tablewriter.Colors
for range resourceRows[0] {
headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor})
}
summaryTable.SetHeaderColor(headerColors...)
data := Matrix{}
data = append(data, resourceRows...)
// For control scan framework will be nil
sort.Sort(data)
summaryTable.AppendBulk(data)
summaryTable.AppendRows(resourceRows)
summaryTable.Render()
}
}
func generateResourceRows(controls []resourcesresults.ResourceAssociatedControl, summaryDetails *reportsummary.SummaryDetails) [][]string {
rows := [][]string{}
func generateResourceRows(controls []resourcesresults.ResourceAssociatedControl, summaryDetails *reportsummary.SummaryDetails, resource workloadinterface.IMetadata) []table.Row {
var rows []table.Row
for i := range controls {
row := make([]string, _resourceRowLen)
row := make(table.Row, _resourceRowLen)
if !controls[i].GetStatus(nil).IsFailed() {
continue
}
row[resourceColumnURL] = cautils.GetControlLink(controls[i].GetID())
row[resourceColumnPath] = strings.Join(AssistedRemediationPathsToString(&controls[i]), "\n")
paths := AssistedRemediationPathsToString(&controls[i])
addContainerNameToAssistedRemediation(resource, &paths)
row[resourceColumnPath] = strings.Join(paths, "\n")
row[resourceColumnName] = controls[i].GetName()
if c := summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, controls[i].GetID()); c != nil {
@@ -110,22 +97,32 @@ func generateResourceRows(controls []resourcesresults.ResourceAssociatedControl,
return rows
}
func generateResourceHeader(short bool) []string {
headers := make([]string, 0)
if short {
headers = append(headers, "Resources")
} else {
headers = append(headers, []string{"Severity", "Control name", "Docs", "Assisted remediation"}...)
func addContainerNameToAssistedRemediation(resource workloadinterface.IMetadata, paths *[]string) {
for i := range *paths {
re := regexp.MustCompile(`spec\.containers\[(\d+)]`)
match := re.FindStringSubmatch((*paths)[i])
if len(match) == 2 {
index, _ := strconv.Atoi(match[1])
wl := workloadinterface.NewWorkloadObj(resource.GetObject())
containers, _ := wl.GetContainers()
containerName := containers[index].Name
(*paths)[i] = (*paths)[i] + " (" + containerName + ")"
}
}
return headers
}
func shortFormatResource(resourceRows [][]string) [][]string {
rows := [][]string{}
for _, resourceRow := range resourceRows {
rows = append(rows, []string{fmt.Sprintf("Severity"+strings.Repeat(" ", 13)+": %+v\nControl Name"+strings.Repeat(" ", 9)+": %+v\nDocs"+strings.Repeat(" ", 17)+": %+v\nAssisted Remediation"+strings.Repeat(" ", 1)+": %+v", resourceRow[resourceColumnSeverity], resourceRow[resourceColumnName], resourceRow[resourceColumnURL], strings.Replace(resourceRow[resourceColumnPath], "\n", "\n"+strings.Repeat(" ", 23), -1))})
func generateResourceHeader(short bool) table.Row {
if short {
return table.Row{"Resources"}
} else {
return table.Row{"Severity", "Control name", "Docs", "Assisted remediation"}
}
}
func shortFormatResource(resourceRows []table.Row) []table.Row {
rows := make([]table.Row, len(resourceRows))
for i, resourceRow := range resourceRows {
rows[i] = table.Row{fmt.Sprintf("Severity"+strings.Repeat(" ", 13)+": %+v\nControl Name"+strings.Repeat(" ", 9)+": %+v\nDocs"+strings.Repeat(" ", 17)+": %+v\nAssisted Remediation"+strings.Repeat(" ", 1)+": %+v", resourceRow[resourceColumnSeverity], resourceRow[resourceColumnName], resourceRow[resourceColumnURL], strings.Replace(resourceRow[resourceColumnPath].(string), "\n", "\n"+strings.Repeat(" ", 23), -1))}
}
return rows
}

View File

@@ -4,6 +4,8 @@ import (
"testing"
"github.com/armosec/armoapi-go/armotypes"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults"
@@ -326,15 +328,15 @@ func TestFailedPathsToString(t *testing.T) {
func TestShortFormatResource(t *testing.T) {
// Create a test case with an empty resourceRows slice
emptyResourceRows := [][]string{}
emptyResourceRows := []table.Row{}
// Create a test case with a single resource row
singleResourceRow := [][]string{
singleResourceRow := []table.Row{
{"High", "Control1", "https://example.com/doc1", "Path1"},
}
// Create a test case with multiple resource rows
multipleResourceRows := [][]string{
multipleResourceRows := []table.Row{
{"Medium", "Control2", "https://example.com/doc2", "Path2"},
{"Low", "Control3", "https://example.com/doc3", "Path3"},
}
@@ -343,11 +345,11 @@ func TestShortFormatResource(t *testing.T) {
assert.Empty(t, actualRows)
actualRows = shortFormatResource(singleResourceRow)
expectedRows := [][]string{{"Severity : High\nControl Name : Control1\nDocs : https://example.com/doc1\nAssisted Remediation : Path1"}}
expectedRows := []table.Row{{"Severity : High\nControl Name : Control1\nDocs : https://example.com/doc1\nAssisted Remediation : Path1"}}
assert.Equal(t, expectedRows, actualRows)
actualRows = shortFormatResource(multipleResourceRows)
expectedRows = [][]string{{"Severity : Medium\nControl Name : Control2\nDocs : https://example.com/doc2\nAssisted Remediation : Path2"},
expectedRows = []table.Row{{"Severity : Medium\nControl Name : Control2\nDocs : https://example.com/doc2\nAssisted Remediation : Path2"},
{"Severity : Low\nControl Name : Control3\nDocs : https://example.com/doc3\nAssisted Remediation : Path3"}}
assert.Equal(t, expectedRows, actualRows)
}
@@ -355,33 +357,47 @@ func TestShortFormatResource(t *testing.T) {
func TestGenerateResourceHeader(t *testing.T) {
// Test case 1: Short headers
shortHeaders := generateResourceHeader(true)
expectedShortHeaders := []string{"Resources"}
expectedShortHeaders := table.Row{"Resources"}
assert.Equal(t, expectedShortHeaders, shortHeaders)
// Test case 2: Full headers
fullHeaders := generateResourceHeader(false)
expectedFullHeaders := []string{"Severity", "Control name", "Docs", "Assisted remediation"}
expectedFullHeaders := table.Row{"Severity", "Control name", "Docs", "Assisted remediation"}
assert.Equal(t, expectedFullHeaders, fullHeaders)
}
func TestGenerateResourceRows_Loop(t *testing.T) {
tests := []struct {
name string
summaryDetails reportsummary.SummaryDetails
controls []resourcesresults.ResourceAssociatedControl
expectedLen int
name string
summaryDetails reportsummary.SummaryDetails
controls []resourcesresults.ResourceAssociatedControl
resource workloadinterface.IMetadata
expectedLen int
expectedContainerName string
}{
{
name: "Empty controls",
summaryDetails: reportsummary.SummaryDetails{},
controls: []resourcesresults.ResourceAssociatedControl{},
expectedLen: 0,
resource: workloadinterface.NewWorkloadObj(map[string]interface{}{
"kind": "Pod",
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "alpine-container",
"image": "alpine:latest",
},
},
},
}),
expectedLen: 0,
expectedContainerName: "",
},
{
name: "2 Failed Controls",
summaryDetails: reportsummary.SummaryDetails{},
controls: []resourcesresults.ResourceAssociatedControl{
resourcesresults.ResourceAssociatedControl{
{
ControlID: "control-1",
Name: "Control 1",
Status: apis.StatusInfo{},
@@ -393,16 +409,16 @@ func TestGenerateResourceRows_Loop(t *testing.T) {
Paths: []armotypes.PosturePaths{
{
FailedPath: "some-path1",
FailedPath: "spec.template.spec.containers[0].securityContext.runAsNonRoot=true",
},
{
FailedPath: "random-path1",
FailedPath: "spec.template.spec.containers[0].securityContext.runAsGroup=1000",
},
},
},
},
},
resourcesresults.ResourceAssociatedControl{
{
ControlID: "control-2",
Name: "Control 2",
Status: apis.StatusInfo{},
@@ -413,23 +429,35 @@ func TestGenerateResourceRows_Loop(t *testing.T) {
SubStatus: "configuration",
Paths: []armotypes.PosturePaths{
{
FailedPath: "some-path2",
FailedPath: "spec.template.spec.containers[0].securityContext.runAsNonRoot=true",
},
{
FailedPath: "random-path2",
FailedPath: "spec.template.spec.containers[0].securityContext.runAsGroup=true",
},
},
},
},
},
},
expectedLen: 2,
resource: workloadinterface.NewWorkloadObj(map[string]interface{}{
"kind": "Pod",
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "alpine-container",
"image": "alpine:latest",
},
},
},
}),
expectedLen: 2,
expectedContainerName: "alpine-container",
},
{
name: "One failed control",
summaryDetails: reportsummary.SummaryDetails{},
controls: []resourcesresults.ResourceAssociatedControl{
resourcesresults.ResourceAssociatedControl{
{
ControlID: "control-1",
Name: "Control 1",
Status: apis.StatusInfo{},
@@ -441,16 +469,16 @@ func TestGenerateResourceRows_Loop(t *testing.T) {
Paths: []armotypes.PosturePaths{
{
FailedPath: "some-path1",
FailedPath: "spec.template.spec.containers[0].securityContext.runAsNonRoot=true",
},
{
FailedPath: "random-path1",
FailedPath: "spec.template.spec.containers[0].securityContext.runAsGroup=true",
},
},
},
},
},
resourcesresults.ResourceAssociatedControl{
{
ControlID: "control-2",
Name: "Control 2",
Status: apis.StatusInfo{},
@@ -461,24 +489,42 @@ func TestGenerateResourceRows_Loop(t *testing.T) {
SubStatus: "configuration",
Paths: []armotypes.PosturePaths{
{
FailedPath: "some-path2",
FailedPath: "spec.template.spec.containers[0].securityContext.runAsNonRoot=true",
},
{
FailedPath: "random-path2",
FailedPath: "spec.template.spec.containers[0].securityContext.runAsGroup=true",
},
},
},
},
},
},
expectedLen: 1,
resource: workloadinterface.NewWorkloadObj(map[string]interface{}{
"kind": "Pod",
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx-container",
"image": "nginx:latest",
},
},
},
}),
expectedLen: 1,
expectedContainerName: "nginx-container",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rows := generateResourceRows(tt.controls, &tt.summaryDetails)
rows := generateResourceRows(tt.controls, &tt.summaryDetails, tt.resource)
assert.Equal(t, tt.expectedLen, len(rows))
//remediation is the last column of the first row
if len(rows) != 0 {
remediation := rows[0][3]
assert.Contains(t, remediation, tt.expectedContainerName)
}
})
}
}

View File

@@ -12,8 +12,9 @@ import (
"strconv"
"strings"
"github.com/anchore/grype/grype/presenter"
"github.com/anchore/clio"
"github.com/anchore/grype/grype/presenter/models"
grypesarif "github.com/anchore/grype/grype/presenter/sarif"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
@@ -115,12 +116,14 @@ func (sp *SARIFPrinter) addResult(scanRun *sarif.Run, ctl reportsummary.IControl
})
}
func (sp *SARIFPrinter) printImageScan(ctx context.Context, scanResults *models.PresenterConfig) error {
if scanResults == nil {
return fmt.Errorf("no no image vulnerability data provided")
func (sp *SARIFPrinter) printImageScan(ctx context.Context, scanResults cautils.ImageScanData) error {
model, err := models.NewDocument(clio.Identification{}, scanResults.Packages, scanResults.Context,
*scanResults.RemainingMatches, scanResults.IgnoredMatches, scanResults.VulnerabilityProvider, nil, nil, models.DefaultSortStrategy, false)
if err != nil {
return fmt.Errorf("failed to create document: %w", err)
}
pres := presenter.GetPresenter(printer.SARIFFormat, "", false, *scanResults)
pres := grypesarif.NewPresenter(models.PresenterConfig{Document: model, SBOM: scanResults.SBOM})
if err := pres.Present(sp.writer); err != nil {
return err
}
@@ -164,7 +167,7 @@ func (sp *SARIFPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.
}
// image scan
if err := sp.printImageScan(ctx, imageScanData[0].PresenterConfig); err != nil {
if err := sp.printImageScan(ctx, imageScanData[0]); err != nil {
logger.L().Ctx(ctx).Error("failed to write results in sarif format", helpers.Error(err))
return
}
@@ -196,7 +199,7 @@ func (sp *SARIFPrinter) printConfigurationScan(ctx context.Context, opaSessionOb
filepath := resourceSource.RelativePath
// Github Code Scanning considers results not associated to a file path meaningless and invalid when uploading
if filepath == "" || basePath == "" {
if filepath == "" && basePath == "" {
continue
}

View File

@@ -3,7 +3,6 @@ package printer
import (
"context"
"github.com/anchore/grype/grype/presenter/models"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer"
)
@@ -18,9 +17,6 @@ func (silentPrinter *SilentPrinter) PrintNextSteps() {
}
func (silentPrinter *SilentPrinter) PrintImageScan(context.Context, *models.PresenterConfig) {
}
func (silentPrinter *SilentPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
}

View File

@@ -2,12 +2,13 @@ package printer
import (
v5 "github.com/anchore/grype/grype/db/v5"
"github.com/anchore/grype/grype/presenter/models"
"github.com/anchore/grype/grype/match"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/prioritization"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults"
@@ -16,6 +17,122 @@ import (
const indicator = "†"
// ControlSummaryWithSeverity wraps ControlSummary to add severity field for JSON output
type ControlSummaryWithSeverity struct {
reportsummary.ControlSummary
Severity string `json:"severity"`
}
// ResourceAssociatedControlWithSeverity wraps ResourceAssociatedControl to add severity field
type ResourceAssociatedControlWithSeverity struct {
resourcesresults.ResourceAssociatedControl
Severity string `json:"severity"`
}
// ResultWithSeverity wraps Result to include severity in associated controls
type ResultWithSeverity struct {
ResourceID string `json:"resourceID"`
AssociatedControls []ResourceAssociatedControlWithSeverity `json:"controls,omitempty"`
PrioritizedResource *prioritization.PrioritizedResource `json:"prioritizedResource,omitempty"`
}
// SummaryDetailsWithSeverity wraps SummaryDetails to include enriched controls
type SummaryDetailsWithSeverity struct {
Controls map[string]ControlSummaryWithSeverity `json:"controls,omitempty"`
Status apis.ScanningStatus `json:"status"`
Frameworks []reportsummary.FrameworkSummary `json:"frameworks"`
ResourcesSeverityCounters reportsummary.SeverityCounters `json:"resourcesSeverityCounters,omitempty"`
ControlsSeverityCounters reportsummary.SeverityCounters `json:"controlsSeverityCounters,omitempty"`
StatusCounters reportsummary.StatusCounters `json:"ResourceCounters"`
Vulnerabilities reportsummary.VulnerabilitySummary `json:"vulnerabilities,omitempty"`
Score float32 `json:"score"`
ComplianceScore float32 `json:"complianceScore"`
}
// PostureReportWithSeverity wraps PostureReport to include severity in controls
type PostureReportWithSeverity struct {
ReportGenerationTime string `json:"generationTime"`
ClusterAPIServerInfo interface{} `json:"clusterAPIServerInfo"`
ClusterCloudProvider string `json:"clusterCloudProvider"`
CustomerGUID string `json:"customerGUID"`
ClusterName string `json:"clusterName"`
SummaryDetails SummaryDetailsWithSeverity `json:"summaryDetails,omitempty"`
Resources []reporthandling.Resource `json:"resources,omitempty"`
Attributes []reportsummary.PostureAttributes `json:"attributes"`
Results []ResultWithSeverity `json:"results,omitempty"`
Metadata reporthandlingv2.Metadata `json:"metadata,omitempty"`
}
// enrichControlsWithSeverity adds severity field to controls based on scoreFactor
func enrichControlsWithSeverity(controls reportsummary.ControlSummaries) map[string]ControlSummaryWithSeverity {
enrichedControls := make(map[string]ControlSummaryWithSeverity)
for controlID, control := range controls {
enrichedControl := ControlSummaryWithSeverity{
ControlSummary: control,
Severity: apis.ControlSeverityToString(control.GetScoreFactor()),
}
enrichedControls[controlID] = enrichedControl
}
return enrichedControls
}
// enrichResultsWithSeverity adds severity field to controls in results
func enrichResultsWithSeverity(results []resourcesresults.Result, controlSummaries reportsummary.ControlSummaries) []ResultWithSeverity {
enrichedResults := make([]ResultWithSeverity, len(results))
for i, result := range results {
enrichedControls := make([]ResourceAssociatedControlWithSeverity, len(result.AssociatedControls))
for j, control := range result.AssociatedControls {
// Get the severity from the control summary
severity := "Unknown"
if controlSummary, exists := controlSummaries[control.GetID()]; exists {
severity = apis.ControlSeverityToString(controlSummary.GetScoreFactor())
}
enrichedControls[j] = ResourceAssociatedControlWithSeverity{
ResourceAssociatedControl: control,
Severity: severity,
}
}
enrichedResults[i] = ResultWithSeverity{
ResourceID: result.ResourceID,
AssociatedControls: enrichedControls,
PrioritizedResource: result.PrioritizedResource,
}
}
return enrichedResults
}
// ConvertToPostureReportWithSeverity converts PostureReport to PostureReportWithSeverity
func ConvertToPostureReportWithSeverity(report *reporthandlingv2.PostureReport) *PostureReportWithSeverity {
if report == nil {
return nil
}
enrichedControls := enrichControlsWithSeverity(report.SummaryDetails.Controls)
enrichedResults := enrichResultsWithSeverity(report.Results, report.SummaryDetails.Controls)
return &PostureReportWithSeverity{
ReportGenerationTime: report.ReportGenerationTime.Format("2006-01-02T15:04:05Z07:00"),
ClusterAPIServerInfo: report.ClusterAPIServerInfo,
ClusterCloudProvider: report.ClusterCloudProvider,
CustomerGUID: report.CustomerGUID,
ClusterName: report.ClusterName,
SummaryDetails: SummaryDetailsWithSeverity{
Controls: enrichedControls,
Status: report.SummaryDetails.Status,
Frameworks: report.SummaryDetails.Frameworks,
ResourcesSeverityCounters: report.SummaryDetails.ResourcesSeverityCounters,
ControlsSeverityCounters: report.SummaryDetails.ControlsSeverityCounters,
StatusCounters: report.SummaryDetails.StatusCounters,
Vulnerabilities: report.SummaryDetails.Vulnerabilities,
Score: report.SummaryDetails.Score,
ComplianceScore: report.SummaryDetails.ComplianceScore,
},
Resources: report.Resources,
Attributes: report.Attributes,
Results: enrichedResults,
Metadata: report.Metadata,
}
}
// FinalizeResults finalize the results objects by copying data from map to lists
func FinalizeResults(data *cautils.OPASessionObj) *reporthandlingv2.PostureReport {
report := reporthandlingv2.PostureReport{
@@ -103,39 +220,39 @@ func setSeverityToSummaryMap(cves []imageprinter.CVE, mapSeverityToSummary map[s
}
}
func setPkgNameToScoreMap(matches []models.Match, pkgScores map[string]*imageprinter.PackageScore) {
for i := range matches {
func setPkgNameToScoreMap(matches match.Matches, pkgScores map[string]*imageprinter.PackageScore) {
for _, m := range matches.Sorted() {
// key is pkg name + version to avoid version conflicts
key := matches[i].Artifact.Name + matches[i].Artifact.Version
key := m.Package.Name + m.Package.Version
if _, ok := pkgScores[key]; !ok {
pkgScores[key] = &imageprinter.PackageScore{
Version: matches[i].Artifact.Version,
Name: matches[i].Artifact.Name,
Version: m.Package.Version,
Name: m.Package.Name,
MapSeverityToCVEsNumber: make(map[string]int, 0),
}
}
if _, ok := pkgScores[key].MapSeverityToCVEsNumber[matches[i].Vulnerability.Severity]; !ok {
pkgScores[key].MapSeverityToCVEsNumber[matches[i].Vulnerability.Severity] = 1
if _, ok := pkgScores[key].MapSeverityToCVEsNumber[m.Vulnerability.Metadata.Severity]; !ok {
pkgScores[key].MapSeverityToCVEsNumber[m.Vulnerability.Metadata.Severity] = 1
} else {
pkgScores[key].MapSeverityToCVEsNumber[matches[i].Vulnerability.Severity] += 1
pkgScores[key].MapSeverityToCVEsNumber[m.Vulnerability.Metadata.Severity] += 1
}
pkgScores[key].Score += utils.ImageSeverityToInt(matches[i].Vulnerability.Severity)
pkgScores[key].Score += utils.ImageSeverityToInt(m.Vulnerability.Metadata.Severity)
}
}
func extractCVEs(matches []models.Match) []imageprinter.CVE {
CVEs := []imageprinter.CVE{}
for i := range matches {
func extractCVEs(matches match.Matches) []imageprinter.CVE {
var CVEs []imageprinter.CVE
for _, m := range matches.Sorted() {
cve := imageprinter.CVE{
ID: matches[i].Vulnerability.ID,
Severity: matches[i].Vulnerability.Severity,
Package: matches[i].Artifact.Name,
Version: matches[i].Artifact.Version,
FixVersions: matches[i].Vulnerability.Fix.Versions,
FixedState: matches[i].Vulnerability.Fix.State,
ID: m.Vulnerability.Metadata.ID,
Severity: m.Vulnerability.Metadata.Severity,
Package: m.Package.Name,
Version: m.Package.Version,
FixVersions: m.Vulnerability.Fix.Versions,
FixedState: m.Vulnerability.Fix.State.String(),
}
CVEs = append(CVEs, cve)
}

View File

@@ -4,7 +4,9 @@ import (
"testing"
v5 "github.com/anchore/grype/grype/db/v5"
"github.com/anchore/grype/grype/presenter/models"
"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/pkg"
"github.com/anchore/grype/grype/vulnerability"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
"github.com/stretchr/testify/assert"
)
@@ -12,29 +14,30 @@ import (
func TestExtractCVEs(t *testing.T) {
tests := []struct {
name string
matches []models.Match
matches match.Matches
want []imageprinter.CVE
}{
{
name: "single vuln",
matches: []models.Match{
matches: match.NewMatches([]match.Match{
{
Artifact: models.Package{
Package: pkg.Package{
ID: "1",
Name: "foo",
Version: "1.2.3",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
ID: "CVE-2020-1234",
Severity: "High",
},
Fix: models.Fix{
Fix: vulnerability.Fix{
Versions: []string{"1.2.3"},
State: "Fixed",
},
},
},
},
}...),
want: []imageprinter.CVE{
{
ID: "CVE-2020-1234",
@@ -48,56 +51,59 @@ func TestExtractCVEs(t *testing.T) {
},
{
name: "multiple vulns",
matches: []models.Match{
matches: match.NewMatches([]match.Match{
{
Artifact: models.Package{
Package: pkg.Package{
ID: "1",
Name: "foo",
Version: "1.2.3",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
ID: "CVE-2020-1234",
Severity: "High",
},
Fix: models.Fix{
Fix: vulnerability.Fix{
Versions: []string{"1.2.3"},
State: "Fixed",
},
},
},
{
Artifact: models.Package{
Package: pkg.Package{
ID: "2",
Name: "test",
Version: "1",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
ID: "CVE-2020-1235",
Severity: "Critical",
},
Fix: models.Fix{
Fix: vulnerability.Fix{
Versions: []string{"1"},
State: "Fixed",
},
},
},
{
Artifact: models.Package{
Package: pkg.Package{
ID: "3",
Name: "test2",
Version: "3",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
ID: "CVE-2020-1236",
Severity: "Low",
},
Fix: models.Fix{
Fix: vulnerability.Fix{
Versions: []string{"2", "3", "4"},
State: "Not fixed",
},
},
},
},
}...),
want: []imageprinter.CVE{
{
ID: "CVE-2020-1234",
@@ -127,7 +133,7 @@ func TestExtractCVEs(t *testing.T) {
},
{
name: "empty vulns",
matches: []models.Match{},
matches: match.NewMatches([]match.Match{}...),
want: []imageprinter.CVE{},
},
}
@@ -171,25 +177,26 @@ func TestExtractCVEs(t *testing.T) {
func TestSetPkgNameToScoreMap(t *testing.T) {
tests := []struct {
name string
matches []models.Match
matches match.Matches
originalMap map[string]*imageprinter.PackageScore
want map[string]*imageprinter.PackageScore
}{
{
name: "single package",
matches: []models.Match{
matches: match.NewMatches([]match.Match{
{
Artifact: models.Package{
Package: pkg.Package{
ID: "1",
Name: "foo",
Version: "1.2.3",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "High",
},
},
},
},
}...),
want: map[string]*imageprinter.PackageScore{
"foo1.2.3": {
Name: "foo",
@@ -203,41 +210,44 @@ func TestSetPkgNameToScoreMap(t *testing.T) {
},
{
name: "multiple packages - different versions",
matches: []models.Match{
matches: match.NewMatches([]match.Match{
{
Artifact: models.Package{
Package: pkg.Package{
ID: "1",
Name: "pkg1",
Version: "version1",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "Critical",
},
},
},
{
Artifact: models.Package{
Package: pkg.Package{
ID: "2",
Name: "pkg2",
Version: "1.2",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "Low",
},
},
},
{
Artifact: models.Package{
Package: pkg.Package{
ID: "3",
Name: "pkg3",
Version: "1.2.3",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "High",
},
},
},
},
}...),
want: map[string]*imageprinter.PackageScore{
"pkg1version1": {
Name: "pkg1",
@@ -267,74 +277,80 @@ func TestSetPkgNameToScoreMap(t *testing.T) {
},
{
name: "multiple packages - mixed versions",
matches: []models.Match{
matches: match.NewMatches([]match.Match{
{
Artifact: models.Package{
Package: pkg.Package{
ID: "1",
Name: "pkg1",
Version: "version1",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "High",
},
},
},
{
Artifact: models.Package{
Package: pkg.Package{
ID: "2",
Name: "pkg1",
Version: "version1",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "High",
},
},
},
{
Artifact: models.Package{
Package: pkg.Package{
ID: "3",
Name: "pkg1",
Version: "version2",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "Critical",
},
},
},
{
Artifact: models.Package{
Package: pkg.Package{
ID: "4",
Name: "pkg3",
Version: "1.2",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "Medium",
},
},
},
{
Artifact: models.Package{
Package: pkg.Package{
ID: "5",
Name: "pkg3",
Version: "1.2",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "Low",
},
},
},
{
Artifact: models.Package{
Package: pkg.Package{
ID: "6",
Name: "pkg4",
Version: "1.2.3",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "High",
},
},
},
},
}...),
want: map[string]*imageprinter.PackageScore{
"pkg1version1": {
Name: "pkg1",
@@ -373,46 +389,49 @@ func TestSetPkgNameToScoreMap(t *testing.T) {
},
{
name: "empty packages",
matches: []models.Match{},
matches: match.NewMatches(),
want: map[string]*imageprinter.PackageScore{},
},
{
name: "original map not empty",
matches: []models.Match{
matches: match.NewMatches([]match.Match{
{
Artifact: models.Package{
Package: pkg.Package{
ID: "1",
Name: "pkg1",
Version: "version2",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "Critical",
},
},
},
{
Artifact: models.Package{
Package: pkg.Package{
ID: "2",
Name: "pkg1",
Version: "version1",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "High",
},
},
},
{
Artifact: models.Package{
Package: pkg.Package{
ID: "3",
Name: "pkg1",
Version: "version1",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "High",
},
},
},
},
}...),
originalMap: map[string]*imageprinter.PackageScore{
"pkg41.2.3": {
Name: "pkg4",
@@ -452,41 +471,44 @@ func TestSetPkgNameToScoreMap(t *testing.T) {
},
{
name: "original map with same package",
matches: []models.Match{
matches: match.NewMatches([]match.Match{
{
Artifact: models.Package{
Package: pkg.Package{
ID: "1",
Name: "pkg1",
Version: "version2",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "Critical",
},
},
},
{
Artifact: models.Package{
Package: pkg.Package{
ID: "2",
Name: "pkg1",
Version: "version1",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "High",
},
},
},
{
Artifact: models.Package{
Package: pkg.Package{
ID: "3",
Name: "pkg1",
Version: "version1",
},
Vulnerability: models.Vulnerability{
VulnerabilityMetadata: models.VulnerabilityMetadata{
Vulnerability: vulnerability.Vulnerability{
Metadata: &vulnerability.Metadata{
Severity: "High",
},
},
},
},
}...),
originalMap: map[string]*imageprinter.PackageScore{
"pkg1version1": {
Name: "pkg1",
@@ -518,37 +540,37 @@ func TestSetPkgNameToScoreMap(t *testing.T) {
},
}
for i := range tests {
t.Run(tests[i].name, func(t *testing.T) {
if tests[i].originalMap == nil {
tests[i].originalMap = make(map[string]*imageprinter.PackageScore)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.originalMap == nil {
tt.originalMap = make(map[string]*imageprinter.PackageScore)
}
setPkgNameToScoreMap(tests[i].matches, tests[i].originalMap)
if len(tests[i].originalMap) == 0 {
assert.Equal(t, tests[i].want, tests[i].originalMap)
setPkgNameToScoreMap(tt.matches, tt.originalMap)
if len(tt.originalMap) == 0 {
assert.Equal(t, tt.want, tt.originalMap)
return
}
if len(tests[i].originalMap) != len(tests[i].want) {
t.Errorf("%s failed for length, got = %v, want %v", tests[i].name, len(tests[i].originalMap), len(tests[i].want))
if len(tt.originalMap) != len(tt.want) {
t.Errorf("%s failed for length, got = %v, want %v", tt.name, len(tt.originalMap), len(tt.want))
}
for k := range tests[i].originalMap {
if tests[i].originalMap[k].Score != tests[i].want[k].Score {
t.Errorf("%s failed for score, got = %v, want %v", tests[i].name, tests[i].want[k].Score, tests[i].originalMap[k].Score)
for k := range tt.originalMap {
if tt.originalMap[k].Score != tt.want[k].Score {
t.Errorf("%s failed for score, got = %v, want %v", tt.name, tt.want[k].Score, tt.originalMap[k].Score)
}
if tests[i].originalMap[k].Version != tests[i].want[k].Version {
t.Errorf("%s failed for version, got = %v, want %v", tests[i].name, tests[i].want[k].Version, tests[i].originalMap[k].Version)
if tt.originalMap[k].Version != tt.want[k].Version {
t.Errorf("%s failed for version, got = %v, want %v", tt.name, tt.want[k].Version, tt.originalMap[k].Version)
}
if tests[i].originalMap[k].Name != tests[i].want[k].Name {
t.Errorf("%s failed for name, got = %v, want %v", tests[i].name, tests[i].want[k].Name, tests[i].originalMap[k].Name)
if tt.originalMap[k].Name != tt.want[k].Name {
t.Errorf("%s failed for name, got = %v, want %v", tt.name, tt.want[k].Name, tt.originalMap[k].Name)
}
for s := range tests[i].originalMap[k].MapSeverityToCVEsNumber {
if tests[i].originalMap[k].MapSeverityToCVEsNumber[s] != tests[i].want[k].MapSeverityToCVEsNumber[s] {
t.Errorf("%s failed for severity %s, got = %v, want %v", tests[i].name, s, tests[i].want[k].MapSeverityToCVEsNumber[s], tests[i].originalMap[k].MapSeverityToCVEsNumber[s])
for s := range tt.originalMap[k].MapSeverityToCVEsNumber {
if tt.originalMap[k].MapSeverityToCVEsNumber[s] != tt.want[k].MapSeverityToCVEsNumber[s] {
t.Errorf("%s failed for severity %s, got = %v, want %v", tt.name, s, tt.want[k].MapSeverityToCVEsNumber[s], tt.originalMap[k].MapSeverityToCVEsNumber[s])
}
}
}

View File

@@ -75,7 +75,7 @@ func (rh *ResultsHandler) GetResults() *reporthandlingv2.PostureReport {
}
// HandleResults handles all necessary actions for the scan results
func (rh *ResultsHandler) HandleResults(ctx context.Context) error {
func (rh *ResultsHandler) HandleResults(ctx context.Context, scanInfo *cautils.ScanInfo) error {
// Display scan results in the UI first to give immediate value.
rh.UiPrinter.ActionPrint(ctx, rh.ScanData, rh.ImageScanData)
@@ -92,7 +92,7 @@ func (rh *ResultsHandler) HandleResults(ctx context.Context) error {
// We should submit only after printing results, so a user can see
// results at all times, even if submission fails
if rh.ReporterObj != nil {
if rh.ReporterObj != nil && scanInfo.Submit {
if err := rh.ReporterObj.Submit(ctx, rh.ScanData); err != nil {
return err
}

View File

@@ -50,7 +50,7 @@ func TestResultsHandlerHandleResultsPrintsResultsToUI(t *testing.T) {
rh := NewResultsHandler(reporter, printers, uiPrinter)
rh.SetData(fakeScanData)
err := rh.HandleResults(context.TODO())
err := rh.HandleResults(context.TODO(), &cautils.ScanInfo{})
assert.NoError(t, err)
want := 1

97
docs/README.md Normal file
View File

@@ -0,0 +1,97 @@
# Kubescape Documentation
Welcome to the Kubescape documentation. This directory contains detailed guides and references for using Kubescape.
[![Docs](https://img.shields.io/badge/docs-latest-brightgreen?logo=gitbook)](https://kubescape.io/docs/)
[![GitHub](https://img.shields.io/github/license/kubescape/kubescape)](https://github.com/kubescape/kubescape/blob/master/LICENSE)
[![Contributions Welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md)
## 📚 Documentation Index
### Getting Started
| Document | Description |
|----------|-------------|
| [Installation Guide](installation.md) | How to install Kubescape on various platforms |
| [Getting Started](getting-started.md) | First steps with Kubescape, basic usage examples |
### Reference
| Document | Description |
|----------|-------------|
| [CLI Reference](cli-reference.md) | Complete command-line reference for all Kubescape commands |
| [Architecture](architecture.md) | Technical architecture of Kubescape CLI and Operator |
### Features
| Document | Description |
|----------|-------------|
| [MCP Server](mcp-server.md) | AI assistant integration via Model Context Protocol |
| [Providers](providers.md) | Backend services compatible with Kubescape |
### Support
| Document | Description |
|----------|-------------|
| [Troubleshooting](troubleshooting.md) | Common issues and solutions |
### Additional Resources
| Document | Description |
|----------|-------------|
| [Environment Dependencies Policy](environment-dependencies-policy.md) | Policy on external dependencies |
| [Roadmap](roadmap.md) | Project roadmap (see centralized governance repo) |
## 📁 Subdirectories
| Directory | Description |
|-----------|-------------|
| [img/](img/) | Images and diagrams used in documentation |
| [proposals/](proposals/) | Design proposals and RFCs |
| [providers/](providers/) | Provider-specific documentation |
## 🔗 External Documentation
- **[Kubescape Website](https://kubescape.io)** - Official website with additional guides
- **[Kubescape Docs Hub](https://kubescape.io/docs/)** - Comprehensive online documentation
- **[Controls Reference](https://kubescape.io/docs/controls/)** - List of all security controls
- **[Helm Charts](https://github.com/kubescape/helm-charts)** - Operator installation
- **[Regolibrary](https://github.com/kubescape/regolibrary)** - Security controls library
## 🤝 Contributing to Documentation
We welcome contributions to improve our documentation! Please see the [Contributing Guide](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md) for details.
### Documentation Style
- Use clear, concise language
- Include code examples where applicable
- Keep command examples copy-pasteable
- Update the table of contents when adding sections
- Test all commands before documenting them
## 📝 Document Status
| Document | Status | Description |
|----------|--------|-------------|
| [installation.md](installation.md) | ✅ Current | Installation methods for all platforms |
| [getting-started.md](getting-started.md) | ✅ Current | Quick start and usage examples |
| [cli-reference.md](cli-reference.md) | ✅ Current | Complete CLI command reference |
| [architecture.md](architecture.md) | ✅ Current | Technical architecture documentation |
| [mcp-server.md](mcp-server.md) | ✅ Current | AI assistant integration (MCP) |
| [troubleshooting.md](troubleshooting.md) | ✅ Current | Common issues and solutions |
| [providers.md](providers.md) | ✅ Current | Backend service providers |
| [environment-dependencies-policy.md](environment-dependencies-policy.md) | ✅ Current | Dependency policies |
---
## 📖 Quick Links
| I want to... | Go to... |
|--------------|----------|
| Install Kubescape | [Installation Guide](installation.md) |
| Run my first scan | [Getting Started](getting-started.md#run-your-first-scan) |
| See all CLI options | [CLI Reference](cli-reference.md) |
| Use Kubescape with AI assistants | [MCP Server](mcp-server.md) |
| Fix a problem | [Troubleshooting](troubleshooting.md) |
| Understand the architecture | [Architecture](architecture.md) |

View File

@@ -1,19 +1,346 @@
# Kubescape architecture
# Kubescape Architecture
_Please check back soon for more: until then, enjoy these fine diagrams._
This document describes the architecture of Kubescape, covering both the CLI tool and the in-cluster operator.
### [Component architecture](img/architecture.drawio.svg)
## Overview
Kubescape is designed as a modular security platform that can run in two primary modes:
1. **CLI Mode** - On-demand scanning from your local machine
2. **Operator Mode** - Continuous monitoring within your Kubernetes cluster
Both modes share core scanning logic but differ in how they collect data and report results.
---
## CLI Architecture
The Kubescape CLI is a standalone binary that performs security assessments on-demand.
<div align="center">
<img src="img/architecture.drawio.svg" width="600" alt="Component architecture">
<img src="img/ks-cli-arch.png" width="600" alt="CLI Architecture Diagram">
</div>
### [CLI](#kubescape-cli)
### Core Components
#### 1. Command Layer (`cmd/`)
The entry point for all CLI operations. Key commands include:
| Command | Description |
|---------|-------------|
| `scan` | Orchestrates misconfiguration and vulnerability scanning |
| `scan image` | Container image vulnerability scanning |
| `fix` | Auto-remediation of misconfigurations |
| `patch` | Container image patching |
| `list` | Lists available frameworks and controls |
| `download` | Downloads artifacts for offline use |
| `vap` | Validating Admission Policy management |
| `mcpserver` | MCP server for AI integration |
| `operator` | Communicates with in-cluster operator |
#### 2. Core Engine (`core/`)
The main scanning engine that:
- Loads and parses Kubernetes resources
- Evaluates resources against security controls
- Aggregates and formats results
- Manages scan lifecycle and configuration
#### 3. Policy Evaluation (OPA/Rego)
Kubescape uses [Open Policy Agent (OPA)](https://www.openpolicyagent.org/) as its policy engine:
```
┌─────────────────────────────────────────────────────────────┐
│ Policy Evaluation Flow │
├─────────────────────────────────────────────────────────────┤
│ │
│ K8s Resources ──► OPA Engine ──► Rego Policies ──► Results │
│ │ │ │
│ │ ▼ │
│ │ Regolibrary │
│ │ (Control Library) │
│ │ │
│ ▼ │
│ - YAML files │
│ - Helm charts │
│ - Live cluster │
│ - Git repositories │
│ │
└─────────────────────────────────────────────────────────────┘
```
**[Regolibrary](https://github.com/kubescape/regolibrary)** contains:
- Security controls (200+)
- Framework definitions (NSA-CISA, MITRE ATT&CK®, CIS Benchmarks)
- Control metadata and remediation guidance
#### 4. Image Scanner (Grype Integration)
For vulnerability scanning, Kubescape integrates [Grype](https://github.com/anchore/grype):
```
┌─────────────────────────────────────────────────────────────┐
│ Image Scanning Pipeline │
├─────────────────────────────────────────────────────────────┤
│ │
│ Container Image ──► SBOM Generation ──► Vulnerability DB │
│ │ │ │
│ ▼ ▼ │
│ Syft Engine Grype Matching │
│ │ │ │
│ └────────┬───────────┘ │
│ ▼ │
│ CVE Results │
│ │
└─────────────────────────────────────────────────────────────┘
```
#### 5. Image Patcher (Copacetic Integration)
For patching vulnerable images, Kubescape uses [Copacetic](https://github.com/project-copacetic/copacetic):
```
┌─────────────────────────────────────────────────────────────┐
│ Image Patching Pipeline │
├─────────────────────────────────────────────────────────────┤
│ │
│ Vulnerable Image ──► Copa ──► BuildKit ──► Patched Image │
│ │ │ │
│ ▼ ▼ │
│ - Scan for CVEs - Apply OS patches │
│ - Identify fixes - Rebuild layers │
│ - Generate patch plan - Push to registry │
│ │
└─────────────────────────────────────────────────────────────┘
```
### Data Flow (CLI Scan)
```
┌──────────────────────────────────────────────────────────────────────┐
│ CLI Scan Data Flow │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ Input Sources Processing Output │
│ ───────────── ────────── ────── │
│ │
│ ┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Kubernetes │────────►│ │ │ Console │ │
│ │ Cluster │ │ │───►│ (pretty-print) │ │
│ └─────────────┘ │ │ └─────────────────┘ │
│ │ │ │
│ ┌─────────────┐ │ Kubescape │ ┌─────────────────┐ │
│ │ YAML Files │────────►│ Core Engine │───►│ JSON/SARIF │ │
│ └─────────────┘ │ │ └─────────────────┘ │
│ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────────┐ │
│ │ Helm Charts │────────►│ │───►│ HTML/PDF │ │
│ └─────────────┘ │ │ └─────────────────┘ │
│ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────────┐ │
│ │ Git Repos │────────►│ │───►│ JUnit XML │ │
│ └─────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
```
---
## Operator Architecture (In-Cluster)
The Kubescape Operator provides continuous security monitoring within the cluster.
<div align="center">
<img src="img/ks-cli-arch.png" width="600" alt="cli-diagram">
<img src="img/ks-operator-arch.png" width="600" alt="Operator Architecture Diagram">
</div>
### [Operator](https://github.com/kubescape/helm-charts#readme)
<div align="center">
<img src="img/ks-operator-arch.png" width="600" alt="operator-diagram">
</div>
### Components
#### 1. Kubescape Operator
The main controller that:
- Watches for changes to Kubernetes resources
- Triggers scans on schedule or on-demand
- Manages scan lifecycle
- Stores results in Custom Resources
#### 2. Kubevuln
Handles container image vulnerability scanning:
- Scans images running in the cluster
- Generates SBOMs (Software Bill of Materials)
- Matches against vulnerability databases
- Creates `VulnerabilityManifest` CRs
#### 3. Host Scanner
Collects security-relevant information from cluster nodes:
- Kernel parameters
- Kubelet configuration
- Container runtime settings
- File permissions
#### 4. Storage
Kubescape uses Custom Resources to store scan results:
| CRD | Description |
|-----|-------------|
| `VulnerabilityManifest` | Image vulnerability scan results |
| `VulnerabilityManifestSummary` | Aggregated vulnerability summaries |
| `WorkloadConfigurationScan` | Misconfiguration scan results |
| `WorkloadConfigurationScanSummary` | Aggregated configuration summaries |
| `ApplicationProfile` | Runtime behavior profiles |
| `NetworkNeighborhood` | Observed network connections |
#### 5. Node Agent (Runtime Security)
For runtime security, the Node Agent uses eBPF via [Inspektor Gadget](https://github.com/inspektor-gadget/inspektor-gadget):
```
┌─────────────────────────────────────────────────────────────┐
│ Runtime Security Flow │
├─────────────────────────────────────────────────────────────┤
│ │
│ Kernel ──► eBPF Probes ──► Node Agent ──► Kubescape │
│ │ │ │
│ ▼ ▼ │
│ System calls - Process exec │
│ Network events - File access │
│ File operations - Network connections │
│ - Anomaly detection │
│ │
└─────────────────────────────────────────────────────────────┘
```
### Data Flow (Operator)
```
┌──────────────────────────────────────────────────────────────────────┐
│ Operator Data Flow │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Kubernetes │ │ Kubescape │ │ Custom Resources │ │
│ │ API Server │────►│ Operator │────►│ (Scan Results) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ ┌─────────────────────────┐ │
│ │ │ │ Prometheus Metrics │ │
│ │ │ └─────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Kubevuln │ │ Node Agent │ │ External Integrations │ │
│ │ (Images) │ │ (Runtime) │ │ (ARMO Platform, etc.) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
```
---
## Frameworks and Controls
Kubescape evaluates resources against security frameworks:
### Supported Frameworks
| Framework | Description |
|-----------|-------------|
| **NSA-CISA** | Kubernetes Hardening Guidance |
| **MITRE ATT&CK®** | Threat-based security framework |
| **CIS Benchmarks** | Center for Internet Security best practices |
| **SOC2** | Service Organization Control 2 |
| **HIPAA** | Healthcare compliance requirements |
| **PCI-DSS** | Payment Card Industry standards |
### Control Structure
```yaml
Control:
id: C-0005
name: API server insecure port is enabled
description: Check if the API server insecure port is enabled
frameworks:
- NSA
- MITRE
severity: High
remediation: |
Disable the insecure port by setting --insecure-port=0
rules:
- rego: |
# OPA/Rego policy code
```
---
## Security Model
### CLI Mode
- Runs with the permissions of the executing user
- Uses kubeconfig for cluster access
- No persistent state in the cluster
- Results stored locally or sent to configured backend
### Operator Mode
- Runs as a Kubernetes workload
- Uses ServiceAccount with defined RBAC
- Stores results as Custom Resources
- Can send data to external backends (optional)
### Network Requirements
| Component | Outbound Connections |
|-----------|---------------------|
| CLI | Vulnerability DB updates, framework downloads |
| Operator | Vulnerability DB updates, optional backend |
| Offline | All artifacts can be pre-downloaded |
---
## Extensibility
### Custom Controls
You can create custom controls using Rego:
```rego
package armo_builtins
deny[msga] {
# Your custom policy logic
input.kind == "Deployment"
not input.spec.template.spec.securityContext.runAsNonRoot
msga := {
"alertMessage": "Deployment should run as non-root",
"alertScore": 7,
"failedPaths": ["spec.template.spec.securityContext.runAsNonRoot"],
"fixPaths": [{"path": "spec.template.spec.securityContext.runAsNonRoot", "value": "true"}]
}
}
```
### Integration Points
- **HTTP API** - For programmatic access ([see httphandler docs](../httphandler/README.md))
- **MCP Server** - For AI assistant integration ([see mcp-server docs](mcp-server.md))
- **Prometheus Metrics** - For monitoring and alerting
- **Webhook** - For external notifications
---
## Further Reading
- [Getting Started Guide](getting-started.md)
- [Installation Guide](installation.md)
- [Regolibrary (Controls)](https://github.com/kubescape/regolibrary)
- [Helm Charts](https://github.com/kubescape/helm-charts)
- [ARMO Platform Integration](providers.md)

581
docs/cli-reference.md Normal file
View File

@@ -0,0 +1,581 @@
# Kubescape CLI Reference
This document provides a complete reference for all Kubescape CLI commands and options.
## Global Options
These options are available for all commands:
| Option | Description |
|--------|-------------|
| `--cache-dir <path>` | Cache directory (default: `~/.kubescape`) |
| `--kube-context <context>` | Kubernetes context to use (default: current-context) |
| `-l, --logger <level>` | Log level: `debug`, `info`, `warning`, `error`, `fatal` |
| `--server <url>` | Backend discovery server URL |
| `-h, --help` | Help for any command |
---
## kubescape scan
Scan Kubernetes clusters, files, or images for security issues.
### Synopsis
```bash
kubescape scan [target] [flags]
```
### Target Types
- No target: Scans the current cluster
- Path: Scans local YAML files, Helm charts, or Kustomize directories
- URL: Scans a Git repository
### Flags
| Flag | Description | Default |
|------|-------------|---------|
| `--account <id>` | Kubescape SaaS account ID | from cache |
| `--access-key <key>` | Kubescape SaaS access key | from cache |
| `--compliance-threshold <float>` | Fail if compliance score is below threshold | `0` |
| `--controls-config <path>` | Path to controls configuration file | - |
| `-e, --exclude-namespaces <ns>` | Namespaces to exclude (comma-separated) | - |
| `--exceptions <path>` | Path to exceptions file | - |
| `-f, --format <format>` | Output format: `pretty-printer`, `json`, `junit`, `sarif`, `html`, `pdf`, `prometheus` | `pretty-printer` |
| `--include-namespaces <ns>` | Namespaces to include (comma-separated) | - |
| `--keep-local` | Don't report results to backend | `false` |
| `--kubeconfig <path>` | Path to kubeconfig file | - |
| `-o, --output <path>` | Output file path | stdout |
| `--scan-images` | Also scan container images for vulnerabilities | `false` |
| `--severity-threshold <sev>` | Fail if findings at or above severity: `low`, `medium`, `high`, `critical` | - |
| `--submit` | Submit results to Kubescape SaaS | `false` |
| `--use-artifacts-from <path>` | Load artifacts from local directory (offline mode) | - |
| `--use-from <path>` | Load specific policy from path | - |
| `-v, --verbose` | Display all resources, not just failed ones | `false` |
| `--view <type>` | View type: `security`, `control`, `resource` | `security` |
### Examples
```bash
# Scan current cluster
kubescape scan
# Scan with specific framework
kubescape scan framework nsa
kubescape scan framework mitre
kubescape scan framework cis-v1.23-t1.0.1
# Scan specific control
kubescape scan control C-0005 -v
# Scan local files
kubescape scan /path/to/manifests/
# Scan Git repository
kubescape scan https://github.com/org/repo
# Output to JSON file
kubescape scan --format json --output results.json
# Set compliance threshold (exit 1 if below)
kubescape scan --compliance-threshold 80
# Exclude namespaces
kubescape scan --exclude-namespaces kube-system,kube-public
```
---
## kubescape scan framework
Scan against a specific security framework.
### Synopsis
```bash
kubescape scan framework <framework-name> [target] [flags]
```
### Available Frameworks
| Framework | Description |
|-----------|-------------|
| `nsa` | NSA-CISA Kubernetes Hardening Guidance |
| `mitre` | MITRE ATT&CK® for Kubernetes |
| `cis-v1.23-t1.0.1` | CIS Kubernetes Benchmark |
| `soc2` | SOC 2 compliance |
| `pci-dss` | PCI DSS compliance |
| `hipaa` | HIPAA compliance |
### Examples
```bash
kubescape scan framework nsa
kubescape scan framework mitre --include-namespaces production
kubescape scan framework cis-v1.23-t1.0.1 /path/to/manifests
```
---
## kubescape scan control
Scan for a specific control.
### Synopsis
```bash
kubescape scan control <control-id> [target] [flags]
```
### Examples
```bash
# Scan for privileged containers
kubescape scan control C-0057 -v
# Scan specific files for a control
kubescape scan control C-0013 /path/to/deployment.yaml
```
---
## kubescape scan workload
Scan a specific workload.
### Synopsis
```bash
kubescape scan workload <kind>/<name> [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--namespace <ns>` | Namespace of the workload |
### Examples
```bash
kubescape scan workload Deployment/nginx --namespace default
kubescape scan workload DaemonSet/fluentd --namespace logging
```
---
## kubescape scan image
Scan a container image for vulnerabilities.
### Synopsis
```bash
kubescape scan image <image>:<tag> [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--exceptions <path>` | Path to exceptions file |
| `-p, --password <pass>` | Registry password |
| `-u, --username <user>` | Registry username |
| `--use-default-matchers` | Use default vulnerability matchers | `true` |
### Examples
```bash
# Scan public image
kubescape scan image nginx:1.21
# Scan with verbose output
kubescape scan image nginx:1.21 -v
# Scan private registry image
kubescape scan image myregistry.io/myimage:tag -u myuser -p mypass
```
---
## kubescape fix
Auto-fix misconfigurations in Kubernetes manifest files.
### Synopsis
```bash
kubescape fix <report-file> [flags]
```
### Flags
| Flag | Description | Default |
|------|-------------|---------|
| `--dry-run` | Preview changes without applying | `false` |
| `--no-confirm` | Apply without confirmation | `false` |
| `--skip-user-values` | Skip changes requiring user values | `true` |
### Examples
```bash
# Generate scan results
kubescape scan /path/to/manifests --format json --output results.json
# Apply fixes
kubescape fix results.json
# Preview fixes
kubescape fix results.json --dry-run
# Apply without prompts
kubescape fix results.json --no-confirm
```
---
## kubescape patch
Patch container images to fix OS-level vulnerabilities.
### Synopsis
```bash
kubescape patch [flags]
```
### Flags
| Flag | Description | Default |
|------|-------------|---------|
| `-i, --image <image>` | Image to patch (required) | - |
| `-t, --tag <tag>` | Output image tag | `<image>-patched` |
| `-a, --addr <addr>` | BuildKit daemon address | `unix:///run/buildkit/buildkitd.sock` |
| `--timeout <duration>` | Patching timeout | `5m` |
| `--ignore-errors` | Continue on errors | `false` |
| `-u, --username <user>` | Registry username | - |
| `-p, --password <pass>` | Registry password | - |
| `-f, --format <format>` | Output format | - |
| `-o, --output <path>` | Output file | stdout |
| `-v, --verbose` | Verbose output | `false` |
### Examples
```bash
# Start buildkitd first
sudo buildkitd &
# Patch an image
sudo kubescape patch --image nginx:1.22
# Custom output tag
sudo kubescape patch --image nginx:1.22 --tag nginx:1.22-fixed
# Verbose output
sudo kubescape patch --image nginx:1.22 -v
```
---
## kubescape list
List available frameworks and controls.
### Synopsis
```bash
kubescape list <type> [flags]
```
### Types
| Type | Description |
|------|-------------|
| `frameworks` | List available security frameworks |
| `controls` | List available security controls |
### Flags
| Flag | Description | Default |
|------|-------------|---------|
| `--account <id>` | Account ID for custom frameworks | - |
| `--access-key <key>` | Access key | - |
| `--format <format>` | Output format: `pretty-print`, `json` | `pretty-print` |
### Examples
```bash
kubescape list frameworks
kubescape list controls
kubescape list controls --format json
```
---
## kubescape download
Download artifacts for offline/air-gapped use.
### Synopsis
```bash
kubescape download <type> [name] [flags]
```
### Types
| Type | Description |
|------|-------------|
| `artifacts` | Download all artifacts (frameworks, controls, config) |
| `framework` | Download a specific framework |
| `control` | Download a specific control |
### Flags
| Flag | Description | Default |
|------|-------------|---------|
| `-o, --output <path>` | Output path | `~/.kubescape` |
| `--account <id>` | Account ID | - |
| `--access-key <key>` | Access key | - |
### Examples
```bash
# Download all artifacts
kubescape download artifacts --output /path/to/offline
# Download specific framework
kubescape download framework nsa --output /path/to/nsa.json
# Use downloaded artifacts
kubescape scan --use-artifacts-from /path/to/offline
```
---
## kubescape config
Manage Kubescape configuration.
### Subcommands
| Subcommand | Description |
|------------|-------------|
| `view` | View current configuration |
| `set` | Set configuration value |
| `delete` | Delete cached configuration |
### Examples
```bash
# View configuration
kubescape config view
# Set account ID
kubescape config set accountID <account-id>
# Set cloud report URL
kubescape config set cloudReportURL https://api.example.com
# Delete configuration
kubescape config delete
```
---
## kubescape operator
Interact with the in-cluster Kubescape operator.
### Synopsis
```bash
kubescape operator scan <type> [flags]
```
### Scan Types
| Type | Description |
|------|-------------|
| `configurations` | Trigger configuration scan |
| `vulnerabilities` | Trigger vulnerability scan |
### Examples
```bash
kubescape operator scan configurations
kubescape operator scan vulnerabilities
```
---
## kubescape vap
Manage Kubernetes Validating Admission Policies.
### Subcommands
#### deploy-library
Deploy the Kubescape CEL admission policy library.
```bash
kubescape vap deploy-library | kubectl apply -f -
```
#### create-policy-binding
Create a ValidatingAdmissionPolicyBinding.
```bash
kubescape vap create-policy-binding [flags]
```
**Flags:**
| Flag | Description | Required |
|------|-------------|----------|
| `-n, --name <name>` | Binding name | Yes |
| `-p, --policy <id>` | Policy/control ID | Yes |
| `--namespace <ns>` | Namespace selector (repeatable) | No |
| `--label <k=v>` | Label selector (repeatable) | No |
| `-a, --action <action>` | Action: `Deny`, `Audit`, `Warn` | No (default: `Deny`) |
| `-r, --parameter-reference <name>` | Parameter reference | No |
### Examples
```bash
# Deploy policy library
kubescape vap deploy-library | kubectl apply -f -
# Create binding
kubescape vap create-policy-binding \
--name deny-privileged \
--policy c-0057 \
--namespace production \
--action Deny | kubectl apply -f -
```
---
## kubescape mcpserver
Start the MCP (Model Context Protocol) server for AI assistant integration.
### Synopsis
```bash
kubescape mcpserver
```
### Description
Starts an MCP server that exposes Kubescape data to AI assistants. The server communicates via stdio.
### Prerequisites
- Kubescape operator installed in the cluster
- kubectl configured with cluster access
### Examples
```bash
# Start MCP server
kubescape mcpserver
```
### Claude Desktop Configuration
```json
{
"mcpServers": {
"kubescape": {
"command": "kubescape",
"args": ["mcpserver"]
}
}
}
```
---
## kubescape version
Display version information.
### Synopsis
```bash
kubescape version
```
---
## kubescape completion
Generate shell completion scripts.
### Synopsis
```bash
kubescape completion <shell>
```
### Supported Shells
- `bash`
- `zsh`
- `fish`
- `powershell`
### Examples
```bash
# Bash
kubescape completion bash > /etc/bash_completion.d/kubescape
# Zsh
kubescape completion zsh > "${fpath[1]}/_kubescape"
# Fish
kubescape completion fish > ~/.config/fish/completions/kubescape.fish
```
---
## Environment Variables
Kubescape respects the following environment variables:
| Variable | Description |
|----------|-------------|
| `KS_ACCOUNT` | Default account ID |
| `KS_CACHE_DIR` | Cache directory path |
| `KS_EXCLUDE_NAMESPACES` | Default namespaces to exclude |
| `KS_INCLUDE_NAMESPACES` | Default namespaces to include |
| `KS_FORMAT` | Default output format |
| `KS_LOGGER` | Log level |
| `KS_LOGGER_NAME` | Logger name |
| `KUBECONFIG` | Path to kubeconfig file |
| `HTTPS_PROXY` | HTTPS proxy URL |
| `HTTP_PROXY` | HTTP proxy URL |
| `NO_PROXY` | Hosts to exclude from proxy |
---
## Exit Codes
| Code | Description |
|------|-------------|
| `0` | Success |
| `1` | Failure (threshold exceeded, scan failed, etc.) |
---
## See Also
- [Getting Started Guide](getting-started.md)
- [Architecture](architecture.md)
- [Troubleshooting](troubleshooting.md)
- [MCP Server Documentation](mcp-server.md)

View File

@@ -4,6 +4,22 @@ Kubescape can run as a command line tool on a client, as an operator inside a cl
The best way to get started with Kubescape is to download it to the machine you use to manage your Kubernetes cluster.
## Table of Contents
- [Install Kubescape](#install-kubescape)
- [Run your first scan](#run-your-first-scan)
- [Usage](#usage)
- [Misconfigurations Scanning](#misconfigurations-scanning)
- [Image Scanning](#image-scanning)
- [Auto-Fix Misconfigurations](#auto-fix-misconfigurations)
- [Image Patching](#image-patching)
- [Validating Admission Policies (VAP)](#validating-admission-policies-vap)
- [MCP Server (AI Integration)](#mcp-server-ai-integration)
- [Configuration Management](#configuration-management)
- [Offline/Air-gapped Support](#offlineair-gapped-environment-support)
- [Other Ways to Use Kubescape](#other-ways-to-use-kubescape)
- [Tutorial Videos](#tutorial-videos)
## Install Kubescape
```bash
@@ -28,18 +44,18 @@ Kubescape security posture overview for cluster: minikube
In this overview, Kubescape shows you a summary of your cluster security posture, including the number of users who can perform administrative actions. For each result greater than 0, you should evaluate its need, and then define an exception to allow it. This baseline can be used to detect drift in future.
Control plane
────┬─────────────────────────────────────┬────────────────────────────────────
│ │ Control Name │ Docs │
├────┼─────────────────────────────────────┼────────────────────────────────────┤
│ ✅ │ API server insecure port is enabled │ https://hub.armosec.io/docs/c-0005 │
│ ❌ │ Anonymous access enabled │ https://hub.armosec.io/docs/c-0262 │
│ ❌ │ Audit logs enabled │ https://hub.armosec.io/docs/c-0067 │
│ ✅ │ RBAC enabled │ https://hub.armosec.io/docs/c-0088 │
│ ❌ │ Secret/etcd encryption enabled │ https://hub.armosec.io/docs/c-0066 │
────┴─────────────────────────────────────┴────────────────────────────────────
────┬─────────────────────────────────────┬──────────────────────────────────────────────╮
│ │ Control Name │ Docs
├────┼─────────────────────────────────────┼──────────────────────────────────────────────
│ ✅ │ API server insecure port is enabled │ https://kubescape.io/docs/controls/c-0005/
│ ❌ │ Anonymous access enabled │ https://kubescape.io/docs/controls/c-0262/
│ ❌ │ Audit logs enabled │ https://kubescape.io/docs/controls/c-0067/
│ ✅ │ RBAC enabled │ https://kubescape.io/docs/controls/c-0088/
│ ❌ │ Secret/etcd encryption enabled │ https://kubescape.io/docs/controls/c-0066/
────┴─────────────────────────────────────┴──────────────────────────────────────────────╯
Access control
─────────────────────────────────────────────────┬───────────┬────────────────────────────────────
─────────────────────────────────────────────────┬───────────┬────────────────────────────────────
│ Control Name │ Resources │ View Details │
├─────────────────────────────────────────────────┼───────────┼────────────────────────────────────┤
│ Cluster-admin binding │ 1 │ $ kubescape scan control C-0035 -v │
@@ -51,24 +67,24 @@ Access control
│ Portforwarding privileges │ 1 │ $ kubescape scan control C-0063 -v │
│ Validate admission controller (mutating)0 │ $ kubescape scan control C-0039 -v │
│ Validate admission controller (validating)0 │ $ kubescape scan control C-0036 -v │
─────────────────────────────────────────────────┴───────────┴────────────────────────────────────
─────────────────────────────────────────────────┴───────────┴────────────────────────────────────
Secrets
─────────────────────────────────────────────────┬───────────┬────────────────────────────────────
─────────────────────────────────────────────────┬───────────┬────────────────────────────────────
│ Control Name │ Resources │ View Details │
├─────────────────────────────────────────────────┼───────────┼────────────────────────────────────┤
│ Applications credentials in configuration files │ 1 │ $ kubescape scan control C-0012 -v │
─────────────────────────────────────────────────┴───────────┴────────────────────────────────────
─────────────────────────────────────────────────┴───────────┴────────────────────────────────────
Network
────────────────────────┬───────────┬────────────────────────────────────
────────────────────────┬───────────┬────────────────────────────────────
│ Control Name │ Resources │ View Details │
├────────────────────────┼───────────┼────────────────────────────────────┤
│ Missing network policy │ 13 │ $ kubescape scan control C-0260 -v │
────────────────────────┴───────────┴────────────────────────────────────
────────────────────────┴───────────┴────────────────────────────────────
Workload
─────────────────────────┬───────────┬────────────────────────────────────
─────────────────────────┬───────────┬────────────────────────────────────
│ Control Name │ Resources │ View Details │
├─────────────────────────┼───────────┼────────────────────────────────────┤
│ Host PID/IPC privileges │ 2 │ $ kubescape scan control C-0038 -v │
@@ -76,7 +92,7 @@ Workload
│ HostPath mount │ 1 │ $ kubescape scan control C-0048 -v │
│ Non-root containers │ 6 │ $ kubescape scan control C-0013 -v │
│ Privileged container │ 1 │ $ kubescape scan control C-0057 -v │
─────────────────────────┴───────────┴────────────────────────────────────
─────────────────────────┴───────────┴────────────────────────────────────
Highest-stake workloads
────────────────────────
@@ -144,7 +160,7 @@ kubescape scan framework mitre
```
#### Scan a control
Scan for a specific control, using the control name or control ID. [See the list of controls](https://hub.armosec.io/docs/controls?utm_source=github&utm_medium=repository).
Scan for a specific control, using the control name or control ID. [See the list of controls](https://kubescape.io/docs/controls/).
```bash
kubescape scan control c-0005 -v
@@ -170,7 +186,7 @@ kubescape scan --exclude-namespaces kube-system,kube-public
#### Scan local YAML files
```sh
kubescape scan /path/to/directory-or-directory
kubescape scan /path/to/directory-or-file
```
Take a look at the [example](https://youtu.be/Ox6DaR7_4ZI).
@@ -305,7 +321,7 @@ You can also download a single artifact, and scan with the `--use-from` flag:
```
## Image scanning
Kubescape can scan container images for vulnerabilities. It uses [Grype]() to scan the images.
Kubescape can scan container images for vulnerabilities. It uses [Grype](https://github.com/anchore/grype) to scan the images.
### Examples
@@ -331,7 +347,7 @@ kubescape scan image nginx:1.19.6 -v
### Scan periodically using Helm
We publish [a Helm chart](https://github.com/kubescape/helm-charts) for our in-cluster components. [Please follow the instructions here](https://hub.armosec.io/docs/installation-of-armo-in-cluster?utm_source=github&utm_medium=repository)
We publish [a Helm chart](https://github.com/kubescape/helm-charts) for our in-cluster components. [Please follow the instructions here](https://kubescape.io/docs/install-operator/)
### VS Code Extension
@@ -347,6 +363,207 @@ View Kubescape scan results directly in the [Lens IDE](https://k8slens.dev/) usi
Experiment with Kubescape in the [Kubescape playground](https://killercoda.com/saiyampathak/scenario/kubescape): this scenario will install a K3s cluster and Kubescape. You can start with any of the `kubescape scan` commands in the [examples](#examples).
## Auto-Fix Misconfigurations
Kubescape can automatically fix misconfigurations found in your Kubernetes manifest files.
### Usage
```bash
# First, scan and save results to JSON
kubescape scan /path/to/manifests --format json --output results.json
# Then apply fixes based on the scan results
kubescape fix results.json
```
### Options
| Flag | Description |
|------|-------------|
| `--dry-run` | Preview changes without applying them |
| `--no-confirm` | Apply fixes without confirmation prompts |
| `--skip-user-values` | Skip changes that require user-defined values (default: true) |
### Example
```bash
# Preview fixes without applying
kubescape fix results.json --dry-run
# Apply fixes without prompts (useful for CI/CD)
kubescape fix results.json --no-confirm
```
> **Warning**
> The fix command modifies files in-place. Always review changes or use `--dry-run` first.
## Image Patching
Kubescape can patch container images to fix OS-level vulnerabilities using [Copacetic](https://github.com/project-copacetic/copacetic) and [BuildKit](https://github.com/moby/buildkit).
### Prerequisites
- Docker daemon installed and running
- BuildKit daemon installed
### Usage
```bash
# Start buildkitd (if not already running)
sudo buildkitd &
# Patch an image
sudo kubescape patch --image docker.io/library/nginx:1.22
```
### Options
| Flag | Description | Default |
|------|-------------|---------|
| `-i, --image` | Image name to patch (required) | - |
| `-t, --tag` | Tag for the patched image | `<image>-patched` |
| `-a, --addr` | BuildKit daemon address | `unix:///run/buildkit/buildkitd.sock` |
| `--timeout` | Patching timeout | `5m` |
| `-u, --username` | Registry username | - |
| `-p, --password` | Registry password | - |
| `-v, --verbose` | Show detailed output | `false` |
### Example without sudo
```bash
export BUILDKIT_VERSION=v0.11.4
export BUILDKIT_PORT=8888
# Start BuildKit in Docker
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
# Patch using TCP connection
kubescape patch -i nginx:1.22 -a tcp://0.0.0.0:$BUILDKIT_PORT
```
> **Note**
> Image patching can only fix OS-level vulnerabilities, not application-level ones.
For more details, see the [Patch Command Documentation](/cmd/patch/README.md).
## Validating Admission Policies (VAP)
Kubescape can help manage Kubernetes [Validating Admission Policies](https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/) using CEL (Common Expression Language).
### Deploy the Policy Library
Install the Kubescape CEL admission policy library:
```bash
kubescape vap deploy-library | kubectl apply -f -
```
This deploys:
- Policy configuration CRD
- Basic control configurations
- Kubescape validating admission policies
### Create Policy Bindings
Bind policies to specific resources:
```bash
kubescape vap create-policy-binding \
--name my-policy-binding \
--policy c-0016 \
--namespace my-namespace | kubectl apply -f -
```
### Options for `create-policy-binding`
| Flag | Description | Required |
|------|-------------|----------|
| `-n, --name` | Name of the policy binding | Yes |
| `-p, --policy` | Policy/control to bind | Yes |
| `--namespace` | Namespace selector (can be repeated) | No |
| `--label` | Label selector in `key=value` format | No |
| `-a, --action` | Action on failure: `Deny`, `Audit`, `Warn` | No (default: `Deny`) |
| `-r, --parameter-reference` | Parameter reference object name | No |
### Example
```bash
# Create a policy that denies non-compliant resources in production
kubescape vap create-policy-binding \
--name deny-privileged-containers \
--policy c-0057 \
--namespace production \
--action Deny | kubectl apply -f -
```
## MCP Server (AI Integration)
Kubescape provides an MCP (Model Context Protocol) server for AI assistant integration, allowing natural language queries about your cluster's security posture.
### Prerequisites
- Kubescape operator installed in your cluster
- kubectl configured with cluster access
### Start the Server
```bash
kubescape mcpserver
```
### Available Tools
The MCP server exposes these tools to AI assistants:
| Tool | Description |
|------|-------------|
| `list_vulnerability_manifests` | Discover vulnerability scan results |
| `list_vulnerabilities_in_manifest` | List CVEs in a specific manifest |
| `list_vulnerability_matches_for_cve` | Get details for a specific CVE |
| `list_configuration_security_scan_manifests` | List configuration scan results |
| `get_configuration_security_scan_manifest` | Get configuration scan details |
### Integration with Claude Desktop
Add to your Claude Desktop configuration:
```json
{
"mcpServers": {
"kubescape": {
"command": "kubescape",
"args": ["mcpserver"]
}
}
}
```
For more details, see the [MCP Server Documentation](mcp-server.md).
## Configuration Management
Manage Kubescape's cached configurations:
```bash
# View current configuration
kubescape config view
# Set account ID
kubescape config set accountID <your-account-id>
# Set cloud report URL
kubescape config set cloudReportURL <url>
# Delete cached configuration
kubescape config delete
```
## Tutorial videos
* [Kubescape overview](https://youtu.be/wdBkt_0Qhbg)

View File

@@ -1,116 +1,251 @@
# Installation
## Manually
> **Note**: We do not recommend this method if you want to get auto-updating from package managers or have more platforms supported.
### X86_64 or ARM64 (M1/M2) Linux / macOS
# Installation Guide
This guide covers all the ways to install Kubescape on your system.
## Table of Contents
- [Quick Install](#quick-install)
- [Package Managers](#package-managers)
- [Manual Installation](#manual-installation)
- [Verification](#verification)
- [Updating](#updating)
- [Uninstalling](#uninstalling)
---
## Quick Install
### Linux / macOS (Recommended)
```bash
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
```
To install a previous version, you can specify it in the command line.
This script:
- Detects your OS and architecture (x86_64, ARM64/M1/M2)
- Downloads the latest release
- Installs to `~/.kubescape/`
- Adds to your PATH
```bash
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash -s -- -v v2.3.6
```
### Windows (PowerShell)
Requires PowerShell v5.0 or higher:
### X86_64 Windows
You must have PowerShell v5.0 or higher:
```powershell
iwr -useb https://raw.githubusercontent.com/kubescape/kubescape/master/install.ps1 | iex
```
If you get an error, you may need to change the execution policy:
If you get an execution policy error:
```powershell
Set-ExecutionPolicy RemoteSigned -scope CurrentUser
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
```
## openSUSE
> **Note**: openSUSE community-supported.
### Install a Specific Version
```bash
sudo zypper refresh
sudo zypper install kubescape
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash -s -- -v v3.0.0
```
## Arch
---
## Package Managers
### Homebrew (macOS/Linux)
```bash
yay -S kubescape
```
If you would like to save some time and do not want to compile, install `kubescape-bin` instead:
> **Note**: kubescape-bin is AUR community-supported.
```bash
yay -S kubescape-bin
brew install kubescape
```
## Ubuntu
> **Note**: The [official Homebrew formula](https://formulae.brew.sh/formula/kubescape#default) has git scanning disabled. For full functionality:
> ```bash
> brew tap kubescape/tap
> brew install kubescape-cli
> ```
### Krew (kubectl plugin)
```bash
kubectl krew update
kubectl krew install kubescape
# Use as kubectl plugin
kubectl kubescape scan
```
### Ubuntu / Debian
```bash
sudo add-apt-repository ppa:kubescape/kubescape
sudo apt update
sudo apt install kubescape
```
## Other Debian-based or RPM-based Linux Distros
Please follow the [guidelines here](https://software.opensuse.org/download.html?project=home%3Akubescape&package=kubescape).
For other Debian-based or RPM-based distributions, see the [OpenSUSE Build Service](https://software.opensuse.org/download.html?project=home%3Akubescape&package=kubescape).
## Homebrew
> **Note**: The kubescape delivered by [official Homebrew](https://formulae.brew.sh/formula/kubescape#default) comes with git disabled.
### Arch Linux
```bash
brew install kubescape
# Build from source
yay -S kubescape
# Or install pre-built binary (faster)
yay -S kubescape-bin
```
If you want to have the git enabled one, you can install via the [homebrew-tap](https://github.com/kubescape/homebrew-tap):
> **Note**: AUR packages are community-supported.
### openSUSE
```bash
brew tap kubescape/tap
brew install kubescape-cli
sudo zypper refresh
sudo zypper install kubescape
```
## Chocolatey
> **Note**: Chocolatey [community-supported](https://community.chocolatey.org/packages/kubescape).
> **Note**: Community-supported.
### NixOS / Nix
```bash
# Try in ephemeral shell
nix-shell -p kubescape
# Or add to configuration.nix
environment.systemPackages = with pkgs; [ kubescape ];
# Or with home-manager
home.packages = with pkgs; [ kubescape ];
```
> **Note**: Community-supported. See [NixOS support](https://nixos.wiki/wiki/Support) for issues.
### Snap
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/kubescape)
```bash
sudo snap install kubescape
```
### Chocolatey (Windows)
```powershell
choco install kubescape
```
## Scoop
> **Note**: Scoop [community-supported](https://scoop.sh/#/apps?q=kubescape&s=0&d=1&o=true&id=1f5ae05eaafe3e7a26505f0889101e0da91ffe91).
> **Note**: [Community-supported](https://community.chocolatey.org/packages/kubescape).
### Scoop (Windows)
```powershell
scoop install kubescape
```
## Krew
> **Note**: [Community-supported](https://scoop.sh/#/apps?q=kubescape).
---
## Manual Installation
### Download from GitHub Releases
1. Go to the [Releases page](https://github.com/kubescape/kubescape/releases)
2. Download the appropriate binary for your OS/architecture
3. Make it executable and move to your PATH:
```bash
kubectl krew update
kubectl krew install kubescape
kubectl kubescape
# Linux/macOS example
chmod +x kubescape
sudo mv kubescape /usr/local/bin/
```
## Snap
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/kubescape)
### Build from Source
## NixOS or with nix
> **Note**: This method is community-supported. If you are having trouble, please reach out to [NixOS support](https://nixos.wiki/wiki/Support).
You can use `nix` on Linux or macOS.
Try it out in an ephemeral shell: `nix-shell -p kubescape`.
NixOS:
```
# your other config ...
environment.systemPackages = with pkgs; [
# your other packages ...
kubescape
];
```bash
git clone https://github.com/kubescape/kubescape.git
cd kubescape
make build
```
home-manager:
---
```
# your other config ...
home.packages = with pkgs; [
# your other packages ...
kubescape
];
## Verification
After installation, verify Kubescape is working:
```bash
# Check version
kubescape version
# Run a simple scan (requires cluster access)
kubescape scan
# Or scan a sample file
kubescape scan https://raw.githubusercontent.com/kubernetes/examples/master/guestbook/all-in-one/guestbook-all-in-one.yaml
```
Or, to your profile (not preferred): `nix-env --install -A nixpkgs.kubescape`.
### Expected Output
```
Kubescape version: vX.X.X
```
---
## Updating
### Script Installation
Re-run the install script to get the latest version:
```bash
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
```
### Package Managers
Use your package manager's update command:
```bash
# Homebrew
brew upgrade kubescape
# apt
sudo apt update && sudo apt upgrade kubescape
# Krew
kubectl krew upgrade kubescape
```
---
## Uninstalling
### Script Installation
```bash
rm -rf ~/.kubescape
# Remove from PATH in your shell config (.bashrc, .zshrc, etc.)
```
### Package Managers
Use your package manager's uninstall command:
```bash
# Homebrew
brew uninstall kubescape
# apt
sudo apt remove kubescape
# Krew
kubectl krew uninstall kubescape
```
---
## Next Steps
- [Getting Started Guide](getting-started.md) - Run your first scan
- [CLI Reference](cli-reference.md) - Full command reference
- [Troubleshooting](troubleshooting.md) - Common issues and solutions

259
docs/mcp-server.md Normal file
View File

@@ -0,0 +1,259 @@
# Kubescape MCP Server
The Kubescape MCP (Model Context Protocol) Server enables AI assistants to query your Kubernetes cluster's security posture using natural language. It exposes Kubescape's vulnerability and configuration scan data through the [MCP protocol](https://modelcontextprotocol.io/).
## Overview
The MCP server allows AI assistants (like Claude, ChatGPT, or custom AI tools) to:
- List and query vulnerability manifests for images and workloads
- Retrieve CVE details and vulnerability matches
- Access configuration security scan results
- Provide security recommendations based on real cluster data
## Prerequisites
Before using the MCP server, you need:
1. **Kubescape Operator installed in your cluster** - The MCP server reads data from Custom Resources created by the operator
2. **kubectl configured** - With access to the cluster running the Kubescape operator
3. **Kubescape CLI** - Version 3.x or later
### Installing the Kubescape Operator
```bash
helm repo add kubescape https://kubescape.github.io/helm-charts/
helm repo update
helm upgrade --install kubescape kubescape/kubescape-operator \
--namespace kubescape \
--create-namespace \
--set capabilities.vulnerabilityScan=enable \
--set capabilities.configurationScan=enable
```
Wait for the operator to complete initial scans:
```bash
kubectl -n kubescape get vulnerabilitymanifests
kubectl -n kubescape get workloadconfigurationscans
```
## Starting the MCP Server
```bash
kubescape mcpserver
```
The server starts and communicates via stdio, making it compatible with MCP-enabled AI tools.
## Available Tools
The MCP server exposes the following tools to AI assistants:
### Vulnerability Tools
#### `list_vulnerability_manifests`
Discover available vulnerability manifests at image and workload levels.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `namespace` | string | No | Filter by namespace |
| `level` | string | No | Type of manifests: `"image"`, `"workload"`, or `"both"` (default) |
**Example Response:**
```json
{
"vulnerability_manifests": {
"manifests": [
{
"type": "workload",
"namespace": "default",
"manifest_name": "deployment-nginx-nginx",
"image-level": false,
"workload-level": true,
"image-id": "sha256:abc123...",
"image-tag": "nginx:1.21",
"resource_uri": "kubescape://vulnerability-manifests/default/deployment-nginx-nginx"
}
]
}
}
```
#### `list_vulnerabilities_in_manifest`
List all vulnerabilities (CVEs) found in a specific manifest.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `namespace` | string | No | Namespace of the manifest (default: `"kubescape"`) |
| `manifest_name` | string | Yes | Name of the manifest |
**Example Response:**
```json
[
{
"id": "CVE-2023-12345",
"severity": "High",
"description": "Buffer overflow in libfoo",
"fix": {
"versions": ["1.2.4"],
"state": "fixed"
}
}
]
```
#### `list_vulnerability_matches_for_cve`
Get detailed information about a specific CVE in a manifest, including affected packages and fix information.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `namespace` | string | No | Namespace of the manifest (default: `"kubescape"`) |
| `manifest_name` | string | Yes | Name of the manifest |
| `cve_id` | string | Yes | CVE identifier (e.g., `"CVE-2023-12345"`) |
### Configuration Tools
#### `list_configuration_security_scan_manifests`
Discover available security configuration scan results at the workload level.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `namespace` | string | No | Filter by namespace (default: `"kubescape"`) |
**Example Response:**
```json
{
"configuration_manifests": {
"manifests": [
{
"namespace": "default",
"manifest_name": "deployment-nginx",
"resource_uri": "kubescape://configuration-manifests/default/deployment-nginx"
}
]
}
}
```
#### `get_configuration_security_scan_manifest`
Get detailed configuration scan results for a specific workload, including failed controls and remediation guidance.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `namespace` | string | No | Namespace of the manifest (default: `"kubescape"`) |
| `manifest_name` | string | Yes | Name of the configuration manifest |
## Resource Templates
The MCP server also exposes resource templates for direct access to data:
### Vulnerability Manifest
```
kubescape://vulnerability-manifests/{namespace}/{manifest_name}
```
### Configuration Manifest
```
kubescape://configuration-manifests/{namespace}/{manifest_name}
```
## Integration with AI Assistants
### Claude Desktop
Add to your Claude Desktop configuration (`~/.config/claude/config.json` on Linux or `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
```json
{
"mcpServers": {
"kubescape": {
"command": "kubescape",
"args": ["mcpserver"]
}
}
}
```
### Custom Integration
For custom AI applications using the MCP SDK:
```python
from mcp import Client
async with Client("kubescape", ["kubescape", "mcpserver"]) as client:
# List vulnerability manifests
result = await client.call_tool(
"list_vulnerability_manifests",
{"level": "workload"}
)
print(result)
```
## Example AI Queries
Once connected, you can ask your AI assistant questions like:
- "What vulnerabilities exist in my production namespace?"
- "Show me all critical CVEs affecting my nginx deployments"
- "What configuration issues does my cluster have?"
- "Which workloads have the most security issues?"
- "Give me details about CVE-2023-12345 in my cluster"
## Troubleshooting
### No vulnerability manifests found
Ensure the Kubescape operator has completed vulnerability scanning:
```bash
kubectl -n kubescape get vulnerabilitymanifests
```
If empty, check operator logs:
```bash
kubectl -n kubescape logs -l app=kubescape
```
### Connection issues
Verify your kubeconfig is correctly configured:
```bash
kubectl get nodes
```
### MCP server not responding
Check that you're running Kubescape v3.x or later:
```bash
kubescape version
```
## Security Considerations
- The MCP server runs with the same Kubernetes permissions as your kubeconfig
- It provides read-only access to vulnerability and configuration data
- No cluster modifications are made through the MCP server
- Consider running with a service account that has limited permissions in production
## Related Documentation
- [Kubescape Operator Installation](https://kubescape.io/docs/operator/)
- [Vulnerability Scanning](https://kubescape.io/docs/vulnerabilities/)
- [Configuration Scanning](https://kubescape.io/docs/configuration-scanning/)
- [MCP Protocol Specification](https://modelcontextprotocol.io/)

528
docs/troubleshooting.md Normal file
View File

@@ -0,0 +1,528 @@
# Troubleshooting Guide
This guide covers common issues you may encounter when using Kubescape and how to resolve them.
## Table of Contents
- [Installation Issues](#installation-issues)
- [Scanning Issues](#scanning-issues)
- [Image Scanning Issues](#image-scanning-issues)
- [Image Patching Issues](#image-patching-issues)
- [Operator Issues](#operator-issues)
- [MCP Server Issues](#mcp-server-issues)
- [Output and Reporting Issues](#output-and-reporting-issues)
- [Performance Issues](#performance-issues)
- [Getting Help](#getting-help)
---
## Installation Issues
### Command not found after installation
**Symptom:** After running the install script, `kubescape` command is not found.
**Solution:**
1. Check if the binary was installed:
```bash
ls -la ~/.kubescape/kubescape
```
2. Add to your PATH:
```bash
# For bash
echo 'export PATH=$PATH:~/.kubescape' >> ~/.bashrc
source ~/.bashrc
# For zsh
echo 'export PATH=$PATH:~/.kubescape' >> ~/.zshrc
source ~/.zshrc
```
3. Alternatively, move the binary to a directory already in your PATH:
```bash
sudo mv ~/.kubescape/kubescape /usr/local/bin/
```
### Permission denied during installation
**Symptom:** Installation fails with permission errors.
**Solution:**
```bash
# Create the directory with proper permissions
mkdir -p ~/.kubescape
chmod 755 ~/.kubescape
# Re-run the installation
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
```
### Installation fails on Windows
**Symptom:** PowerShell script fails to execute.
**Solution:**
1. Check PowerShell version (must be v5.0+):
```powershell
$PSVersionTable.PSVersion
```
2. Set execution policy:
```powershell
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
```
3. Retry installation:
```powershell
iwr -useb https://raw.githubusercontent.com/kubescape/kubescape/master/install.ps1 | iex
```
---
## Scanning Issues
### Cannot connect to cluster
**Symptom:** `kubescape scan` fails with connection errors.
**Solutions:**
1. Verify kubectl works:
```bash
kubectl get nodes
```
2. Check your kubeconfig:
```bash
kubectl config current-context
kubectl config view
```
3. Use an explicit kubeconfig:
```bash
kubescape scan --kubeconfig /path/to/kubeconfig
```
4. Use a specific context:
```bash
kubescape scan --kube-context my-context
```
### Scan times out
**Symptom:** Scanning large clusters takes too long or times out.
**Solutions:**
1. Scan specific namespaces:
```bash
kubescape scan --include-namespaces production,staging
```
2. Exclude non-essential namespaces:
```bash
kubescape scan --exclude-namespaces kube-system,kube-public,monitoring
```
3. Scan a specific framework instead of all:
```bash
kubescape scan framework nsa
```
### No results returned
**Symptom:** Scan completes but shows no results.
**Solutions:**
1. Check if the cluster has workloads:
```bash
kubectl get pods --all-namespaces
```
2. Run with verbose output:
```bash
kubescape scan -v
```
3. Check for namespace filtering issues:
```bash
# Make sure you're not excluding all namespaces
kubescape scan --include-namespaces default
```
### Framework or control not found
**Symptom:** Error about unknown framework or control.
**Solutions:**
1. List available frameworks:
```bash
kubescape list frameworks
```
2. List available controls:
```bash
kubescape list controls
```
3. Update Kubescape to get latest controls:
```bash
# Re-run installation to get latest version
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
```
4. Download latest artifacts:
```bash
kubescape download artifacts
```
### RBAC errors during scan
**Symptom:** Scan fails with permission denied errors.
**Solution:**
Ensure your kubeconfig user has sufficient permissions. At minimum, you need read access to:
- Deployments, DaemonSets, StatefulSets, Jobs, CronJobs
- Pods, Services, ConfigMaps, Secrets
- Roles, RoleBindings, ClusterRoles, ClusterRoleBindings
- NetworkPolicies
- ServiceAccounts
---
## Image Scanning Issues
### Image not found
**Symptom:** `kubescape scan image` fails to find the image.
**Solutions:**
1. Use the full image reference:
```bash
kubescape scan image docker.io/library/nginx:1.21
```
2. For private registries, provide credentials:
```bash
kubescape scan image myregistry.io/myimage:tag \
--username myuser \
--password mypassword
```
3. Check if the image exists locally:
```bash
docker images | grep myimage
```
### Authentication failed for private registry
**Symptom:** Scan fails with authentication errors.
**Solutions:**
1. Verify credentials work with docker:
```bash
docker login myregistry.io
docker pull myregistry.io/myimage:tag
```
2. Use environment variables for credentials:
```bash
export KUBESCAPE_REGISTRY_USERNAME=myuser
export KUBESCAPE_REGISTRY_PASSWORD=mypassword
kubescape scan image myregistry.io/myimage:tag
```
### Vulnerability database outdated
**Symptom:** Known CVEs are not being detected.
**Solution:**
The vulnerability database is updated automatically. To force an update:
```bash
# Clear the cache
rm -rf ~/.kubescape/grype-db
# Run a new scan
kubescape scan image nginx:latest
```
---
## Image Patching Issues
### BuildKit not running
**Symptom:** `kubescape patch` fails with BuildKit connection errors.
**Solutions:**
1. Start BuildKit:
```bash
sudo buildkitd &
```
2. Or run BuildKit in Docker:
```bash
docker run --detach --rm --privileged \
-p 127.0.0.1:8888:8888/tcp \
--name buildkitd \
--entrypoint buildkitd \
moby/buildkit:latest \
--addr tcp://0.0.0.0:8888
kubescape patch -i nginx:1.22 -a tcp://0.0.0.0:8888
```
3. Check BuildKit socket:
```bash
ls -la /run/buildkit/buildkitd.sock
```
### Patching fails with no fixes available
**Symptom:** Patch command reports no patches available.
**Explanation:** Image patching only fixes OS-level vulnerabilities that have available patches. Application-level vulnerabilities or vulnerabilities without fixes cannot be patched.
**Solution:**
1. Check the vulnerability report:
```bash
kubescape scan image myimage:tag -v
```
2. Look for vulnerabilities marked as "wont-fix" or without fix versions.
3. Consider updating the base image to a newer version.
### Permission denied during patching
**Symptom:** Patch fails with permission errors.
**Solution:**
Run with sudo when using the default Unix socket:
```bash
sudo kubescape patch --image nginx:1.22
```
Or use the Docker-based BuildKit approach which doesn't require sudo.
---
## Operator Issues
### Operator not responding to CLI commands
**Symptom:** `kubescape operator scan` hangs or fails.
**Solutions:**
1. Verify the operator is installed:
```bash
kubectl -n kubescape get pods
```
2. Check operator logs:
```bash
kubectl -n kubescape logs -l app=kubescape-operator
```
3. Verify the operator service:
```bash
kubectl -n kubescape get svc
```
### No vulnerability manifests in cluster
**Symptom:** No VulnerabilityManifest CRs found.
**Solutions:**
1. Check if vulnerability scanning is enabled:
```bash
kubectl -n kubescape get configmap kubescape-config -o yaml
```
2. Verify kubevuln is running:
```bash
kubectl -n kubescape get pods -l app=kubevuln
```
3. Check kubevuln logs:
```bash
kubectl -n kubescape logs -l app=kubevuln
```
---
## MCP Server Issues
### MCP server fails to start
**Symptom:** `kubescape mcpserver` exits with errors.
**Solutions:**
1. Verify kubectl connectivity:
```bash
kubectl get nodes
```
2. Check if the operator CRDs are installed:
```bash
kubectl get crd vulnerabilitymanifests.spdx.softwarecomposition.kubescape.io
kubectl get crd workloadconfigurationscans.spdx.softwarecomposition.kubescape.io
```
3. Install the Kubescape operator if not present:
```bash
helm repo add kubescape https://kubescape.github.io/helm-charts/
helm upgrade --install kubescape kubescape/kubescape-operator \
--namespace kubescape --create-namespace
```
### AI assistant cannot connect to MCP server
**Symptom:** AI tool reports connection failures.
**Solutions:**
1. Verify the MCP server is running:
```bash
kubescape mcpserver
```
2. Check your AI tool's MCP configuration:
```json
{
"mcpServers": {
"kubescape": {
"command": "kubescape",
"args": ["mcpserver"]
}
}
}
```
3. Ensure kubescape is in your PATH.
---
## Output and Reporting Issues
### JSON output is malformed
**Symptom:** JSON output cannot be parsed.
**Solution:**
Ensure you're redirecting to a file, not mixing with console output:
```bash
kubescape scan --format json --output results.json
```
### SARIF format fails
**Symptom:** SARIF output not working.
**Note:** SARIF format is only supported for file/repository scans, not live cluster scans.
**Solution:**
```bash
# This works
kubescape scan /path/to/manifests --format sarif --output results.sarif
# This does NOT work
kubescape scan --format sarif --output results.sarif # cluster scan
```
### HTML/PDF report generation fails
**Symptom:** Report generation fails or produces empty files.
**Solutions:**
1. Ensure you have write permissions to the output directory.
2. Check available disk space.
3. Try JSON first to verify scan works:
```bash
kubescape scan --format json --output test.json
```
---
## Performance Issues
### High memory usage during scan
**Solutions:**
1. Scan fewer namespaces:
```bash
kubescape scan --include-namespaces production
```
2. Scan one framework at a time:
```bash
kubescape scan framework nsa
```
3. Use the operator for large clusters instead of CLI scanning.
### Slow vulnerability database downloads
**Solutions:**
1. Use offline mode with pre-downloaded artifacts:
```bash
# On a machine with good connectivity
kubescape download artifacts --output /path/to/artifacts
# On the target machine
kubescape scan --use-artifacts-from /path/to/artifacts
```
2. Configure a proxy if needed:
```bash
export HTTPS_PROXY=http://proxy:8080
kubescape scan
```
---
## Getting Help
If you're still experiencing issues:
1. **Check the logs** with debug logging:
```bash
kubescape scan -l debug
```
2. **Search existing issues:**
https://github.com/kubescape/kubescape/issues
3. **Join the community Slack:**
- [Users Channel](https://cloud-native.slack.com/archives/C04EY3ZF9GE)
- [Developers Channel](https://cloud-native.slack.com/archives/C04GY6H082K)
4. **Open a new issue** with:
- Kubescape version (`kubescape version`)
- Kubernetes version (`kubectl version`)
- Full error message
- Steps to reproduce
- Debug logs (`kubescape scan -l debug 2>&1 | tee debug.log`)

View File

@@ -1,111 +1,129 @@
# Kubescape Exceptions
Kubescape Exceptions is the proper way of excluding failed resources from affecting the risk score.
Kubescape Exceptions allow you to exclude specific resources from affecting your security risk score. This is useful when certain resources intentionally deviate from security best practices and you want to acknowledge this without impacting your overall compliance metrics.
e.g. When a `kube-system` resource fails and it is ok, simply add the resource to the exceptions configurations.
## Table of Contents
## Definitions
- [Use Cases](#use-cases)
- [Exception Structure](#exception-structure)
- [Usage](#usage)
- [Examples](#examples)
- [Related Documentation](#related-documentation)
---
* `name`- Exception name - unique name representing the exception
* `policyType`- Do not change
* `actions`- List of available actions. Currently, alertOnly is supported
* `resources`- List of resources to apply this exception on
* `designatorType: Attributes`- An attribute-based declaration {key: value}
Supported keys:
* `name`: k8s resource name (case-sensitive, regex supported)
* `kind`: k8s resource kind (case-sensitive, regex supported)
* `namespace`: k8s resource namespace (case-sensitive, regex supported)
* `cluster`: k8s cluster name (usually it is the `current-context`) (case-sensitive, regex supported)
* resource labels as key value (case-sensitive, regex NOT supported)
* `posturePolicies`- An attribute-based declaration {key: value}
* `frameworkName` - Framework names can be found [here](https://github.com/armosec/regolibrary/tree/master/frameworks) (regex supported)
* `controlName` - Control names can be found [here](https://github.com/armosec/regolibrary/tree/master/controls) (regex supported)
* `controlID` - Control ID can be found [here](https://github.com/armosec/regolibrary/tree/master/controls) (regex supported)
You can find [here](https://github.com/kubescape/kubescape/tree/master/examples/exceptions) some examples of exceptions files
## Use Cases
- Exclude `kube-system` resources that are expected to have elevated privileges
- Ignore development/test namespaces from production compliance reports
- Accept known risks for specific workloads after security review
- Temporarily exclude resources while fixes are being implemented
---
## Exception Structure
An exception file is a JSON array containing one or more exception objects:
```json
[
{
"name": "exception-name",
"policyType": "postureExceptionPolicy",
"actions": ["alertOnly"],
"resources": [...],
"posturePolicies": [...]
}
]
```
### Fields
| Field | Description |
|-------|-------------|
| `name` | Unique name for this exception |
| `policyType` | Must be `"postureExceptionPolicy"` |
| `actions` | List of actions. Currently only `"alertOnly"` is supported |
| `resources` | List of resources to apply this exception to |
| `posturePolicies` | List of policies/controls to exclude |
### Resource Attributes
Resources are defined using attribute-based selectors. Supported attributes:
| Attribute | Description | Regex Support |
|-----------|-------------|---------------|
| `name` | Kubernetes resource name | ✅ Yes |
| `kind` | Kubernetes resource kind (e.g., `Deployment`, `Pod`) | ✅ Yes |
| `namespace` | Kubernetes namespace | ✅ Yes |
| `cluster` | Cluster name (usually the `current-context`) | ✅ Yes |
| `<label-key>` | Any resource label (e.g., `app`, `environment`) | ❌ No |
### Policy Attributes
Policies can be specified by:
| Attribute | Description | Regex Support |
|-----------|-------------|---------------|
| `frameworkName` | Framework name (e.g., `NSA`, `MITRE`) | ✅ Yes |
| `controlName` | Control name (e.g., `HostPath mount`) | ✅ Yes |
| `controlID` | Control ID (e.g., `C-0048`) | ✅ Yes |
Find framework names in the [frameworks directory](https://github.com/kubescape/regolibrary/tree/master/frameworks) and control information in the [controls directory](https://github.com/kubescape/regolibrary/tree/master/controls).
---
## Usage
The `resources` list and `posturePolicies` list are designed to be a combination of the resources and policies to exclude.
### Running a Scan with Exceptions
> **Warning**
> You must declare at least one resource and one policy.
e.g. If you wish to exclude all namespaces with the label `"environment": "dev"`, the resource list should look as follows:
```bash
kubescape scan --exceptions /path/to/exceptions.json
```
Resources matching exceptions will be marked as `excluded` rather than `failed` in the results.
### Logic Rules
> ⚠️ **Important**: You must declare at least one resource AND one policy in each exception.
#### Within a list: OR logic
Multiple items in the `resources` list are evaluated with **OR** logic:
```json
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"namespace": ".*",
"environment": "dev"
}
}
{ "attributes": { "namespace": "dev" } },
{ "attributes": { "namespace": "test" } }
]
```
This matches resources in the `dev` namespace **OR** the `test` namespace.
But if you wish to exclude all namespaces **OR** any resource with the label `"environment": "dev"`, the resource list should look as follows:
```
#### Within an object: AND logic
Multiple attributes in a single object are evaluated with **AND** logic:
```json
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"namespace": ".*"
}
},
{
"designatorType": "Attributes",
"attributes": {
"environment": "dev"
}
}
{ "attributes": { "namespace": "production", "kind": "Deployment" } }
]
```
This matches only `Deployment` resources **AND** in the `production` namespace.
Same works with the `posturePolicies` list ->
e.g. If you wish to exclude the resources declared in the `resources` list that failed when scanning the `NSA` framework **AND** failed the `HostPath mount` control, the `posturePolicies` list should look as follows:
```
"posturePolicies": [
{
"frameworkName": "NSA",
"controlName": "HostPath mount"
}
]
```
But if you wish to exclude the resources declared in the `resources` list that failed when scanning the `NSA` framework **OR** failed the `HostPath mount` control, the `posturePolicies` list should look as follows:
```
"posturePolicies": [
{
"frameworkName": "NSA"
},
{
"controlName": "HostPath mount"
}
]
```
---
## Examples
Here are some examples demonstrating the different ways the exceptions file can be configured
### Exclude a Specific Control Everywhere
Exclude control [C-0048 (HostPath mount)](https://kubescape.io/docs/controls/c-0048/) for all resources:
### Exclude control
Exclude the [C-0060 control](https://github.com/armosec/regolibrary/blob/master/controls/allowedhostpath.json#L2) by declaring the control ID in the `"posturePolicies"` section.
The resources
```
```json
[
{
"name": "exclude-allowed-hostPath-control",
"name": "exclude-hostpath-control",
"policyType": "postureExceptionPolicy",
"actions": [
"alertOnly"
],
"actions": ["alertOnly"],
"resources": [
{
"designatorType": "Attributes",
@@ -116,22 +134,48 @@ The resources
],
"posturePolicies": [
{
"controlID": "C-0060"
"controlID": "C-0048"
}
]
}
]
```
### Exclude deployments in the default namespace that failed the "HostPath mount" control
```
### Exclude All kube-system Resources
Exclude all resources in the `kube-system` namespace from all frameworks:
```json
[
{
"name": "exclude-deployments-in-ns-default",
"name": "exclude-kube-system",
"policyType": "postureExceptionPolicy",
"actions": [
"alertOnly"
"actions": ["alertOnly"],
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"namespace": "kube-system"
}
}
],
"posturePolicies": [
{
"frameworkName": ".*"
}
]
}
]
```
### Exclude Deployments in Default Namespace for a Specific Control
```json
[
{
"name": "exclude-deployments-in-default",
"policyType": "postureExceptionPolicy",
"actions": ["alertOnly"],
"resources": [
{
"designatorType": "Attributes",
@@ -143,22 +187,53 @@ The resources
],
"posturePolicies": [
{
"controlName": "HostPath mount"
"controlName": "HostPath mount"
}
]
}
]
```
### Exclude resources with label "app=nginx" running in a minikube cluster that failed the "NSA" or "MITRE" framework
### Exclude Resources by Label
Exclude resources with label `environment=dev` from NSA and MITRE frameworks:
```json
[
{
"name": "exclude-dev-environment",
"policyType": "postureExceptionPolicy",
"actions": ["alertOnly"],
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"environment": "dev"
}
}
],
"posturePolicies": [
{
"frameworkName": "NSA"
},
{
"frameworkName": "MITRE"
}
]
}
]
```
### Exclude Specific Workload in Specific Cluster
Exclude nginx resources in a minikube cluster:
```json
[
{
"name": "exclude-nginx-minikube",
"policyType": "postureExceptionPolicy",
"actions": [
"alertOnly"
],
"actions": ["alertOnly"],
"resources": [
{
"designatorType": "Attributes",
@@ -170,12 +245,71 @@ The resources
],
"posturePolicies": [
{
"frameworkName": "NSA"
},
{
"frameworkName": "MITRE"
"frameworkName": ".*"
}
]
}
]
```
### Multiple Exceptions in One File
You can combine multiple exceptions in a single file:
```json
[
{
"name": "exclude-kube-namespaces",
"policyType": "postureExceptionPolicy",
"actions": ["alertOnly"],
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"namespace": "kube-system"
}
},
{
"designatorType": "Attributes",
"attributes": {
"namespace": "kube-public"
}
}
],
"posturePolicies": [
{
"frameworkName": ".*"
}
]
},
{
"name": "exclude-privileged-control-for-monitoring",
"policyType": "postureExceptionPolicy",
"actions": ["alertOnly"],
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"namespace": "monitoring"
}
}
],
"posturePolicies": [
{
"controlID": "C-0057"
}
]
}
]
```
---
## Related Documentation
- [Getting Started Guide](../../docs/getting-started.md)
- [CLI Reference](../../docs/cli-reference.md)
- [Controls Reference](https://kubescape.io/docs/controls/)
- [Regolibrary - Frameworks](https://github.com/kubescape/regolibrary/tree/master/frameworks)
- [Regolibrary - Controls](https://github.com/kubescape/regolibrary/tree/master/controls)
- [Accepting Risk Documentation](https://kubescape.io/docs/accepting-risk/)

View File

@@ -1,26 +1,47 @@
# Helm chart - DEPRECATED
# Helm Chart Examples
[helm chart repo](https://github.com/armosec/armo-helm)
> ⚠️ **DEPRECATED**: This directory contains legacy Helm chart examples that are no longer maintained.
## Current Helm Charts
## Values
For the latest Kubescape Helm charts, please visit:
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| affinity | object | `{}` | |
| configMap | object | `{"create":true,"params":{"clusterName":"<MyK8sClusterName>","customerGUID":"<MyGUID>,"}}` | ARMO customer information |
| fullnameOverride | string | `""` | |
| image | object | `{"imageName":"kubescape","pullPolicy":"IfNotPresent","repository":"quay.io/armosec","tag":"latest"}` | Image and version to deploy |
| imagePullSecrets | list | `[]` | |
| nameOverride | string | `""` | |
| nodeSelector | object | `{}` | |
| podAnnotations | object | `{}` | |
| podSecurityContext | object | `{}` | |
| resources | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"200m","memory":"256Mi"}}` | Default resources for running the service in cluster |
| schedule | string | `"0 0 * * *"` | Frequency of running the scan |
| securityContext | object | `{}` | |
| serviceAccount | object | `{"annotations":{},"create":true,"name":"kubescape-discovery"}` | Service account that runs the scan and has permissions to view the cluster |
| tolerations | list | `[]` | |
**[Kubescape Helm Charts Repository](https://github.com/kubescape/helm-charts)**
----------------------------------------------
Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0)
## Quick Install
```bash
# Add the Kubescape Helm repository
helm repo add kubescape https://kubescape.github.io/helm-charts/
helm repo update
# Install the Kubescape operator
helm upgrade --install kubescape kubescape/kubescape-operator \
--namespace kubescape \
--create-namespace
```
## Available Charts
| Chart | Description |
|-------|-------------|
| [kubescape-operator](https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator) | Full Kubescape in-cluster operator |
## Documentation
- [Operator Installation Guide](https://kubescape.io/docs/install-operator/)
- [Operator Configuration Options](https://github.com/kubescape/helm-charts/blob/main/charts/kubescape-operator/README.md)
- [Prometheus Integration](https://github.com/kubescape/helm-charts/blob/main/charts/kubescape-operator/README.md#kubescape-prometheus-integration)
## Migration from Legacy Charts
If you were using the legacy `armo-helm` charts, please migrate to the new `kubescape/helm-charts` repository. The new charts provide:
- Continuous vulnerability scanning
- Configuration scanning
- Runtime threat detection (eBPF-based)
- Network policy generation
- Prometheus metrics
- And more...
See the [migration guide](https://kubescape.io/docs/install-operator/) for detailed instructions.

View File

@@ -708,14 +708,14 @@
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Network mapping</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0049">C-0049</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0049/">C-0049</a></td>
<td class="resourceRemediationCell"></td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Cluster internal networking</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0054">C-0054</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0054/">C-0054</a></td>
<td class="resourceRemediationCell"></td>
</tr>
@@ -742,77 +742,77 @@
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Allow privilege escalation</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0016">C-0016</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0016/">C-0016</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation=false</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Ingress and Egress blocked</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0030">C-0030</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0030/">C-0030</a></td>
<td class="resourceRemediationCell"></td>
</tr>
<tr>
<td class="resourceSeverityCell">High</td>
<td class="resourceNameCell">Resource limits</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0009">C-0009</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0009/">C-0009</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].resources.limits.cpu=YOUR_VALUE</p> <p>spec.template.spec.containers[0].resources.limits.memory=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Configured readiness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0018">C-0018</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0018/">C-0018</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].readinessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Non-root containers</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0013">C-0013</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0013/">C-0013</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].securityContext.runAsNonRoot=true</p> <p>spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation=false</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Automatic mapping of service account</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0034">C-0034</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0034/">C-0034</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.automountServiceAccountToken=false</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Linux hardening</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0055">C-0055</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0055/">C-0055</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].securityContext.seccompProfile=YOUR_VALUE</p> <p>spec.template.spec.containers[0].securityContext.seLinuxOptions=YOUR_VALUE</p> <p>spec.template.spec.containers[0].securityContext.capabilities.drop[0]=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Configured liveness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0056">C-0056</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0056/">C-0056</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].livenessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">K8s common labels usage</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0077">C-0077</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0077/">C-0077</a></td>
<td class="resourceRemediationCell"> <p>metadata.labels=YOUR_VALUE</p> <p>spec.template.metadata.labels=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Pods in default namespace</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0061">C-0061</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0061/">C-0061</a></td>
<td class="resourceRemediationCell"> <p>metadata.namespace</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Immutable container filesystem</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0017">C-0017</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0017/">C-0017</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem=true</p> </td>
</tr>
@@ -839,7 +839,7 @@
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Access container service account</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0053">C-0053</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0053/">C-0053</a></td>
<td class="resourceRemediationCell"></td>
</tr>
@@ -866,7 +866,7 @@
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Automatic mapping of service account</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0034">C-0034</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0034/">C-0034</a></td>
<td class="resourceRemediationCell"> <p>automountServiceAccountToken=false</p> </td>
</tr>
@@ -893,77 +893,77 @@
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Allow privilege escalation</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0016">C-0016</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0016/">C-0016</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation=false</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Ingress and Egress blocked</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0030">C-0030</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0030/">C-0030</a></td>
<td class="resourceRemediationCell"></td>
</tr>
<tr>
<td class="resourceSeverityCell">High</td>
<td class="resourceNameCell">Resource limits</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0009">C-0009</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0009/">C-0009</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].resources.limits.cpu=YOUR_VALUE</p> <p>spec.template.spec.containers[0].resources.limits.memory=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Configured readiness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0018">C-0018</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0018/">C-0018</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].readinessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Non-root containers</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0013">C-0013</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0013/">C-0013</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].securityContext.runAsNonRoot=true</p> <p>spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation=false</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Automatic mapping of service account</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0034">C-0034</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0034/">C-0034</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.automountServiceAccountToken=false</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Linux hardening</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0055">C-0055</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0055/">C-0055</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].securityContext.seccompProfile=YOUR_VALUE</p> <p>spec.template.spec.containers[0].securityContext.seLinuxOptions=YOUR_VALUE</p> <p>spec.template.spec.containers[0].securityContext.capabilities.drop[0]=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Configured liveness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0056">C-0056</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0056/">C-0056</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].livenessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">K8s common labels usage</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0077">C-0077</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0077/">C-0077</a></td>
<td class="resourceRemediationCell"> <p>metadata.labels=YOUR_VALUE</p> <p>spec.template.metadata.labels=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Pods in default namespace</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0061">C-0061</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0061/">C-0061</a></td>
<td class="resourceRemediationCell"> <p>metadata.namespace</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Immutable container filesystem</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0017">C-0017</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0017/">C-0017</a></td>
<td class="resourceRemediationCell"> <p>spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem=true</p> </td>
</tr>
@@ -990,21 +990,21 @@
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Audit logs enabled</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0067">C-0067</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0067/">C-0067</a></td>
<td class="resourceRemediationCell"> <p>spec.containers[0].command</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">PSP enabled</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0068">C-0068</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0068/">C-0068</a></td>
<td class="resourceRemediationCell"> <p>spec.containers[0].command[5]</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Secret/ETCD encryption enabled</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0066">C-0066</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0066/">C-0066</a></td>
<td class="resourceRemediationCell"> <p>spec.containers[0].command</p> </td>
</tr>
@@ -1031,14 +1031,14 @@
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Data Destruction</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0007">C-0007</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0007/">C-0007</a></td>
<td class="resourceRemediationCell"> <p>relatedObjects[1].rules[1].resources[1]</p> <p>relatedObjects[1].rules[1].verbs[0]</p> <p>relatedObjects[1].rules[1].apiGroups[0]</p> <p>relatedObjects[1].rules[1].apiGroups[1]</p> <p>relatedObjects[0].subjects[0]</p> <p>relatedObjects[0].roleRef.name</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">High</td>
<td class="resourceNameCell">List Kubernetes secrets</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0015">C-0015</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0015/">C-0015</a></td>
<td class="resourceRemediationCell"> <p>relatedObjects[1].rules[0].resources[0]</p> <p>relatedObjects[1].rules[0].verbs[0]</p> <p>relatedObjects[1].rules[0].verbs[1]</p> <p>relatedObjects[1].rules[0].verbs[3]</p> <p>relatedObjects[1].rules[0].apiGroups[0]</p> <p>relatedObjects[0].subjects[0]</p> <p>relatedObjects[0].roleRef.name</p> </td>
</tr>
@@ -1065,7 +1065,7 @@
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Automatic mapping of service account</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0034">C-0034</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0034/">C-0034</a></td>
<td class="resourceRemediationCell"> <p>automountServiceAccountToken=false</p> </td>
</tr>
@@ -1092,56 +1092,56 @@
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Ingress and Egress blocked</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0030">C-0030</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0030/">C-0030</a></td>
<td class="resourceRemediationCell"></td>
</tr>
<tr>
<td class="resourceSeverityCell">High</td>
<td class="resourceNameCell">Resource limits</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0009">C-0009</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0009/">C-0009</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].resources.limits.cpu=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].resources.limits.memory=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Configured readiness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0018">C-0018</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0018/">C-0018</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].readinessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Kubernetes CronJob</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0026">C-0026</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0026/">C-0026</a></td>
<td class="resourceRemediationCell"></td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Label usage for resources</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0076">C-0076</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0076/">C-0076</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.metadata.labels=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Linux hardening</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0055">C-0055</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0055/">C-0055</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.seccompProfile=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.seLinuxOptions=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.capabilities.drop[0]=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Configured liveness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0056">C-0056</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0056/">C-0056</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].livenessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">K8s common labels usage</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0077">C-0077</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0077/">C-0077</a></td>
<td class="resourceRemediationCell"> <p>metadata.labels=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.metadata.labels=YOUR_VALUE</p> </td>
</tr>
@@ -1168,63 +1168,63 @@
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Allow privilege escalation</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0016">C-0016</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0016/">C-0016</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation=false</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Ingress and Egress blocked</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0030">C-0030</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0030/">C-0030</a></td>
<td class="resourceRemediationCell"></td>
</tr>
<tr>
<td class="resourceSeverityCell">High</td>
<td class="resourceNameCell">Resource limits</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0009">C-0009</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0009/">C-0009</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].resources.limits.cpu=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].resources.limits.memory=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Configured readiness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0018">C-0018</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0018/">C-0018</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].readinessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Kubernetes CronJob</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0026">C-0026</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0026/">C-0026</a></td>
<td class="resourceRemediationCell"></td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Non-root containers</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0013">C-0013</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0013/">C-0013</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.runAsNonRoot=true</p> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation=false</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Linux hardening</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0055">C-0055</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0055/">C-0055</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.seccompProfile=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.seLinuxOptions=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.capabilities.drop[0]=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Configured liveness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0056">C-0056</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0056/">C-0056</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].livenessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Immutable container filesystem</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0017">C-0017</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0017/">C-0017</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem=true</p> </td>
</tr>
@@ -1251,21 +1251,21 @@
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Data Destruction</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0007">C-0007</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0007/">C-0007</a></td>
<td class="resourceRemediationCell"> <p>relatedObjects[1].rules[1].resources[0]</p> <p>relatedObjects[1].rules[1].verbs[0]</p> <p>relatedObjects[1].rules[1].apiGroups[0]</p> <p>relatedObjects[0].subjects[0]</p> <p>relatedObjects[0].roleRef.name</p> <p>relatedObjects[1].rules[2].resources[1]</p> <p>relatedObjects[1].rules[2].verbs[0]</p> <p>relatedObjects[1].rules[2].apiGroups[0]</p> <p>relatedObjects[0].subjects[0]</p> <p>relatedObjects[0].roleRef.name</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">CoreDNS poisoning</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0037">C-0037</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0037/">C-0037</a></td>
<td class="resourceRemediationCell"> <p>relatedObjects[1].rules[2].resources[0]</p> <p>relatedObjects[1].rules[2].verbs[0]</p> <p>relatedObjects[1].rules[2].apiGroups[0]</p> <p>relatedObjects[0].subjects[0]</p> <p>relatedObjects[0].roleRef.name</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">High</td>
<td class="resourceNameCell">List Kubernetes secrets</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0015">C-0015</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0015/">C-0015</a></td>
<td class="resourceRemediationCell"> <p>relatedObjects[1].rules[0].resources[0]</p> <p>relatedObjects[1].rules[0].verbs[0]</p> <p>relatedObjects[1].rules[0].verbs[1]</p> <p>relatedObjects[1].rules[0].apiGroups[0]</p> <p>relatedObjects[0].subjects[0]</p> <p>relatedObjects[0].roleRef.name</p> <p>relatedObjects[1].rules[2].resources[1]</p> <p>relatedObjects[1].rules[2].verbs[0]</p> <p>relatedObjects[1].rules[2].apiGroups[0]</p> <p>relatedObjects[0].subjects[0]</p> <p>relatedObjects[0].roleRef.name</p> </td>
</tr>
@@ -1292,56 +1292,56 @@
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Ingress and Egress blocked</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0030">C-0030</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0030/">C-0030</a></td>
<td class="resourceRemediationCell"></td>
</tr>
<tr>
<td class="resourceSeverityCell">High</td>
<td class="resourceNameCell">Resource limits</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0009">C-0009</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0009/">C-0009</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].resources.limits.cpu=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].resources.limits.memory=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Configured readiness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0018">C-0018</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0018/">C-0018</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].readinessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Kubernetes CronJob</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0026">C-0026</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0026/">C-0026</a></td>
<td class="resourceRemediationCell"></td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Label usage for resources</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0076">C-0076</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0076/">C-0076</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.metadata.labels=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Linux hardening</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0055">C-0055</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0055/">C-0055</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.seccompProfile=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.seLinuxOptions=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.capabilities.drop[0]=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Configured liveness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0056">C-0056</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0056/">C-0056</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].livenessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">K8s common labels usage</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0077">C-0077</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0077/">C-0077</a></td>
<td class="resourceRemediationCell"> <p>metadata.labels=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.metadata.labels=YOUR_VALUE</p> </td>
</tr>
@@ -1368,56 +1368,56 @@
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Ingress and Egress blocked</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0030">C-0030</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0030/">C-0030</a></td>
<td class="resourceRemediationCell"></td>
</tr>
<tr>
<td class="resourceSeverityCell">High</td>
<td class="resourceNameCell">Resource limits</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0009">C-0009</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0009/">C-0009</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].resources.limits.cpu=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].resources.limits.memory=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Configured readiness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0018">C-0018</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0018/">C-0018</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].readinessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Kubernetes CronJob</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0026">C-0026</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0026/">C-0026</a></td>
<td class="resourceRemediationCell"></td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Label usage for resources</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0076">C-0076</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0076/">C-0076</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.metadata.labels=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Linux hardening</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0055">C-0055</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0055/">C-0055</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.seccompProfile=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.seLinuxOptions=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.capabilities.drop[0]=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Configured liveness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0056">C-0056</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0056/">C-0056</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].livenessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">K8s common labels usage</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0077">C-0077</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0077/">C-0077</a></td>
<td class="resourceRemediationCell"> <p>metadata.labels=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.metadata.labels=YOUR_VALUE</p> </td>
</tr>
@@ -1444,7 +1444,7 @@
<tr>
<td class="resourceSeverityCell">High</td>
<td class="resourceNameCell">List Kubernetes secrets</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0015">C-0015</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0015/">C-0015</a></td>
<td class="resourceRemediationCell"> <p>relatedObjects[1].rules[0].resources[0]</p> <p>relatedObjects[1].rules[0].verbs[0]</p> <p>relatedObjects[1].rules[0].verbs[1]</p> <p>relatedObjects[1].rules[0].verbs[2]</p> <p>relatedObjects[1].rules[0].apiGroups[0]</p> <p>relatedObjects[0].subjects[0]</p> <p>relatedObjects[0].roleRef.name</p> </td>
</tr>
@@ -1471,63 +1471,63 @@
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Allow privilege escalation</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0016">C-0016</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0016/">C-0016</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation=false</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Ingress and Egress blocked</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0030">C-0030</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0030/">C-0030</a></td>
<td class="resourceRemediationCell"></td>
</tr>
<tr>
<td class="resourceSeverityCell">High</td>
<td class="resourceNameCell">Resource limits</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0009">C-0009</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0009/">C-0009</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].resources.limits.cpu=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].resources.limits.memory=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Configured readiness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0018">C-0018</a></td>
<td class="resourceURLCell"><a href=" https://kubescape.io/docs/controls/c-0018/">C-0018</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].readinessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Kubernetes CronJob</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0026">C-0026</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0026/">C-0026</a></td>
<td class="resourceRemediationCell"></td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Non-root containers</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0013">C-0013</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0013/">C-0013</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.runAsNonRoot=true</p> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation=false</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Linux hardening</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0055">C-0055</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0055/">C-0055</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.seccompProfile=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.seLinuxOptions=YOUR_VALUE</p> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.capabilities.drop[0]=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Medium</td>
<td class="resourceNameCell">Configured liveness probe</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0056">C-0056</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0056/">C-0056</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].livenessProbe=YOUR_VALUE</p> </td>
</tr>
<tr>
<td class="resourceSeverityCell">Low</td>
<td class="resourceNameCell">Immutable container filesystem</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/c-0017">C-0017</a></td>
<td class="resourceURLCell"><a href="https://kubescape.io/docs/controls/c-0017/">C-0017</a></td>
<td class="resourceRemediationCell"> <p>spec.jobTemplate.spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem=true</p> </td>
</tr>

469
go.mod
View File

@@ -1,35 +1,38 @@
module github.com/kubescape/kubescape/v3
go 1.23.6
go 1.24.1
toolchain go1.24.2
toolchain go1.24.6
require (
github.com/adrg/xdg v0.5.3
github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837
github.com/anchore/grype v0.81.0
github.com/anchore/stereoscope v0.0.11
github.com/anchore/syft v1.18.1
github.com/anchore/clio v0.0.0-20250715152405-a0fa658e5084
github.com/anchore/grype v0.99.1
github.com/anchore/stereoscope v0.1.9
github.com/anchore/syft v1.32.0
github.com/anubhav06/copa-grype v1.0.3-alpha.1
github.com/armosec/armoapi-go v0.0.562
github.com/armosec/utils-go v0.0.58
github.com/armosec/utils-k8s-go v0.0.30
github.com/briandowns/spinner v1.23.1
github.com/briandowns/spinner v1.23.2
github.com/chainguard-dev/git-urls v1.0.2
github.com/containerd/platforms v1.0.0-rc.1
github.com/distribution/reference v0.6.0
github.com/docker/distribution v2.8.3+incompatible
github.com/docker/buildx v0.21.3
github.com/docker/cli v28.3.3+incompatible
github.com/enescakir/emoji v1.0.0
github.com/francoispqt/gojay v1.2.13
github.com/go-git/go-git/v5 v5.13.0
github.com/google/go-containerregistry v0.20.3
github.com/go-git/go-git/v5 v5.16.2
github.com/google/go-containerregistry v0.20.6
github.com/google/uuid v1.6.0
github.com/jedib0t/go-pretty/v6 v6.6.8
github.com/johnfercher/go-tree v1.1.0
github.com/johnfercher/maroto/v2 v2.2.2
github.com/json-iterator/go v1.1.12
github.com/jwalton/gchalk v1.3.0
github.com/kubescape/backend v0.0.20
github.com/kubescape/go-git-url v0.0.30
github.com/kubescape/go-logger v0.0.23
github.com/kubescape/go-logger v0.0.25
github.com/kubescape/k8s-interface v0.0.195
github.com/kubescape/opa-utils v0.0.288
github.com/kubescape/rbac-utils v0.0.21-0.20230806101615-07e36f555520
@@ -41,48 +44,48 @@ require (
github.com/matthyx/go-gitlog v0.0.0-20231005131906-9ffabe3c5bcd
github.com/mattn/go-isatty v0.0.20
github.com/mikefarah/yq/v4 v4.29.1
github.com/olekukonko/tablewriter v0.0.6-0.20230417144759-edd1a71a5576
github.com/open-policy-agent/opa v1.3.0
github.com/moby/buildkit v0.21.0
github.com/open-policy-agent/opa v1.4.0
github.com/owenrumney/go-sarif/v2 v2.2.0
github.com/project-copacetic/copacetic v0.4.1-0.20231017020916-013c118454b8
github.com/project-copacetic/copacetic v0.10.0
github.com/quay/claircore v1.5.35
github.com/schollz/progressbar/v3 v3.13.0
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
github.com/sergi/go-diff v1.4.0
github.com/sigstore/cosign/v2 v2.2.4
github.com/sirupsen/logrus v1.9.3
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
go.opentelemetry.io/otel v1.35.0
go.opentelemetry.io/otel/metric v1.35.0
golang.org/x/mod v0.24.0
golang.org/x/term v0.31.0
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/otel v1.37.0
go.opentelemetry.io/otel/metric v1.37.0
golang.org/x/mod v0.29.0
golang.org/x/term v0.37.0
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v3 v3.0.1
helm.sh/helm/v3 v3.17.3
k8s.io/api v0.32.3
k8s.io/apimachinery v0.32.3
k8s.io/client-go v0.32.3
helm.sh/helm/v3 v3.18.5
k8s.io/api v0.33.3
k8s.io/apimachinery v0.33.3
k8s.io/client-go v0.33.3
k8s.io/utils v0.0.0-20241210054802-24370beab758
sigs.k8s.io/kustomize/api v0.18.0
sigs.k8s.io/kustomize/kyaml v0.18.1
sigs.k8s.io/yaml v1.4.0
sigs.k8s.io/kustomize/api v0.19.0
sigs.k8s.io/kustomize/kyaml v0.19.0
sigs.k8s.io/yaml v1.5.0
)
require github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
require (
cel.dev/expr v0.19.1 // indirect
cloud.google.com/go v0.118.3 // indirect
cloud.google.com/go/auth v0.15.0 // indirect
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go v0.121.3 // indirect
cloud.google.com/go/auth v0.16.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/container v1.42.2 // indirect
cloud.google.com/go/iam v1.4.1 // indirect
cloud.google.com/go/monitoring v1.24.0 // indirect
cloud.google.com/go/storage v1.50.0 // indirect
dario.cat/mergo v1.0.1 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/container v1.43.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/storage v1.55.0 // indirect
cyphar.com/go-pathrs v0.2.1 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20250520111509-a70c2aa677fa // indirect
github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 // indirect
@@ -91,7 +94,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.1.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.4.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect
@@ -102,22 +105,27 @@ require (
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/CycloneDX/cyclonedx-go v0.9.1 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect
github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
github.com/Intevation/gval v1.3.0 // indirect
github.com/Intevation/jsonpath v0.2.1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.3.1 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.12.9 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/Microsoft/hcsshim v0.13.0 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
github.com/a8m/envsubst v1.3.0 // indirect
github.com/acobaugh/osrelease v0.1.0 // indirect
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/alecthomas/participle/v2 v2.1.0 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
@@ -132,126 +140,136 @@ require (
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/credentials-go v1.3.1 // indirect
github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51 // indirect
github.com/anchore/fangs v0.0.0-20241014201141-b6e4b3469f10 // indirect
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 // indirect
github.com/anchore/go-logger v0.0.0-20241205183533-4fc29b5832e7 // indirect
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
github.com/anchore/fangs v0.0.0-20250716230140-94c22408c232 // indirect
github.com/anchore/go-collections v0.0.0-20241211140901-567f400e9a46 // indirect
github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d // indirect
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 // indirect
github.com/anchore/go-lzo v0.1.0 // indirect
github.com/anchore/go-macholibre v0.0.0-20250320151634-807da7ad2331 // indirect
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec // indirect
github.com/anchore/go-struct-converter v0.0.0-20250211213226-cce56d595160 // indirect
github.com/anchore/go-sync v0.0.0-20250714163430-add63db73ad1 // indirect
github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect
github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect
github.com/aquasecurity/go-pep440-version v0.0.1 // indirect
github.com/aquasecurity/go-version v0.0.1 // indirect
github.com/armosec/gojay v1.2.17 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go v1.55.6 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.63 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go v1.55.7 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.17 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.34.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.25.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.45.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.33.2 // indirect
github.com/aws/aws-sdk-go-v2/service/eks v1.48.5 // indirect
github.com/aws/aws-sdk-go-v2/service/iam v1.35.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
github.com/aws/smithy-go v1.22.2 // indirect
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect
github.com/aws/smithy-go v1.22.4 // indirect
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.10.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/becheran/wildmatch-go v1.0.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bitnami/go-version v0.0.0-20250505154626-452e8c5ee607 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/bmatcuk/doublestar/v2 v2.0.4 // indirect
github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/boombuler/barcode v1.0.2 // indirect
github.com/bugsnag/bugsnag-go/v2 v2.3.0 // indirect
github.com/bugsnag/panicwrap v1.3.4 // indirect
github.com/buildkite/agent/v3 v3.62.0 // indirect
github.com/buildkite/go-pipeline v0.3.2 // indirect
github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/lipgloss v1.0.0 // indirect
github.com/charmbracelet/x/ansi v0.4.5 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/charmbracelet/colorprofile v0.3.1 // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
github.com/cilium/cilium v1.16.9 // indirect
github.com/cilium/cilium v1.16.17 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.3.8 // indirect
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/containerd/cgroups/v3 v3.0.5 // indirect
github.com/containerd/console v1.0.4 // indirect
github.com/containerd/containerd v1.7.27 // indirect
github.com/containerd/containerd/api v1.8.0 // indirect
github.com/containerd/continuity v0.4.4 // indirect
github.com/containerd/containerd v1.7.29 // indirect
github.com/containerd/containerd/api v1.9.0 // indirect
github.com/containerd/containerd/v2 v2.0.7 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/containers/common v0.63.0 // indirect
github.com/coreos/go-oidc/v3 v3.14.1 // indirect
github.com/cpuguy83/dockercfg v0.3.1 // indirect
github.com/cpuguy83/go-docker v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/cpuguy83/go-docker v0.3.0 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da // indirect
github.com/deitch/magic v0.0.0-20240306090643-c67ab88f10cb // indirect
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/docker/buildx v0.11.2 // indirect
github.com/docker/cli v27.5.0+incompatible // indirect
github.com/docker/docker v28.0.4+incompatible // indirect
github.com/diskfs/go-diskfs v1.7.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v28.3.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/elliotchance/orderedmap v1.5.0 // indirect
github.com/elliotchance/phpserialize v1.4.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/f-amaral/go-async v0.3.0 // indirect
github.com/facebookincubator/nvdtools v0.1.5 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fvbommel/sortorder v1.1.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
github.com/github/go-spdx/v2 v2.3.2 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/github/go-spdx/v2 v2.3.3 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/glebarez/sqlite v1.11.0 // indirect
github.com/go-chi/chi v4.1.2+incompatible // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.0 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-gota/gota v0.12.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/errors v0.22.1 // indirect
@@ -265,44 +283,46 @@ require (
github.com/go-openapi/validate v0.24.0 // indirect
github.com/go-piv/piv-go v1.11.0 // indirect
github.com/go-restruct/restruct v1.2.0-alpha // indirect
github.com/go-test/deep v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-yaml v1.9.6 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/gocsaf/csaf/v3 v3.3.0 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gofrs/uuid v4.3.1+incompatible // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/gohugoio/hashstructure v0.5.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/certificate-transparency-go v1.3.1 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-github/v55 v55.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/licensecheck v0.3.1 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/gookit/color v1.6.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-getter v1.7.6 // indirect
github.com/hashicorp/go-getter v1.7.9 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl/v2 v2.24.0 // indirect
github.com/hhrutter/lzw v1.0.0 // indirect
github.com/hhrutter/tiff v1.0.1 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
@@ -323,91 +343,103 @@ require (
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f // indirect
github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 // indirect
github.com/knqyf263/go-deb-version v0.0.0-20241115132648-6f4aee6ccd23 // indirect
github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075 // indirect
github.com/knqyf263/go-rpmdb v0.1.1 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mackerelio/go-osstat v0.2.5 // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/masahiro331/go-mvn-version v0.0.0-20250131095131-f4974fa13b8a // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mholt/archiver/v3 v3.5.1 // indirect
github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 // indirect
github.com/mholt/archives v0.1.5 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/minlz v1.0.1 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/buildkit v0.12.5 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/patternmatcher v0.5.0 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/signal v0.7.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/mozillazg/docker-credential-acr-helper v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1 // indirect
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/oleiade/reflections v1.0.1 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect
github.com/olekukonko/tablewriter v1.0.9 // indirect
github.com/olvrng/ujson v1.1.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runtime-spec v1.2.1 // indirect
github.com/opencontainers/selinux v1.12.0 // indirect
github.com/opencontainers/selinux v1.13.0 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/openvex/go-vex v0.2.5 // indirect
github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 // indirect
github.com/package-url/packageurl-go v0.1.2-0.20230812223828-f8bb31c1f10b // indirect
github.com/package-url/packageurl-go v0.1.3 // indirect
github.com/pandatix/go-cvss v0.6.2 // indirect
github.com/pborman/indent v1.2.1 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/pdfcpu/pdfcpu v0.9.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/petermattis/goid v0.0.0-20241211131331-93ee7e083c43 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pjbgf/sha1cd v0.4.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/pkg/xattr v0.4.12 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.21.1 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/quay/claircore/toolkit v1.2.4 // indirect
github.com/quay/zlog v1.1.8 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/saferwall/pe v1.5.6 // indirect
github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/rs/zerolog v1.30.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c // indirect
github.com/sagikazarmark/locafero v0.9.0 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/sasha-s/go-deadlock v0.3.5 // indirect
github.com/sassoftware/go-rpmutils v0.4.0 // indirect
github.com/sassoftware/relic v7.2.1+incompatible // indirect
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e // indirect
github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d // indirect
github.com/seccomp/libseccomp-golang v0.10.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
@@ -416,136 +448,133 @@ require (
github.com/sigstore/fulcio v1.6.6 // indirect
github.com/sigstore/protobuf-specs v0.4.1 // indirect
github.com/sigstore/rekor v1.3.10 // indirect
github.com/sigstore/sigstore v1.9.3 // indirect
github.com/sigstore/sigstore v1.9.5 // indirect
github.com/sigstore/timestamp-authority v1.2.2 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spdx/gordf v0.0.0-20250128162952-000978ccd6fb // indirect
github.com/spdx/tools-golang v0.5.5 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/spf13/viper v1.19.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.4.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.9.2 // indirect
github.com/spf13/pflag v1.0.7 // indirect
github.com/spf13/viper v1.20.1 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/stripe/stripe-go/v74 v74.30.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/sylabs/sif/v2 v2.21.1 // indirect
github.com/sylabs/squashfs v1.0.4 // indirect
github.com/sylabs/sif/v2 v2.22.0 // indirect
github.com/sylabs/squashfs v1.0.6 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tchap/go-patricia/v2 v2.3.2 // indirect
github.com/thales-e-security/pool v0.0.2 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/theupdateframework/go-tuf v0.7.0 // indirect
github.com/theupdateframework/notary v0.6.1 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb // indirect
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
github.com/tonistiigi/fsutil v0.0.0-20250410151801-5b74a7ad7583 // indirect
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 // indirect
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
github.com/transparency-dev/merkle v0.0.2 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 // indirect
github.com/uptrace/opentelemetry-go-extra/otelzap v0.3.2 // indirect
github.com/uptrace/uptrace-go v1.30.1 // indirect
github.com/uptrace/uptrace-go v1.37.0 // indirect
github.com/vbatts/go-mtree v0.5.4 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/vifraa/gopom v1.0.0 // indirect
github.com/vishvananda/netlink v1.3.1-0.20241022031324-976bd8de7d81 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 // indirect
github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b // indirect
github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/go-gitlab v0.102.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yashtewari/glob-intersection v0.2.0 // indirect
github.com/yl2chen/cidranger v1.0.2 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/zclconf/go-cty v1.14.0 // indirect
github.com/zeebo/errs v1.3.0 // indirect
github.com/zclconf/go-cty v1.16.3 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.etcd.io/bbolt v1.4.2 // indirect
go.mongodb.org/mongo-driver v1.17.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.44.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/runtime v0.55.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.6.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.41.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.37.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/contrib/instrumentation/runtime v0.62.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.13.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 // indirect
go.opentelemetry.io/otel/log v0.6.0 // indirect
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.6.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 // indirect
go.opentelemetry.io/otel/log v0.13.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.13.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.step.sm/crypto v0.60.0 // indirect
go.uber.org/mock v0.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
golang.org/x/image v0.24.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
gonum.org/v1/gonum v0.9.1 // indirect
google.golang.org/api v0.228.0 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
google.golang.org/grpc v1.71.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.38.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
gonum.org/v1/gonum v0.12.0 // indirect
google.golang.org/api v0.242.0 // indirect
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 // indirect
google.golang.org/grpc v1.74.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gorm.io/gorm v1.25.12 // indirect
k8s.io/apiextensions-apiserver v0.32.2 // indirect
k8s.io/apiserver v0.32.3 // indirect
k8s.io/component-base v0.32.3 // indirect
gorm.io/gorm v1.30.2 // indirect
k8s.io/apiextensions-apiserver v0.33.3 // indirect
k8s.io/apiserver v0.33.3 // indirect
k8s.io/cli-runtime v0.33.3 // indirect
k8s.io/component-base v0.33.3 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect
modernc.org/libc v1.61.13 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/kubectl v0.33.3 // indirect
modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/sqlite v1.36.2 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.38.2 // indirect
oras.land/oras-go/v2 v2.6.0 // indirect
sigs.k8s.io/controller-runtime v0.18.4 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/release-utils v0.9.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
)
// Using the forked version of tablewriter
replace github.com/olekukonko/tablewriter => github.com/kubescape/tablewriter v0.0.6-0.20231106230230-aac7d2659c94
replace github.com/anchore/stereoscope => github.com/matthyx/stereoscope v0.0.0-20250916161743-dd57158479de
replace github.com/anchore/stereoscope => github.com/matthyx/stereoscope v0.0.0-20240426103125-b762a3538c32
replace github.com/google/go-containerregistry => github.com/matthyx/go-containerregistry v0.0.0-20240227132928-63ceb71ae0b9
replace github.com/docker/distribution v2.8.3+incompatible => github.com/docker/distribution v2.8.2+incompatible
replace github.com/mholt/archiver/v3 v3.5.1 => github.com/anchore/archiver/v3 v3.5.2
replace github.com/docker/docker => github.com/docker/docker v26.1.5+incompatible
replace github.com/docker/cli => github.com/docker/cli v26.1.0+incompatible
replace github.com/sylabs/squashfs => github.com/sylabs/squashfs v0.6.1
replace github.com/google/go-containerregistry => github.com/matthyx/go-containerregistry v0.0.0-20250916162850-293c5b36a9f8

1763
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -1,174 +1,340 @@
# Kubescape HTTP Handler Package
# Kubescape HTTP Handler
Running `kubescape` will start up a web-server on port `8080` which will serve the following API's:
The HTTP Handler provides a REST API for running Kubescape scans programmatically. This enables integration with CI/CD pipelines, custom dashboards, and automation workflows.
### Trigger scan
## Table of Contents
* POST `/v1/scan` - triggers a Kubescape scan. The server will return an ID and will execute the scanning asynchronously. The request body should look [as follows](#trigger-scan-object).
* * `wait=true`: scan synchronously (return results and not ID). Use only in small clusters or with an increased timeout. Default is `wait=false`
* * `keep=true`: do not delete results from local storage after returning. Default is `keep=false`
- [Overview](#overview)
- [API Reference](#api-reference)
- [Trigger Scan](#trigger-scan)
- [Get Results](#get-results)
- [Check Status](#check-status)
- [Delete Results](#delete-results)
- [Request/Response Objects](#requestresponse-objects)
- [API Examples](#api-examples)
- [Environment Variables](#environment-variables)
- [Deployment Examples](#deployment-examples)
- [Debugging](#debugging)
[Response](#response-object):
---
```
## Overview
When running Kubescape as a service, it starts a web server on port `8080` that exposes REST APIs for:
- Triggering security scans (async or sync)
- Retrieving scan results
- Checking scan status
- Managing cached results
---
## API Reference
### Trigger Scan
**Endpoint:** `POST /v1/scan`
Triggers a Kubescape scan. By default, scans run asynchronously and return a scan ID immediately.
**Query Parameters:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `wait` | bool | `false` | Wait for scan to complete (synchronous mode) |
| `keep` | bool | `false` | Keep results in cache after returning |
**Request Body:** See [Trigger Scan Object](#trigger-scan-object)
**Response (async):**
```json
{
"id": <str>, // scan ID
"type": "busy", // response object type
"response": <message:string> // message indicating scanning is still in progress
"id": "scan-12345",
"type": "busy",
"response": "scanning in progress"
}
```
> When scanning was triggered with the `wait=true` query param, the response is like the [`/v1/results` API](#get-results) response
**Response (sync with `wait=true`):** Same as [Get Results](#get-results) response.
### Get results
* GET `/v1/results` - request kubescape scan results
* * query `id=<string>` -> request results of a specific scan ID. If empty will return the latest results
* * query `keep=true` -> keep the results in the local storage after returning. default is `keep=false` - the results will be deleted from local storage after they are returned
---
[Response](#response-object):
### Get Results
When scanning was done successfully
```
**Endpoint:** `GET /v1/results`
Retrieve scan results.
**Query Parameters:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `id` | string | - | Scan ID. If empty, returns latest results |
| `keep` | bool | `false` | Keep results in cache after returning |
**Response (success):**
```json
{
"id": <str>, // scan ID
"type": "v1results", // response object type
"response": <object:v1results> // v1 results payload
"id": "scan-12345",
"type": "v1results",
"response": { /* scan results object */ }
}
```
When scanning failed
```
**Response (error):**
```json
{
"id": <str>, // scan ID
"type": "error", // response object type
"response": <error:string> // error string
"id": "scan-12345",
"type": "error",
"response": "error message"
}
```
When scanning is in progress
```
**Response (in progress):**
```json
{
"id": <str>, // scan ID
"type": "busy", // response object type
"response": <message:string> // message indicating scanning is still in progress
}
```
### Check scanning progress status
Check the scanning status - is the scanning in progress or done. This is meant for a waiting mechanize since the API does not return the entire results object when the scanning is done
* GET `/v1/status` - Request kubescape scan status
* * query `id=<string>` -> Check status of a specific scan. If empty, it will check if any scan is still in progress
[Response](#response-object):
When scanning is in progress
```
{
"id": <str>, // scan ID
"type": "busy", // response object type
"response": <message:string> // message indicating scanning is still in process
"id": "scan-12345",
"type": "busy",
"response": "scanning in progress"
}
```
When scanning is not in progress
```
---
### Check Status
**Endpoint:** `GET /v1/status`
Check if a scan is still in progress. Useful for polling without retrieving full results.
**Query Parameters:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `id` | string | - | Scan ID. If empty, checks if any scan is in progress |
**Response (in progress):**
```json
{
"id": <str>, // scan ID
"type": "notBusy", // response object type
"response": <message:string> // message indicating scanning is successfully done
"id": "scan-12345",
"type": "busy",
"response": "scanning in progress"
}
```
### Delete cached results
* DELETE `/v1/results` - Delete kubescape scan results from storage. If empty will delete the latest results
* * query `id=<string>`: Delete ID of specific results
* * query `all`: Delete all cached results
**Response (complete):**
## Objects
### Trigger scan object
```
```json
{
"format": <str>, // results format [default: json] (same as 'kubescape scan --format')
"excludedNamespaces": [<str>], // list of namespaces to exclude (same as 'kubescape scan --excluded-namespaces')
"includeNamespaces": [<str>], // list of namespaces to include (same as 'kubescape scan --include-namespaces')
"useCachedArtifacts"`: <bool>, // use the cached artifacts instead of downloading (offline support)
"hostScanner": <bool>, // 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
"keepLocal": <bool>, // do not submit results to Kubescape cloud (same as 'kubescape scan --keep-local')
"account": <str>, // account ID (same as 'kubescape scan --account')
"access-key": <str>, // account ID (same as 'kubescape scan --accessKey')
"targetType": <str>, // framework/control
"targetNames": [<str>] // names. e.g. when targetType==framework, targetNames=["nsa", "mitre"]
"id": "scan-12345",
"type": "notBusy",
"response": "scanning completed"
}
```
### Response object
---
```
### Delete Results
**Endpoint:** `DELETE /v1/results`
Delete cached scan results.
**Query Parameters:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `id` | string | - | Scan ID to delete. If empty, deletes latest |
| `all` | bool | `false` | Delete all cached results |
---
## Request/Response Objects
### Trigger Scan Object
```json
{
"id": <str>, // scan ID
"type": <responseType:str>, // response object type
"response": <object:interface> // response payload as list of bytes
"format": "json",
"excludedNamespaces": ["kube-system", "kube-public"],
"includeNamespaces": ["production", "staging"],
"useCachedArtifacts": false,
"keepLocal": true,
"account": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"accessKey": "your-access-key",
"targetType": "framework",
"targetNames": ["nsa", "mitre"]
}
```
#### Response object types
* "v1results" - v1 results object
* "busy" - server is busy processing previous requests
* "notBusy" - server is not busy processing previous requests
* "ready" - server is done processing request and results are ready
* "error" - error object
| Field | Type | Description |
|-------|------|-------------|
| `format` | string | Output format (default: `json`) |
| `excludedNamespaces` | []string | Namespaces to exclude from scan |
| `includeNamespaces` | []string | Namespaces to include in scan |
| `useCachedArtifacts` | bool | Use cached artifacts (offline mode) |
| `keepLocal` | bool | Don't submit results to backend |
| `account` | string | Kubescape SaaS account ID |
| `accessKey` | string | Kubescape SaaS access key |
| `targetType` | string | `"framework"` or `"control"` |
| `targetNames` | []string | Frameworks/controls to scan |
### Response Object
```json
{
"id": "scan-12345",
"type": "v1results",
"response": { /* payload */ }
}
```
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Scan identifier |
| `type` | string | Response type (see below) |
| `response` | any | Response payload |
**Response Types:**
| Type | Description |
|------|-------------|
| `v1results` | Scan results object |
| `busy` | Scan in progress |
| `notBusy` | No scan in progress |
| `ready` | Scan complete, results ready |
| `error` | Error occurred |
---
## API Examples
#### Default scan
1. Trigger kubescape scan
```bash
curl --header "Content-Type: application/json" --request POST --data '{"hostScanner":true}' http://127.0.0.1:8080/v1/scan
```
2. Get kubescape scan results
```bash
curl --request GET http://127.0.0.1:8080/v1/results -o response.json
```
#### Trigger scan and wait for the scan to end
### Basic Scan (Async)
```bash
curl --header "Content-Type: application/json" --request POST --data '{"hostScanner":true}' http://127.0.0.1:8080/v1/scan?wait -o scan_results.json
```
#### Scan single namespace with a specific framework
```bash
curl --header "Content-Type: application/json" \
--request POST \
--data '{"hostScanner":true, "includeNamespaces": ["kubescape"], "targetType": "framework", "targetNames": ["nsa"] }' \
http://127.0.0.1:8080/v1/scan
# 1. Trigger scan
curl -X POST http://127.0.0.1:8080/v1/scan \
-H "Content-Type: application/json" \
-d '{"targetType": "framework", "targetNames": ["nsa"]}'
# 2. Check status
curl http://127.0.0.1:8080/v1/status
# 3. Get results
curl http://127.0.0.1:8080/v1/results -o results.json
```
#### Data profiling
Analyze profiled data using [pprof](https://github.com/google/pprof/blob/main/doc/README.md).
[How to use](https://pkg.go.dev/net/http/pprof)
### Synchronous Scan
example:
```bash
curl -X POST "http://127.0.0.1:8080/v1/scan?wait=true" \
-H "Content-Type: application/json" \
-d '{"targetType": "framework", "targetNames": ["nsa"]}' \
-o results.json
```
### Scan Specific Namespaces
```bash
curl -X POST http://127.0.0.1:8080/v1/scan \
-H "Content-Type: application/json" \
-d '{
"includeNamespaces": ["production"],
"targetType": "framework",
"targetNames": ["nsa", "mitre"]
}'
```
### Scan with Account Integration
```bash
curl -X POST http://127.0.0.1:8080/v1/scan \
-H "Content-Type: application/json" \
-d '{
"account": "YOUR-ACCOUNT-ID",
"accessKey": "YOUR-ACCESS-KEY",
"targetType": "framework",
"targetNames": ["nsa"]
}'
```
### Delete All Cached Results
```bash
curl -X DELETE "http://127.0.0.1:8080/v1/results?all=true"
```
---
## Environment Variables
Configure the HTTP handler using environment variables:
| Variable | Description | Example |
|----------|-------------|---------|
| `KS_ACCOUNT` | Default account ID | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` |
| `KS_EXCLUDE_NAMESPACES` | Default namespaces to exclude | `kube-system,kube-public` |
| `KS_INCLUDE_NAMESPACES` | Default namespaces to include | `production,staging` |
| `KS_FORMAT` | Default output format | `json` |
| `KS_LOGGER_NAME` | Logger name | `kubescape` |
| `KS_LOGGER_LEVEL` | Log level | `info`, `debug`, `warning`, `error` |
| `KS_DOWNLOAD_ARTIFACTS` | Download artifacts on each scan | `true`, `false` |
---
## Deployment Examples
### Microservice Deployment
Deploy Kubescape as a microservice in your cluster for API-driven scanning.
📖 **[Microservice Deployment Guide →](examples/microservice/README.md)**
### Prometheus Integration
Expose Kubescape metrics for Prometheus scraping.
📖 **[Prometheus Integration Guide →](examples/prometheus/README.md)**
---
## Debugging
### Enable Debug Logging
Set the log level to debug for more verbose output:
```bash
export KS_LOGGER_LEVEL=debug
```
### Performance Profiling
The HTTP handler exposes pprof endpoints for performance analysis:
```bash
# Heap profile
go tool pprof http://localhost:6060/debug/pprof/heap
# CPU profile
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# Goroutine profile
go tool pprof http://localhost:6060/debug/pprof/goroutine
```
## Examples
For more information on pprof, see the [pprof documentation](https://pkg.go.dev/net/http/pprof).
* [Prometheus](examples/prometheus/README.md)
* [Microservice](examples/microservice/README.md)
---
## Related Documentation
## Supported environment variables
* `KS_ACCOUNT`: Account ID
* `KS_EXCLUDE_NAMESPACES`: List of namespaces to exclude, e.g. `KS_EXCLUDE_NAMESPACES=kube-system,kube-public`
* `KS_INCLUDE_NAMESPACES`: List of namespaces to include, rest of the namespaces will be ignored. e.g. `KS_INCLUDE_NAMESPACES=dev,prod`
* `KS_HOST_SCAN_YAML`: Full path to the host scanner YAML
* `KS_FORMAT`: Output file format. default is json
* `KS_ENABLE_HOST_SCANNER`: Enable the host scanner feature
* `KS_DOWNLOAD_ARTIFACTS`: Download the artifacts every scan
* `KS_LOGGER_NAME`: Set logger name
* `KS_LOGGER_LEVEL`: Set logger level
- [CLI Reference](../docs/cli-reference.md)
- [Architecture](../docs/architecture.md)
- [Getting Started Guide](../docs/getting-started.md)
- [Troubleshooting](../docs/troubleshooting.md)

View File

@@ -1,21 +1,274 @@
# Kubescape as a microservice
# Kubescape as a Microservice
1. Deploy kubescape microservice
```bash
kubectl apply -f ks-deployment.yaml
```
> **Note**
> Make sure the configurations suit your cluster (e.g. `serviceType`, namespace, etc.)
This guide explains how to deploy Kubescape as a microservice in your Kubernetes cluster, enabling API-driven security scanning.
2. Trigger scan
```bash
curl --header "Content-Type: application/json" \
--request POST \
--data '{"account":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","hostScanner":true}' \
http://127.0.0.1:8080/v1/scan
```
## Table of Contents
3. Get results
```bash
curl --request GET http://127.0.0.1:8080/v1/results -o results.json
```
- [Overview](#overview)
- [Prerequisites](#prerequisites)
- [Deployment](#deployment)
- [API Usage](#api-usage)
- [Configuration](#configuration)
- [Troubleshooting](#troubleshooting)
---
## Overview
Running Kubescape as a microservice allows you to:
- Trigger security scans via REST API
- Integrate with CI/CD pipelines
- Build custom dashboards and automation
- Schedule and manage scans programmatically
---
## Prerequisites
- Kubernetes cluster with `kubectl` access
- Cluster admin permissions (for RBAC setup)
- Network access to the Kubescape service endpoint
---
## Deployment
### 1. Deploy Kubescape Microservice
```bash
kubectl apply -f ks-deployment.yaml
```
> **Note**: Review and modify `ks-deployment.yaml` to match your cluster configuration:
> - `serviceType` (ClusterIP, NodePort, LoadBalancer)
> - Namespace
> - Resource limits
> - Service account permissions
### 2. Verify Deployment
```bash
# Check pod status
kubectl get pods -l app=kubescape
# Check service
kubectl get svc kubescape
```
### 3. Access the Service
```bash
# Port-forward for local access
kubectl port-forward svc/kubescape 8080:8080
# Or get the external IP (if using LoadBalancer)
kubectl get svc kubescape -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
```
---
## API Usage
### Trigger a Scan
```bash
curl --header "Content-Type: application/json" \
--request POST \
--data '{
"account": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"targetType": "framework",
"targetNames": ["nsa", "mitre"]
}' \
http://127.0.0.1:8080/v1/scan
```
**Response:**
```json
{
"id": "scan-12345",
"type": "busy",
"response": "scanning in progress"
}
```
### Trigger Scan and Wait for Results
```bash
curl --header "Content-Type: application/json" \
--request POST \
--data '{"targetType": "framework", "targetNames": ["nsa"]}' \
"http://127.0.0.1:8080/v1/scan?wait=true" \
-o results.json
```
### Check Scan Status
```bash
curl --request GET "http://127.0.0.1:8080/v1/status?id=scan-12345"
```
### Get Scan Results
```bash
curl --request GET "http://127.0.0.1:8080/v1/results?id=scan-12345" -o results.json
```
### Get Latest Results
```bash
curl --request GET http://127.0.0.1:8080/v1/results -o results.json
```
### Delete Cached Results
```bash
# Delete specific results
curl --request DELETE "http://127.0.0.1:8080/v1/results?id=scan-12345"
# Delete all cached results
curl --request DELETE "http://127.0.0.1:8080/v1/results?all=true"
```
---
## Configuration
### Scan Request Options
| Field | Type | Description |
|-------|------|-------------|
| `account` | string | Kubescape SaaS account ID (optional) |
| `accessKey` | string | Kubescape SaaS access key (optional) |
| `targetType` | string | `"framework"` or `"control"` |
| `targetNames` | array | List of frameworks/controls to scan |
| `excludedNamespaces` | array | Namespaces to exclude |
| `includeNamespaces` | array | Namespaces to include |
| `format` | string | Output format (default: `"json"`) |
| `keepLocal` | boolean | Don't submit results to backend |
| `useCachedArtifacts` | boolean | Use cached artifacts (offline mode) |
### Query Parameters
| Parameter | Description |
|-----------|-------------|
| `wait=true` | Wait for scan to complete (synchronous) |
| `keep=true` | Keep results in cache after returning |
| `id=<scan-id>` | Specify a particular scan ID |
### Environment Variables
Configure the microservice using environment variables in your deployment:
| Variable | Description |
|----------|-------------|
| `KS_ACCOUNT` | Default account ID |
| `KS_EXCLUDE_NAMESPACES` | Default namespaces to exclude |
| `KS_INCLUDE_NAMESPACES` | Default namespaces to include |
| `KS_FORMAT` | Default output format |
| `KS_LOGGER_LEVEL` | Log level (`debug`, `info`, `warning`, `error`) |
---
## Example Workflows
### CI/CD Integration
```bash
#!/bin/bash
# Trigger scan and wait for results
RESULT=$(curl -s --header "Content-Type: application/json" \
--request POST \
--data '{"targetType": "framework", "targetNames": ["nsa"]}' \
"http://kubescape:8080/v1/scan?wait=true")
# Extract compliance score
SCORE=$(echo $RESULT | jq '.response.summaryDetails.complianceScore')
# Fail pipeline if score is below threshold
if (( $(echo "$SCORE < 80" | bc -l) )); then
echo "Compliance score $SCORE is below threshold (80)"
exit 1
fi
```
### Scheduled Scanning
Use a Kubernetes CronJob to trigger regular scans:
```yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: kubescape-scheduled-scan
spec:
schedule: "0 */6 * * *" # Every 6 hours
jobTemplate:
spec:
template:
spec:
containers:
- name: scanner
image: curlimages/curl
command:
- /bin/sh
- -c
- |
curl -X POST http://kubescape:8080/v1/scan \
-H "Content-Type: application/json" \
-d '{"targetType": "framework", "targetNames": ["nsa", "mitre"]}'
restartPolicy: OnFailure
```
---
## Troubleshooting
### Service Not Accessible
```bash
# Check pod logs
kubectl logs -l app=kubescape
# Check service endpoints
kubectl get endpoints kubescape
# Verify network policies
kubectl get networkpolicies
```
### Scan Times Out
For large clusters, use asynchronous scanning:
```bash
# Trigger scan (returns immediately)
curl -X POST http://127.0.0.1:8080/v1/scan \
-H "Content-Type: application/json" \
-d '{"targetType": "framework", "targetNames": ["nsa"]}'
# Poll for status
while true; do
STATUS=$(curl -s http://127.0.0.1:8080/v1/status | jq -r '.type')
if [ "$STATUS" != "busy" ]; then
break
fi
sleep 10
done
# Get results
curl http://127.0.0.1:8080/v1/results -o results.json
```
### Permission Errors
Ensure the service account has sufficient RBAC permissions to read cluster resources.
---
## Related Documentation
- [HTTP Handler API Reference](../../README.md)
- [Kubescape CLI Reference](../../../docs/cli-reference.md)
- [Prometheus Integration](../prometheus/README.md)
- [Getting Started Guide](../../../docs/getting-started.md)

View File

@@ -99,7 +99,7 @@ spec:
fieldPath: metadata.namespace
- name: "KS_SKIP_UPDATE_CHECK" # do not check latest version
value: "true"
- name: KS_ENABLE_HOST_SCANNER # enable host scanner -> https://hub.armosec.io/docs/host-sensor
- name: KS_ENABLE_HOST_SCANNER # enable host scanner -> https://kubescape.io/docs/components/host-sensor/
value: "true"
- name: KS_DOWNLOAD_ARTIFACTS # When set to true the artifacts will be downloaded every scan execution
value: "true"

View File

@@ -1,3 +1,69 @@
# Prometheus Kubescape Integration
# Prometheus Integration
Please find the new documentation at the following link: [Kubescape Prometheus Integration](https://github.com/kubescape/helm-charts/blob/main/charts/kubescape-operator/README.md#kubescape-prometheus-integration)
> **Note**: The Prometheus integration documentation has moved to the Kubescape Helm Charts repository.
## Current Documentation
For the latest Prometheus integration guide, please visit:
**[Kubescape Prometheus Integration →](https://github.com/kubescape/helm-charts/blob/main/charts/kubescape-operator/README.md#kubescape-prometheus-integration)**
## Quick Overview
The Kubescape Operator exposes Prometheus metrics for monitoring your cluster's security posture.
### Features
- Compliance score metrics per framework
- Control pass/fail counts
- Vulnerability counts by severity
- Resource scan statistics
### Installation with Prometheus Support
```bash
helm repo add kubescape https://kubescape.github.io/helm-charts/
helm repo update
helm upgrade --install kubescape kubescape/kubescape-operator \
--namespace kubescape \
--create-namespace \
--set capabilities.prometheusExporter=enable
```
### Available Metrics
| Metric | Description |
|--------|-------------|
| `kubescape_compliance_score` | Compliance score per framework (0-100) |
| `kubescape_controls_passed` | Number of passed controls |
| `kubescape_controls_failed` | Number of failed controls |
| `kubescape_resources_scanned` | Total resources scanned |
| `kubescape_vulnerabilities_total` | Vulnerabilities by severity |
### ServiceMonitor (for Prometheus Operator)
If you're using the Prometheus Operator, the Helm chart can create a ServiceMonitor:
```bash
helm upgrade --install kubescape kubescape/kubescape-operator \
--namespace kubescape \
--create-namespace \
--set capabilities.prometheusExporter=enable \
--set serviceMonitor.enabled=true
```
### Grafana Dashboard
A pre-built Grafana dashboard is available for visualizing Kubescape metrics:
- [Kubescape Grafana Dashboard](https://grafana.com/grafana/dashboards/18183-kubescape/)
---
## Related Documentation
- [Kubescape Operator Installation](https://kubescape.io/docs/install-operator/)
- [Helm Charts Repository](https://github.com/kubescape/helm-charts)
- [HTTP Handler API](../../README.md)
- [Microservice Deployment](../microservice/README.md)

View File

@@ -1,8 +1,8 @@
module github.com/kubescape/kubescape/v3/httphandler
go 1.23.6
go 1.24.1
toolchain go1.24.2
toolchain go1.24.6
replace github.com/kubescape/kubescape/v3 => ../
@@ -15,47 +15,48 @@ require (
github.com/gorilla/mux v1.8.1
github.com/gorilla/schema v1.4.1
github.com/kubescape/backend v0.0.20
github.com/kubescape/go-logger v0.0.23
github.com/kubescape/go-logger v0.0.25
github.com/kubescape/k8s-interface v0.0.195
github.com/kubescape/kubescape/v3 v3.0.4
github.com/kubescape/opa-utils v0.0.288
github.com/kubescape/storage v0.0.184
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.10.0
github.com/spf13/viper v1.20.1
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.45.0
go.opentelemetry.io/otel v1.35.0
k8s.io/apimachinery v0.32.3
k8s.io/client-go v0.32.3
go.opentelemetry.io/otel v1.37.0
k8s.io/apimachinery v0.33.3
k8s.io/client-go v0.33.3
k8s.io/utils v0.0.0-20241210054802-24370beab758
)
require (
go.opentelemetry.io/otel/trace v1.35.0
go.opentelemetry.io/otel/trace v1.37.0
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/grpc v1.71.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 // indirect
google.golang.org/grpc v1.74.0 // indirect
)
require (
cel.dev/expr v0.19.1 // indirect
cloud.google.com/go v0.118.3 // indirect
cloud.google.com/go/auth v0.15.0 // indirect
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go v0.121.3 // indirect
cloud.google.com/go/auth v0.16.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/container v1.42.2 // indirect
cloud.google.com/go/iam v1.4.1 // indirect
cloud.google.com/go/monitoring v1.24.0 // indirect
cloud.google.com/go/storage v1.50.0 // indirect
dario.cat/mergo v1.0.1 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/container v1.43.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/storage v1.55.0 // indirect
cyphar.com/go-pathrs v0.2.1 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20250520111509-a70c2aa677fa // indirect
github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 // indirect
@@ -64,7 +65,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.1.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.4.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect
@@ -75,23 +76,28 @@ require (
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/CycloneDX/cyclonedx-go v0.9.1 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect
github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
github.com/Intevation/gval v1.3.0 // indirect
github.com/Intevation/jsonpath v0.2.1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.3.1 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.12.9 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/Microsoft/hcsshim v0.13.0 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
github.com/a8m/envsubst v1.3.0 // indirect
github.com/acobaugh/osrelease v0.1.0 // indirect
github.com/adrg/xdg v0.5.3 // indirect
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/alecthomas/participle/v2 v2.1.0 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
@@ -106,105 +112,118 @@ require (
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/credentials-go v1.3.1 // indirect
github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51 // indirect
github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837 // indirect
github.com/anchore/fangs v0.0.0-20241014201141-b6e4b3469f10 // indirect
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 // indirect
github.com/anchore/go-logger v0.0.0-20241205183533-4fc29b5832e7 // indirect
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
github.com/anchore/clio v0.0.0-20250715152405-a0fa658e5084 // indirect
github.com/anchore/fangs v0.0.0-20250716230140-94c22408c232 // indirect
github.com/anchore/go-collections v0.0.0-20241211140901-567f400e9a46 // indirect
github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d // indirect
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 // indirect
github.com/anchore/go-lzo v0.1.0 // indirect
github.com/anchore/go-macholibre v0.0.0-20250320151634-807da7ad2331 // indirect
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec // indirect
github.com/anchore/go-struct-converter v0.0.0-20250211213226-cce56d595160 // indirect
github.com/anchore/go-sync v0.0.0-20250714163430-add63db73ad1 // indirect
github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect
github.com/anchore/grype v0.81.0 // indirect
github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f // indirect
github.com/anchore/stereoscope v0.0.11 // indirect
github.com/anchore/syft v1.18.1 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/anchore/grype v0.99.1 // indirect
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 // indirect
github.com/anchore/stereoscope v0.1.9 // indirect
github.com/anchore/syft v1.32.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/anubhav06/copa-grype v1.0.3-alpha.1 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect
github.com/aquasecurity/go-pep440-version v0.0.1 // indirect
github.com/aquasecurity/go-version v0.0.1 // indirect
github.com/armosec/gojay v1.2.17 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go v1.55.6 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.63 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go v1.55.7 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.17 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.34.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.25.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.45.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.33.2 // indirect
github.com/aws/aws-sdk-go-v2/service/eks v1.48.5 // indirect
github.com/aws/aws-sdk-go-v2/service/iam v1.35.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
github.com/aws/smithy-go v1.22.2 // indirect
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect
github.com/aws/smithy-go v1.22.4 // indirect
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.10.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/becheran/wildmatch-go v1.0.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bitnami/go-version v0.0.0-20250505154626-452e8c5ee607 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/bmatcuk/doublestar/v2 v2.0.4 // indirect
github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/boombuler/barcode v1.0.2 // indirect
github.com/briandowns/spinner v1.23.1 // indirect
github.com/briandowns/spinner v1.23.2 // indirect
github.com/buildkite/agent/v3 v3.62.0 // indirect
github.com/buildkite/go-pipeline v0.3.2 // indirect
github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/chainguard-dev/git-urls v1.0.2 // indirect
github.com/charmbracelet/lipgloss v1.0.0 // indirect
github.com/charmbracelet/x/ansi v0.4.5 // indirect
github.com/charmbracelet/colorprofile v0.3.1 // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
github.com/cilium/cilium v1.16.9 // indirect
github.com/cilium/cilium v1.16.17 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.3.8 // indirect
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/containerd/cgroups/v3 v3.0.5 // indirect
github.com/containerd/console v1.0.4 // indirect
github.com/containerd/containerd v1.7.27 // indirect
github.com/containerd/containerd/api v1.8.0 // indirect
github.com/containerd/continuity v0.4.4 // indirect
github.com/containerd/containerd v1.7.29 // indirect
github.com/containerd/containerd/api v1.9.0 // indirect
github.com/containerd/containerd/v2 v2.0.7 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/platforms v1.0.0-rc.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/containers/common v0.63.0 // indirect
github.com/coreos/go-oidc/v3 v3.14.1 // indirect
github.com/cpuguy83/dockercfg v0.3.1 // indirect
github.com/cpuguy83/go-docker v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/cpuguy83/go-docker v0.3.0 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da // indirect
github.com/deitch/magic v0.0.0-20240306090643-c67ab88f10cb // indirect
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/diskfs/go-diskfs v1.7.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/buildx v0.11.2 // indirect
github.com/docker/cli v27.5.0+incompatible // indirect
github.com/docker/buildx v0.21.3 // indirect
github.com/docker/cli v28.3.3+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v28.0.4+incompatible // indirect
github.com/docker/docker v28.3.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/elliotchance/orderedmap v1.5.0 // indirect
github.com/elliotchance/phpserialize v1.4.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
@@ -212,29 +231,31 @@ require (
github.com/enescakir/emoji v1.0.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/f-amaral/go-async v0.3.0 // indirect
github.com/facebookincubator/nvdtools v0.1.5 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fvbommel/sortorder v1.1.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
github.com/github/go-spdx/v2 v2.3.2 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/github/go-spdx/v2 v2.3.3 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/glebarez/sqlite v1.11.0 // indirect
github.com/go-chi/chi v4.1.2+incompatible // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.0 // indirect
github.com/go-git/go-git/v5 v5.13.0 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.16.2 // indirect
github.com/go-gota/gota v0.12.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/errors v0.22.1 // indirect
@@ -247,44 +268,46 @@ require (
github.com/go-openapi/validate v0.24.0 // indirect
github.com/go-piv/piv-go v1.11.0 // indirect
github.com/go-restruct/restruct v1.2.0-alpha // indirect
github.com/go-test/deep v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-yaml v1.9.6 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/gocsaf/csaf/v3 v3.3.0 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/gohugoio/hashstructure v0.5.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/certificate-transparency-go v1.3.1 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-containerregistry v0.20.3 // indirect
github.com/google/go-containerregistry v0.20.6 // indirect
github.com/google/go-github/v55 v55.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/licensecheck v0.3.1 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/gookit/color v1.6.0 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-getter v1.7.6 // indirect
github.com/hashicorp/go-getter v1.7.9 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl/v2 v2.24.0 // indirect
github.com/hhrutter/lzw v1.0.0 // indirect
github.com/hhrutter/tiff v1.0.1 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
@@ -292,6 +315,7 @@ require (
github.com/in-toto/in-toto-golang v0.9.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jedib0t/go-pretty/v6 v6.6.8 // indirect
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
@@ -309,201 +333,220 @@ require (
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f // indirect
github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 // indirect
github.com/knqyf263/go-deb-version v0.0.0-20241115132648-6f4aee6ccd23 // indirect
github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075 // indirect
github.com/knqyf263/go-rpmdb v0.1.1 // indirect
github.com/kubescape/go-git-url v0.0.30 // indirect
github.com/kubescape/rbac-utils v0.0.21-0.20230806101615-07e36f555520 // indirect
github.com/kubescape/regolibrary/v2 v2.0.1 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mackerelio/go-osstat v0.2.5 // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/maruel/natural v1.1.1 // indirect
github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 // indirect
github.com/masahiro331/go-mvn-version v0.0.0-20250131095131-f4974fa13b8a // indirect
github.com/matthyx/go-gitlog v0.0.0-20231005131906-9ffabe3c5bcd // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mholt/archiver/v3 v3.5.1 // indirect
github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 // indirect
github.com/mholt/archives v0.1.5 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mikefarah/yq/v4 v4.29.1 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/minlz v1.0.1 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/buildkit v0.12.5 // indirect
github.com/moby/buildkit v0.21.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/signal v0.7.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/mozillazg/docker-credential-acr-helper v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1 // indirect
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/oleiade/reflections v1.0.1 // indirect
github.com/olekukonko/tablewriter v0.0.6-0.20230417144759-edd1a71a5576 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect
github.com/olekukonko/tablewriter v1.0.9 // indirect
github.com/olvrng/ujson v1.1.0 // indirect
github.com/open-policy-agent/opa v1.3.0 // indirect
github.com/open-policy-agent/opa v1.4.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runtime-spec v1.2.1 // indirect
github.com/opencontainers/selinux v1.12.0 // indirect
github.com/opencontainers/selinux v1.13.0 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/openvex/go-vex v0.2.5 // indirect
github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 // indirect
github.com/owenrumney/go-sarif/v2 v2.3.0 // indirect
github.com/package-url/packageurl-go v0.1.2 // indirect
github.com/package-url/packageurl-go v0.1.3 // indirect
github.com/pandatix/go-cvss v0.6.2 // indirect
github.com/pborman/indent v1.2.1 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/pdfcpu/pdfcpu v0.9.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/petermattis/goid v0.0.0-20241211131331-93ee7e083c43 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pjbgf/sha1cd v0.4.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/pkg/xattr v0.4.12 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/project-copacetic/copacetic v0.4.1-0.20231017020916-013c118454b8 // indirect
github.com/prometheus/client_golang v1.21.1 // indirect
github.com/project-copacetic/copacetic v0.10.0 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/quay/claircore v1.5.35 // indirect
github.com/quay/claircore/toolkit v1.2.4 // indirect
github.com/quay/zlog v1.1.8 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/saferwall/pe v1.5.6 // indirect
github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/rs/zerolog v1.30.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c // indirect
github.com/sagikazarmark/locafero v0.9.0 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/sasha-s/go-deadlock v0.3.5 // indirect
github.com/sassoftware/go-rpmutils v0.4.0 // indirect
github.com/sassoftware/relic v7.2.1+incompatible // indirect
github.com/schollz/progressbar/v3 v3.13.0 // indirect
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e // indirect
github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d // indirect
github.com/seccomp/libseccomp-golang v0.10.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sigstore/cosign/v2 v2.2.4 // indirect
github.com/sigstore/fulcio v1.6.6 // indirect
github.com/sigstore/protobuf-specs v0.4.1 // indirect
github.com/sigstore/rekor v1.3.10 // indirect
github.com/sigstore/sigstore v1.9.3 // indirect
github.com/sigstore/sigstore v1.9.5 // indirect
github.com/sigstore/timestamp-authority v1.2.2 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spdx/gordf v0.0.0-20250128162952-000978ccd6fb // indirect
github.com/spdx/tools-golang v0.5.5 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.9.2 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/spiffe/go-spiffe/v2 v2.4.0 // indirect
github.com/spf13/pflag v1.0.7 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/stripe/stripe-go/v74 v74.30.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/sylabs/sif/v2 v2.21.1 // indirect
github.com/sylabs/squashfs v1.0.4 // indirect
github.com/sylabs/sif/v2 v2.22.0 // indirect
github.com/sylabs/squashfs v1.0.6 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tchap/go-patricia/v2 v2.3.2 // indirect
github.com/thales-e-security/pool v0.0.2 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/theupdateframework/go-tuf v0.7.0 // indirect
github.com/theupdateframework/notary v0.6.1 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb // indirect
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
github.com/tonistiigi/fsutil v0.0.0-20250410151801-5b74a7ad7583 // indirect
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 // indirect
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
github.com/transparency-dev/merkle v0.0.2 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 // indirect
github.com/uptrace/opentelemetry-go-extra/otelzap v0.3.2 // indirect
github.com/uptrace/uptrace-go v1.30.1 // indirect
github.com/uptrace/uptrace-go v1.37.0 // indirect
github.com/vbatts/go-mtree v0.5.4 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/vifraa/gopom v1.0.0 // indirect
github.com/vishvananda/netlink v1.3.1-0.20241022031324-976bd8de7d81 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 // indirect
github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b // indirect
github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/go-gitlab v0.102.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yashtewari/glob-intersection v0.2.0 // indirect
github.com/yl2chen/cidranger v1.0.2 // indirect
github.com/zclconf/go-cty v1.14.4 // indirect
github.com/zeebo/errs v1.3.0 // indirect
github.com/zclconf/go-cty v1.16.3 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.etcd.io/bbolt v1.4.2 // indirect
go.mongodb.org/mongo-driver v1.17.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.44.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/runtime v0.55.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.6.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.41.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.37.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/contrib/instrumentation/runtime v0.62.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.13.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 // indirect
go.opentelemetry.io/otel/log v0.6.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.6.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 // indirect
go.opentelemetry.io/otel/log v0.13.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.13.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.step.sm/crypto v0.60.0 // indirect
go.uber.org/mock v0.5.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/image v0.24.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/term v0.31.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
gonum.org/v1/gonum v0.9.1 // indirect
google.golang.org/api v0.228.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.38.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
gonum.org/v1/gonum v0.12.0 // indirect
google.golang.org/api v0.242.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
@@ -512,38 +555,31 @@ require (
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/gorm v1.25.12 // indirect
helm.sh/helm/v3 v3.17.3 // indirect
k8s.io/api v0.32.3 // indirect
k8s.io/apiextensions-apiserver v0.32.2 // indirect
k8s.io/apiserver v0.32.3 // indirect
k8s.io/component-base v0.32.3 // indirect
gorm.io/gorm v1.30.2 // indirect
helm.sh/helm/v3 v3.18.5 // indirect
k8s.io/api v0.33.3 // indirect
k8s.io/apiextensions-apiserver v0.33.3 // indirect
k8s.io/apiserver v0.33.3 // indirect
k8s.io/cli-runtime v0.33.3 // indirect
k8s.io/component-base v0.33.3 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect
modernc.org/libc v1.61.13 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/kubectl v0.33.3 // indirect
modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/sqlite v1.36.2 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.38.2 // indirect
oras.land/oras-go/v2 v2.6.0 // indirect
sigs.k8s.io/controller-runtime v0.18.4 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/kustomize/api v0.18.0 // indirect
sigs.k8s.io/kustomize/kyaml v0.18.1 // indirect
sigs.k8s.io/kustomize/api v0.19.0 // indirect
sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/release-utils v0.9.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/yaml v1.5.0 // indirect
)
// Using the forked version of tablewriter
replace github.com/olekukonko/tablewriter => github.com/kubescape/tablewriter v0.0.6-0.20231106230230-aac7d2659c94
replace github.com/docker/distribution v2.8.3+incompatible => github.com/docker/distribution v2.8.2+incompatible
replace github.com/docker/docker v27.1.1+incompatible => github.com/docker/docker v26.1.5+incompatible
replace github.com/mholt/archiver/v3 v3.5.1 => github.com/anchore/archiver/v3 v3.5.2
replace github.com/docker/docker => github.com/docker/docker v26.1.5+incompatible
replace github.com/docker/cli => github.com/docker/cli v26.1.0+incompatible
replace github.com/sylabs/squashfs => github.com/sylabs/squashfs v0.6.1

File diff suppressed because it is too large Load Diff

View File

@@ -6,18 +6,27 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"github.com/google/uuid"
"github.com/gorilla/schema"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
utilsapisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
utilsmetav1 "github.com/kubescape/opa-utils/httpserver/meta/v1"
"go.opentelemetry.io/otel/trace"
)
// MetricsQueryParams query params for metrics endpoint
type MetricsQueryParams struct {
// Frameworks is a comma-separated list of frameworks to scan
// Example: "nsa,mitre,cis-v1.10.0"
// If not provided, all available frameworks will be scanned
Frameworks string `schema:"frameworks" json:"frameworks"`
}
// Metrics http listener for prometheus support
func (handler *HTTPHandler) Metrics(w http.ResponseWriter, r *http.Request) {
@@ -25,8 +34,16 @@ func (handler *HTTPHandler) Metrics(w http.ResponseWriter, r *http.Request) {
handler.state.setBusy(scanID)
defer handler.state.setNotBusy(scanID)
// Parse query parameters
metricsQueryParams := &MetricsQueryParams{}
if err := schema.NewDecoder().Decode(metricsQueryParams, r.URL.Query()); err != nil {
w.WriteHeader(http.StatusBadRequest)
handler.writeError(w, fmt.Errorf("failed to parse query params, reason: %s", err.Error()), scanID)
return
}
resultsFile := filepath.Join(OutputDir, scanID)
scanInfo := getPrometheusDefaultScanCommand(scanID, resultsFile)
scanInfo := getPrometheusDefaultScanCommand(scanID, resultsFile, metricsQueryParams.Frameworks)
scanParams := &scanRequestParams{
scanQueryParams: &ScanQueryParams{
@@ -69,19 +86,41 @@ func (handler *HTTPHandler) Metrics(w http.ResponseWriter, r *http.Request) {
w.Write(f)
}
func getPrometheusDefaultScanCommand(scanID, resultsFile string) *cautils.ScanInfo {
func getPrometheusDefaultScanCommand(scanID, resultsFile, frameworksParam string) *cautils.ScanInfo {
scanInfo := defaultScanInfo()
scanInfo.UseArtifactsFrom = getter.DefaultLocalStore // Load files from cache (this will prevent kubescape fom downloading the artifacts every time)
scanInfo.UseArtifactsFrom = getter.DefaultLocalStore // Load files from cache (this will prevent kubescape from downloading the artifacts every time)
scanInfo.Submit = false // do not submit results every scan
scanInfo.Local = true // do not submit results every scan
scanInfo.FrameworkScan = true
scanInfo.HostSensorEnabled.SetBool(false) // disable host scanner
scanInfo.ScanAll = false // do not scan all frameworks
scanInfo.ScanID = scanID // scan ID
scanInfo.FailThreshold = 100 // Do not fail scanning
scanInfo.ComplianceThreshold = 0 // Do not fail scanning
scanInfo.Output = resultsFile // results output
scanInfo.Format = envToString("KS_FORMAT", "prometheus") // default output should be json
scanInfo.SetPolicyIdentifiers(getter.NativeFrameworks, apisv1.KindFramework)
scanInfo.Format = envToString("KS_FORMAT", "prometheus") // default output format is prometheus
// Check if specific frameworks are requested via query parameter
if frameworksParam != "" {
// Scan specific frameworks (comma-separated list)
frameworks := splitAndTrim(frameworksParam, ",")
scanInfo.SetPolicyIdentifiers(frameworks, utilsapisv1.KindFramework)
} else {
// Default: scan all available frameworks (including CIS)
scanInfo.ScanAll = true
// Framework identifiers will be set dynamically by the scan process when ScanAll is true
}
return scanInfo
}
// splitAndTrim splits a string by delimiter and trims whitespace from each element
func splitAndTrim(s, sep string) []string {
parts := strings.Split(s, sep)
result := make([]string, 0, len(parts))
for _, part := range parts {
if trimmed := strings.TrimSpace(part); trimmed != "" {
result = append(result, trimmed)
}
}
return result
}

View File

@@ -9,17 +9,88 @@ import (
)
func TestGetPrometheusDefaultScanCommand(t *testing.T) {
scanID := "1234"
outputFile := filepath.Join(OutputDir, scanID)
scanInfo := getPrometheusDefaultScanCommand(scanID, outputFile)
t.Run("default behavior - scan all frameworks", func(t *testing.T) {
scanID := "1234"
outputFile := filepath.Join(OutputDir, scanID)
scanInfo := getPrometheusDefaultScanCommand(scanID, outputFile, "")
assert.Equal(t, scanID, scanInfo.ScanID)
assert.Equal(t, outputFile, scanInfo.Output)
assert.Equal(t, "prometheus", scanInfo.Format)
assert.False(t, scanInfo.Submit)
assert.True(t, scanInfo.Local)
assert.True(t, scanInfo.FrameworkScan)
assert.False(t, scanInfo.ScanAll)
assert.False(t, scanInfo.HostSensorEnabled.GetBool())
assert.Equal(t, getter.DefaultLocalStore, scanInfo.UseArtifactsFrom)
assert.Equal(t, scanID, scanInfo.ScanID)
assert.Equal(t, outputFile, scanInfo.Output)
assert.Equal(t, "prometheus", scanInfo.Format)
assert.False(t, scanInfo.Submit)
assert.True(t, scanInfo.Local)
assert.True(t, scanInfo.FrameworkScan)
assert.True(t, scanInfo.ScanAll) // Scan all available frameworks by default
assert.False(t, scanInfo.HostSensorEnabled.GetBool())
assert.Equal(t, getter.DefaultLocalStore, scanInfo.UseArtifactsFrom)
})
t.Run("specific frameworks via query parameter", func(t *testing.T) {
scanID := "5678"
outputFile := filepath.Join(OutputDir, scanID)
scanInfo := getPrometheusDefaultScanCommand(scanID, outputFile, "nsa,mitre,cis-v1.10.0")
assert.Equal(t, scanID, scanInfo.ScanID)
assert.Equal(t, outputFile, scanInfo.Output)
assert.Equal(t, "prometheus", scanInfo.Format)
assert.False(t, scanInfo.Submit)
assert.True(t, scanInfo.Local)
assert.True(t, scanInfo.FrameworkScan)
assert.False(t, scanInfo.ScanAll) // Don't scan all when specific frameworks are set
assert.False(t, scanInfo.HostSensorEnabled.GetBool())
assert.Equal(t, getter.DefaultLocalStore, scanInfo.UseArtifactsFrom)
// Verify specific frameworks are set
assert.Len(t, scanInfo.PolicyIdentifier, 3)
assert.Equal(t, "nsa", scanInfo.PolicyIdentifier[0].Identifier)
assert.Equal(t, "mitre", scanInfo.PolicyIdentifier[1].Identifier)
assert.Equal(t, "cis-v1.10.0", scanInfo.PolicyIdentifier[2].Identifier)
})
}
func TestSplitAndTrim(t *testing.T) {
tests := []struct {
name string
input string
sep string
expected []string
}{
{
name: "comma-separated with spaces",
input: "nsa, mitre, cis-v1.10.0",
sep: ",",
expected: []string{"nsa", "mitre", "cis-v1.10.0"},
},
{
name: "no spaces",
input: "nsa,mitre,cis-v1.10.0",
sep: ",",
expected: []string{"nsa", "mitre", "cis-v1.10.0"},
},
{
name: "single item",
input: "nsa",
sep: ",",
expected: []string{"nsa"},
},
{
name: "empty string",
input: "",
sep: ",",
expected: []string{},
},
{
name: "whitespace only",
input: " , , ",
sep: ",",
expected: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := splitAndTrim(tt.input, tt.sep)
assert.Equal(t, tt.expected, result)
})
}
}

View File

@@ -83,14 +83,15 @@ func scan(ctx context.Context, scanInfo *cautils.ScanInfo, scanID string) (*repo
if err != nil {
return nil, writeScanErrorToFile(err, scanID)
}
if err := result.HandleResults(ctx); err != nil {
if err := result.HandleResults(ctx, scanInfo); err != nil {
return nil, err
}
storage := storage.GetStorage()
if storage != nil {
store := storage.GetStorage()
// do not store results locally when we are sending them
if store != nil && config.GetAccount() == "" {
pr := result.GetResults()
if err := storage.StorePostureReportResults(ctx, pr); err != nil {
if err := store.StorePostureReportResults(ctx, pr); err != nil {
return nil, err
}
} else {

View File

@@ -61,10 +61,6 @@ func main() {
}
func initializeStorage(clusterName string, cfg config.Config) {
if !cfg.ContinuousPostureScan {
logger.L().Debug("continuous posture scan - skipping storage initialization")
return
}
namespace := getNamespace(cfg)
logger.L().Debug("initializing storage", helpers.String("namespace", namespace))
@@ -74,7 +70,7 @@ func initializeStorage(clusterName string, cfg config.Config) {
logger.L().Fatal("storage initialization error", helpers.Error(err))
}
s, err := storage.NewAPIServerStorage(clusterName, namespace, ksClient)
s, err := storage.NewAPIServerStorage(clusterName, namespace, ksClient, cfg.ContinuousPostureScan)
if err != nil {
logger.L().Fatal("storage initialization error", helpers.Error(err))
}

View File

@@ -30,15 +30,17 @@ var storageInstance *APIServerStore
type PostureRepository interface {
GetWorkloadConfigurationScanResult(ctx context.Context, name, namespace string) (*v1beta1.WorkloadConfigurationScan, error)
StoreWorkloadConfigurationScanResult(ctx context.Context, report *v2.PostureReport, result *resourcesresults.Result) (*v1beta1.WorkloadConfigurationScan, error)
BuildWorkloadConfigurationScan(ctx context.Context, report *v2.PostureReport, result *resourcesresults.Result) (*v1beta1.WorkloadConfigurationScan, error)
StoreWorkloadConfigurationScanResult(ctx context.Context, manifest *v1beta1.WorkloadConfigurationScan) error
StoreWorkloadConfigurationScanResultSummary(ctx context.Context, workloadScan *v1beta1.WorkloadConfigurationScan) (*v1beta1.WorkloadConfigurationScanSummary, error)
}
// APIServerStore implements both PostureRepository with in-cluster storage (apiserver) to be used for production
type APIServerStore struct {
StorageClient spdxv1beta1.SpdxV1beta1Interface
clusterName string
namespace string
StorageClient spdxv1beta1.SpdxV1beta1Interface
clusterName string
namespace string
continuousPostureScan bool
}
var _ PostureRepository = (*APIServerStore)(nil)
@@ -52,22 +54,31 @@ func GetStorage() *APIServerStore {
}
// NewAPIServerStorage initializes the APIServerStore struct
func NewAPIServerStorage(clusterName string, namespace string, ksClient spdxv1beta1.SpdxV1beta1Interface) (*APIServerStore, error) {
func NewAPIServerStorage(clusterName string, namespace string, ksClient spdxv1beta1.SpdxV1beta1Interface, continuousPostureScan bool) (*APIServerStore, error) {
return &APIServerStore{
StorageClient: ksClient,
clusterName: clusterName,
namespace: namespace,
StorageClient: ksClient,
clusterName: clusterName,
namespace: namespace,
continuousPostureScan: continuousPostureScan,
}, nil
}
func (a *APIServerStore) StorePostureReportResults(ctx context.Context, pr *v2.PostureReport) error {
for i := range pr.Results {
detailedObj, err := a.StoreWorkloadConfigurationScanResult(ctx, pr, &pr.Results[i])
workloadScan, err := a.BuildWorkloadConfigurationScan(ctx, pr, &pr.Results[i])
if err != nil {
return err
}
if _, err := a.StoreWorkloadConfigurationScanResultSummary(ctx, detailedObj); err != nil {
// Only store full WorkloadConfigurationScan when continuousPostureScan is enabled
if a.continuousPostureScan {
if err := a.StoreWorkloadConfigurationScanResult(ctx, workloadScan); err != nil {
return err
}
}
// Always store summaries for headlamp plugin
if _, err := a.StoreWorkloadConfigurationScanResultSummary(ctx, workloadScan); err != nil {
return err
}
@@ -135,7 +146,8 @@ func (a *APIServerStore) getResourceNamespace(resource workloadinterface.IMetada
return resource.GetNamespace()
}
func (a *APIServerStore) StoreWorkloadConfigurationScanResult(ctx context.Context, report *v2.PostureReport, result *resourcesresults.Result) (*v1beta1.WorkloadConfigurationScan, error) {
// BuildWorkloadConfigurationScan builds a WorkloadConfigurationScan manifest without storing it
func (a *APIServerStore) BuildWorkloadConfigurationScan(ctx context.Context, report *v2.PostureReport, result *resourcesresults.Result) (*v1beta1.WorkloadConfigurationScan, error) {
resource, err := findResourceInReport(result.ResourceID, report)
if err != nil {
return nil, err
@@ -166,7 +178,13 @@ func (a *APIServerStore) StoreWorkloadConfigurationScanResult(ctx context.Contex
},
}
_, err = a.StorageClient.WorkloadConfigurationScans(namespace).Create(context.Background(), &manifest, metav1.CreateOptions{})
return &manifest, nil
}
// StoreWorkloadConfigurationScanResult stores a WorkloadConfigurationScan manifest
func (a *APIServerStore) StoreWorkloadConfigurationScanResult(ctx context.Context, manifest *v1beta1.WorkloadConfigurationScan) error {
namespace := manifest.GetNamespace()
_, err := a.StorageClient.WorkloadConfigurationScans(namespace).Create(context.Background(), manifest, metav1.CreateOptions{})
switch {
case errors.IsAlreadyExists(err):
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
@@ -192,11 +210,11 @@ func (a *APIServerStore) StoreWorkloadConfigurationScanResult(ctx context.Contex
}
case err != nil:
logger.L().Ctx(ctx).Warning("failed to store WorkloadConfigurationScan manifest in storage", helpers.Error(err), helpers.String("name", manifest.Name))
return nil, err
return err
default:
logger.L().Debug("stored WorkloadConfigurationScan manifest in storage", helpers.String("name", manifest.Name))
}
return &manifest, nil
return nil
}
func mergeWorkloadConfigurationScanSpec(existingSpec v1beta1.WorkloadConfigurationScanSpec, newSpec v1beta1.WorkloadConfigurationScanSpec) v1beta1.WorkloadConfigurationScanSpec {

View File

@@ -96,5 +96,6 @@ if ! kubectl get nodes &> /dev/null; then
fi
echo -e "\033[0;37;40m"
echo -e "\033[0;37;32mExecuting Kubescape."
$KUBESCAPE_EXEC scan
echo -e "\033[0;37;32mFinished Installation.\n"
$KUBESCAPE_EXEC version
echo -e "\033[0;37;35m\nUsage: $ kubescape scan"

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