mirror of
https://github.com/kubescape/kubescape.git
synced 2026-03-03 10:10:36 +00:00
Compare commits
188 Commits
v3.0.23-rc
...
v3.0.44-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
b37c20aed9 | ||
|
|
3de8204c43 | ||
|
|
d5bd3708b8 | ||
|
|
2bd686131e | ||
|
|
1ea4e0c304 | ||
|
|
b3251306d0 | ||
|
|
91ecdaba4e | ||
|
|
fa05dcd00d | ||
|
|
1c2c928732 | ||
|
|
69ac490006 | ||
|
|
b67b9f3af2 | ||
|
|
628ed4a374 | ||
|
|
a6fe34b466 | ||
|
|
2e9406d96a | ||
|
|
6b1bf07f7f | ||
|
|
318c2c7ae6 | ||
|
|
45f60b6fe0 | ||
|
|
20557bc721 | ||
|
|
d8bfb27bc3 | ||
|
|
9776691816 | ||
|
|
80e419df24 | ||
|
|
143f831f5b | ||
|
|
a4897304e8 | ||
|
|
80d1165e2c | ||
|
|
31ed7d5160 | ||
|
|
d62e9ce207 | ||
|
|
3b10443ff5 | ||
|
|
218e3914b1 | ||
|
|
2b07fbe782 | ||
|
|
0655d0496b | ||
|
|
02bf31fbc9 | ||
|
|
4d8a3f51e3 | ||
|
|
43d29f7b8b | ||
|
|
ee0d4cba98 | ||
|
|
d860c0234a | ||
|
|
f13ded61bf | ||
|
|
fe8fc700f4 | ||
|
|
47b670637b | ||
|
|
97c83a652b | ||
|
|
a089831720 | ||
|
|
e4f1720a0c | ||
|
|
233eb2134c | ||
|
|
06da926455 | ||
|
|
37f6193fe0 | ||
|
|
27ac036b7d | ||
|
|
321d335b39 | ||
|
|
91b7d8fc2b | ||
|
|
2b28911db0 | ||
|
|
667e5e8258 | ||
|
|
728b341048 | ||
|
|
75b295d579 | ||
|
|
75298eabf2 | ||
|
|
2458f2ceb9 | ||
|
|
f57948ad97 | ||
|
|
d0befc5f16 | ||
|
|
5d4bd2e94e | ||
|
|
ae37fdc295 | ||
|
|
3dd95ff3a3 | ||
|
|
daadb5b804 | ||
|
|
d250017faf | ||
|
|
835bcbeb12 | ||
|
|
2e4f7c4477 | ||
|
|
66bf93eb0c | ||
|
|
3a036ed0e3 | ||
|
|
fe7dad4560 | ||
|
|
fb36b09f3a | ||
|
|
e71b0c75a9 | ||
|
|
d615099ce1 | ||
|
|
f265b91939 | ||
|
|
825694ade1 | ||
|
|
979a30aea7 | ||
|
|
39c4aa4faa | ||
|
|
475b672a7a | ||
|
|
815c87b532 | ||
|
|
82120f9d31 | ||
|
|
0545818f82 | ||
|
|
046da1940c | ||
|
|
a31154897f | ||
|
|
199c57be30 | ||
|
|
7d55c79f11 | ||
|
|
ee76364371 | ||
|
|
4f2c7ac1de | ||
|
|
00340827be | ||
|
|
708fe64240 | ||
|
|
8985bbe3a9 | ||
|
|
1ffca5648e | ||
|
|
76b1ecb022 | ||
|
|
fc69a3692e | ||
|
|
e159458129 | ||
|
|
b259f117ff | ||
|
|
13cf34bffd | ||
|
|
0300fee38b | ||
|
|
d61d641e81 | ||
|
|
2added0f7c | ||
|
|
b6f6573ed8 | ||
|
|
4215771134 | ||
|
|
fd37446e1b | ||
|
|
351498aac5 | ||
|
|
2005010568 | ||
|
|
e16c4cc9b4 | ||
|
|
544ba9831a | ||
|
|
b6c919feb1 |
5
.github/workflows/00-pr-scanner.yaml
vendored
5
.github/workflows/00-pr-scanner.yaml
vendored
@@ -1,6 +1,7 @@
|
||||
name: 00-pr_scanner
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths-ignore:
|
||||
@@ -26,6 +27,7 @@ jobs:
|
||||
deployments: read
|
||||
id-token: write
|
||||
issues: read
|
||||
models: read
|
||||
discussions: read
|
||||
packages: read
|
||||
pages: read
|
||||
@@ -53,6 +55,7 @@ jobs:
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
models: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
@@ -65,7 +68,7 @@ jobs:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.21"
|
||||
GO_VERSION: "1.25"
|
||||
RELEASE: "latest"
|
||||
CLIENT: test
|
||||
secrets: inherit
|
||||
|
||||
10
.github/workflows/02-release.yaml
vendored
10
.github/workflows/02-release.yaml
vendored
@@ -8,7 +8,7 @@ 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
|
||||
@@ -23,6 +23,7 @@ jobs:
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
models: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
@@ -37,7 +38,7 @@ jobs:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.21"
|
||||
GO_VERSION: "1.25"
|
||||
RELEASE: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
CLIENT: release
|
||||
secrets: inherit
|
||||
@@ -50,6 +51,7 @@ jobs:
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
models: read
|
||||
packages: read
|
||||
pages: read
|
||||
pull-requests: read
|
||||
@@ -72,6 +74,7 @@ jobs:
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
models: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
@@ -97,6 +100,7 @@ jobs:
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
models: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
@@ -106,7 +110,7 @@ jobs:
|
||||
attestations: read
|
||||
contents: write
|
||||
uses: ./.github/workflows/e-post-release.yaml
|
||||
needs: [publish-image]
|
||||
needs: [retag, publish-image]
|
||||
with:
|
||||
TAG: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
secrets: inherit
|
||||
|
||||
24
.github/workflows/a-pr-scanner.yaml
vendored
24
.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
|
||||
@@ -39,7 +39,6 @@ jobs:
|
||||
name: Installing go
|
||||
with:
|
||||
go-version: ${{ inputs.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Test core pkg
|
||||
run: ${{ env.DOCKER_CMD }} go test -v ./...
|
||||
@@ -49,10 +48,10 @@ 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@v5
|
||||
- uses: goreleaser/goreleaser-action@v6
|
||||
name: Build
|
||||
with:
|
||||
distribution: goreleaser
|
||||
@@ -71,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:
|
||||
@@ -93,8 +90,7 @@ jobs:
|
||||
- uses: actions/setup-go@v4
|
||||
name: Installing go
|
||||
with:
|
||||
go-version: '1.21'
|
||||
cache: true
|
||||
go-version: "1.25"
|
||||
- name: Scanning - Forbidden Licenses (go-licenses)
|
||||
id: licenses-scan
|
||||
continue-on-error: true
|
||||
@@ -107,7 +103,7 @@ jobs:
|
||||
if: ${{ env.GITGUARDIAN_API_KEY }}
|
||||
continue-on-error: true
|
||||
id: credentials-scan
|
||||
uses: GitGuardian/ggshield-action@4ab2994172fadab959240525e6b833d9ae3aca61 # ratchet:GitGuardian/ggshield-action@master
|
||||
uses: GitGuardian/ggshield-action@master
|
||||
with:
|
||||
args: -v --all-policies
|
||||
env:
|
||||
@@ -120,7 +116,7 @@ jobs:
|
||||
if: ${{ env.SNYK_TOKEN }}
|
||||
id: vulnerabilities-scan
|
||||
continue-on-error: true
|
||||
uses: snyk/actions/golang@806182742461562b67788a64410098c9d9b96adb # ratchet:snyk/actions/golang@master
|
||||
uses: snyk/actions/golang@master
|
||||
with:
|
||||
command: test --all-projects
|
||||
env:
|
||||
@@ -142,7 +138,7 @@ jobs:
|
||||
|
||||
- name: Comment results to PR
|
||||
continue-on-error: true # Warning: This might break opening PRs from forks
|
||||
uses: peter-evans/create-or-update-comment@5adcb0bb0f9fb3f95ef05400558bdb3f329ee808 # ratchet:peter-evans/create-or-update-comment@v2.1.0
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
|
||||
@@ -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
|
||||
@@ -163,7 +163,6 @@ jobs:
|
||||
name: Installing go
|
||||
with:
|
||||
go-version: ${{ inputs.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: (debug) Step 3 - Check disk space before build
|
||||
run: df -h
|
||||
@@ -182,13 +181,13 @@ jobs:
|
||||
- name: (debug) Step 5 - Check disk space before setting up Syft
|
||||
run: df -h
|
||||
|
||||
- uses: anchore/sbom-action/download-syft@v0.15.2
|
||||
- uses: anchore/sbom-action/download-syft@v0
|
||||
name: Setup Syft
|
||||
|
||||
- name: (debug) Step 6 - Check disk space before goreleaser
|
||||
run: df -h
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v5
|
||||
- uses: goreleaser/goreleaser-action@v6
|
||||
name: Build
|
||||
with:
|
||||
distribution: goreleaser
|
||||
@@ -224,11 +223,11 @@ jobs:
|
||||
- name: (debug) Step 9 - Check disk space before uploading artifacts
|
||||
run: df -h
|
||||
|
||||
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # ratchet:actions/upload-artifact@v3.1.1
|
||||
- uses: actions/upload-artifact@v4
|
||||
name: Upload artifacts
|
||||
with:
|
||||
name: kubescape
|
||||
path: dist/kubescape*
|
||||
path: dist/*
|
||||
if-no-files-found: error
|
||||
|
||||
- name: (debug) Step 10 - Check disk space after uploading artifacts
|
||||
@@ -249,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",
|
||||
@@ -290,7 +289,7 @@ jobs:
|
||||
if: ${{ (needs.wf-preparation.outputs.is-secret-set == 'true') && (always() && (contains(needs.*.result, 'success') || contains(needs.*.result, 'skipped')) && !(contains(needs.*.result, 'failure')) && !(contains(needs.*.result, 'cancelled'))) }}
|
||||
runs-on: ubuntu-latest # This cannot change
|
||||
steps:
|
||||
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3.0.2
|
||||
- uses: actions/download-artifact@v4
|
||||
id: download-artifact
|
||||
with:
|
||||
name: kubescape
|
||||
@@ -307,9 +306,9 @@ jobs:
|
||||
repository: armosec/system-tests
|
||||
path: .
|
||||
|
||||
- uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # ratchet:actions/setup-python@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.8.13'
|
||||
python-version: '3.9'
|
||||
cache: 'pip'
|
||||
|
||||
- name: create env
|
||||
@@ -352,7 +351,7 @@ jobs:
|
||||
deactivate
|
||||
|
||||
- name: Test Report
|
||||
uses: mikepenz/action-junit-report@6e9933f4a97f4d2b99acef4d7b97924466037882 # ratchet:mikepenz/action-junit-report@v3.6.1
|
||||
uses: mikepenz/action-junit-report@v5
|
||||
if: always() # always run even if the previous step fails
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
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.21"
|
||||
GO_VERSION: "1.25"
|
||||
REQUIRED_TESTS: '[]'
|
||||
COSIGN: ${{ inputs.CO_SIGN }}
|
||||
HELM_E2E_TEST: false
|
||||
|
||||
59
.github/workflows/c-create-release.yaml
vendored
59
.github/workflows/c-create-release.yaml
vendored
@@ -27,14 +27,15 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3.0.2
|
||||
- uses: actions/download-artifact@v4
|
||||
id: download-artifact
|
||||
with:
|
||||
name: kubescape
|
||||
path: .
|
||||
|
||||
# TODO: kubescape-windows-latest is deprecated and should be removed
|
||||
- name: Get kubescape.exe from kubescape-windows-latest.exe
|
||||
run: cp ${{steps.download-artifact.outputs.download-path}}/kubescape/kubescape-${{ env.WINDOWS_OS }}.exe ${{steps.download-artifact.outputs.download-path}}/kubescape/kubescape.exe
|
||||
run: cp ${{steps.download-artifact.outputs.download-path}}/kubescape-${{ env.WINDOWS_OS }}.exe ${{steps.download-artifact.outputs.download-path}}/kubescape.exe
|
||||
|
||||
- name: Set release token
|
||||
id: set-token
|
||||
@@ -50,7 +51,7 @@ jobs:
|
||||
find . -type f -print
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
token: ${{ steps.set-token.outputs.token }}
|
||||
name: ${{ inputs.RELEASE_NAME }}
|
||||
@@ -60,32 +61,26 @@ jobs:
|
||||
prerelease: false
|
||||
fail_on_unmatched_files: true
|
||||
files: |
|
||||
./kubescape/kubescape-${{ env.MAC_OS }}
|
||||
./kubescape/kubescape-${{ env.MAC_OS }}.sbom
|
||||
./kubescape/kubescape-${{ env.MAC_OS }}.sha256
|
||||
./kubescape/kubescape-${{ env.MAC_OS }}.tar.gz
|
||||
./kubescape/kubescape-${{ env.UBUNTU_OS }}
|
||||
./kubescape/kubescape-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape/kubescape-${{ env.UBUNTU_OS }}.sha256
|
||||
./kubescape/kubescape-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape/kubescape-${{ env.WINDOWS_OS }}.exe
|
||||
./kubescape/kubescape-${{ env.WINDOWS_OS }}.exe.sbom
|
||||
./kubescape/kubescape-${{ env.WINDOWS_OS }}.exe.sha256
|
||||
./kubescape/kubescape-${{ env.WINDOWS_OS }}.tar.gz
|
||||
./kubescape/kubescape-arm64-${{ env.MAC_OS }}
|
||||
./kubescape/kubescape-arm64-${{ env.MAC_OS }}.sbom
|
||||
./kubescape/kubescape-arm64-${{ env.MAC_OS }}.sha256
|
||||
./kubescape/kubescape-arm64-${{ env.MAC_OS }}.tar.gz
|
||||
./kubescape/kubescape-arm64-${{ env.UBUNTU_OS }}
|
||||
./kubescape/kubescape-arm64-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape/kubescape-arm64-${{ env.UBUNTU_OS }}.sha256
|
||||
./kubescape/kubescape-arm64-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape/kubescape-arm64-${{ env.WINDOWS_OS }}.exe
|
||||
./kubescape/kubescape-arm64-${{ env.WINDOWS_OS }}.exe.sbom
|
||||
./kubescape/kubescape-arm64-${{ env.WINDOWS_OS }}.exe.sha256
|
||||
./kubescape/kubescape-arm64-${{ env.WINDOWS_OS }}.tar.gz
|
||||
./kubescape/kubescape-riscv64-${{ env.UBUNTU_OS }}
|
||||
./kubescape/kubescape-riscv64-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape/kubescape-riscv64-${{ env.UBUNTU_OS }}.sha256
|
||||
./kubescape/kubescape-riscv64-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape/kubescape.exe
|
||||
./checksums.sha256
|
||||
./kubescape-${{ env.MAC_OS }}
|
||||
./kubescape-${{ env.MAC_OS }}.sbom
|
||||
./kubescape-${{ env.MAC_OS }}.tar.gz
|
||||
./kubescape-${{ env.UBUNTU_OS }}
|
||||
./kubescape-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape-${{ env.WINDOWS_OS }}.exe
|
||||
./kubescape-${{ env.WINDOWS_OS }}.exe.sbom
|
||||
./kubescape-${{ env.WINDOWS_OS }}.tar.gz
|
||||
./kubescape-arm64-${{ env.MAC_OS }}
|
||||
./kubescape-arm64-${{ env.MAC_OS }}.sbom
|
||||
./kubescape-arm64-${{ env.MAC_OS }}.tar.gz
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape-arm64-${{ env.WINDOWS_OS }}.exe
|
||||
./kubescape-arm64-${{ env.WINDOWS_OS }}.exe.sbom
|
||||
./kubescape-arm64-${{ env.WINDOWS_OS }}.tar.gz
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape.exe
|
||||
|
||||
13
.github/workflows/d-publish-image.yaml
vendored
13
.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
|
||||
@@ -63,22 +64,21 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # ratchet:docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # ratchet:docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to Quay.io
|
||||
env:
|
||||
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
||||
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3.0.2
|
||||
- uses: actions/download-artifact@v4
|
||||
id: download-artifact
|
||||
with:
|
||||
name: kubescape
|
||||
path: .
|
||||
- name: mv kubescape amd64 binary
|
||||
run: mv ${{steps.download-artifact.outputs.download-path}}/kubescape/kubescape-ubuntu-latest kubescape-amd64-ubuntu-latest
|
||||
- name: mv kubescape arm64 binary
|
||||
run: mv ${{steps.download-artifact.outputs.download-path}}/kubescape/kubescape-arm64-ubuntu-latest kubescape-arm64-ubuntu-latest
|
||||
run: mv kubescape-ubuntu-latest kubescape-amd64-ubuntu-latest
|
||||
- name: chmod +x
|
||||
run: chmod +x -v kubescape-a*
|
||||
- name: Build and push images
|
||||
@@ -106,4 +106,3 @@ jobs:
|
||||
# Verify the image
|
||||
echo "$COSIGN_PUBLIC_KEY" > cosign.pub
|
||||
cosign verify -key cosign.pub ${{ inputs.image_name }}:${{ inputs.image_tag }}
|
||||
|
||||
|
||||
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'
|
||||
|
||||
8
.github/workflows/scorecard.yml
vendored
8
.github/workflows/scorecard.yml
vendored
@@ -32,12 +32,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||
uses: ossf/scorecard-action@v2.4.0
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -67,6 +67,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
4
.github/workflows/z-close-typos-issues.yaml
vendored
4
.github/workflows/z-close-typos-issues.yaml
vendored
@@ -7,14 +7,14 @@ jobs:
|
||||
if: github.event.label.name == 'typo'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: ben-z/actions-comment-on-issue@10be23f9c43ac792663043420fda29dde07e2f0f # ratchet:ben-z/actions-comment-on-issue@1.0.2
|
||||
- uses: ben-z/actions-comment-on-issue@1.0.2
|
||||
with:
|
||||
message: "Hello! :wave:\n\nThis issue is being automatically closed, Please open a PR with a relevant fix."
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto_close_issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: lee-dohm/close-matching-issues@e9e43aad2fa6f06a058cedfd8fb975fd93b56d8f # ratchet:lee-dohm/close-matching-issues@v2
|
||||
- uses: lee-dohm/close-matching-issues@v2
|
||||
with:
|
||||
query: 'label:typo'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -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$
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
|
||||
# The lines bellow are called `modelines`. See `:help modeline`
|
||||
# The lines below are called `modelines`. See `:help modeline`
|
||||
# Feel free to remove those if you don't want/need to use them.
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- go mod tidy
|
||||
|
||||
archives:
|
||||
- id: binaries
|
||||
formats:
|
||||
- binary
|
||||
name_template: >-
|
||||
{{ .Binary }}
|
||||
- id: default
|
||||
formats:
|
||||
- tar.gz
|
||||
name_template: >-
|
||||
{{ .Binary }}
|
||||
|
||||
builds:
|
||||
- goos:
|
||||
- linux
|
||||
@@ -33,15 +46,6 @@ builds:
|
||||
{{- else }}{{ .Os }}{{ end }}-latest
|
||||
no_unique_dist_dir: true
|
||||
|
||||
archives:
|
||||
- format: binary
|
||||
id: binaries
|
||||
name_template: >-
|
||||
{{ .Binary }}
|
||||
- format: tar.gz
|
||||
name_template: >-
|
||||
{{ .Binary }}
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
@@ -50,9 +54,7 @@ changelog:
|
||||
- "^test:"
|
||||
|
||||
checksum:
|
||||
ids:
|
||||
- binaries
|
||||
split: true
|
||||
name_template: "checksums.sha256"
|
||||
|
||||
sboms:
|
||||
- artifacts: binary
|
||||
|
||||
10
README.md
10
README.md
@@ -3,7 +3,7 @@
|
||||
[](https://goreportcard.com/report/github.com/kubescape/kubescape)
|
||||
[](https://gitpod.io/#https://github.com/kubescape/kubescape)
|
||||
[](https://github.com/kubescape/kubescape/blob/master/LICENSE)
|
||||
[](https://landscape.cncf.io/card-mode?project=sandbox&selected=kubescape)
|
||||
[](https://landscape.cncf.io/?item=provisioning--security-compliance--kubescape)
|
||||
[](https://artifacthub.io/packages/search?repo=kubescape)
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fkubescape%2Fkubescape?ref=badge_shield&issueType=license)
|
||||
[](https://www.bestpractices.dev/projects/6944)
|
||||
@@ -26,7 +26,7 @@ Kubescape is an open-source Kubernetes security platform that provides comprehen
|
||||
|
||||
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 was created by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository) and is a [Cloud Native Computing Foundation (CNCF) sandbox project](https://www.cncf.io/sandbox-projects/).
|
||||
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! 😀_
|
||||
|
||||
@@ -112,10 +112,10 @@ Kubescape changes are tracked on the [release](https://github.com/kubescape/kube
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2021-2024, 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. See the [LICENSE](LICENSE) file for details.
|
||||
|
||||
Kubescape is a [Cloud Native Computing Foundation (CNCF) sandbox project](https://www.cncf.io/sandbox-projects/) and was contributed by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository).
|
||||
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/master/other/cncf-sandbox/horizontal/color/cncf-sandbox-horizontal-color.svg" width="300" alt="CNCF Sandbox Project">
|
||||
<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>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:1.23-bookworm AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.25-bookworm AS builder
|
||||
|
||||
ENV GO111MODULE=on CGO_ENABLED=0
|
||||
WORKDIR /work
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
@@ -15,7 +13,7 @@ func getDeleteCmd(ks meta.IKubescape) *cobra.Command {
|
||||
Short: "Delete cached configurations",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := ks.DeleteCachedConfig(context.TODO(), &v1.DeleteConfig{}); err != nil {
|
||||
if err := ks.DeleteCachedConfig(&v1.DeleteConfig{}); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package download
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -74,11 +73,9 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
downloadInfo.Target = args[0]
|
||||
if len(args) >= 2 {
|
||||
|
||||
downloadInfo.Identifier = args[1]
|
||||
|
||||
}
|
||||
if err := ks.Download(context.TODO(), &downloadInfo); err != nil {
|
||||
if err := ks.Download(&downloadInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
@@ -35,7 +34,7 @@ func GetFixCmd(ks meta.IKubescape) *cobra.Command {
|
||||
}
|
||||
fixInfo.ReportFile = args[0]
|
||||
|
||||
return ks.Fix(context.TODO(), &fixInfo)
|
||||
return ks.Fix(&fixInfo)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -27,7 +26,7 @@ var (
|
||||
%[1]s list controls
|
||||
|
||||
Control documentation:
|
||||
https://hub.armosec.io/docs/controls
|
||||
https://kubescape.io/docs/controls/
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
@@ -62,7 +61,7 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
listPolicies.Target = args[0]
|
||||
|
||||
if err := ks.List(context.TODO(), &listPolicies); err != nil {
|
||||
if err := ks.List(&listPolicies); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
|
||||
466
cmd/mcpserver/mcpserver.go
Normal file
466
cmd/mcpserver/mcpserver.go
Normal file
@@ -0,0 +1,466 @@
|
||||
package mcpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
helpersv1 "github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers"
|
||||
"github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1"
|
||||
spdxv1beta1 "github.com/kubescape/storage/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type KubescapeMcpserver struct {
|
||||
s *server.MCPServer
|
||||
ksClient spdxv1beta1.SpdxV1beta1Interface
|
||||
}
|
||||
|
||||
func createVulnerabilityToolsAndResources(ksServer *KubescapeMcpserver) {
|
||||
// Tool to list vulnerability manifests
|
||||
listManifestsTool := mcp.NewTool(
|
||||
"list_vulnerability_manifests",
|
||||
mcp.WithDescription("Discover available vulnerability manifests at image and workload levels"),
|
||||
mcp.WithString("namespace",
|
||||
mcp.Description("Filter by namespace (optional)"),
|
||||
),
|
||||
mcp.WithString("level",
|
||||
mcp.Description("Type of vulnerability manifests to list"),
|
||||
mcp.Enum("image", "workload", "both"),
|
||||
),
|
||||
)
|
||||
|
||||
ksServer.s.AddTool(listManifestsTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return ksServer.CallTool("list_vulnerability_manifests", request.Params.Arguments.(map[string]interface{}))
|
||||
})
|
||||
|
||||
listVulnerabilitiesTool := mcp.NewTool(
|
||||
"list_vulnerabilities_in_manifest",
|
||||
mcp.WithDescription("List all vulnerabilities in a given manifest"),
|
||||
mcp.WithString("namespace",
|
||||
mcp.Description("Filter by namespace (optional)"),
|
||||
),
|
||||
mcp.WithString("manifest_name",
|
||||
mcp.Required(),
|
||||
mcp.Description("Name of the manifest to list vulnerabilities from"),
|
||||
),
|
||||
)
|
||||
|
||||
ksServer.s.AddTool(listVulnerabilitiesTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return ksServer.CallTool("list_vulnerabilities_in_manifest", request.Params.Arguments.(map[string]interface{}))
|
||||
})
|
||||
|
||||
listVulnerabilityMatchesForCVE := mcp.NewTool(
|
||||
"list_vulnerability_matches_for_cve",
|
||||
mcp.WithDescription("List all vulnerability matches for a given CVE in a given manifest"),
|
||||
mcp.WithString("namespace",
|
||||
mcp.Description("Filter by namespace (optional)"),
|
||||
),
|
||||
mcp.WithString("manifest_name",
|
||||
mcp.Required(),
|
||||
mcp.Description("Name of the manifest to list vulnerabilities from"),
|
||||
),
|
||||
mcp.WithString("cve_id",
|
||||
mcp.Required(),
|
||||
mcp.Description("ID of the CVE to list matches for"),
|
||||
),
|
||||
)
|
||||
|
||||
ksServer.s.AddTool(listVulnerabilityMatchesForCVE, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return ksServer.CallTool("list_vulnerability_matches_for_cve", request.Params.Arguments.(map[string]interface{}))
|
||||
})
|
||||
|
||||
vulnerabilityManifestTemplate := mcp.NewResourceTemplate(
|
||||
"kubescape://vulnerability-manifests/{namespace}/{manifest_name}",
|
||||
"Vulnerability Manifest",
|
||||
mcp.WithTemplateDescription("Complete vulnerability manifest either for a specific workload or image. Use 'list_vulnerability_manifests' tool to discover available manifests."),
|
||||
mcp.WithTemplateMIMEType("application/json"),
|
||||
)
|
||||
|
||||
ksServer.s.AddResourceTemplate(vulnerabilityManifestTemplate, ksServer.ReadResource)
|
||||
|
||||
}
|
||||
|
||||
func createConfigurationsToolsAndResources(ksServer *KubescapeMcpserver) {
|
||||
// Tool to list configuration manifests
|
||||
listConfigsTool := mcp.NewTool(
|
||||
"list_configuration_security_scan_manifests",
|
||||
mcp.WithDescription("Discover available security configuration scan results at workload level (this returns a list of manifests, not the scan results themselves, to get the scan results, use the get_configuration_security_scan_manifest tool)"),
|
||||
mcp.WithString("namespace",
|
||||
mcp.Description("Filter by namespace (optional)"),
|
||||
),
|
||||
)
|
||||
|
||||
ksServer.s.AddTool(listConfigsTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return ksServer.CallTool("list_configuration_security_scan_manifests", request.Params.Arguments.(map[string]interface{}))
|
||||
})
|
||||
|
||||
getConfigDetailsTool := mcp.NewTool(
|
||||
"get_configuration_security_scan_manifest",
|
||||
mcp.WithDescription("Get details of a specific security configuration scan result"),
|
||||
mcp.WithString("namespace",
|
||||
mcp.Description("Namespace of the manifest (optional, defaults to 'kubescape')"),
|
||||
),
|
||||
mcp.WithString("manifest_name",
|
||||
mcp.Required(),
|
||||
mcp.Description("Name of the configuration manifest to get details for (get this from the list_configuration_security_scan_manifests tool)"),
|
||||
),
|
||||
)
|
||||
|
||||
ksServer.s.AddTool(getConfigDetailsTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return ksServer.CallTool("get_configuration_security_scan_manifest", request.Params.Arguments.(map[string]interface{}))
|
||||
})
|
||||
|
||||
configManifestTemplate := mcp.NewResourceTemplate(
|
||||
"kubescape://configuration-manifests/{namespace}/{manifest_name}",
|
||||
"Configuration Security Scan Manifest",
|
||||
mcp.WithTemplateDescription("Complete configuration scan manifest for a specific workload. Use 'list_configuration_security_scan_manifests' tool to discover available manifests."),
|
||||
mcp.WithTemplateMIMEType("application/json"),
|
||||
)
|
||||
|
||||
ksServer.s.AddResourceTemplate(configManifestTemplate, ksServer.ReadConfigurationResource)
|
||||
}
|
||||
|
||||
func (ksServer *KubescapeMcpserver) ReadResource(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
|
||||
uri := request.Params.URI
|
||||
// Validate the URI and check if it starts with kubescape://vulnerability-manifests/
|
||||
if !strings.HasPrefix(uri, "kubescape://vulnerability-manifests/") {
|
||||
return nil, fmt.Errorf("invalid URI: %s", uri)
|
||||
}
|
||||
|
||||
// Verify that the URI is either the CVE list or CVE details
|
||||
if !strings.HasSuffix(uri, "/cve_list") && !strings.Contains(uri, "/cve_details/") {
|
||||
return nil, fmt.Errorf("invalid URI: %s", uri)
|
||||
}
|
||||
|
||||
// Split the URI into namespace and manifest name
|
||||
parts := strings.Split(uri, "/")
|
||||
if len(parts) != 4 && len(parts) != 5 {
|
||||
return nil, fmt.Errorf("invalid URI: %s", uri)
|
||||
}
|
||||
|
||||
namespace := parts[1]
|
||||
manifestName := parts[2]
|
||||
cveID := ""
|
||||
if len(parts) == 5 {
|
||||
cveID = parts[3]
|
||||
}
|
||||
|
||||
// Get the vulnerability manifest
|
||||
manifest, err := ksServer.ksClient.VulnerabilityManifests(namespace).Get(ctx, manifestName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get vulnerability manifest: %s", err)
|
||||
}
|
||||
|
||||
var responseJson []byte
|
||||
if cveID == "" {
|
||||
// CVE list
|
||||
var cveList []v1beta1.Vulnerability
|
||||
for _, match := range manifest.Spec.Payload.Matches {
|
||||
cveList = append(cveList, match.Vulnerability)
|
||||
}
|
||||
responseJson, err = json.Marshal(cveList)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal cve list: %s", err)
|
||||
}
|
||||
} else {
|
||||
// CVE details
|
||||
var match []v1beta1.Match
|
||||
for _, m := range manifest.Spec.Payload.Matches {
|
||||
if m.Vulnerability.ID == cveID {
|
||||
match = append(match, m)
|
||||
}
|
||||
}
|
||||
responseJson, err = json.Marshal(match)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal cve details: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return []mcp.ResourceContents{mcp.TextResourceContents{
|
||||
URI: uri,
|
||||
Text: string(responseJson),
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (ksServer *KubescapeMcpserver) ReadConfigurationResource(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
|
||||
uri := request.Params.URI
|
||||
if !strings.HasPrefix(uri, "kubescape://configuration-manifests/") {
|
||||
return nil, fmt.Errorf("invalid URI: %s", uri)
|
||||
}
|
||||
parts := strings.Split(uri[len("kubescape://configuration-manifests/"):], "/")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid URI: %s", uri)
|
||||
}
|
||||
namespace := parts[0]
|
||||
manifestName := parts[1]
|
||||
manifest, err := ksServer.ksClient.WorkloadConfigurationScans(namespace).Get(ctx, manifestName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get configuration manifest: %s", err)
|
||||
}
|
||||
responseJson, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal configuration manifest: %s", err)
|
||||
}
|
||||
return []mcp.ResourceContents{mcp.TextResourceContents{
|
||||
URI: uri,
|
||||
Text: string(responseJson),
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (ksServer *KubescapeMcpserver) CallTool(name string, arguments map[string]interface{}) (*mcp.CallToolResult, error) {
|
||||
switch name {
|
||||
case "list_vulnerability_manifests":
|
||||
//namespace, ok := arguments["namespace"]
|
||||
//if !ok {
|
||||
// namespace = ""
|
||||
//}
|
||||
level, ok := arguments["level"]
|
||||
if !ok {
|
||||
level = "both"
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"vulnerability_manifests": map[string]interface{}{},
|
||||
}
|
||||
|
||||
// Get workload-level manifests
|
||||
labelSelector := ""
|
||||
if level == "workload" {
|
||||
labelSelector = "kubescape.io/context=filtered"
|
||||
} else if level == "image" {
|
||||
labelSelector = "kubescape.io/context=non-filtered"
|
||||
}
|
||||
|
||||
var manifests *v1beta1.VulnerabilityManifestList
|
||||
var err error
|
||||
if labelSelector == "" {
|
||||
manifests, err = ksServer.ksClient.VulnerabilityManifests(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{})
|
||||
} else {
|
||||
manifests, err = ksServer.ksClient.VulnerabilityManifests(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{
|
||||
LabelSelector: labelSelector,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("Found %d manifests", len(manifests.Items))
|
||||
|
||||
vulnerabilityManifests := []map[string]interface{}{}
|
||||
for _, manifest := range manifests.Items {
|
||||
isImageLevel := manifest.Annotations[helpersv1.WlidMetadataKey] == ""
|
||||
manifestMap := map[string]interface{}{
|
||||
"type": "workload",
|
||||
"namespace": manifest.Namespace,
|
||||
"manifest_name": manifest.Name,
|
||||
"image-level": isImageLevel,
|
||||
"workload-level": !isImageLevel,
|
||||
"image-id": manifest.Annotations[helpersv1.ImageIDMetadataKey],
|
||||
"image-tag": manifest.Annotations[helpersv1.ImageTagMetadataKey],
|
||||
"workload-id": manifest.Annotations[helpersv1.WlidMetadataKey],
|
||||
"workload-container-name": manifest.Annotations[helpersv1.ContainerNameMetadataKey],
|
||||
"resource_uri": fmt.Sprintf("kubescape://vulnerability-manifests/%s/%s",
|
||||
manifest.Namespace, manifest.Name),
|
||||
}
|
||||
vulnerabilityManifests = append(vulnerabilityManifests, manifestMap)
|
||||
}
|
||||
result["vulnerability_manifests"].(map[string]interface{})["manifests"] = vulnerabilityManifests
|
||||
|
||||
// Add template information
|
||||
result["available_templates"] = map[string]string{
|
||||
"vulnerability_manifest_cve_list": "kubescape://vulnerability-manifests/{namespace}/{manifest_name}/cve_list",
|
||||
"vulnerability_manifest_cve_details": "kubescape://vulnerability-manifests/{namespace}/{manifest_name}/cve_details/{cve_id}",
|
||||
}
|
||||
|
||||
content, _ := json.Marshal(result)
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(content),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case "list_vulnerabilities_in_manifest":
|
||||
namespace, ok := arguments["namespace"]
|
||||
if !ok {
|
||||
namespace = "kubescape"
|
||||
}
|
||||
manifestName, ok := arguments["manifest_name"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("manifest_name is required")
|
||||
}
|
||||
manifest, err := ksServer.ksClient.VulnerabilityManifests(namespace.(string)).Get(context.Background(), manifestName.(string), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get vulnerability manifest: %s", err)
|
||||
}
|
||||
var cveList []v1beta1.Vulnerability
|
||||
for _, match := range manifest.Spec.Payload.Matches {
|
||||
cveList = append(cveList, match.Vulnerability)
|
||||
}
|
||||
responseJson, err := json.Marshal(cveList)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal cve list: %s", err)
|
||||
}
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(responseJson),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case "list_vulnerability_matches_for_cve":
|
||||
namespace, ok := arguments["namespace"]
|
||||
if !ok {
|
||||
namespace = "kubescape"
|
||||
}
|
||||
manifestName, ok := arguments["manifest_name"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("manifest_name is required")
|
||||
}
|
||||
cveID, ok := arguments["cve_id"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cve_id is required")
|
||||
}
|
||||
manifest, err := ksServer.ksClient.VulnerabilityManifests(namespace.(string)).Get(context.Background(), manifestName.(string), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get vulnerability manifest: %s", err)
|
||||
}
|
||||
var match []v1beta1.Match
|
||||
for _, m := range manifest.Spec.Payload.Matches {
|
||||
if m.Vulnerability.ID == cveID.(string) {
|
||||
match = append(match, m)
|
||||
}
|
||||
}
|
||||
responseJson, err := json.Marshal(match)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal cve details: %s", err)
|
||||
}
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(responseJson),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case "list_configuration_security_scan_manifests":
|
||||
namespace, ok := arguments["namespace"]
|
||||
if !ok {
|
||||
namespace = "kubescape"
|
||||
}
|
||||
manifests, err := ksServer.ksClient.WorkloadConfigurationScans(namespace.(string)).List(context.Background(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("Found %d configuration manifests", len(manifests.Items))
|
||||
configManifests := []map[string]interface{}{}
|
||||
for _, manifest := range manifests.Items {
|
||||
item := map[string]interface{}{
|
||||
"namespace": manifest.Namespace,
|
||||
"manifest_name": manifest.Name,
|
||||
"resource_uri": fmt.Sprintf("kubescape://configuration-manifests/%s/%s", manifest.Namespace, manifest.Name),
|
||||
}
|
||||
configManifests = append(configManifests, item)
|
||||
}
|
||||
result := map[string]interface{}{
|
||||
"configuration_manifests": map[string]interface{}{
|
||||
"manifests": configManifests,
|
||||
},
|
||||
"available_templates": map[string]string{
|
||||
"configuration_manifest_details": "kubescape://configuration-manifests/{namespace}/{manifest_name}",
|
||||
},
|
||||
}
|
||||
content, _ := json.Marshal(result)
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(content),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case "get_configuration_security_scan_manifest":
|
||||
namespace, ok := arguments["namespace"]
|
||||
if !ok {
|
||||
namespace = "kubescape"
|
||||
}
|
||||
manifestName, ok := arguments["manifest_name"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("manifest_name is required")
|
||||
}
|
||||
manifest, err := ksServer.ksClient.WorkloadConfigurationScans(namespace.(string)).Get(context.Background(), manifestName.(string), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get configuration manifest: %s", err)
|
||||
}
|
||||
responseJson, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal configuration manifest: %s", err)
|
||||
}
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(responseJson),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown tool: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func mcpServerEntrypoint() error {
|
||||
logger.L().Info("Starting MCP server...")
|
||||
|
||||
// Create a kubernetes client and verify it's working
|
||||
client, err := CreateKsObjectConnection("default", 10*time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create kubernetes client: %v", err)
|
||||
}
|
||||
|
||||
// Create a new MCP server
|
||||
s := server.NewMCPServer(
|
||||
"Kubescape MCP Server",
|
||||
"0.0.1",
|
||||
server.WithToolCapabilities(false),
|
||||
server.WithRecovery(),
|
||||
)
|
||||
|
||||
ksServer := &KubescapeMcpserver{
|
||||
s: s,
|
||||
ksClient: client,
|
||||
}
|
||||
|
||||
// Creating Kubescape tools and resources
|
||||
|
||||
createVulnerabilityToolsAndResources(ksServer)
|
||||
createConfigurationsToolsAndResources(ksServer)
|
||||
|
||||
// Start the server
|
||||
if err := server.ServeStdio(s); err != nil {
|
||||
return fmt.Errorf("Server error: %v\n", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetMCPServerCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "mcpserver",
|
||||
Short: "Start the Kubescape MCP server",
|
||||
Long: `Start the Kubescape MCP server`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return mcpServerEntrypoint()
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
14
cmd/mcpserver/storage.go
Normal file
14
cmd/mcpserver/storage.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package mcpserver
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/pkg/ksinit"
|
||||
|
||||
spdxv1beta1 "github.com/kubescape/storage/pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1"
|
||||
)
|
||||
|
||||
// CreateKsObjectConnection delegates to the shared ksinit package
|
||||
func CreateKsObjectConnection(namespace string, maxElapsedTime time.Duration) (spdxv1beta1.SpdxV1beta1Interface, error) {
|
||||
return ksinit.CreateKsObjectConnection(namespace, maxElapsedTime)
|
||||
}
|
||||
@@ -1,19 +1,17 @@
|
||||
package patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -29,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]",
|
||||
@@ -50,12 +49,15 @@ func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
results, err := ks.Patch(context.Background(), &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())
|
||||
}
|
||||
|
||||
@@ -77,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
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package patch
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -50,3 +52,18 @@ func TestGetPatchCmdWithNonExistentImage(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
func Test_validateImagePatchInfo_EmptyImage(t *testing.T) {
|
||||
patchInfo := &metav1.PatchInfo{}
|
||||
err := validateImagePatchInfo(patchInfo)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "image tag is required", err.Error())
|
||||
}
|
||||
|
||||
func Test_validateImagePatchInfo_Image(t *testing.T) {
|
||||
patchInfo := &metav1.PatchInfo{
|
||||
Image: "testing",
|
||||
}
|
||||
err := validateImagePatchInfo(patchInfo)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
51
cmd/prerequisites/prerequisites.go
Normal file
51
cmd/prerequisites/prerequisites.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package prerequisites
|
||||
|
||||
import (
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
"github.com/kubescape/sizing-checker/pkg/checks/connectivitycheck"
|
||||
"github.com/kubescape/sizing-checker/pkg/checks/ebpfcheck"
|
||||
"github.com/kubescape/sizing-checker/pkg/checks/pvcheck"
|
||||
"github.com/kubescape/sizing-checker/pkg/checks/sizing"
|
||||
"github.com/kubescape/sizing-checker/pkg/common"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func GetPreReqCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var kubeconfigPath *string
|
||||
|
||||
// preReqCmd represents the prerequisites command
|
||||
preReqCmd := &cobra.Command{
|
||||
Use: "prerequisites",
|
||||
Short: "Check prerequisites for installing Kubescape Operator",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clientSet, inCluster := common.BuildKubeClient(*kubeconfigPath)
|
||||
if clientSet == nil {
|
||||
logger.L().Fatal("Could not create kube client. Exiting.")
|
||||
}
|
||||
|
||||
// 1) Collect cluster data
|
||||
clusterData, err := common.CollectClusterData(ks.Context(), clientSet)
|
||||
if err != nil {
|
||||
logger.L().Error("Failed to collect cluster data", helpers.Error(err))
|
||||
}
|
||||
|
||||
// 2) Run checks
|
||||
sizingResult := sizing.RunSizingChecker(clusterData)
|
||||
pvResult := pvcheck.RunPVProvisioningCheck(ks.Context(), clientSet, clusterData, inCluster)
|
||||
connectivityResult := connectivitycheck.RunConnectivityChecks(ks.Context(), clientSet, clusterData, inCluster)
|
||||
ebpfResult := ebpfcheck.RunEbpfCheck(ks.Context(), clientSet, clusterData, inCluster)
|
||||
|
||||
// 3) Build and export the final ReportData
|
||||
finalReport := common.BuildReportData(clusterData, sizingResult, pvResult, connectivityResult, ebpfResult)
|
||||
finalReport.InCluster = inCluster
|
||||
|
||||
common.GenerateOutput(finalReport, inCluster)
|
||||
},
|
||||
}
|
||||
|
||||
kubeconfigPath = preReqCmd.PersistentFlags().String("kubeconfig", "", "Path to the kubeconfig file. If not set, in-cluster config is used or $HOME/.kube/config if outside a cluster.")
|
||||
|
||||
return preReqCmd
|
||||
}
|
||||
21
cmd/root.go
21
cmd/root.go
@@ -1,6 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -12,8 +13,10 @@ import (
|
||||
"github.com/kubescape/kubescape/v3/cmd/download"
|
||||
"github.com/kubescape/kubescape/v3/cmd/fix"
|
||||
"github.com/kubescape/kubescape/v3/cmd/list"
|
||||
"github.com/kubescape/kubescape/v3/cmd/mcpserver"
|
||||
"github.com/kubescape/kubescape/v3/cmd/operator"
|
||||
"github.com/kubescape/kubescape/v3/cmd/patch"
|
||||
"github.com/kubescape/kubescape/v3/cmd/prerequisites"
|
||||
"github.com/kubescape/kubescape/v3/cmd/scan"
|
||||
"github.com/kubescape/kubescape/v3/cmd/update"
|
||||
"github.com/kubescape/kubescape/v3/cmd/vap"
|
||||
@@ -41,8 +44,8 @@ var ksExamples = fmt.Sprintf(`
|
||||
%[1]s config view
|
||||
`, cautils.ExecName())
|
||||
|
||||
func NewDefaultKubescapeCommand() *cobra.Command {
|
||||
ks := core.NewKubescape()
|
||||
func NewDefaultKubescapeCommand(ctx context.Context) *cobra.Command {
|
||||
ks := core.NewKubescape(ctx)
|
||||
return getRootCmd(ks)
|
||||
}
|
||||
|
||||
@@ -50,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)
|
||||
@@ -83,8 +86,6 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&rootInfo.Logger, "logger", "l", helpers.InfoLevel.String(), fmt.Sprintf("Logger level. Supported: %s [$KS_LOGGER]", strings.Join(helpers.SupportedLevels(), "/")))
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.CacheDir, "cache-dir", getter.DefaultLocalStore, "Cache directory [$KS_CACHE_DIR]")
|
||||
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable color output for logging")
|
||||
rootCmd.PersistentFlags().BoolVarP(&rootInfo.EnableColor, "enable-color", "", false, "Force enable color output for logging")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&rootInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
|
||||
// Supported commands
|
||||
@@ -92,13 +93,15 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
rootCmd.AddCommand(download.GetDownloadCmd(ks))
|
||||
rootCmd.AddCommand(list.GetListCmd(ks))
|
||||
rootCmd.AddCommand(completion.GetCompletionCmd())
|
||||
rootCmd.AddCommand(version.GetVersionCmd())
|
||||
rootCmd.AddCommand(version.GetVersionCmd(ks))
|
||||
rootCmd.AddCommand(config.GetConfigCmd(ks))
|
||||
rootCmd.AddCommand(update.GetUpdateCmd())
|
||||
rootCmd.AddCommand(update.GetUpdateCmd(ks))
|
||||
rootCmd.AddCommand(fix.GetFixCmd(ks))
|
||||
rootCmd.AddCommand(patch.GetPatchCmd(ks))
|
||||
rootCmd.AddCommand(vap.GetVapHelperCmd())
|
||||
rootCmd.AddCommand(operator.GetOperatorCmd(ks))
|
||||
rootCmd.AddCommand(prerequisites.GetPreReqCmd(ks))
|
||||
rootCmd.AddCommand(mcpserver.GetMCPServerCmd())
|
||||
|
||||
// deprecated commands
|
||||
rootCmd.AddCommand(&cobra.Command{
|
||||
@@ -113,7 +116,7 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
func Execute() error {
|
||||
ks := NewDefaultKubescapeCommand()
|
||||
func Execute(ctx context.Context) error {
|
||||
ks := NewDefaultKubescapeCommand(ctx)
|
||||
return ks.Execute()
|
||||
}
|
||||
|
||||
24
cmd/root_test.go
Normal file
24
cmd/root_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewDefaultKubescapeCommand(t *testing.T) {
|
||||
t.Run("NewDefaultKubescapeCommand", func(t *testing.T) {
|
||||
cmd := NewDefaultKubescapeCommand(context.Background())
|
||||
assert.NotNil(t, cmd)
|
||||
})
|
||||
}
|
||||
|
||||
func TestExecute(t *testing.T) {
|
||||
t.Run("Execute", func(t *testing.T) {
|
||||
err := Execute(context.Background())
|
||||
if err != nil {
|
||||
assert.EqualErrorf(t, err, "unknown command \"^\\\\QTestExecute\\\\E$\" for \"kubescape\"", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -18,9 +18,6 @@ import (
|
||||
)
|
||||
|
||||
func initLogger() {
|
||||
logger.DisableColor(rootInfo.DisableColor)
|
||||
logger.EnableColor(rootInfo.EnableColor)
|
||||
|
||||
if rootInfo.LoggerName == "" {
|
||||
if l := os.Getenv("KS_LOGGER_NAME"); l != "" {
|
||||
rootInfo.LoggerName = l
|
||||
@@ -34,8 +31,8 @@ func initLogger() {
|
||||
}
|
||||
|
||||
logger.InitLogger(rootInfo.LoggerName)
|
||||
|
||||
}
|
||||
|
||||
func initLoggerLevel() {
|
||||
if rootInfo.Logger == helpers.InfoLevel.String() {
|
||||
} else if l := os.Getenv("KS_LOGGER"); l != "" {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -30,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())
|
||||
)
|
||||
|
||||
@@ -96,12 +95,11 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
results, err := ks.Scan(ctx, scanInfo)
|
||||
results, err := ks.Scan(scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
if err := results.HandleResults(ctx); err != nil {
|
||||
if err := results.HandleResults(ks.Context(), scanInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
if !scanInfo.VerboseMode {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
reporthandlingapis "github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -113,13 +112,12 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
||||
|
||||
scanInfo.SetPolicyIdentifiers(frameworks, apisv1.KindFramework)
|
||||
|
||||
ctx := context.TODO()
|
||||
results, err := ks.Scan(ctx, scanInfo)
|
||||
results, err := ks.Scan(scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
if err = results.HandleResults(ctx); err != nil {
|
||||
if err = results.HandleResults(ks.Context(), scanInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
@@ -9,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"
|
||||
)
|
||||
|
||||
@@ -24,12 +22,18 @@ var (
|
||||
# Scan the 'nginx' image and see the full report
|
||||
%[1]s scan image "nginx" -v
|
||||
|
||||
# Scan the 'nginx' image and use exceptions
|
||||
%[1]s scan image "nginx" --exceptions exceptions.json
|
||||
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
// getImageCmd returns the scan image command
|
||||
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]",
|
||||
Short: "Scan an image for vulnerabilities",
|
||||
@@ -50,17 +54,19 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command
|
||||
}
|
||||
|
||||
imgScanInfo := &metav1.ImageScanInfo{
|
||||
Image: args[0],
|
||||
Username: imgCredentials.Username,
|
||||
Password: imgCredentials.Password,
|
||||
Image: args[0],
|
||||
Username: imgCredentials.Username,
|
||||
Password: imgCredentials.Password,
|
||||
Exceptions: exceptions,
|
||||
UseDefaultMatchers: useDefaultMatchers,
|
||||
}
|
||||
|
||||
results, err := ks.ScanImage(context.Background(), 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())
|
||||
}
|
||||
|
||||
@@ -68,8 +74,11 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command
|
||||
},
|
||||
}
|
||||
|
||||
// The exceptions flag
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -93,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.")
|
||||
@@ -135,15 +135,12 @@ func setSecurityViewScanInfo(args []string, scanInfo *cautils.ScanInfo) {
|
||||
}
|
||||
|
||||
func securityScan(scanInfo cautils.ScanInfo, ks meta.IKubescape) error {
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
results, err := ks.Scan(ctx, &scanInfo)
|
||||
results, err := ks.Scan(&scanInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = results.HandleResults(ctx); 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]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -66,13 +65,12 @@ func getWorkloadCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comma
|
||||
setWorkloadScanInfo(scanInfo, kind, name)
|
||||
|
||||
// todo: add api version if provided
|
||||
ctx := context.TODO()
|
||||
results, err := ks.Scan(ctx, scanInfo)
|
||||
results, err := ks.Scan(scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
if err = results.HandleResults(ctx); err != nil {
|
||||
if err = results.HandleResults(ks.Context(), scanInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
|
||||
@@ -94,3 +94,17 @@ func TestGetWorkloadCmd_ChartPathAndFilePathEmpty(t *testing.T) {
|
||||
expectedErrorMessage = "invalid workload identifier"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
func Test_parseWorkloadIdentifierString_Empty(t *testing.T) {
|
||||
t.Run("empty identifier", func(t *testing.T) {
|
||||
_, _, err := parseWorkloadIdentifierString("")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_parseWorkloadIdentifierString_NoError(t *testing.T) {
|
||||
t.Run("valid identifier", func(t *testing.T) {
|
||||
_, _, err := parseWorkloadIdentifierString("default/Deployment")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -5,10 +5,11 @@ package update
|
||||
// kubescape update
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
|
||||
"github.com/kubescape/backend/pkg/versioncheck"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
@@ -25,17 +26,18 @@ var updateCmdExamples = fmt.Sprintf(`
|
||||
%[1]s update
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetUpdateCmd() *cobra.Command {
|
||||
func GetUpdateCmd(ks meta.IKubescape) *cobra.Command {
|
||||
updateCmd := &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update to latest release version",
|
||||
Long: ``,
|
||||
Example: updateCmdExamples,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
ctx := context.TODO()
|
||||
v := versioncheck.NewVersionCheckHandler()
|
||||
versionCheckRequest := versioncheck.NewVersionCheckRequest("", versioncheck.BuildNumber, "", "", "update", nil)
|
||||
v.CheckLatestVersion(ctx, versionCheckRequest)
|
||||
if err := v.CheckLatestVersion(ks.Context(), versionCheckRequest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Checking the user's version of kubescape to the latest release
|
||||
if versioncheck.BuildNumber == "" || strings.Contains(versioncheck.BuildNumber, "rc") {
|
||||
|
||||
18
cmd/update/update_test.go
Normal file
18
cmd/update/update_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetUpdateCmd(t *testing.T) {
|
||||
ks := core.NewKubescape(context.TODO())
|
||||
cmd := GetUpdateCmd(ks)
|
||||
assert.NotNil(t, cmd)
|
||||
|
||||
err := cmd.RunE(cmd, []string{})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
|
||||
"github.com/kubescape/backend/pkg/versioncheck"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func GetVersionCmd() *cobra.Command {
|
||||
func GetVersionCmd(ks meta.IKubescape) *cobra.Command {
|
||||
versionCmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Get current version",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.TODO()
|
||||
v := versioncheck.NewIVersionCheckHandler(ctx)
|
||||
versionCheckRequest := versioncheck.NewVersionCheckRequest("", versioncheck.BuildNumber, "", "", "version", nil)
|
||||
v.CheckLatestVersion(ctx, versionCheckRequest)
|
||||
fmt.Fprintf(cmd.OutOrStdout(),
|
||||
v := versioncheck.NewIVersionCheckHandler(ks.Context())
|
||||
_ = v.CheckLatestVersion(ks.Context(), versioncheck.NewVersionCheckRequest("", versioncheck.BuildNumber, "", "", "version", nil))
|
||||
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(),
|
||||
"Your current version is: %s\n",
|
||||
versionCheckRequest.ClientVersion,
|
||||
versioncheck.BuildNumber,
|
||||
)
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -2,9 +2,12 @@ package version
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
|
||||
"github.com/kubescape/backend/pkg/versioncheck"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -17,7 +20,7 @@ func TestGetVersionCmd(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Undefined Build Number",
|
||||
buildNumber: "",
|
||||
buildNumber: "unknown",
|
||||
want: "Your current version is: unknown\n",
|
||||
},
|
||||
{
|
||||
@@ -30,7 +33,8 @@ func TestGetVersionCmd(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
versioncheck.BuildNumber = tt.buildNumber
|
||||
|
||||
if cmd := GetVersionCmd(); cmd != nil {
|
||||
ks := core.NewKubescape(context.TODO())
|
||||
if cmd := GetVersionCmd(ks); cmd != nil {
|
||||
buf := bytes.NewBufferString("")
|
||||
cmd.SetOut(buf)
|
||||
cmd.Execute()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
||||
"golang.org/x/exp/slices"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ type RootInfo struct {
|
||||
Logger string // logger level
|
||||
LoggerName string // logger name ("pretty"/"zap"/"none")
|
||||
CacheDir string // cached dir
|
||||
DisableColor bool // Disable Color
|
||||
EnableColor bool // Force enable Color
|
||||
DiscoveryServerURL string // Discovery Server URL (See https://github.com/kubescape/backend/tree/main/pkg/servicediscovery)
|
||||
KubeContext string // context 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
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
@@ -35,8 +34,8 @@ func (ks *Kubescape) ViewCachedConfig(viewConfig *metav1.ViewConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ks *Kubescape) DeleteCachedConfig(ctx context.Context, deleteConfig *metav1.DeleteConfig) error {
|
||||
func (ks *Kubescape) DeleteCachedConfig(deleteConfig *metav1.DeleteConfig) error {
|
||||
|
||||
tenant := cautils.GetTenantConfig("", "", "", "", nil) // change k8sinterface
|
||||
return tenant.DeleteCachedConfig(ctx)
|
||||
return tenant.DeleteCachedConfig(ks.Context())
|
||||
}
|
||||
|
||||
@@ -44,12 +44,12 @@ func DownloadSupportCommands() []string {
|
||||
return commands
|
||||
}
|
||||
|
||||
func (ks *Kubescape) Download(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
|
||||
func (ks *Kubescape) Download(downloadInfo *metav1.DownloadInfo) error {
|
||||
setPathAndFilename(downloadInfo)
|
||||
if err := os.MkdirAll(downloadInfo.Path, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := downloadArtifact(ctx, downloadInfo, downloadFunc); err != nil {
|
||||
if err := downloadArtifact(ks.Context(), downloadInfo, downloadFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -16,14 +15,14 @@ const (
|
||||
confirmationQuestion = "Would you like to apply the changes to the files above? [y|n]: "
|
||||
)
|
||||
|
||||
func (ks *Kubescape) Fix(ctx context.Context, fixInfo *metav1.FixInfo) error {
|
||||
func (ks *Kubescape) Fix(fixInfo *metav1.FixInfo) error {
|
||||
logger.L().Info("Reading report file...")
|
||||
handler, err := fixhandler.NewFixHandler(fixInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resourcesToFix := handler.PrepareResourcesToFix(ctx)
|
||||
resourcesToFix := handler.PrepareResourcesToFix(ks.Context())
|
||||
|
||||
if len(resourcesToFix) == 0 {
|
||||
logger.L().Info(noResourcesToFix)
|
||||
@@ -42,12 +41,12 @@ func (ks *Kubescape) Fix(ctx context.Context, fixInfo *metav1.FixInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
updatedFilesCount, errors := handler.ApplyChanges(ctx, resourcesToFix)
|
||||
updatedFilesCount, errors := handler.ApplyChanges(ks.Context(), resourcesToFix)
|
||||
logger.L().Info(fmt.Sprintf("Fixed resources in %d files.", updatedFilesCount))
|
||||
|
||||
if len(errors) > 0 {
|
||||
for _, err := range errors {
|
||||
logger.L().Ctx(ctx).Warning(err.Error())
|
||||
logger.L().Ctx(ks.Context()).Warning(err.Error())
|
||||
}
|
||||
return fmt.Errorf("Failed to fix some resources, check the logs for more details")
|
||||
}
|
||||
|
||||
@@ -33,10 +33,11 @@ func TestUserConfirmed(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.input), func(t *testing.T) {
|
||||
originalStdin := os.Stdin
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdin = r
|
||||
defer func() {
|
||||
os.Stdin = os.Stdin
|
||||
os.Stdin = originalStdin
|
||||
}()
|
||||
|
||||
go func() {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"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"
|
||||
@@ -12,39 +14,199 @@ import (
|
||||
"github.com/kubescape/kubescape/v3/pkg/imagescan"
|
||||
)
|
||||
|
||||
func (ks *Kubescape) ScanImage(ctx context.Context, imgScanInfo *ksmetav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
|
||||
// Data structure to represent attributes
|
||||
type Attributes struct {
|
||||
Registry string `json:"registry"`
|
||||
Organization string `json:"organization,omitempty"`
|
||||
ImageName string `json:"imageName"`
|
||||
ImageTag string `json:"imageTag,omitempty"`
|
||||
}
|
||||
|
||||
// Data structure for a target
|
||||
type Target struct {
|
||||
DesignatorType string `json:"designatorType"`
|
||||
Attributes Attributes `json:"attributes"`
|
||||
}
|
||||
|
||||
// Data structure for metadata
|
||||
type Metadata struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Data structure for vulnerabilities and severities
|
||||
type VulnerabilitiesIgnorePolicy struct {
|
||||
Metadata Metadata `json:"metadata"`
|
||||
Kind string `json:"kind"`
|
||||
Targets []Target `json:"targets"`
|
||||
Vulnerabilities []string `json:"vulnerabilities"`
|
||||
Severities []string `json:"severities"`
|
||||
}
|
||||
|
||||
// Loads excpetion policies from exceptions json object.
|
||||
func GetImageExceptionsFromFile(filePath string) ([]VulnerabilitiesIgnorePolicy, error) {
|
||||
// Read the JSON file
|
||||
jsonFile, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading exceptions file: %w", err)
|
||||
}
|
||||
|
||||
// Unmarshal the JSON data into an array of VulnerabilitiesIgnorePolicy
|
||||
var policies []VulnerabilitiesIgnorePolicy
|
||||
err = json.Unmarshal(jsonFile, &policies)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling exceptions file: %w", err)
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
// This function will identify the registry, organization and image tag from the image name
|
||||
func getAttributesFromImage(imgName string) (Attributes, error) {
|
||||
canonicalImageName, err := cautils.NormalizeImageName(imgName)
|
||||
if err != nil {
|
||||
return Attributes{}, err
|
||||
}
|
||||
|
||||
tokens := strings.Split(canonicalImageName, "/")
|
||||
registry := tokens[0]
|
||||
organization := tokens[1]
|
||||
|
||||
imageNameAndTag := strings.Split(tokens[2], ":")
|
||||
imageName := imageNameAndTag[0]
|
||||
|
||||
// Intialize the image tag with default value
|
||||
imageTag := "latest"
|
||||
if len(imageNameAndTag) > 1 {
|
||||
imageTag = imageNameAndTag[1]
|
||||
}
|
||||
|
||||
attributes := Attributes{
|
||||
Registry: registry,
|
||||
Organization: organization,
|
||||
ImageName: imageName,
|
||||
ImageTag: imageTag,
|
||||
}
|
||||
|
||||
return attributes, nil
|
||||
}
|
||||
|
||||
// Checks if the target string matches the regex pattern
|
||||
func regexStringMatch(pattern, target string) bool {
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
logger.L().StopError(fmt.Sprintf("Failed to generate regular expression: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
if re.MatchString(target) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Compares the registry, organization, image name, image tag against the targets specified
|
||||
// in the exception policy object to check if the image being scanned qualifies for an
|
||||
// exception policy.
|
||||
func isTargetImage(targets []Target, attributes Attributes) bool {
|
||||
for _, target := range targets {
|
||||
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
|
||||
}
|
||||
|
||||
// Generates a list of unique CVE-IDs and the severities which are to be excluded for
|
||||
// the image being scanned.
|
||||
func getUniqueVulnerabilitiesAndSeverities(policies []VulnerabilitiesIgnorePolicy, image string) ([]string, []string) {
|
||||
// Create maps with slices as values to store unique vulnerabilities and severities (case-insensitive)
|
||||
uniqueVulns := make(map[string][]string)
|
||||
uniqueSevers := make(map[string][]string)
|
||||
|
||||
imageAttributes, err := getAttributesFromImage(image)
|
||||
if err != nil {
|
||||
logger.L().StopError(fmt.Sprintf("Failed to generate image attributes: %s", err))
|
||||
}
|
||||
|
||||
// Iterate over each policy and its vulnerabilities/severities
|
||||
for _, policy := range policies {
|
||||
// Include the exceptions only if the image is one of the targets
|
||||
if isTargetImage(policy.Targets, imageAttributes) {
|
||||
for _, vulnerability := range policy.Vulnerabilities {
|
||||
// Add to slice directly
|
||||
vulnerabilityUppercase := strings.ToUpper(vulnerability)
|
||||
uniqueVulns[vulnerabilityUppercase] = append(uniqueVulns[vulnerabilityUppercase], vulnerability)
|
||||
}
|
||||
|
||||
for _, severity := range policy.Severities {
|
||||
// Add to slice directly
|
||||
severityUppercase := strings.ToUpper(severity)
|
||||
uniqueSevers[severityUppercase] = append(uniqueSevers[severityUppercase], severity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract unique keys (which are unique vulnerabilities/severities) and their slices
|
||||
uniqueVulnsList := make([]string, 0, len(uniqueVulns))
|
||||
for vuln := range uniqueVulns {
|
||||
uniqueVulnsList = append(uniqueVulnsList, vuln)
|
||||
}
|
||||
|
||||
uniqueSeversList := make([]string, 0, len(uniqueSevers))
|
||||
for sever := range uniqueSevers {
|
||||
uniqueSeversList = append(uniqueSeversList, sever)
|
||||
}
|
||||
|
||||
return uniqueVulnsList, uniqueSeversList
|
||||
}
|
||||
|
||||
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,
|
||||
Password: imgScanInfo.Password,
|
||||
}
|
||||
|
||||
scanResults, err := svc.Scan(ctx, imgScanInfo.Image, creds)
|
||||
var vulnerabilityExceptions []string
|
||||
var severityExceptions []string
|
||||
if imgScanInfo.Exceptions != "" {
|
||||
exceptionPolicies, err := GetImageExceptionsFromFile(imgScanInfo.Exceptions)
|
||||
if err != nil {
|
||||
logger.L().StopError(fmt.Sprintf("Failed to load exceptions from file: %s", imgScanInfo.Exceptions))
|
||||
return false, err
|
||||
}
|
||||
|
||||
vulnerabilityExceptions, severityExceptions = getUniqueVulnerabilitiesAndSeverities(exceptionPolicies, imgScanInfo.Image)
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
scanInfo.SetScanType(cautils.ScanTypeImage)
|
||||
|
||||
outputPrinters := GetOutputPrinters(scanInfo, ctx, "")
|
||||
outputPrinters := GetOutputPrinters(scanInfo, ks.Context(), "")
|
||||
|
||||
uiPrinter := GetUIPrinter(ctx, scanInfo, "")
|
||||
uiPrinter := GetUIPrinter(ks.Context(), scanInfo, "")
|
||||
|
||||
resultsHandler := resultshandling.NewResultsHandler(nil, outputPrinters, uiPrinter)
|
||||
|
||||
resultsHandler.ImageScanData = []cautils.ImageScanData{
|
||||
{
|
||||
PresenterConfig: scanResults,
|
||||
Image: imgScanInfo.Image,
|
||||
},
|
||||
}
|
||||
resultsHandler.ImageScanData = []cautils.ImageScanData{*imageScanData}
|
||||
|
||||
return scanResults, resultsHandler.HandleResults(ctx)
|
||||
return svc.ExceedsSeverityThreshold(imagescan.ParseSeverity(scanInfo.FailThresholdSeverity), imageScanData.Matches), resultsHandler.HandleResults(ks.Context(), scanInfo)
|
||||
}
|
||||
|
||||
447
core/core/image_scan_test.go
Normal file
447
core/core/image_scan_test.go
Normal file
@@ -0,0 +1,447 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetImageExceptionsFromFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
filePath string
|
||||
expectedPolicies []VulnerabilitiesIgnorePolicy
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
filePath: "./testdata/exceptions.json",
|
||||
expectedPolicies: []VulnerabilitiesIgnorePolicy{
|
||||
{
|
||||
Metadata: Metadata{
|
||||
Name: "medium-severity-vulnerabilites-exceptions",
|
||||
},
|
||||
Kind: "VulnerabilitiesIgnorePolicy",
|
||||
Targets: []Target{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: Attributes{
|
||||
Registry: "docker.io",
|
||||
Organization: "",
|
||||
ImageName: "",
|
||||
ImageTag: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Vulnerabilities: []string{},
|
||||
Severities: []string{"medium"},
|
||||
},
|
||||
{
|
||||
Metadata: Metadata{
|
||||
Name: "exclude-allowed-hostPath-control",
|
||||
},
|
||||
Kind: "VulnerabilitiesIgnorePolicy",
|
||||
Targets: []Target{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: Attributes{
|
||||
Registry: "",
|
||||
Organization: "",
|
||||
ImageName: "",
|
||||
ImageTag: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Vulnerabilities: []string{"CVE-2023-42366", "CVE-2023-42365"},
|
||||
Severities: []string{"critical", "low"},
|
||||
},
|
||||
{
|
||||
Metadata: Metadata{
|
||||
Name: "regex-example",
|
||||
},
|
||||
Kind: "VulnerabilitiesIgnorePolicy",
|
||||
Targets: []Target{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: Attributes{
|
||||
Registry: "quay.*",
|
||||
Organization: "kube*",
|
||||
ImageName: "kubescape*",
|
||||
ImageTag: "v2*",
|
||||
},
|
||||
},
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: Attributes{
|
||||
Registry: "docker.io",
|
||||
Organization: ".*",
|
||||
ImageName: "kube*",
|
||||
ImageTag: "v3*",
|
||||
},
|
||||
},
|
||||
},
|
||||
Vulnerabilities: []string{"CVE-2023-6879", "CVE-2023-44487"},
|
||||
Severities: []string{"critical", "low"},
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
filePath: "./testdata/empty_exceptions.json",
|
||||
expectedPolicies: []VulnerabilitiesIgnorePolicy{},
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.filePath, func(t *testing.T) {
|
||||
policies, err := GetImageExceptionsFromFile(tt.filePath)
|
||||
assert.Equal(t, tt.expectedPolicies, policies)
|
||||
assert.Equal(t, tt.expectedErr, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAttributesFromImage(t *testing.T) {
|
||||
tests := []struct {
|
||||
imageName string
|
||||
expectedAttributes Attributes
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
imageName: "quay.io/kubescape/kubescape-cli:v3.0.0",
|
||||
expectedAttributes: Attributes{
|
||||
Registry: "quay.io",
|
||||
Organization: "kubescape",
|
||||
ImageName: "kubescape-cli",
|
||||
ImageTag: "v3.0.0",
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
imageName: "alpine",
|
||||
expectedAttributes: Attributes{
|
||||
Registry: "docker.io",
|
||||
Organization: "library",
|
||||
ImageName: "alpine",
|
||||
ImageTag: "latest",
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.imageName, func(t *testing.T) {
|
||||
attributes, err := getAttributesFromImage(tt.imageName)
|
||||
assert.Equal(t, tt.expectedErr, err)
|
||||
assert.Equal(t, tt.expectedAttributes, attributes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexStringMatch(t *testing.T) {
|
||||
tests := []struct {
|
||||
pattern string
|
||||
target string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
pattern: ".*",
|
||||
target: "quay.io",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "kubescape",
|
||||
target: "kubescape",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "kubescape*",
|
||||
target: "kubescape-cli",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "",
|
||||
target: "v3.0.0",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "docker.io",
|
||||
target: "quay.io",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.target+"/"+tt.pattern, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, regexStringMatch(tt.pattern, tt.target))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsTargetImage(t *testing.T) {
|
||||
tests := []struct {
|
||||
targets []Target
|
||||
attributes Attributes
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
targets: []Target{
|
||||
{
|
||||
Attributes: Attributes{
|
||||
Registry: "docker.io",
|
||||
Organization: ".*",
|
||||
ImageName: ".*",
|
||||
ImageTag: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
attributes: Attributes{
|
||||
Registry: "quay.io",
|
||||
Organization: "kubescape",
|
||||
ImageName: "kubescape-cli",
|
||||
ImageTag: "v3.0.0",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
targets: []Target{
|
||||
{
|
||||
Attributes: Attributes{
|
||||
Registry: "quay.io",
|
||||
Organization: "kubescape",
|
||||
ImageName: "kubescape*",
|
||||
ImageTag: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
attributes: Attributes{
|
||||
Registry: "quay.io",
|
||||
Organization: "kubescape",
|
||||
ImageName: "kubescape-cli",
|
||||
ImageTag: "v3.0.0",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
targets: []Target{
|
||||
{
|
||||
Attributes: Attributes{
|
||||
Registry: "docker.io",
|
||||
Organization: "library",
|
||||
ImageName: "alpine",
|
||||
ImageTag: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
attributes: Attributes{
|
||||
Registry: "docker.io",
|
||||
Organization: "library",
|
||||
ImageName: "alpine",
|
||||
ImageTag: "latest",
|
||||
},
|
||||
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 {
|
||||
t.Run(tt.attributes.Registry+"/"+tt.attributes.ImageName, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, isTargetImage(tt.targets, tt.attributes))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVulnerabilitiesAndSeverities(t *testing.T) {
|
||||
tests := []struct {
|
||||
policies []VulnerabilitiesIgnorePolicy
|
||||
image string
|
||||
expectedVulnerabilities []string
|
||||
expectedSeverities []string
|
||||
}{
|
||||
{
|
||||
policies: []VulnerabilitiesIgnorePolicy{
|
||||
{
|
||||
Metadata: Metadata{
|
||||
Name: "vulnerabilites-exceptions",
|
||||
},
|
||||
Kind: "VulnerabilitiesIgnorePolicy",
|
||||
Targets: []Target{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: Attributes{
|
||||
Registry: "",
|
||||
Organization: "kubescape*",
|
||||
ImageName: "",
|
||||
ImageTag: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Vulnerabilities: []string{"CVE-2023-42365"},
|
||||
Severities: []string{},
|
||||
},
|
||||
{
|
||||
Metadata: Metadata{
|
||||
Name: "exclude-allowed-hostPath-control",
|
||||
},
|
||||
Kind: "VulnerabilitiesIgnorePolicy",
|
||||
Targets: []Target{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: Attributes{
|
||||
Registry: "docker.io",
|
||||
Organization: "",
|
||||
ImageName: "",
|
||||
ImageTag: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Vulnerabilities: []string{"CVE-2023-42366", "CVE-2023-42365"},
|
||||
Severities: []string{"critical", "low"},
|
||||
},
|
||||
},
|
||||
image: "quay.io/kubescape/kubescape-cli:v3.0.0",
|
||||
expectedVulnerabilities: []string{"CVE-2023-42365"},
|
||||
expectedSeverities: []string{},
|
||||
},
|
||||
{
|
||||
policies: []VulnerabilitiesIgnorePolicy{
|
||||
{
|
||||
Metadata: Metadata{
|
||||
Name: "medium-severity-vulnerabilites-exceptions",
|
||||
},
|
||||
Kind: "VulnerabilitiesIgnorePolicy",
|
||||
Targets: []Target{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: Attributes{
|
||||
Registry: "",
|
||||
Organization: "",
|
||||
ImageName: "",
|
||||
ImageTag: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Vulnerabilities: []string{},
|
||||
Severities: []string{"medium"},
|
||||
},
|
||||
{
|
||||
Metadata: Metadata{
|
||||
Name: "exclude-allowed-hostPath-control",
|
||||
},
|
||||
Kind: "VulnerabilitiesIgnorePolicy",
|
||||
Targets: []Target{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: Attributes{
|
||||
Registry: "quay.io",
|
||||
Organization: "",
|
||||
ImageName: "",
|
||||
ImageTag: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Vulnerabilities: []string{"CVE-2023-42366", "CVE-2023-42365"},
|
||||
Severities: []string{},
|
||||
},
|
||||
},
|
||||
image: "alpine",
|
||||
expectedVulnerabilities: []string{},
|
||||
expectedSeverities: []string{"MEDIUM"},
|
||||
},
|
||||
{
|
||||
policies: []VulnerabilitiesIgnorePolicy{
|
||||
{
|
||||
Metadata: Metadata{
|
||||
Name: "regex-example",
|
||||
},
|
||||
Kind: "VulnerabilitiesIgnorePolicy",
|
||||
Targets: []Target{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: Attributes{
|
||||
Registry: "quay.io",
|
||||
Organization: "kube*",
|
||||
ImageName: "kubescape*",
|
||||
ImageTag: ".*",
|
||||
},
|
||||
},
|
||||
},
|
||||
Vulnerabilities: []string{},
|
||||
Severities: []string{"critical"},
|
||||
},
|
||||
{
|
||||
Metadata: Metadata{
|
||||
Name: "only-for-docker-registry",
|
||||
},
|
||||
Kind: "VulnerabilitiesIgnorePolicy",
|
||||
Targets: []Target{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: Attributes{
|
||||
Registry: "docker.io",
|
||||
ImageTag: "v3*",
|
||||
},
|
||||
},
|
||||
},
|
||||
Vulnerabilities: []string{"CVE-2023-42366", "CVE-2022-28391"},
|
||||
Severities: []string{"high"},
|
||||
},
|
||||
{
|
||||
Metadata: Metadata{
|
||||
Name: "exclude-allowed-hostPath-control",
|
||||
},
|
||||
Kind: "VulnerabilitiesIgnorePolicy",
|
||||
Targets: []Target{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: Attributes{
|
||||
ImageTag: "v3*",
|
||||
},
|
||||
},
|
||||
},
|
||||
Vulnerabilities: []string{"CVE-2022-30065", "CVE-2022-28391"},
|
||||
Severities: []string{},
|
||||
},
|
||||
},
|
||||
image: "quay.io/kubescape/kubescape-cli:v3.0.0",
|
||||
expectedVulnerabilities: []string{"CVE-2022-30065", "CVE-2022-28391"},
|
||||
expectedSeverities: []string{"CRITICAL"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.image, func(t *testing.T) {
|
||||
vulnerabilities, severities := getUniqueVulnerabilitiesAndSeverities(tt.policies, tt.image)
|
||||
sort.Strings(tt.expectedVulnerabilities)
|
||||
sort.Strings(vulnerabilities)
|
||||
assert.Equal(t, tt.expectedVulnerabilities, vulnerabilities)
|
||||
assert.Equal(t, tt.expectedSeverities, severities)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,17 @@
|
||||
package core
|
||||
|
||||
type Kubescape struct{}
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
func NewKubescape() *Kubescape {
|
||||
return &Kubescape{}
|
||||
type Kubescape struct {
|
||||
Ctx context.Context
|
||||
}
|
||||
|
||||
func (ks *Kubescape) Context() context.Context {
|
||||
return ks.Ctx
|
||||
}
|
||||
|
||||
func NewKubescape(ctx context.Context) *Kubescape {
|
||||
return &Kubescape{Ctx: ctx}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -8,7 +9,8 @@ import (
|
||||
|
||||
// The function should return a non-nil pointer.
|
||||
func TestNewKubescape_ReturnsNonNilPointer(t *testing.T) {
|
||||
k := NewKubescape()
|
||||
ctx := context.TODO()
|
||||
k := NewKubescape(ctx)
|
||||
assert.NotNil(t, k)
|
||||
}
|
||||
|
||||
@@ -19,5 +21,6 @@ func TestNewKubescape_DoesNotPanic(t *testing.T) {
|
||||
t.Errorf("Function panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
NewKubescape()
|
||||
ctx := context.TODO()
|
||||
NewKubescape(ctx)
|
||||
}
|
||||
|
||||
@@ -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){
|
||||
@@ -38,16 +37,16 @@ func ListSupportActions() []string {
|
||||
sort.Strings(commands)
|
||||
return commands
|
||||
}
|
||||
func (ks *Kubescape) List(ctx context.Context, listPolicies *metav1.ListPolicies) error {
|
||||
func (ks *Kubescape) List(listPolicies *metav1.ListPolicies) error {
|
||||
if policyListerFunc, ok := listFunc[listPolicies.Target]; ok {
|
||||
policies, err := policyListerFunc(ctx, listPolicies)
|
||||
policies, err := policyListerFunc(ks.Context(), listPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
policies = naturalSortPolicies(policies)
|
||||
|
||||
if listFormatFunction, ok := listFormatFunc[listPolicies.Format]; ok {
|
||||
listFormatFunction(ctx, listPolicies.Target, policies)
|
||||
listFormatFunction(ks.Context(), listPolicies.Target, policies)
|
||||
} else {
|
||||
return fmt.Errorf("Invalid format \"%s\", Supported formats: 'pretty-print'/'json' ", listPolicies.Format)
|
||||
}
|
||||
@@ -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,45 +24,67 @@ 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(ctx context.Context, 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,
|
||||
}
|
||||
// Scan the image
|
||||
scanResults, err := svc.Scan(ctx, patchInfo.Image, creds)
|
||||
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, "/", "-")
|
||||
|
||||
writer := printer.GetWriter(ctx, fileName)
|
||||
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))
|
||||
|
||||
@@ -68,8 +97,8 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo, s
|
||||
disableCopaLogger()
|
||||
}
|
||||
|
||||
if err = copaPatch(ctx, patchInfo.Timeout, patchInfo.BuildkitAddress, patchInfo.Image, fileName, patchedImageName, "", patchInfo.IgnoreError, patchInfo.BuildKitOpts); err != nil {
|
||||
return nil, err
|
||||
if err = copaPatch(ks.Context(), patchInfo.Timeout, patchInfo.BuildkitAddress, patchInfo.Image, fileName, patchedImageName, "", patchInfo.IgnoreError, patchInfo.BuildKitOpts); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Restore the output streams
|
||||
@@ -81,9 +110,9 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo, s
|
||||
|
||||
logger.L().Start(fmt.Sprintf("Re-scanning image: %s", patchedImageName))
|
||||
|
||||
scanResultsPatched, err := svc.Scan(ctx, patchedImageName, creds)
|
||||
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))
|
||||
|
||||
@@ -96,17 +125,12 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo, s
|
||||
// ===================== Results Handling =====================
|
||||
|
||||
scanInfo.SetScanType(cautils.ScanTypeImage)
|
||||
outputPrinters := GetOutputPrinters(scanInfo, ctx, "")
|
||||
uiPrinter := GetUIPrinter(ctx, scanInfo, "")
|
||||
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(ctx)
|
||||
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) {
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package core
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/kubescape/backend/pkg/versioncheck"
|
||||
"github.com/kubescape/go-logger"
|
||||
@@ -23,7 +24,6 @@ import (
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"github.com/kubescape/opa-utils/resources"
|
||||
"go.opentelemetry.io/otel"
|
||||
"golang.org/x/exp/slices"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
@@ -121,8 +121,8 @@ func GetOutputPrinters(scanInfo *cautils.ScanInfo, ctx context.Context, clusterN
|
||||
return outputPrinters
|
||||
}
|
||||
|
||||
func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
|
||||
ctxInit, spanInit := otel.Tracer("").Start(ctx, "initialization")
|
||||
func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
|
||||
ctxInit, spanInit := otel.Tracer("").Start(ks.Context(), "initialization")
|
||||
logger.L().Start("Kubescape scanner initializing...")
|
||||
|
||||
// ===================== Initialization =====================
|
||||
@@ -148,7 +148,7 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
|
||||
// remove host scanner components
|
||||
defer func() {
|
||||
if err := interfaces.hostSensorHandler.TearDown(); err != nil {
|
||||
logger.L().Ctx(ctx).StopError("Failed to tear down host scanner", helpers.Error(err))
|
||||
logger.L().Ctx(ks.Context()).StopError("Failed to tear down host scanner", helpers.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -177,7 +177,7 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
|
||||
spanInit.End()
|
||||
|
||||
// ========================= opa testing =====================
|
||||
ctxOpa, spanOpa := otel.Tracer("").Start(ctx, "opa testing")
|
||||
ctxOpa, spanOpa := otel.Tracer("").Start(ks.Context(), "opa testing")
|
||||
defer spanOpa.End()
|
||||
|
||||
deps := resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), interfaces.tenantConfig.GetContextName())
|
||||
@@ -191,7 +191,7 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
|
||||
if scanInfo.PrintAttackTree || isPrioritizationScanType(scanInfo.ScanType) {
|
||||
_, spanPrioritization := otel.Tracer("").Start(ctxOpa, "prioritization")
|
||||
if priotizationHandler, err := resourcesprioritization.NewResourcesPrioritizationHandler(ctxOpa, scanInfo.Getters.AttackTracksGetter, scanInfo.PrintAttackTree); err != nil {
|
||||
logger.L().Ctx(ctx).Warning("failed to get attack tracks, this may affect the scanning results", helpers.Error(err))
|
||||
logger.L().Ctx(ks.Context()).Warning("failed to get attack tracks, this may affect the scanning results", helpers.Error(err))
|
||||
} else if err := priotizationHandler.PrioritizeResources(scanData); err != nil {
|
||||
return resultsHandling, fmt.Errorf("%w", err)
|
||||
}
|
||||
@@ -202,7 +202,7 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
|
||||
}
|
||||
|
||||
if scanInfo.ScanImages {
|
||||
scanImages(scanInfo.ScanType, scanData, ctx, resultsHandling)
|
||||
scanImages(scanInfo.ScanType, scanData, ks.Context(), resultsHandling, scanInfo)
|
||||
}
|
||||
// ========================= results handling =====================
|
||||
resultsHandling.SetData(scanData)
|
||||
@@ -214,7 +214,7 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
|
||||
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 +243,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,17 +260,14 @@ 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{})
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
67
core/core/testdata/alpine-nginx-exceptions.json
vendored
Normal file
67
core/core/testdata/alpine-nginx-exceptions.json
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
[
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alpine-exceptions"
|
||||
},
|
||||
"kind": "VulnerabilitiesIgnorePolicy",
|
||||
"targets": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"imageName": "alpine*"
|
||||
}
|
||||
}
|
||||
],
|
||||
"severities": [
|
||||
"medium"
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "nginx-exceptions"
|
||||
},
|
||||
"kind": "VulnerabilitiesIgnorePolicy",
|
||||
"targets": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"imageName": "nginx*"
|
||||
}
|
||||
}
|
||||
],
|
||||
"vulnerabilities": [
|
||||
"invalid-cve",
|
||||
"CVE-2023-45853",
|
||||
"CVE-2023-49463"
|
||||
],
|
||||
"severities": [
|
||||
"critical",
|
||||
"medium",
|
||||
"invalid-severity"
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "applicable-only-to-quay-registry-images"
|
||||
},
|
||||
"kind": "VulnerabilitiesIgnorePolicy",
|
||||
"targets": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"registry": "quay.io"
|
||||
}
|
||||
}
|
||||
],
|
||||
"vulnerabilities": [
|
||||
"CVE-2023-42365"
|
||||
],
|
||||
"severities": [
|
||||
"critical",
|
||||
"medium",
|
||||
"high",
|
||||
"low"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
1
core/core/testdata/empty_exceptions.json
vendored
Normal file
1
core/core/testdata/empty_exceptions.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
78
core/core/testdata/exceptions.json
vendored
Normal file
78
core/core/testdata/exceptions.json
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
[
|
||||
{
|
||||
"metadata": {
|
||||
"name": "medium-severity-vulnerabilites-exceptions"
|
||||
},
|
||||
"kind": "VulnerabilitiesIgnorePolicy",
|
||||
"targets": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"Registry": "docker.io",
|
||||
"Organization": "",
|
||||
"ImageName": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"vulnerabilities": [
|
||||
],
|
||||
"severities": [
|
||||
"medium"
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "exclude-allowed-hostPath-control"
|
||||
},
|
||||
"kind": "VulnerabilitiesIgnorePolicy",
|
||||
"targets": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
}
|
||||
}
|
||||
],
|
||||
"vulnerabilities": [
|
||||
"CVE-2023-42366",
|
||||
"CVE-2023-42365"
|
||||
],
|
||||
"severities": [
|
||||
"critical",
|
||||
"low"
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "regex-example"
|
||||
},
|
||||
"kind": "VulnerabilitiesIgnorePolicy",
|
||||
"targets": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"Registry": "quay.*",
|
||||
"Organization": "kube*",
|
||||
"ImageName": "kubescape*",
|
||||
"ImageTag": "v2*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"Registry": "docker.io",
|
||||
"Organization": ".*",
|
||||
"ImageName": "kube*",
|
||||
"ImageTag": "v3*"
|
||||
}
|
||||
}
|
||||
],
|
||||
"vulnerabilities": [
|
||||
"CVE-2023-6879",
|
||||
"CVE-2023-44487"
|
||||
],
|
||||
"severities": [
|
||||
"critical",
|
||||
"low"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,7 +1,9 @@
|
||||
package v1
|
||||
|
||||
type ImageScanInfo struct {
|
||||
Username string
|
||||
Password string
|
||||
Image string
|
||||
Username string
|
||||
Password string
|
||||
Image string
|
||||
Exceptions string
|
||||
UseDefaultMatchers bool
|
||||
}
|
||||
|
||||
@@ -3,30 +3,31 @@ 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"
|
||||
)
|
||||
|
||||
type IKubescape interface {
|
||||
Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) // TODO - use scanInfo from v1
|
||||
Context() context.Context
|
||||
|
||||
Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) // TODO - use scanInfo from v1
|
||||
|
||||
// policies
|
||||
List(ctx context.Context, listPolicies *metav1.ListPolicies) error // TODO - return list response
|
||||
Download(ctx context.Context, downloadInfo *metav1.DownloadInfo) error // TODO - return downloaded policies
|
||||
List(listPolicies *metav1.ListPolicies) error // TODO - return list response
|
||||
Download(downloadInfo *metav1.DownloadInfo) error // TODO - return downloaded policies
|
||||
|
||||
// config
|
||||
SetCachedConfig(setConfig *metav1.SetConfig) error
|
||||
ViewCachedConfig(viewConfig *metav1.ViewConfig) error
|
||||
DeleteCachedConfig(ctx context.Context, deleteConfig *metav1.DeleteConfig) error
|
||||
DeleteCachedConfig(deleteConfig *metav1.DeleteConfig) error
|
||||
|
||||
// fix
|
||||
Fix(ctx context.Context, fixInfo *metav1.FixInfo) error
|
||||
Fix(fixInfo *metav1.FixInfo) error
|
||||
|
||||
// patch
|
||||
Patch(ctx context.Context, patchInfo *metav1.PatchInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error)
|
||||
Patch(patchInfo *metav1.PatchInfo, scanInfo *cautils.ScanInfo) (bool, error)
|
||||
|
||||
// scan image
|
||||
ScanImage(ctx context.Context, 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"
|
||||
@@ -11,38 +10,42 @@ import (
|
||||
|
||||
type MockIKubescape struct{}
|
||||
|
||||
func (m *MockIKubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
|
||||
func (m *MockIKubescape) Context() context.Context {
|
||||
return context.TODO()
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) Scan(_ *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) List(ctx context.Context, listPolicies *metav1.ListPolicies) error {
|
||||
func (m *MockIKubescape) List(_ *metav1.ListPolicies) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) Download(ctx context.Context, 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(ctx context.Context, deleteConfig *metav1.DeleteConfig) error {
|
||||
func (m *MockIKubescape) DeleteCachedConfig(_ *metav1.DeleteConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) Fix(ctx context.Context, fixInfo *metav1.FixInfo) error {
|
||||
func (m *MockIKubescape) Fix(_ *metav1.FixInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) Patch(ctx context.Context, 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(ctx context.Context, 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
|
||||
|
||||
@@ -3,6 +3,7 @@ package opaprocessor
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -18,12 +19,11 @@ import (
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
"github.com/kubescape/opa-utils/resources"
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/rego"
|
||||
"github.com/open-policy-agent/opa/storage"
|
||||
opaprint "github.com/open-policy-agent/opa/topdown/print"
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/rego"
|
||||
"github.com/open-policy-agent/opa/v1/storage"
|
||||
opaprint "github.com/open-policy-agent/opa/v1/topdown/print"
|
||||
"go.opentelemetry.io/otel"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const ScoreConfigPath = "/resources/config"
|
||||
@@ -324,6 +324,7 @@ func (opap *OPAProcessor) runRegoOnK8s(ctx context.Context, rule *reporthandling
|
||||
// NOTE: OPA module compilation is the most resource-intensive operation.
|
||||
compiled, err := ast.CompileModulesWithOpt(modules, ast.CompileOpts{
|
||||
EnablePrintStatements: opap.printEnabled,
|
||||
ParserOptions: ast.ParserOptions{RegoVersion: ast.RegoV0},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("in 'runRegoOnK8s', failed to compile rule, name: %s, reason: %w", rule.Name, err)
|
||||
@@ -351,6 +352,7 @@ func (opap *OPAProcessor) Print(ctx opaprint.Context, str string) error {
|
||||
|
||||
func (opap *OPAProcessor) regoEval(ctx context.Context, inputObj []map[string]interface{}, compiledRego *ast.Compiler, store *storage.Store) ([]reporthandling.RuleResponse, error) {
|
||||
rego := rego.New(
|
||||
rego.SetRegoVersion(ast.RegoV0),
|
||||
rego.Query("data.armo_builtins"), // get package name from rule
|
||||
rego.Compiler(compiledRego),
|
||||
rego.Input(inputObj),
|
||||
|
||||
@@ -2,6 +2,7 @@ package opaprocessor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
@@ -10,11 +11,10 @@ import (
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/rego"
|
||||
"github.com/open-policy-agent/opa/topdown/builtins"
|
||||
"github.com/open-policy-agent/opa/types"
|
||||
"golang.org/x/exp/slices"
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/rego"
|
||||
"github.com/open-policy-agent/opa/v1/topdown/builtins"
|
||||
"github.com/open-policy-agent/opa/v1/types"
|
||||
)
|
||||
|
||||
// convertFrameworksToPolicies convert list of frameworks to list of policies
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -3,78 +3,39 @@ package printer
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/jwalton/gchalk"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
const (
|
||||
columnSeverity = iota
|
||||
columnRef = iota
|
||||
columnName = iota
|
||||
columnCounterFailed = iota
|
||||
columnCounterAll = iota
|
||||
columnComplianceScore = iota
|
||||
_rowLen = iota
|
||||
controlNameMaxLength = 70
|
||||
)
|
||||
const controlNameMaxLength = 70
|
||||
|
||||
func generateRow(controlSummary reportsummary.IControlSummary, infoToPrintInfo []infoStars, verbose bool) []string {
|
||||
row := make([]string, _rowLen)
|
||||
|
||||
// ignore passed results
|
||||
if !verbose && (controlSummary.GetStatus().IsPassed()) {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
row[columnSeverity] = getSeverityColumn(controlSummary)
|
||||
if len(controlSummary.GetName()) > controlNameMaxLength {
|
||||
row[columnName] = controlSummary.GetName()[:controlNameMaxLength] + "..."
|
||||
} else {
|
||||
row[columnName] = controlSummary.GetName()
|
||||
}
|
||||
row[columnCounterFailed] = fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed())
|
||||
row[columnCounterAll] = fmt.Sprintf("%d", controlSummary.NumberOfResources().All())
|
||||
row[columnComplianceScore] = getComplianceScoreColumn(controlSummary, infoToPrintInfo)
|
||||
if row[columnComplianceScore] == "-1%" {
|
||||
row[columnComplianceScore] = "N/A"
|
||||
}
|
||||
|
||||
return row
|
||||
type TableRow struct {
|
||||
ref string
|
||||
name string
|
||||
counterFailed string
|
||||
counterAll string
|
||||
severity string
|
||||
complianceScore string
|
||||
}
|
||||
|
||||
func shortFormatRow(dataRows [][]string) [][]string {
|
||||
rows := [][]string{}
|
||||
for _, dataRow := range dataRows {
|
||||
rows = append(rows, []string{fmt.Sprintf("Severity"+strings.Repeat(" ", 11)+": %+v\nControl Name"+strings.Repeat(" ", 7)+": %+v\nFailed Resources"+strings.Repeat(" ", 3)+": %+v\nAll Resources"+strings.Repeat(" ", 6)+": %+v\n%% Compliance-Score"+strings.Repeat(" ", 1)+": %+v", dataRow[columnSeverity], dataRow[columnName], dataRow[columnCounterFailed], dataRow[columnCounterAll], dataRow[columnComplianceScore])})
|
||||
// generateTableRow is responsible for generating the row that will be printed in the table
|
||||
func generateTableRow(controlSummary reportsummary.IControlSummary, infoToPrintInfo []infoStars) *TableRow {
|
||||
tableRow := &TableRow{
|
||||
ref: controlSummary.GetID(),
|
||||
name: controlSummary.GetName(),
|
||||
counterFailed: fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed()),
|
||||
counterAll: fmt.Sprintf("%d", controlSummary.NumberOfResources().All()),
|
||||
severity: apis.ControlSeverityToString(controlSummary.GetScoreFactor()),
|
||||
complianceScore: getComplianceScoreColumn(controlSummary, infoToPrintInfo),
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func generateRowPdf(controlSummary reportsummary.IControlSummary, infoToPrintInfo []infoStars, verbose bool) []string {
|
||||
row := make([]string, _rowLen)
|
||||
|
||||
// ignore passed results
|
||||
if !verbose && (controlSummary.GetStatus().IsPassed()) {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
row[columnSeverity] = apis.ControlSeverityToString(controlSummary.GetScoreFactor())
|
||||
row[columnRef] = controlSummary.GetID()
|
||||
if len(controlSummary.GetName()) > controlNameMaxLength {
|
||||
row[columnName] = controlSummary.GetName()[:controlNameMaxLength] + "..."
|
||||
} else {
|
||||
row[columnName] = controlSummary.GetName()
|
||||
tableRow.name = controlSummary.GetName()[:controlNameMaxLength] + "..."
|
||||
}
|
||||
row[columnCounterFailed] = fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed())
|
||||
row[columnCounterAll] = fmt.Sprintf("%d", controlSummary.NumberOfResources().All())
|
||||
row[columnComplianceScore] = getComplianceScoreColumn(controlSummary, infoToPrintInfo)
|
||||
|
||||
return row
|
||||
return tableRow
|
||||
}
|
||||
|
||||
func getInfoColumn(controlSummary reportsummary.IControlSummary, infoToPrintInfo []infoStars) string {
|
||||
@@ -90,7 +51,12 @@ func getComplianceScoreColumn(controlSummary reportsummary.IControlSummary, info
|
||||
if controlSummary.GetStatus().IsSkipped() {
|
||||
return fmt.Sprintf("%s %s", "Action Required", getInfoColumn(controlSummary, infoToPrintInfo))
|
||||
}
|
||||
return fmt.Sprintf("%d", cautils.Float32ToInt(controlSummary.GetComplianceScore())) + "%"
|
||||
if compliance := cautils.Float32ToInt(controlSummary.GetComplianceScore()); compliance < 0 {
|
||||
return "N/A"
|
||||
} else {
|
||||
return fmt.Sprintf("%d", cautils.Float32ToInt(controlSummary.GetComplianceScore())) + "%"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getSeverityColumn(controlSummary reportsummary.IControlSummary) string {
|
||||
@@ -124,45 +90,3 @@ func getSortedControlsIDs(controls reportsummary.ControlSummaries) [][]string {
|
||||
}
|
||||
return controlIDs
|
||||
}
|
||||
|
||||
/* unused for now
|
||||
func getSortedControlsNames(controls reportsummary.ControlSummaries) [][]string {
|
||||
controlNames := make([][]string, 5)
|
||||
for k := range controls {
|
||||
c := controls[k]
|
||||
i := apis.ControlSeverityToInt(c.GetScoreFactor())
|
||||
controlNames[i] = append(controlNames[i], c.GetName())
|
||||
}
|
||||
for i := range controlNames {
|
||||
sort.Strings(controlNames[i])
|
||||
}
|
||||
return controlNames
|
||||
}
|
||||
*/
|
||||
|
||||
func getControlTableHeaders(short bool) []string {
|
||||
var headers []string
|
||||
if short {
|
||||
headers = make([]string, 1)
|
||||
headers[0] = "Controls"
|
||||
} else {
|
||||
headers = make([]string, _rowLen)
|
||||
headers[columnRef] = "Control reference"
|
||||
headers[columnName] = "Control name"
|
||||
headers[columnCounterFailed] = "Failed resources"
|
||||
headers[columnCounterAll] = "All resources"
|
||||
headers[columnSeverity] = "Severity"
|
||||
headers[columnComplianceScore] = "Compliance score"
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func getColumnsAlignments() []int {
|
||||
alignments := make([]int, _rowLen)
|
||||
alignments[columnName] = tablewriter.ALIGN_LEFT
|
||||
alignments[columnCounterFailed] = tablewriter.ALIGN_CENTER
|
||||
alignments[columnCounterAll] = tablewriter.ALIGN_CENTER
|
||||
alignments[columnSeverity] = tablewriter.ALIGN_LEFT
|
||||
alignments[columnComplianceScore] = tablewriter.ALIGN_CENTER
|
||||
return alignments
|
||||
}
|
||||
|
||||
@@ -23,45 +23,43 @@ func Test_generateRowPdf(t *testing.T) {
|
||||
infoToPrintInfoMap := mapInfoToPrintInfo(mockSummary.Controls)
|
||||
sortedControlIDs := getSortedControlsIDs(mockSummary.Controls)
|
||||
|
||||
var results [][]string
|
||||
var rows []TableRow
|
||||
|
||||
for i := len(sortedControlIDs) - 1; i >= 0; i-- {
|
||||
for _, c := range sortedControlIDs[i] {
|
||||
result := generateRowPdf(mockSummary.Controls.GetControl(reportsummary.EControlCriteriaID, c), infoToPrintInfoMap, true)
|
||||
if len(result) > 0 {
|
||||
results = append(results, result)
|
||||
}
|
||||
row := *generateTableRow(mockSummary.Controls.GetControl(reportsummary.EControlCriteriaID, c), infoToPrintInfoMap)
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range results {
|
||||
for _, row := range rows {
|
||||
//validating severity column
|
||||
if c[0] != "Low" && c[0] != "Medium" && c[0] != "High" && c[0] != "Critical" {
|
||||
t.Errorf("got %s, want either of these: %s", c[0], "Low, Medium, High, Critical")
|
||||
if row.severity != "Low" && row.severity != "Medium" && row.severity != "High" && row.severity != "Critical" {
|
||||
t.Errorf("got %s, want either of these: %s", row.severity, "Low, Medium, High, Critical")
|
||||
}
|
||||
|
||||
// Validating length of control ID
|
||||
if len(c[1]) > 6 {
|
||||
t.Errorf("got %s, want %s", c[1], "less than 7 characters")
|
||||
if len(row.ref) > 6 {
|
||||
t.Errorf("got %s, want %s", row.ref, "less than 7 characters")
|
||||
}
|
||||
|
||||
// Validating length of control name
|
||||
if len(c[2]) > controlNameMaxLength {
|
||||
t.Errorf("got %s, want %s", c[1], fmt.Sprintf("less than %d characters", controlNameMaxLength))
|
||||
if len(row.name) > controlNameMaxLength {
|
||||
t.Errorf("got %s, want %s", row.name, fmt.Sprintf("less than %d characters", controlNameMaxLength))
|
||||
}
|
||||
|
||||
// Validating numeric fields
|
||||
_, err := strconv.Atoi(c[3])
|
||||
_, err := strconv.Atoi(row.counterFailed)
|
||||
if err != nil {
|
||||
t.Errorf("got %s, want an integer %s", c[2], err)
|
||||
t.Errorf("got %s, want an integer %s", row.counterFailed, err)
|
||||
}
|
||||
|
||||
_, err = strconv.Atoi(c[4])
|
||||
_, err = strconv.Atoi(row.counterAll)
|
||||
if err != nil {
|
||||
t.Errorf("got %s, want an integer %s", c[3], err)
|
||||
t.Errorf("got %s, want an integer %s", row.counterAll, err)
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, c[5], "expected a non-empty string")
|
||||
assert.NotEmpty(t, row.complianceScore, "expected a non-empty string")
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -168,14 +167,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() {
|
||||
|
||||
}
|
||||
|
||||
@@ -3,21 +3,18 @@ package printer
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/johnfercher/maroto/pkg/color"
|
||||
"github.com/johnfercher/maroto/pkg/consts"
|
||||
"github.com/johnfercher/maroto/pkg/pdf"
|
||||
"github.com/johnfercher/maroto/pkg/props"
|
||||
"github.com/johnfercher/maroto/v2/pkg/props"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer"
|
||||
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/pdf"
|
||||
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
)
|
||||
@@ -27,11 +24,6 @@ const (
|
||||
pdfOutputExt = ".pdf"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed pdf/logo.png
|
||||
kubescapeLogo []byte
|
||||
)
|
||||
|
||||
var _ printer.IPrinter = &PdfPrinter{}
|
||||
|
||||
type PdfPrinter struct {
|
||||
@@ -66,219 +58,79 @@ func (pp *PdfPrinter) Score(score float32) {
|
||||
|
||||
fmt.Fprintf(os.Stderr, "\nOverall compliance-score (100- Excellent, 0- All failed): %d\n", cautils.Float32ToInt(score))
|
||||
}
|
||||
func (pp *PdfPrinter) printInfo(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails, infoMap []infoStars) {
|
||||
emptyRowCounter := 1
|
||||
for i := range infoMap {
|
||||
if infoMap[i].info != "" {
|
||||
m.Row(5, func() {
|
||||
m.Col(12, func() {
|
||||
m.Text(fmt.Sprintf("%v %v", infoMap[i].stars, infoMap[i].info), props.Text{
|
||||
Style: consts.Bold,
|
||||
Align: consts.Left,
|
||||
Size: 8,
|
||||
Extrapolate: false,
|
||||
Color: color.Color{
|
||||
Red: 0,
|
||||
Green: 0,
|
||||
Blue: 255,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
if emptyRowCounter < len(infoMap) {
|
||||
m.Row(2.5, func() {})
|
||||
emptyRowCounter++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (pp *PdfPrinter) PrintNextSteps() {
|
||||
|
||||
}
|
||||
|
||||
// ActionPrint is responsible for generating a report in pdf format
|
||||
func (pp *PdfPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
|
||||
if opaSessionObj == nil {
|
||||
logger.L().Ctx(ctx).Error("failed to print results, missing data")
|
||||
return
|
||||
}
|
||||
|
||||
sortedControlIDs := getSortedControlsIDs(opaSessionObj.Report.SummaryDetails.Controls)
|
||||
|
||||
infoToPrintInfo := mapInfoToPrintInfo(opaSessionObj.Report.SummaryDetails.Controls)
|
||||
m := pdf.NewMaroto(consts.Portrait, consts.A4)
|
||||
pp.printHeader(m)
|
||||
pp.printFramework(m, opaSessionObj.Report.SummaryDetails.ListFrameworks())
|
||||
pp.printTable(m, &opaSessionObj.Report.SummaryDetails, sortedControlIDs)
|
||||
pp.printFinalResult(m, &opaSessionObj.Report.SummaryDetails)
|
||||
pp.printInfo(m, &opaSessionObj.Report.SummaryDetails, infoToPrintInfo)
|
||||
|
||||
// Extrat output buffer.
|
||||
outBuff, err := m.Output()
|
||||
outBuff, err := pp.generatePdf(&opaSessionObj.Report.SummaryDetails)
|
||||
if err != nil {
|
||||
logger.L().Ctx(ctx).Error("failed to generate pdf format", helpers.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := pp.writer.Write(outBuff.Bytes()); err != nil {
|
||||
if _, err := pp.writer.Write(outBuff); err != nil {
|
||||
logger.L().Ctx(ctx).Error("failed to write results", helpers.Error(err))
|
||||
return
|
||||
}
|
||||
printer.LogOutputFile(pp.writer.Name())
|
||||
}
|
||||
|
||||
// printHeader prints the Kubescape logo and report date
|
||||
func (pp *PdfPrinter) printHeader(m pdf.Maroto) {
|
||||
// Retrieve current time (we need it for the report timestamp).
|
||||
t := time.Now()
|
||||
// Enconde PNG into Base64 to embed it into the pdf.
|
||||
kubescapeLogoEnc := b64.StdEncoding.EncodeToString(kubescapeLogo)
|
||||
func (pp *PdfPrinter) generatePdf(summaryDetails *reportsummary.SummaryDetails) ([]byte, error) {
|
||||
sortedControlIDs := getSortedControlsIDs(summaryDetails.Controls)
|
||||
infoToPrintInfo := mapInfoToPrintInfo(summaryDetails.Controls)
|
||||
|
||||
m.SetPageMargins(10, 15, 10)
|
||||
m.Row(40, func() {
|
||||
//m.Text(fmt.Sprintf("Security Assessment"), props.Text{
|
||||
// Align: consts.Center,
|
||||
// Size: 24,
|
||||
// Family: consts.Arial,
|
||||
// Style: consts.Bold,
|
||||
//})
|
||||
_ = m.Base64Image(kubescapeLogoEnc, consts.Png, props.Rect{
|
||||
Center: true,
|
||||
Percent: 100,
|
||||
})
|
||||
})
|
||||
m.Row(6, func() {
|
||||
m.Text(fmt.Sprintf("Report date: %d-%02d-%02dT%02d:%02d:%02d",
|
||||
t.Year(),
|
||||
t.Month(),
|
||||
t.Day(),
|
||||
t.Hour(),
|
||||
t.Minute(),
|
||||
t.Second()), props.Text{
|
||||
Align: consts.Left,
|
||||
Size: 6.0,
|
||||
Style: consts.Bold,
|
||||
Family: consts.Arial,
|
||||
})
|
||||
})
|
||||
m.Line(1)
|
||||
template := pdf.NewReportTemplate()
|
||||
template.GenerateHeader(utils.FrameworksScoresToString(summaryDetails.ListFrameworks()), time.Now().Format(time.DateTime))
|
||||
err := template.GenerateTable(pp.getTableObjects(summaryDetails, sortedControlIDs),
|
||||
summaryDetails.NumberOfResources().Failed(), summaryDetails.NumberOfResources().All(), summaryDetails.ComplianceScore)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
template.GenerateInfoRows(pp.getFormattedInformation(infoToPrintInfo))
|
||||
return template.GetPdf()
|
||||
}
|
||||
|
||||
// printFramework prints the PDF frameworks after the PDF header
|
||||
func (pp *PdfPrinter) printFramework(m pdf.Maroto, frameworks []reportsummary.IFrameworkSummary) {
|
||||
m.Row(10, func() {
|
||||
m.Text(utils.FrameworksScoresToString(frameworks), props.Text{
|
||||
Align: consts.Center,
|
||||
Size: 8,
|
||||
Family: consts.Arial,
|
||||
Style: consts.Bold,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// printTable creates the PDF table
|
||||
func (pp *PdfPrinter) printTable(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
headers := getControlTableHeaders(false)
|
||||
infoToPrintInfoMap := mapInfoToPrintInfo(summaryDetails.Controls)
|
||||
var controls [][]string
|
||||
for i := len(sortedControlIDs) - 1; i >= 0; i-- {
|
||||
for _, c := range sortedControlIDs[i] {
|
||||
row := generateRowPdf(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, c), infoToPrintInfoMap, true)
|
||||
if len(row) > 0 {
|
||||
controls = append(controls, row)
|
||||
}
|
||||
func (pp *PdfPrinter) getFormattedInformation(infoMap []infoStars) []string {
|
||||
rows := make([]string, 0, len(infoMap))
|
||||
for i := range infoMap {
|
||||
if infoMap[i].info != "" {
|
||||
rows = append(rows, fmt.Sprintf("%v %v", infoMap[i].stars, infoMap[i].info))
|
||||
}
|
||||
}
|
||||
|
||||
size := 6.0
|
||||
gridSize := []uint{1, 1, 6, 1, 1, 2}
|
||||
|
||||
m.TableList(headers, controls, props.TableList{
|
||||
HeaderProp: props.TableListContent{
|
||||
Family: consts.Arial,
|
||||
Style: consts.Bold,
|
||||
Size: size,
|
||||
GridSizes: gridSize,
|
||||
},
|
||||
ContentProp: props.TableListContent{
|
||||
Family: consts.Courier,
|
||||
Style: consts.Normal,
|
||||
Size: size,
|
||||
GridSizes: gridSize,
|
||||
CellTextColorChangerColumnIndex: 0,
|
||||
CellTextColorChangerFunc: func(cellValue string) color.Color {
|
||||
if cellValue == "Critical" {
|
||||
return color.Color{
|
||||
Red: 255,
|
||||
Green: 0,
|
||||
Blue: 0,
|
||||
}
|
||||
} else if cellValue == "High" {
|
||||
return color.Color{
|
||||
Red: 0,
|
||||
Green: 0,
|
||||
Blue: 255,
|
||||
}
|
||||
} else if cellValue == "Medium" {
|
||||
return color.Color{
|
||||
Red: 252,
|
||||
Green: 186,
|
||||
Blue: 3,
|
||||
}
|
||||
}
|
||||
return color.NewBlack()
|
||||
},
|
||||
},
|
||||
Align: consts.Left,
|
||||
AlternatedBackground: &color.Color{
|
||||
Red: 224,
|
||||
Green: 224,
|
||||
Blue: 224,
|
||||
},
|
||||
HeaderContentSpace: 2.0,
|
||||
Line: false,
|
||||
})
|
||||
m.Line(1)
|
||||
m.Row(2, func() {})
|
||||
return rows
|
||||
}
|
||||
|
||||
// printFinalResult adds the final results
|
||||
func (pp *PdfPrinter) printFinalResult(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails) {
|
||||
m.Row(_rowLen, func() {
|
||||
m.Col(1, func() {
|
||||
})
|
||||
m.Col(5, func() {
|
||||
m.Text("Resource summary", props.Text{
|
||||
Align: consts.Left,
|
||||
Size: 8.0,
|
||||
Style: consts.Bold,
|
||||
Family: consts.Arial,
|
||||
})
|
||||
})
|
||||
m.Col(2, func() {
|
||||
m.Text(fmt.Sprintf("%d", summaryDetails.NumberOfResources().Failed()), props.Text{
|
||||
Align: consts.Left,
|
||||
Size: 8.0,
|
||||
Style: consts.Bold,
|
||||
Family: consts.Arial,
|
||||
})
|
||||
})
|
||||
m.Col(2, func() {
|
||||
m.Text(fmt.Sprintf("%d", summaryDetails.NumberOfResources().All()), props.Text{
|
||||
Align: consts.Left,
|
||||
Size: 8.0,
|
||||
Style: consts.Bold,
|
||||
Family: consts.Arial,
|
||||
})
|
||||
})
|
||||
m.Col(2, func() {
|
||||
m.Text(fmt.Sprintf("%.2f%s", summaryDetails.ComplianceScore, "%"), props.Text{
|
||||
Align: consts.Left,
|
||||
Size: 8.0,
|
||||
Style: consts.Bold,
|
||||
Family: consts.Arial,
|
||||
})
|
||||
})
|
||||
})
|
||||
// getTableData is responsible for getting the table data in a standardized format
|
||||
func (pp *PdfPrinter) getTableObjects(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) *[]pdf.TableObject {
|
||||
infoToPrintInfoMap := mapInfoToPrintInfo(summaryDetails.Controls)
|
||||
var controls []pdf.TableObject
|
||||
for i := len(sortedControlIDs) - 1; i >= 0; i-- {
|
||||
for _, c := range sortedControlIDs[i] {
|
||||
row := generateTableRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, c), infoToPrintInfoMap)
|
||||
controls = append(controls, *pdf.NewTableRow(
|
||||
row.ref, row.name, row.counterFailed, row.counterAll, row.severity, row.complianceScore, getSeverityColor,
|
||||
))
|
||||
}
|
||||
}
|
||||
return &controls
|
||||
}
|
||||
|
||||
func getSeverityColor(severity string) *props.Color {
|
||||
if severity == "Critical" {
|
||||
return &props.Color{Red: 255, Green: 0, Blue: 0}
|
||||
} else if severity == "High" {
|
||||
return &props.Color{Red: 0, Green: 0, Blue: 255}
|
||||
} else if severity == "Medium" {
|
||||
return &props.Color{Red: 252, Green: 186, Blue: 3}
|
||||
}
|
||||
return &props.BlackColor
|
||||
}
|
||||
|
||||
1
core/pkg/resultshandling/printer/v2/pdf/.maroto.yml
Normal file
1
core/pkg/resultshandling/printer/v2/pdf/.maroto.yml
Normal file
@@ -0,0 +1 @@
|
||||
test_path: "testStructure/"
|
||||
195
core/pkg/resultshandling/printer/v2/pdf/report_template.go
Normal file
195
core/pkg/resultshandling/printer/v2/pdf/report_template.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package pdf
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/johnfercher/go-tree/node"
|
||||
"github.com/johnfercher/maroto/v2"
|
||||
"github.com/johnfercher/maroto/v2/pkg/components/image"
|
||||
"github.com/johnfercher/maroto/v2/pkg/components/line"
|
||||
"github.com/johnfercher/maroto/v2/pkg/components/list"
|
||||
"github.com/johnfercher/maroto/v2/pkg/components/row"
|
||||
"github.com/johnfercher/maroto/v2/pkg/components/text"
|
||||
"github.com/johnfercher/maroto/v2/pkg/config"
|
||||
"github.com/johnfercher/maroto/v2/pkg/consts/align"
|
||||
"github.com/johnfercher/maroto/v2/pkg/consts/extension"
|
||||
"github.com/johnfercher/maroto/v2/pkg/consts/fontfamily"
|
||||
"github.com/johnfercher/maroto/v2/pkg/consts/fontstyle"
|
||||
"github.com/johnfercher/maroto/v2/pkg/consts/orientation"
|
||||
"github.com/johnfercher/maroto/v2/pkg/consts/pagesize"
|
||||
"github.com/johnfercher/maroto/v2/pkg/core"
|
||||
"github.com/johnfercher/maroto/v2/pkg/props"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed logo.png
|
||||
kubescapeLogo []byte
|
||||
)
|
||||
|
||||
type getTextColorFunc func(severity string) *props.Color
|
||||
|
||||
type Template struct {
|
||||
maroto core.Maroto
|
||||
}
|
||||
|
||||
// New Report Template is responsible for creating an object that generates a report with the submitted data
|
||||
func NewReportTemplate() *Template {
|
||||
return &Template{
|
||||
maroto: maroto.New(
|
||||
config.NewBuilder().
|
||||
WithPageSize(pagesize.A4).
|
||||
WithOrientation(orientation.Vertical).
|
||||
WithLeftMargin(10).
|
||||
WithTopMargin(15).
|
||||
WithRightMargin(10).
|
||||
Build()),
|
||||
}
|
||||
}
|
||||
|
||||
// GetPdf is responsible for generating the pdf and returning the file's bytes
|
||||
func (t *Template) GetPdf() ([]byte, error) {
|
||||
doc, err := t.maroto.Generate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return doc.GetBytes(), nil
|
||||
}
|
||||
|
||||
// printHeader prints the Kubescape logo, report date and framework
|
||||
func (t *Template) GenerateHeader(scoreOfScannedFrameworks, reportDate string) *Template {
|
||||
t.maroto.AddRow(40, image.NewFromBytesCol(12, kubescapeLogo, extension.Png, props.Rect{
|
||||
Center: true,
|
||||
Percent: 100,
|
||||
}))
|
||||
|
||||
t.maroto.AddRow(6, text.NewCol(12, fmt.Sprintf("Report date: %s", reportDate),
|
||||
props.Text{
|
||||
Align: align.Left,
|
||||
Size: 6.0,
|
||||
Style: fontstyle.Bold,
|
||||
Family: fontfamily.Arial,
|
||||
}))
|
||||
|
||||
t.maroto.AddAutoRow(line.NewCol(12, props.Line{Thickness: 0.3, SizePercent: 100}))
|
||||
|
||||
t.maroto.AddRow(10, text.NewCol(12, scoreOfScannedFrameworks, props.Text{
|
||||
Align: align.Center,
|
||||
Size: 8,
|
||||
Family: fontfamily.Arial,
|
||||
Style: fontstyle.Bold,
|
||||
}))
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// GenerateTable is responsible for adding data in table format to the pdf
|
||||
func (t *Template) GenerateTable(tableRows *[]TableObject, totalFailed, total int, score float32) error {
|
||||
rows, err := list.Build[TableObject](*tableRows)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.maroto.AddRows(rows...)
|
||||
t.maroto.AddRows(
|
||||
line.NewAutoRow(props.Line{Thickness: 0.3, SizePercent: 100}),
|
||||
row.New(2),
|
||||
)
|
||||
t.generateTableTableResult(totalFailed, total, score)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateInfoRows is responsible for adding the information in pdf
|
||||
func (t *Template) GenerateInfoRows(rows []string) *Template {
|
||||
for _, row := range rows {
|
||||
t.maroto.AddAutoRow(text.NewCol(12, row, props.Text{
|
||||
Style: fontstyle.Bold,
|
||||
Align: align.Left,
|
||||
Top: 2.5,
|
||||
Size: 8,
|
||||
Color: &props.Color{
|
||||
Red: 0,
|
||||
Green: 0,
|
||||
Blue: 255,
|
||||
},
|
||||
}))
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Template) generateTableTableResult(totalFailed, total int, score float32) {
|
||||
defaultProps := props.Text{
|
||||
Align: align.Left,
|
||||
Size: 8,
|
||||
Style: fontstyle.Bold,
|
||||
Family: fontfamily.Arial,
|
||||
}
|
||||
|
||||
t.maroto.AddRow(10,
|
||||
text.NewCol(5, "Resource summary", defaultProps),
|
||||
text.NewCol(2, fmt.Sprintf("%d", totalFailed), defaultProps),
|
||||
text.NewCol(2, fmt.Sprintf("%d", total), defaultProps),
|
||||
text.NewCol(2, fmt.Sprintf("%.2f%s", score, "%"), defaultProps),
|
||||
)
|
||||
}
|
||||
|
||||
func (t *Template) GetStructure() *node.Node[core.Structure] {
|
||||
return t.maroto.GetStructure()
|
||||
}
|
||||
|
||||
// TableObject is responsible for mapping the table data, it will be sent to Maroto and will make it possible to generate the table
|
||||
type TableObject struct {
|
||||
ref string
|
||||
name string
|
||||
counterFailed string
|
||||
counterAll string
|
||||
severity string
|
||||
complianceScore string
|
||||
getTextColor getTextColorFunc
|
||||
}
|
||||
|
||||
func NewTableRow(ref, name, counterFailed, counterAll, severity, score string, getTextColor getTextColorFunc) *TableObject {
|
||||
return &TableObject{
|
||||
ref: ref,
|
||||
name: name,
|
||||
counterFailed: counterFailed,
|
||||
counterAll: counterAll,
|
||||
severity: severity,
|
||||
complianceScore: score,
|
||||
getTextColor: getTextColor,
|
||||
}
|
||||
}
|
||||
|
||||
func (t TableObject) GetHeader() core.Row {
|
||||
return row.New(10).Add(
|
||||
text.NewCol(1, "Severity", props.Text{Size: 6, Family: fontfamily.Arial, Style: fontstyle.Bold}),
|
||||
text.NewCol(1, "Control reference", props.Text{Size: 6, Family: fontfamily.Arial, Style: fontstyle.Bold}),
|
||||
text.NewCol(6, "Control name", props.Text{Size: 6, Family: fontfamily.Arial, Style: fontstyle.Bold}),
|
||||
text.NewCol(1, "Failed resources", props.Text{Size: 6, Family: fontfamily.Arial, Style: fontstyle.Bold}),
|
||||
text.NewCol(1, "All resources", props.Text{Size: 6, Family: fontfamily.Arial, Style: fontstyle.Bold}),
|
||||
text.NewCol(2, "Compliance score", props.Text{Size: 6, Family: fontfamily.Arial, Style: fontstyle.Bold}),
|
||||
)
|
||||
}
|
||||
|
||||
func (t TableObject) GetContent(i int) core.Row {
|
||||
r := row.New(3).Add(
|
||||
text.NewCol(1, t.severity, props.Text{Style: fontstyle.Normal, Family: fontfamily.Courier, Size: 6, Color: t.getTextColor(t.severity)}),
|
||||
text.NewCol(1, t.ref, props.Text{Style: fontstyle.Normal, Family: fontfamily.Courier, Size: 6, Color: &props.Color{}}),
|
||||
text.NewCol(6, t.name, props.Text{Style: fontstyle.Normal, Family: fontfamily.Courier, Size: 6}),
|
||||
text.NewCol(1, t.counterFailed, props.Text{Style: fontstyle.Normal, Family: fontfamily.Courier, Size: 6}),
|
||||
text.NewCol(1, t.counterAll, props.Text{Style: fontstyle.Normal, Family: fontfamily.Courier, Size: 6}),
|
||||
text.NewCol(2, t.complianceScore, props.Text{VerticalPadding: 1, Style: fontstyle.Normal, Family: fontfamily.Courier, Size: 6}),
|
||||
)
|
||||
|
||||
if i%2 == 0 {
|
||||
r.WithStyle(&props.Cell{
|
||||
BackgroundColor: &props.Color{
|
||||
Red: 224,
|
||||
Green: 224,
|
||||
Blue: 224,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package pdf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/johnfercher/maroto/v2/pkg/props"
|
||||
"github.com/johnfercher/maroto/v2/pkg/test"
|
||||
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/pdf"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetPdf(t *testing.T) {
|
||||
t.Run("when GetPdf is called, it should return pdf bytes", func(t *testing.T) {
|
||||
|
||||
template := pdf.NewReportTemplate().GenerateHeader("Framework test 1, Framework test 2", "2024-04-01 20:31:00")
|
||||
bytes, err := template.GetPdf()
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, bytes)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateHeader(t *testing.T) {
|
||||
t.Run("when generateHeader is called, it should set the header in the pdf", func(t *testing.T) {
|
||||
template := pdf.NewReportTemplate().GenerateHeader("Framework test 1, Framework test 2", "2024-04-01 20:31:00")
|
||||
|
||||
node := template.GetStructure()
|
||||
|
||||
assert.NotNil(t, node)
|
||||
test.New(t).Assert(node).Equals("headerTemplate.json")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateTable(t *testing.T) {
|
||||
t.Run("when generateTable is called, it should set the table in the pdf", func(t *testing.T) {
|
||||
TableObjectMock := pdf.NewTableRow(
|
||||
"ref", "name", "failed", "all", "severity", "score",
|
||||
func(severity string) *props.Color { return &props.Color{Red: 0, Blue: 0, Green: 0} },
|
||||
)
|
||||
|
||||
template := pdf.NewReportTemplate()
|
||||
|
||||
err := template.GenerateTable(&[]pdf.TableObject{*TableObjectMock}, 100, 10, 10.0)
|
||||
|
||||
assert.Nil(t, err)
|
||||
test.New(t).Assert(template.GetStructure()).Equals("tableTemplate.json")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateInfoRows(t *testing.T) {
|
||||
t.Run("when generateInfoRows is called, it should set the info rows in the pdf", func(t *testing.T) {
|
||||
|
||||
template := pdf.NewReportTemplate().GenerateInfoRows([]string{"row info 1", "row info 2", "row info 3"})
|
||||
|
||||
assert.NotNil(t, template)
|
||||
test.New(t).Assert(template.GetStructure()).Equals("infoTemplate.json")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"type": "maroto",
|
||||
"details": {
|
||||
"chunk_workers": 1,
|
||||
"config_margin_bottom": 20.0025,
|
||||
"config_margin_left": 10,
|
||||
"config_margin_right": 10,
|
||||
"config_margin_top": 15,
|
||||
"config_max_grid_sum": 12,
|
||||
"config_provider_type": "gofpdf",
|
||||
"generation_mode": "sequential",
|
||||
"maroto_dimension_height": 297,
|
||||
"maroto_dimension_width": 210,
|
||||
"prop_font_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 10
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"type": "page",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 40,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 12,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "iVBORw0KGgoAAA==",
|
||||
"type": "bytesImage",
|
||||
"details": {
|
||||
"bytes_size": 54270,
|
||||
"extension": "png",
|
||||
"prop_center": true,
|
||||
"prop_percent": 100
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 6,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 12,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "Report date: 2024-04-01 20:31:00",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 6,
|
||||
"prop_font_style": "B"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 0.3,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 12,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "line",
|
||||
"details": {
|
||||
"prop_offset_percent": 5,
|
||||
"prop_orientation": "horizontal",
|
||||
"prop_size_percent": 100,
|
||||
"prop_style": "solid",
|
||||
"prop_thickness": 0.3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 10,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 12,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "Framework test 1, Framework test 2",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "C",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 8,
|
||||
"prop_font_style": "B"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 205.6975,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 12,
|
||||
"type": "col"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"type": "maroto",
|
||||
"details": {
|
||||
"chunk_workers": 1,
|
||||
"config_margin_bottom": 20.0025,
|
||||
"config_margin_left": 10,
|
||||
"config_margin_right": 10,
|
||||
"config_margin_top": 15,
|
||||
"config_max_grid_sum": 12,
|
||||
"config_provider_type": "gofpdf",
|
||||
"generation_mode": "sequential",
|
||||
"maroto_dimension_height": 297,
|
||||
"maroto_dimension_width": 210,
|
||||
"prop_font_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 10
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"type": "page",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 5.322222222222223,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 12,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "row info 1",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 255)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 8,
|
||||
"prop_font_style": "B",
|
||||
"prop_top": 2.5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 5.322222222222223,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 12,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "row info 2",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 255)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 8,
|
||||
"prop_font_style": "B",
|
||||
"prop_top": 2.5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 5.322222222222223,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 12,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "row info 3",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 255)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 8,
|
||||
"prop_font_style": "B",
|
||||
"prop_top": 2.5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 246.03083333333333,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 12,
|
||||
"type": "col"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
{
|
||||
"type": "maroto",
|
||||
"details": {
|
||||
"chunk_workers": 1,
|
||||
"config_margin_bottom": 20.0025,
|
||||
"config_margin_left": 10,
|
||||
"config_margin_right": 10,
|
||||
"config_margin_top": 15,
|
||||
"config_max_grid_sum": 12,
|
||||
"config_provider_type": "gofpdf",
|
||||
"generation_mode": "sequential",
|
||||
"maroto_dimension_height": 297,
|
||||
"maroto_dimension_width": 210,
|
||||
"prop_font_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 10
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"type": "page",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 10,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 1,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "Severity",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 6,
|
||||
"prop_font_style": "B"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "Control reference",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 6,
|
||||
"prop_font_style": "B"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 6,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "Control name",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 6,
|
||||
"prop_font_style": "B"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "Failed resources",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 6,
|
||||
"prop_font_style": "B"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "All resources",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 6,
|
||||
"prop_font_style": "B"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "Compliance score",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 6,
|
||||
"prop_font_style": "B"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"type": "row",
|
||||
"details": {
|
||||
"prop_background_color": "RGB(224, 224, 224)"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"value": 1,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "severity",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "courier",
|
||||
"prop_font_size": 6
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "ref",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "courier",
|
||||
"prop_font_size": 6
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 6,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "name",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "courier",
|
||||
"prop_font_size": 6
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "failed",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "courier",
|
||||
"prop_font_size": 6
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "all",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "courier",
|
||||
"prop_font_size": 6
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "score",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "courier",
|
||||
"prop_font_size": 6,
|
||||
"prop_vertical_padding": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 0.3,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 0,
|
||||
"type": "col",
|
||||
"details": {
|
||||
"is_max": true
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"type": "line",
|
||||
"details": {
|
||||
"prop_offset_percent": 5,
|
||||
"prop_orientation": "horizontal",
|
||||
"prop_size_percent": 100,
|
||||
"prop_style": "solid",
|
||||
"prop_thickness": 0.3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 0,
|
||||
"type": "col",
|
||||
"details": {
|
||||
"is_max": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 10,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 5,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "Resource summary",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 8,
|
||||
"prop_font_style": "B"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "100",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 8,
|
||||
"prop_font_style": "B"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "10",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 8,
|
||||
"prop_font_style": "B"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"type": "col",
|
||||
"nodes": [
|
||||
{
|
||||
"value": "10.00%",
|
||||
"type": "text",
|
||||
"details": {
|
||||
"prop_align": "L",
|
||||
"prop_breakline_strategy": "empty_space_strategy",
|
||||
"prop_color": "RGB(0, 0, 0)",
|
||||
"prop_font_family": "arial",
|
||||
"prop_font_size": 8,
|
||||
"prop_font_style": "B"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": 236.6975,
|
||||
"type": "row",
|
||||
"nodes": [
|
||||
{
|
||||
"value": 12,
|
||||
"type": "col"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user