mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
Compare commits
127 Commits
v3.0.35-rc
...
v3.0.46
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60d7276de3 | ||
|
|
c05427ff38 | ||
|
|
3e245da02b | ||
|
|
cc7aae470f | ||
|
|
8d59a6074e | ||
|
|
1f7dd6e5f5 | ||
|
|
bf5ca3c1f0 | ||
|
|
a8574c61ea | ||
|
|
6f9c0ae85f | ||
|
|
be2c74e48a | ||
|
|
68da73855f | ||
|
|
5b3f2d0ff9 | ||
|
|
02637c7a8e | ||
|
|
7d5b374f9d | ||
|
|
1dd6d7a1b3 | ||
|
|
6b80b85555 | ||
|
|
d88bc067e2 | ||
|
|
ba78527c80 | ||
|
|
4c8692bf8c | ||
|
|
742e3bb67f | ||
|
|
a39f36c9fb | ||
|
|
9bc29032e1 | ||
|
|
a4a290a3ce | ||
|
|
379a3fbc27 | ||
|
|
a46098c034 | ||
|
|
440f39ba3e | ||
|
|
b6a4e282f9 | ||
|
|
8deff34d12 | ||
|
|
acc9b54b2b | ||
|
|
1ffa29fbaa | ||
|
|
2ae30a8162 | ||
|
|
0ca5378c6b | ||
|
|
f51a1281f7 | ||
|
|
5469d8bc04 | ||
|
|
bd7c0c580e | ||
|
|
154fec1385 | ||
|
|
5c2275e32a | ||
|
|
2da4736201 | ||
|
|
aefafeae6f | ||
|
|
1772b38b8c | ||
|
|
c844f42208 | ||
|
|
b86d051998 | ||
|
|
aaa8d1ed35 | ||
|
|
441d16aa08 | ||
|
|
b33f1c8cc7 | ||
|
|
4929af510e | ||
|
|
f28bb11c55 | ||
|
|
8bff4a02e1 | ||
|
|
33d1e018ec | ||
|
|
0c74599314 | ||
|
|
c23b85cc84 | ||
|
|
aab10d14a2 | ||
|
|
2fcbe54e4e | ||
|
|
078d154ab8 | ||
|
|
cc9dcf827e | ||
|
|
76943d05fb | ||
|
|
621ac111cb | ||
|
|
3f80bce811 | ||
|
|
cc6895fc50 | ||
|
|
7d9d8e4b59 | ||
|
|
f8d4bf515d | ||
|
|
774ebe4a5f | ||
|
|
45a07a8046 | ||
|
|
ff96edae4d | ||
|
|
34b82cad27 | ||
|
|
1a4c979ab8 | ||
|
|
3481af4a5b | ||
|
|
71dc7a702c | ||
|
|
6d92389285 | ||
|
|
bd0be45c0b | ||
|
|
7ebf078d0c | ||
|
|
1bd729cf83 | ||
|
|
88b9b22bca | ||
|
|
182162d521 | ||
|
|
1c02191bb1 | ||
|
|
ca66ccb33d | ||
|
|
07eda20b88 | ||
|
|
108c84d97d | ||
|
|
35e7fa2b94 | ||
|
|
abb7917b29 | ||
|
|
31ba56a0cf | ||
|
|
b3efe4d003 | ||
|
|
5faade2b66 | ||
|
|
79207f66be | ||
|
|
af39f9a7ef | ||
|
|
482b7c1f67 | ||
|
|
82e2fd0be2 | ||
|
|
6eec751027 | ||
|
|
4a6480c8b4 | ||
|
|
a51bfa4c3e | ||
|
|
2a48af3c17 | ||
|
|
ffeb4577e3 | ||
|
|
b5c7422355 | ||
|
|
e41b5d77a0 | ||
|
|
5afaae8847 | ||
|
|
011fc0689d | ||
|
|
db30020c95 | ||
|
|
c5341a356b | ||
|
|
85a7f57373 | ||
|
|
cd9ebdf08f | ||
|
|
bc602a78ab | ||
|
|
a2361fd155 | ||
|
|
aa8d41fc2e | ||
|
|
5bd4beb41f | ||
|
|
dbf21dee37 | ||
|
|
be49d9b7be | ||
|
|
7a5699fba3 | ||
|
|
1f8afecea8 | ||
|
|
3ebb1d749e | ||
|
|
f80c9d947d | ||
|
|
03b76ff4aa | ||
|
|
01531b6276 | ||
|
|
aedfe1c4c0 | ||
|
|
d2bedc1d2b | ||
|
|
35288e7b85 | ||
|
|
cd046fa695 | ||
|
|
407b8be08f | ||
|
|
b211fe9148 | ||
|
|
525e51d68e | ||
|
|
daabd6c81a | ||
|
|
5b351d5eec | ||
|
|
a5b607ae2e | ||
|
|
fec51b00ba | ||
|
|
4f9809eec1 | ||
|
|
c0c25c3430 | ||
|
|
6ed3e408be | ||
|
|
6042818a71 |
4
.github/workflows/00-pr-scanner.yaml
vendored
4
.github/workflows/00-pr-scanner.yaml
vendored
@@ -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
|
||||
|
||||
11
.github/workflows/02-release.yaml
vendored
11
.github/workflows/02-release.yaml
vendored
@@ -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
|
||||
|
||||
14
.github/workflows/a-pr-scanner.yaml
vendored
14
.github/workflows/a-pr-scanner.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
2
.github/workflows/build-image.yaml
vendored
2
.github/workflows/build-image.yaml
vendored
@@ -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
|
||||
|
||||
1
.github/workflows/d-publish-image.yaml
vendored
1
.github/workflows/d-publish-image.yaml
vendored
@@ -7,6 +7,7 @@ permissions:
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
models: read
|
||||
packages: read
|
||||
pages: read
|
||||
pull-requests: read
|
||||
|
||||
2
.github/workflows/e-post-release.yaml
vendored
2
.github/workflows/e-post-release.yaml
vendored
@@ -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
4
.gitignore
vendored
@@ -11,3 +11,7 @@ ca.srl
|
||||
ks
|
||||
|
||||
dist/
|
||||
|
||||
# Test output files
|
||||
customFilename.pdf
|
||||
customFilename.xml
|
||||
|
||||
@@ -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
471
README.md
@@ -8,6 +8,7 @@
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fkubescape%2Fkubescape?ref=badge_shield&issueType=license)
|
||||
[](https://www.bestpractices.dev/projects/6944)
|
||||
[](https://securityscorecards.dev/viewer/?uri=github.com/kubescape/kubescape)
|
||||
[](https://kubescape.io/docs/)
|
||||
[](https://github.com/kubescape/kubescape/stargazers)
|
||||
[](https://twitter.com/kubescape)
|
||||
[](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:
|
||||
|
||||

|
||||
```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>
|
||||
@@ -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/
|
||||
|
||||
263
build/README.md
263
build/README.md
@@ -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)
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -26,7 +26,7 @@ var (
|
||||
%[1]s list controls
|
||||
|
||||
Control documentation:
|
||||
https://hub.armosec.io/docs/controls
|
||||
https://kubescape.io/docs/controls/
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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: ¶mAction,
|
||||
}
|
||||
}
|
||||
// Marshal the policy binding to YAML
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestGetVersionCmd(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Undefined Build Number",
|
||||
buildNumber: "",
|
||||
buildNumber: "unknown",
|
||||
want: "Your current version is: unknown\n",
|
||||
},
|
||||
{
|
||||
|
||||
250
core/README.md
250
core/README.md
@@ -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)
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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 != ""
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ spec:
|
||||
allowPrivilegeEscalation: true
|
||||
privileged: true
|
||||
readOnlyRootFilesystem: true
|
||||
procMount: Unmasked
|
||||
ports:
|
||||
- name: scanner # Do not change port name
|
||||
containerPort: 7888
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,7 +37,6 @@ spec:
|
||||
allowPrivilegeEscalation: true
|
||||
privileged: true
|
||||
readOnlyRootFilesystem: true
|
||||
procMount: Unmasked
|
||||
ports:
|
||||
- name: scanner # Do not change port name
|
||||
containerPort: 7888
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"}},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
97
docs/README.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Kubescape Documentation
|
||||
|
||||
Welcome to the Kubescape documentation. This directory contains detailed guides and references for using Kubescape.
|
||||
|
||||
[](https://kubescape.io/docs/)
|
||||
[](https://github.com/kubescape/kubescape/blob/master/LICENSE)
|
||||
[](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) |
|
||||
@@ -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
581
docs/cli-reference.md
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
[](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
|
||||
[](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
259
docs/mcp-server.md
Normal 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
528
docs/troubleshooting.md
Normal 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`)
|
||||
@@ -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/)
|
||||
@@ -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.
|
||||
@@ -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
469
go.mod
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
1755
httphandler/go.sum
1755
httphandler/go.sum
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user