mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
Compare commits
143 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ae2d3646b | ||
|
|
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 | ||
|
|
1c3b2831a2 | ||
|
|
8a19a73bb1 | ||
|
|
d966b0acbc | ||
|
|
14ffe35437 | ||
|
|
985d72e5fb | ||
|
|
70a9380966 | ||
|
|
f706d126f5 | ||
|
|
600f19406e | ||
|
|
d7ebf3239b | ||
|
|
5e0b25b04a | ||
|
|
98fe2347fa | ||
|
|
9b22d3284e | ||
|
|
9544e9cd66 | ||
|
|
1ed1bb11f2 | ||
|
|
b8ca1fcbce | ||
|
|
326a3e4c63 | ||
|
|
b348acd291 | ||
|
|
4fc3eacf7b | ||
|
|
d6030a9c03 | ||
|
|
e87bf7b723 | ||
|
|
4ef0b27ccf | ||
|
|
219582b92a | ||
|
|
07ed8c61f1 | ||
|
|
c585abc21a | ||
|
|
08696c583a | ||
|
|
7d94dc74bb | ||
|
|
570369a66f | ||
|
|
97f24920e8 | ||
|
|
f57305280f | ||
|
|
53c134cbc3 | ||
|
|
3c3a1838e3 | ||
|
|
adfd09a9d4 | ||
|
|
43ac47ec51 | ||
|
|
ec715ab68b | ||
|
|
fbff5873f7 | ||
|
|
a81eab0a1a | ||
|
|
cfc52856b3 | ||
|
|
5707d7f7e4 | ||
|
|
4f3ef49f99 | ||
|
|
a9ac880356 | ||
|
|
761d4c6ff4 | ||
|
|
bbb2aafc7e | ||
|
|
7735087937 | ||
|
|
5b9c6491de | ||
|
|
b0e3744140 | ||
|
|
0451cdb345 | ||
|
|
4546465f4a | ||
|
|
52c564b2a4 | ||
|
|
0abc81003e | ||
|
|
817d4902ff | ||
|
|
5553a1adf0 | ||
|
|
e95352d31e | ||
|
|
5655051a95 | ||
|
|
90c359533f | ||
|
|
7013d83aa8 | ||
|
|
c4935671fe | ||
|
|
e67ef1c54d | ||
|
|
5d6b9a5e83 | ||
|
|
a6752a5a6d | ||
|
|
6f47f8dae0 | ||
|
|
d82e8daa25 | ||
|
|
1cad446fe6 | ||
|
|
12c6f34fe7 | ||
|
|
79c2dd9a2b | ||
|
|
f64d5a03af | ||
|
|
e27d1581f1 | ||
|
|
99985e3235 | ||
|
|
27782afeac | ||
|
|
0e846b2dc5 | ||
|
|
45fb0f207e | ||
|
|
b7c91c238d | ||
|
|
76635a0705 | ||
|
|
c91d69e7fd | ||
|
|
5fd7096d67 | ||
|
|
f4189cb5ec | ||
|
|
278ca5b8ae | ||
|
|
729efcb8c3 | ||
|
|
406031d4e9 | ||
|
|
9e1d0d2cd6 | ||
|
|
f3e78f9408 | ||
|
|
eea4cc0b49 | ||
|
|
1127f44c10 | ||
|
|
377509fab8 | ||
|
|
c21e2f3147 | ||
|
|
0b4c5db939 | ||
|
|
6d490fc501 |
2
.github/workflows/00-pr-scanner.yaml
vendored
2
.github/workflows/00-pr-scanner.yaml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.21"
|
||||
GO_VERSION: "1.23"
|
||||
RELEASE: "latest"
|
||||
CLIENT: test
|
||||
secrets: inherit
|
||||
|
||||
26
.github/workflows/02-release.yaml
vendored
26
.github/workflows/02-release.yaml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
retag:
|
||||
outputs:
|
||||
NEW_TAG: ${{ steps.tag-calculator.outputs.NEW_TAG }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu22-core4-mem16-ssd150
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: tag-calculator
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.21"
|
||||
GO_VERSION: "1.23"
|
||||
RELEASE: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
CLIENT: release
|
||||
secrets: inherit
|
||||
@@ -89,4 +89,24 @@ jobs:
|
||||
support_platforms: true
|
||||
cosign: true
|
||||
secrets: inherit
|
||||
|
||||
post-release:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
security-events: read
|
||||
statuses: read
|
||||
attestations: read
|
||||
contents: write
|
||||
uses: ./.github/workflows/e-post-release.yaml
|
||||
needs: [publish-image]
|
||||
with:
|
||||
TAG: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
secrets: inherit
|
||||
|
||||
17
.github/workflows/04-publish-krew-plugin.yaml
vendored
17
.github/workflows/04-publish-krew-plugin.yaml
vendored
@@ -1,17 +0,0 @@
|
||||
name: 04-publish_krew_plugin
|
||||
permissions: read-all
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
jobs:
|
||||
publish_krew_plugin:
|
||||
name: Publish Krew plugin
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'kubescape'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update new version in krew-index
|
||||
uses: rajatjindal/krew-release-bot@92da038bbf995803124a8e50ebd438b2f37bbbb0 # ratchet:rajatjindal/krew-release-bot@v0.0.43
|
||||
14
.github/workflows/a-pr-scanner.yaml
vendored
14
.github/workflows/a-pr-scanner.yaml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
name: Create cross-platform build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu22-core4-mem16-ssd150
|
||||
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 ./...
|
||||
@@ -84,7 +83,7 @@ jobs:
|
||||
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
name: PR Scanner
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu22-core4-mem16-ssd150
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -93,8 +92,7 @@ jobs:
|
||||
- uses: actions/setup-go@v4
|
||||
name: Installing go
|
||||
with:
|
||||
go-version: '1.21'
|
||||
cache: true
|
||||
go-version: "1.23"
|
||||
- name: Scanning - Forbidden Licenses (go-licenses)
|
||||
id: licenses-scan
|
||||
continue-on-error: true
|
||||
@@ -107,7 +105,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 +118,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 +140,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.22"
|
||||
default: "1.23"
|
||||
GO111MODULE:
|
||||
required: false
|
||||
type: string
|
||||
@@ -70,7 +70,7 @@ on:
|
||||
type: string
|
||||
GO_VERSION:
|
||||
type: string
|
||||
default: "1.22"
|
||||
default: "1.23"
|
||||
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,7 +181,7 @@ 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
|
||||
@@ -224,7 +223,7 @@ 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
|
||||
@@ -249,7 +248,7 @@ jobs:
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: "on"
|
||||
BUILD_PLATFORM: linux/amd64,linux/arm64
|
||||
GO_VERSION: "1.22"
|
||||
GO_VERSION: "1.23"
|
||||
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,7 +306,7 @@ 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'
|
||||
cache: 'pip'
|
||||
@@ -322,7 +321,7 @@ jobs:
|
||||
|
||||
- name: Create k8s Kind Cluster
|
||||
id: kind-cluster-install
|
||||
uses: helm/kind-action@d08cf6ff1575077dee99962540d77ce91c62387d # ratchet:helm/kind-action@v1.3.0
|
||||
uses: helm/kind-action@v1.10.0
|
||||
with:
|
||||
cluster_name: ${{ steps.uuid.outputs.RANDOM_UUID }}
|
||||
|
||||
@@ -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.23"
|
||||
REQUIRED_TESTS: '[]'
|
||||
COSIGN: ${{ inputs.CO_SIGN }}
|
||||
HELM_E2E_TEST: false
|
||||
|
||||
65
.github/workflows/c-create-release.yaml
vendored
65
.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,32 @@ 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
|
||||
./kubescape-${{ env.MAC_OS }}
|
||||
./kubescape-${{ env.MAC_OS }}.sbom
|
||||
./kubescape-${{ env.MAC_OS }}.sha256
|
||||
./kubescape-${{ env.MAC_OS }}.tar.gz
|
||||
./kubescape-${{ env.UBUNTU_OS }}
|
||||
./kubescape-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape-${{ env.UBUNTU_OS }}.sha256
|
||||
./kubescape-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape-${{ env.WINDOWS_OS }}.exe
|
||||
./kubescape-${{ env.WINDOWS_OS }}.exe.sbom
|
||||
./kubescape-${{ env.WINDOWS_OS }}.exe.sha256
|
||||
./kubescape-${{ env.WINDOWS_OS }}.tar.gz
|
||||
./kubescape-arm64-${{ env.MAC_OS }}
|
||||
./kubescape-arm64-${{ env.MAC_OS }}.sbom
|
||||
./kubescape-arm64-${{ env.MAC_OS }}.sha256
|
||||
./kubescape-arm64-${{ env.MAC_OS }}.tar.gz
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}.sha256
|
||||
./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 }}.exe.sha256
|
||||
./kubescape-arm64-${{ env.WINDOWS_OS }}.tar.gz
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}.sha256
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape.exe
|
||||
|
||||
12
.github/workflows/d-publish-image.yaml
vendored
12
.github/workflows/d-publish-image.yaml
vendored
@@ -63,22 +63,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 +105,3 @@ jobs:
|
||||
# Verify the image
|
||||
echo "$COSIGN_PUBLIC_KEY" > cosign.pub
|
||||
cosign verify -key cosign.pub ${{ inputs.image_name }}:${{ inputs.image_tag }}
|
||||
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
name: 03-post_release
|
||||
name: e-post_release
|
||||
permissions: read-all
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
branches:
|
||||
- 'master'
|
||||
- 'main'
|
||||
workflow_call:
|
||||
inputs:
|
||||
TAG:
|
||||
description: 'Tag name'
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
post_release:
|
||||
name: Post release jobs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Digest
|
||||
uses: MCJack123/ghaction-generate-release-hashes@c03f3111b39432dde3edebe401c5a8d1ffbbf917 # ratchet:MCJack123/ghaction-generate-release-hashes@v1
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
hash-type: sha1
|
||||
file-name: kubescape-release-digests
|
||||
submodules: recursive
|
||||
- name: Update new version in krew-index
|
||||
uses: rajatjindal/krew-release-bot@v0.0.47
|
||||
if: github.repository_owner == 'kubescape'
|
||||
env:
|
||||
GITHUB_REF: ${{ 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 }}
|
||||
|
||||
23
ADOPTERS.md
23
ADOPTERS.md
@@ -1,24 +1,5 @@
|
||||
# Adopters
|
||||
|
||||
# Well-known companies
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
List of well-known companies who are publicly acknowledge using and/or contributing to Kubescape are (in alphabetical order):
|
||||
* AWS uses Kubescape in the security training material [link](https://catalog.workshops.aws/containersecurity/en-US/module2)
|
||||
* Energi Danmark: Publicly talking about how they use Kubescape in their CI/CD pipeline [link](https://www.armosec.io/energi-danmark-business-support/)
|
||||
* Gitpod: Used Kubescape in their SOC2 compliance process [link](https://www.armosec.io/gitpod/)
|
||||
* Intel: using Kubescape for security prioritization [video](https://youtu.be/1iCW1KboypY?si=OjmnshWbpFNVPGJT)
|
||||
* Orange Business: talking about Kubescape/ARMO service they are doing [video](https://www.youtube.com/watch?v=cbJYCUM8578)
|
||||
* Rabobank: talked at KCD Amsterdam about having Kubescape in their technology stack [video](https://youtu.be/oa_YJmjwepI?si=vSrFW6seMKHj2Lze) [image](/docs/img/kcd-amsterdam-rabo.jpg)
|
||||
* VMWare/Bitnami: listing Kubescape in their public image/helm repository [link](https://github.com/bitnami/containers/tree/main/bitnami/kubescape)
|
||||
|
||||
|
||||
# Users
|
||||
|
||||
If you want to be listed here and share with others your experience, open a PR and add the bellow table:
|
||||
|
||||
|
||||
| Name | Company | Use case | Contact for questions (optional) |
|
||||
| ---- | ------- | -------- | -------------------------------- |
|
||||
| Yonathan Amzallag | ARMO | Vulnerability monitoring | yonatana@armosec.io |
|
||||
| Engin Diri | Schwarz IT (SIT) | Ensure continuous compliance for edge k8s cluster | engin.diri@mail.schwarz |
|
||||
| Idan Bidani | Cox Communications | Security analysis for k8s best practices in CI pipelines of 3,000 applications 🔒☸ | idan.bidani@cox.com |
|
||||
Go to the [centralized ADOPTERS.md](https://github.com/kubescape/project-governance/blob/main/ADOPTERS.md)
|
||||
@@ -1,3 +1,5 @@
|
||||
## Code of Conduct
|
||||
# Code of Conduct
|
||||
|
||||
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized CODE_OF_CONDUCT.md](https://github.com/kubescape/project-governance/blob/main/CODE_OF_CONDUCT.md)
|
||||
|
||||
5
COMMUNITY.md
Normal file
5
COMMUNITY.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Community
|
||||
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized COMMUNITY.md](https://github.com/kubescape/project-governance/blob/main/COMMUNITY.md)
|
||||
@@ -1,98 +1,5 @@
|
||||
# Contributing
|
||||
|
||||
First, it is awesome that you are considering contributing to Kubescape! Contributing is important and fun and we welcome your efforts.
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
When contributing, we categorize contributions into two:
|
||||
* Small code changes or fixes, whose scope is limited to a single or two files
|
||||
* Complex features and improvements, with potentially unlimited scope
|
||||
|
||||
If you have a small change, feel free to fire up a Pull Request.
|
||||
|
||||
When planning a bigger change, please first discuss the change you wish to make via an issue,
|
||||
so the maintainers are able to help guide you and let you know if you are going in the right direction.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Please follow our [code of conduct](CODE_OF_CONDUCT.md) in all of your interactions within the project.
|
||||
|
||||
## Build and test locally
|
||||
|
||||
Please follow the [instructions here](https://github.com/kubescape/kubescape/wiki/Building).
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
|
||||
build.
|
||||
2. Update the README.md with details of changes to the interface, this includes new environment
|
||||
variables, exposed ports, useful file locations and container parameters.
|
||||
3. Open Pull Request to the `master` branch.
|
||||
4. We will merge the Pull Request once you have the sign-off.
|
||||
|
||||
## Developer Certificate of Origin
|
||||
|
||||
All commits to the project must be "signed off", which states that you agree to the terms of the [Developer Certificate of Origin](https://developercertificate.org/). This is done by adding a "Signed-off-by:" line in the commit message, with your name and email address.
|
||||
|
||||
Commits made through the GitHub web application are automatically signed off.
|
||||
|
||||
### Configuring Git to sign off commits
|
||||
|
||||
First, configure your name and email address in Git global settings:
|
||||
|
||||
```
|
||||
$ git config --global user.name "John Doe"
|
||||
$ git config --global user.email johndoe@example.com
|
||||
```
|
||||
|
||||
You can now sign off per-commit, or configure Git to always sign off commits per repository.
|
||||
|
||||
### Sign off per-commit
|
||||
|
||||
Add [`-s`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s) to your Git command line. For example:
|
||||
|
||||
```git commit -s -m "Fix issue 64738"```
|
||||
|
||||
This is tedious, and if you forget, you'll have to [amend your commit](#fixing-a-commit-where-the-dco-failed).
|
||||
|
||||
### Configure a repository to always include sign off
|
||||
|
||||
There are many ways to achieve this with Git hooks, but the simplest is to do the following:
|
||||
|
||||
```
|
||||
cd your-repo
|
||||
curl -Ls https://gist.githubusercontent.com/dixudx/7d7edea35b4d91e1a2a8fbf41d0954fa/raw/prepare-commit-msg -o .git/hooks/prepare-commit-msg
|
||||
chmod +x .git/hooks/prepare-commit-msg
|
||||
```
|
||||
|
||||
### Use semantic commit messages (optional)
|
||||
|
||||
When contributing, you could consider using [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/), in order to improve logs readability and help us to automatically generate `CHANGELOG`s.
|
||||
|
||||
Format: `<type>(<scope>): <subject>`
|
||||
|
||||
`<scope>` is optional
|
||||
|
||||
#### Example
|
||||
|
||||
```
|
||||
feat(cmd): add kubectl plugin
|
||||
^--^ ^-^ ^----------------^
|
||||
| | |
|
||||
| | +-> subject: summary in present tense.
|
||||
| |
|
||||
| +-------> scope: point of interest
|
||||
|
|
||||
+-------> type: chore, docs, feat, fix, refactor, style, or test.
|
||||
```
|
||||
|
||||
More Examples:
|
||||
* `feat`: new feature for the user, not a new feature for build script
|
||||
* `fix`: bug fix for the user, not a fix to a build script
|
||||
* `docs`: changes to the documentation
|
||||
* `style`: formatting, missing semi colons, etc; no production code change
|
||||
* `refactor`: refactoring production code, eg. renaming a variable
|
||||
* `test`: adding missing tests, refactoring tests; no production code change
|
||||
* `chore`: updating grunt tasks etc; no production code change
|
||||
|
||||
## Fixing a commit where the DCO failed
|
||||
|
||||
Check out [this guide](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md).
|
||||
Go to the [centralized CONTRIBUTING.md](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md)
|
||||
|
||||
@@ -1,65 +1,5 @@
|
||||
# Governance of Kubescape
|
||||
# Governance
|
||||
|
||||
## Overview
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
The Kubescape project is an open-source initiative dedicated to improve security and best practices in Kubernetes environments. This document outlines the governance structure of the Kubescape project and provides guidance for its community contributors.
|
||||
|
||||
## Decision Making
|
||||
|
||||
### Maintainers
|
||||
|
||||
- Maintainers are responsible for the smooth operation of the project.
|
||||
- They review and merge pull requests, manage releases, and ensure the quality and stability of the codebase.
|
||||
- Maintainers are chosen based on their ongoing contributions and their demonstrated commitment to the project.
|
||||
- Everyone who had at least 5 code contribution in the last 12 month can submit her/himself for joining the maintainer team
|
||||
- Maintainers who are not taken part in the project work (code, reviews, discussions) for 12 month are automaticaly removed from the maintainer team
|
||||
|
||||
|
||||
### Committers
|
||||
|
||||
- Committers are contributors who have made significant and consistent contributions to the project.
|
||||
- They have the ability to merge minor pull requests if assigned by maintainers.
|
||||
- A contributor can be proposed as a committer by any existing maintainer. The proposal will be reviewed and voted on by the existing maintainers.
|
||||
|
||||
### Community Members
|
||||
|
||||
- Anyone can become a community member by contributing to the project. This can be in the form of code contributions, documentation, or any other form of project support.
|
||||
|
||||
## Processes
|
||||
|
||||
### Proposing Changes
|
||||
|
||||
1. Open an issue on the project repository to discuss the proposed change.
|
||||
2. Once there is consensus around the proposed change, create a pull request.
|
||||
3. Pull requests will be reviewed by committers and/or maintainers.
|
||||
4. Once the pull request has received approval, it can be merged into the main codebase.
|
||||
|
||||
### Conflict Resolution
|
||||
|
||||
1. In case of any conflicts, it is primarily the responsibility of the parties involved to resolve it.
|
||||
2. If the conflict cannot be resolved, it will be escalated to the maintainers for resolution.
|
||||
3. Maintainers' decision will be final in case of unresolved conflicts.
|
||||
|
||||
## Roles and Responsibilities
|
||||
|
||||
### Maintainers
|
||||
|
||||
- Ensure the quality and stability of the project.
|
||||
- Resolve conflicts.
|
||||
- Provide direction and set priorities for the project.
|
||||
|
||||
### Committers
|
||||
|
||||
- Review and merge minor pull requests.
|
||||
- Assist maintainers in project tasks.
|
||||
- Promote best practices within the community.
|
||||
|
||||
### Community Members
|
||||
|
||||
- Contribute to the project in any form.
|
||||
- Participate in discussions and provide feedback.
|
||||
- Respect the code of conduct and governance of the project.
|
||||
|
||||
## Changes to the Governance Document
|
||||
|
||||
Proposed changes to this governance document should follow the same process as any other code change to the Kubescape project (see "Proposing Changes").
|
||||
Go to the [centralized GOVERNANCE.md](https://github.com/kubescape/project-governance/blob/main/GOVERNANCE.md)
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
# Maintainers
|
||||
|
||||
The following table lists the Kubescape project core maintainers:
|
||||
|
||||
| Name | GitHub | Organization | Added/Renewed On |
|
||||
| --- | --- | --- | --- |
|
||||
| [Matthias Bertschy](https://www.linkedin.com/in/matthias-bertschy-b427b815/) | [@matthyx](https://github.com/matthyx) | [ARMO](https://www.armosec.io/) | 2023-01-01 |
|
||||
| [Craig Box](https://www.linkedin.com/in/crbnz/) | [@craigbox](https://github.com/craigbox) | [Solo.io](https://www.solo.io/) | 2022-10-31 |
|
||||
| [Ben Hirschberg](https://www.linkedin.com/in/benyamin-ben-hirschberg-66141890) | [@slashben](https://github.com/slashben) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
|
||||
| [Rotem Refael](https://www.linkedin.com/in/rotem-refael) | [@rotemamsa](https://github.com/rotemamsa) | [ARMO](https://www.armosec.io/) | 2021-10-11 |
|
||||
| [David Wertenteil](https://www.linkedin.com/in/david-wertenteil-0ba277b9) | [@dwertent](https://github.com/dwertent) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized MAINTAINERS.md](https://github.com/kubescape/project-governance/blob/main/MAINTAINERS.md)
|
||||
|
||||
82
README.md
82
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)
|
||||
@@ -22,29 +22,20 @@
|
||||
|
||||
_Comprehensive Kubernetes Security from Development to Runtime_
|
||||
|
||||
Kubescape is an open-source Kubernetes security platform that provides comprehensive security coverage from left to right across the entire development and deployment lifecycle. It offers hardening, posture management, and runtime security capabilities to ensure robust protection for Kubernetes environments.
|
||||
Kubescape is an open-source Kubernetes security platform that provides comprehensive security coverage, from left to right across the entire development and deployment lifecycle. It offers hardening, posture management, and runtime security capabilities to ensure robust protection for Kubernetes environments. It saves Kubernetes users and admins precious time, effort, and resources.
|
||||
|
||||
**Key features of Kubescape include**
|
||||
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)).
|
||||
|
||||
* **Shift-left security**: Kubescape enables developers to scan for misconfigurations as early as the manifest file submission stage, promoting a proactive approach to security.
|
||||
* **IDE and CI/CD integration**: The tool integrates seamlessly with popular IDEs like VSCode and Lens, as well as CI/CD platforms such as GitHub and GitLab, allowing for security checks throughout the development process.
|
||||
* **Cluster scanning**: Kubescape can scan active Kubernetes clusters for vulnerabilities, misconfigurations, and security issues
|
||||
* **Multiple framework support**: Kubescape can test against various security frameworks, including NSA, MITRE, SOC2, and more.
|
||||
* **YAML and Helm chart validation**: The tool checks YAML files and Helm charts for correct configuration according to the frameworks above, without requiring an active cluster.
|
||||
* **Kubernetes hardening**: Kubescape ensures proactive identification and rapid remediation of misconfigurations and vulnerabilities through manual, recurring, or event-triggered scans.
|
||||
* **Runtime security**: Kubescape extends its protection to the runtime environment, providing continuous monitoring and threat detection for deployed applications.
|
||||
* **Compliance management**: The tool aids in maintaining compliance with recognized frameworks and standards, simplifying the process of meeting regulatory requirements.
|
||||
* **Multi-cloud support**: Kubescape offers frictionless security across various cloud providers and Kubernetes distributions.
|
||||
|
||||
By providing this comprehensive security coverage from development to production, Kubescape enables organizations to implement a robust security posture throughout their Kubernetes deployment, addressing potential vulnerabilities and threats at every stage of the application lifecycle.
|
||||
|
||||
Kubescape was created by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository) and is a [Cloud Native Computing Foundation (CNCF) sandbox project](https://www.cncf.io/sandbox-projects/).
|
||||
|
||||
## Demo
|
||||
<img src="docs/img/demo-v3.gif">
|
||||
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! 😀_
|
||||
|
||||
## Demo
|
||||
|
||||
Kubescape has a command line tool that you can use to quickly get a report on the security posture of a Kubernetes cluster:
|
||||
|
||||
<img src="docs/img/demo-v3.gif">
|
||||
|
||||
## Getting started
|
||||
|
||||
Experimenting with Kubescape is as easy as:
|
||||
@@ -53,13 +44,13 @@ Experimenting with Kubescape is as easy as:
|
||||
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
|
||||
```
|
||||
|
||||
This script will automatically download the latest Kubescape CLI release and scan the Kubernetes cluster in your current kubectl context.
|
||||
|
||||
Learn more about:
|
||||
|
||||
* [Installing Kubescape](docs/installation.md)
|
||||
* [Running your first scan](docs/getting-started.md#run-your-first-scan)
|
||||
* [Usage](docs/getting-started.md#examples)
|
||||
* [Architecture](docs/architecture.md)
|
||||
* [Building Kubescape from source](https://github.com/kubescape/kubescape/wiki/Building)
|
||||
* [Installing the Kubescape CLI](https://kubescape.io/docs/install-cli/)
|
||||
* [Running your first scan](https://kubescape.io/docs/scanning/)
|
||||
* [Accepting risk with exceptions](https://kubescape.io/docs/accepting-risk/)
|
||||
|
||||
_Did you know you can use Kubescape in all these places?_
|
||||
|
||||
@@ -67,54 +58,47 @@ _Did you know you can use Kubescape in all these places?_
|
||||
<img src="docs/img/ksfromcodetodeploy.png" alt="Places you can use Kubescape: in your IDE, CI, CD, or against a running cluster.">
|
||||
</div>
|
||||
|
||||
## Kubescape-operator Helm-Chart
|
||||
### Continuous security monitoring with the Kubescape Operator
|
||||
|
||||
Besides the CLI, the Kubescape operator can also be installed via a Helm chart. Installing the Helm chart is an excellent way to begin using Kubescape, as it provides extensive features such as continuous scanning, image vulnerability scanning, runtime analysis, network policy generation, and more. You can find the Helm chart in the [Kubescape-operator documentation](https://kubescape.io/docs/install-operator/).
|
||||
As well as a CLI, Kubescape provides an in-cluster mode, which is installed via a Helm chart. Kubescape in-cluster provides extensive features such as continuous scanning, image vulnerability scanning, runtime analysis, network policy generation, and more. [Learn more about the Kubescape operator](https://kubescape.io/docs/operator/).
|
||||
|
||||
## Kubescape GitHub Action
|
||||
### Using Kubescape as a GitHub Action
|
||||
|
||||
Kubescape can be used as a GitHub Action. This is a great way to integrate Kubescape into your CI/CD pipeline. You can find the Kubescape GitHub Action in the [GitHub Action marketplace](https://github.com/marketplace/actions/kubescape).
|
||||
|
||||
## Under the hood
|
||||
|
||||
Kubescape uses [Open Policy Agent](https://github.com/open-policy-agent/opa) to verify Kubernetes objects against [a library of posture controls](https://github.com/kubescape/regolibrary).
|
||||
For image scanning, it uses [Grype](https://github.com/anchore/grype).
|
||||
For image scanning, it uses [Grype](https://github.com/anchore/grype).
|
||||
For image patching, it uses [Copacetic](https://github.com/project-copacetic/copacetic).
|
||||
For eBPF, it uses [Inspektor Gadget](https://github.com/inspektor-gadget)
|
||||
|
||||
By default, the results are printed in a console-friendly manner, but they can be:
|
||||
By default, CLI scan results are printed in a console-friendly manner, but they can be:
|
||||
|
||||
* exported to JSON, junit XML or SARIF
|
||||
* rendered to HTML or PDF
|
||||
* submitted to a [cloud service](docs/providers.md)
|
||||
|
||||
It retrieves Kubernetes objects from the API server and runs a set of [Rego snippets](https://www.openpolicyagent.org/docs/latest/policy-language/) developed by [ARMO](https://www.armosec.io?utm_source=github&utm_medium=repository).
|
||||
|
||||
## Architecture
|
||||

|
||||
|
||||
**Otel collector** - is not built-in, Otel endpoint spec is need to be added at setup [Setting Otel](https://kubescape.io/docs/operator/telemetry/)
|
||||
### In-cluster architecture
|
||||
|
||||

|
||||
|
||||
## Community
|
||||
|
||||
Kubescape is an open source project, we welcome your feedback and ideas for improvement. We are part of the cloud-native community and are enhancing the project as the ecosystem develops.
|
||||
Kubescape is an open source project. We welcome your feedback and ideas for improvement. We are part of the CNCF community and are evolving Kubescape in sync with the security needs of Kubernetes users. To learn more about where Kubescape is heading, please check out our [ROADMAP](https://github.com/kubescape/project-governance/blob/main/ROADMAP.md).
|
||||
|
||||
If you feel inspired to contribute to Kubescape, check out our [CONTRIBUTING](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md) file to learn how. You can find the issues we are working on (triage to development) on the [Kubescaping board](https://github.com/orgs/kubescape/projects/4/views/1)
|
||||
|
||||
We hold [community meetings](https://zoom.us/j/95174063585) on Zoom, every other week, at 15:00 CET. ([See that in your local time zone](https://time.is/compare/1500_in_CET).
|
||||
* Feel free to pick a task from the [board](https://github.com/orgs/kubescape/projects/4) or suggest a feature of your own.
|
||||
* Open an issue on the board. We aim to respond to all issues within 48 hours.
|
||||
* [Join the CNCF Slack](https://slack.cncf.io/) and then our [users](https://cloud-native.slack.com/archives/C04EY3ZF9GE) or [developers](https://cloud-native.slack.com/archives/C04GY6H082K) channel.
|
||||
|
||||
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
|
||||
### Adopters
|
||||
For more information about the Kubescape community, please visit [COMMUNITY](https://github.com/kubescape/project-governance/blob/main/COMMUNITY.md).
|
||||
|
||||
See [here](ADOPTERS.md) a list of adopters.
|
||||
|
||||
## Contributions
|
||||
|
||||
Thanks to all our contributors! Check out our [CONTRIBUTING](CONTRIBUTING.md) file to learn how to join them.
|
||||
|
||||
* Feel free to pick a task from the [issues](https://github.com/kubescape/kubescape/issues?q=is%3Aissue+is%3Aopen+label%3A%22open+for+contribution%22), [roadmap](docs/roadmap.md) or suggest a feature of your own.
|
||||
* [Open an issue](https://github.com/kubescape/kubescape/issues/new/choose): we aim to respond to all issues within 48 hours.
|
||||
* [Join the CNCF Slack](https://slack.cncf.io/) and then our [users](https://cloud-native.slack.com/archives/C04EY3ZF9GE) or [developers](https://cloud-native.slack.com/archives/C04GY6H082K) channel.
|
||||
We would like to take this opportunity to thank all our contibutors to date.
|
||||
|
||||
<br>
|
||||
|
||||
@@ -124,14 +108,14 @@ Thanks to all our contributors! Check out our [CONTRIBUTING](CONTRIBUTING.md) f
|
||||
|
||||
## Changelog
|
||||
|
||||
Kubescape changes are tracked on the [release](https://github.com/kubescape/kubescape/releases) page
|
||||
Kubescape changes are tracked on the [release](https://github.com/kubescape/kubescape/releases) page.
|
||||
|
||||
## 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.
|
||||
|
||||
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>
|
||||
|
||||
@@ -4,15 +4,19 @@ header:
|
||||
last-reviewed: '2023-10-12'
|
||||
expiration-date: '2024-10-12T01:00:00.000Z'
|
||||
project-url: https://github.com/kubescape/kubescape/
|
||||
project-release: '1.0.0'
|
||||
project-release: 1.0.0
|
||||
project-lifecycle:
|
||||
status: active
|
||||
bug-fixes-only: false
|
||||
core-maintainers:
|
||||
- github:slashben
|
||||
- github:amirmalka
|
||||
- github:amitschendel
|
||||
- github:bezbran
|
||||
- github:craigbox
|
||||
- github:matthyx
|
||||
- github:dwertent
|
||||
- github:matthyx
|
||||
- github:rotemamsa
|
||||
- github:slashben
|
||||
contribution-policy:
|
||||
accepts-pull-requests: true
|
||||
accepts-automated-pull-requests: false
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Reporting Security Issues
|
||||
# Security
|
||||
|
||||
To report a security issue or vulnerability, submit a [private vulnerability report via GitHub](https://github.com/kubescape/kubescape/security/advisories/new) to the repository maintainers with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
The maintainers will respond within 7 working days of your report. If the issue is confirmed as a vulnerability, we will open a Security Advisory and acknowledge your contributions as part of it. This project follows a 90 day disclosure timeline.
|
||||
|
||||
Other contacts: cncf-kubescape-maintainers@lists.cncf.io
|
||||
Go to the [centralized SECURITY.md](https://github.com/kubescape/project-governance/blob/main/SECURITY.md)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:1.22-bullseye AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.23-bookworm AS builder
|
||||
|
||||
ENV GO111MODULE=on CGO_ENABLED=0
|
||||
WORKDIR /work
|
||||
@@ -8,13 +8,18 @@ RUN --mount=target=. \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg \
|
||||
cd httphandler && GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/ksserver .
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg \
|
||||
go run downloader/main.go
|
||||
|
||||
FROM gcr.io/distroless/static-debian11:nonroot
|
||||
FROM gcr.io/distroless/static-debian12:nonroot
|
||||
|
||||
USER nonroot
|
||||
WORKDIR /home/nonroot/
|
||||
|
||||
COPY --from=builder /out/ksserver /usr/bin/ksserver
|
||||
COPY --from=builder /root/.kubescape /home/nonroot/.kubescape
|
||||
|
||||
ARG image_version client
|
||||
ENV RELEASE=$image_version CLIENT=$client
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM gcr.io/distroless/base-debian11:debug-nonroot
|
||||
FROM gcr.io/distroless/static-debian12:debug-nonroot
|
||||
|
||||
USER nonroot
|
||||
WORKDIR /home/nonroot/
|
||||
|
||||
@@ -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,14 +1,12 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -36,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 (
|
||||
@@ -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
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
package patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/cmd/shared"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/kubescape/v3/pkg/imagescan"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -52,7 +49,7 @@ func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
results, err := ks.Patch(context.Background(), &patchInfo, &scanInfo)
|
||||
results, err := ks.Patch(&patchInfo, &scanInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
46
cmd/prerequisites/prerequisites.go
Normal file
46
cmd/prerequisites/prerequisites.go
Normal file
@@ -0,0 +1,46 @@
|
||||
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 {
|
||||
// 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()
|
||||
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)
|
||||
},
|
||||
}
|
||||
return preReqCmd
|
||||
}
|
||||
18
cmd/root.go
18
cmd/root.go
@@ -1,6 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/kubescape/kubescape/v3/cmd/list"
|
||||
"github.com/kubescape/kubescape/v3/cmd/operator"
|
||||
"github.com/kubescape/kubescape/v3/cmd/patch"
|
||||
"github.com/kubescape/kubescape/v3/cmd/prerequisites"
|
||||
"github.com/kubescape/kubescape/v3/cmd/scan"
|
||||
"github.com/kubescape/kubescape/v3/cmd/update"
|
||||
"github.com/kubescape/kubescape/v3/cmd/vap"
|
||||
@@ -22,7 +24,6 @@ import (
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -42,8 +43,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)
|
||||
}
|
||||
|
||||
@@ -84,8 +85,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
|
||||
@@ -93,13 +92,14 @@ 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))
|
||||
|
||||
// deprecated commands
|
||||
rootCmd.AddCommand(&cobra.Command{
|
||||
@@ -114,7 +114,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()
|
||||
}
|
||||
|
||||
@@ -14,14 +14,10 @@ import (
|
||||
"github.com/kubescape/go-logger/zaplogger"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
func initLogger() {
|
||||
logger.DisableColor(rootInfo.DisableColor)
|
||||
logger.EnableColor(rootInfo.EnableColor)
|
||||
|
||||
if rootInfo.LoggerName == "" {
|
||||
if l := os.Getenv("KS_LOGGER_NAME"); l != "" {
|
||||
rootInfo.LoggerName = l
|
||||
@@ -35,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,20 +1,17 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v3/cmd/shared"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -98,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()); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
if !scanInfo.VerboseMode {
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
reporthandlingapis "github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v3/cmd/shared"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
reporthandlingapis "github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -115,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()); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
@@ -10,7 +9,6 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -25,12 +23,17 @@ 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
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "image <image>:<tag> [flags]",
|
||||
Short: "Scan an image for vulnerabilities",
|
||||
@@ -51,12 +54,13 @@ 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,
|
||||
}
|
||||
|
||||
results, err := ks.ScanImage(context.Background(), imgScanInfo, scanInfo)
|
||||
results, err := ks.ScanImage(imgScanInfo, scanInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -69,6 +73,8 @@ 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")
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -15,12 +14,12 @@ import (
|
||||
)
|
||||
|
||||
var scanCmdExamples = fmt.Sprintf(`
|
||||
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
|
||||
|
||||
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
|
||||
|
||||
# Scan current cluster
|
||||
%[1]s scan
|
||||
|
||||
# Scan kubernetes manifest files
|
||||
# Scan kubernetes manifest files
|
||||
%[1]s scan .
|
||||
|
||||
# Scan and save the results in the JSON format
|
||||
@@ -29,7 +28,7 @@ var scanCmdExamples = fmt.Sprintf(`
|
||||
# Display all resources
|
||||
%[1]s scan --verbose
|
||||
|
||||
# Scan different clusters from the kubectl context
|
||||
# Scan different clusters from the kubectl context
|
||||
%[1]s scan --kube-context <kubernetes context>
|
||||
`, cautils.ExecName())
|
||||
|
||||
@@ -64,6 +63,8 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
scanInfo.TriggeredByCLI = true
|
||||
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.AccessKey, "access-key", "", "", "Kubescape SaaS access key. Default will load access key from cache")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
|
||||
@@ -89,6 +90,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Submit the scan results to Kubescape SaaS where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not submitted")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.OmitRawResources, "omit-raw-resources", "", false, "Omit raw resources from the output. By default the raw resources are included in the output")
|
||||
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().MarkDeprecated("fail-threshold", "use '--compliance-threshold' flag instead. Flag will be removed at 1.Dec.2023")
|
||||
@@ -132,15 +134,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()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -2,20 +2,18 @@ package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/cmd/shared"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExceedsSeverity(t *testing.T) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -11,7 +10,6 @@ import (
|
||||
"github.com/kubescape/kubescape/v3/core/meta"
|
||||
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -67,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()); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -9,11 +9,10 @@ import (
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
admissionv1 "k8s.io/api/admissionregistration/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
var vapHelperCmdExamples = fmt.Sprintf(`
|
||||
|
||||
@@ -1,23 +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)
|
||||
v := versioncheck.NewIVersionCheckHandler(ks.Context())
|
||||
versionCheckRequest := versioncheck.NewVersionCheckRequest("", versioncheck.BuildNumber, "", "", "version", nil)
|
||||
v.CheckLatestVersion(ctx, versionCheckRequest)
|
||||
if err := v.CheckLatestVersion(ks.Context(), versionCheckRequest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(cmd.OutOrStdout(),
|
||||
"Your current version is: %s\n",
|
||||
versionCheckRequest.ClientVersion,
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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()
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/google/uuid"
|
||||
v1 "github.com/kubescape/backend/pkg/client/v1"
|
||||
"github.com/kubescape/backend/pkg/servicediscovery"
|
||||
@@ -19,6 +17,7 @@ import (
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -59,6 +59,7 @@ type OPASessionObj struct {
|
||||
SingleResourceScan workloadinterface.IWorkload // single resource scan
|
||||
TopWorkloadsByScore []reporthandling.IResource
|
||||
TemplateMapping map[string]MappingNodes // Map chart obj to template (only for rendering from path)
|
||||
TriggeredByCLI bool
|
||||
}
|
||||
|
||||
func NewOPASessionObj(ctx context.Context, frameworks []reporthandling.Framework, k8sResources K8SResources, scanInfo *ScanInfo) *OPASessionObj {
|
||||
@@ -75,6 +76,7 @@ func NewOPASessionObj(ctx context.Context, frameworks []reporthandling.Framework
|
||||
SessionID: scanInfo.ScanID,
|
||||
Metadata: scanInfoToScanMetadata(ctx, scanInfo),
|
||||
OmitRawResources: scanInfo.OmitRawResources,
|
||||
TriggeredByCLI: scanInfo.TriggeredByCLI,
|
||||
TemplateMapping: make(map[string]MappingNodes),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"golang.org/x/mod/semver"
|
||||
|
||||
"github.com/kubescape/backend/pkg/versioncheck"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
func NewPolicies() *Policies {
|
||||
|
||||
@@ -7,16 +7,14 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -38,7 +36,7 @@ type Chart struct {
|
||||
}
|
||||
|
||||
// LoadResourcesFromHelmCharts scans a given path (recursively) for helm charts, renders the templates and returns a map of workloads and a map of chart names
|
||||
func LoadResourcesFromHelmCharts(ctx context.Context, basePath string) (map[string][]workloadinterface.IMetadata, map[string]Chart, map[string]MappingNodes) {
|
||||
func LoadResourcesFromHelmCharts(ctx context.Context, basePath string) (map[string][]workloadinterface.IMetadata, map[string]Chart) {
|
||||
directories, _ := listDirs(basePath)
|
||||
helmDirectories := make([]string, 0)
|
||||
for _, dir := range directories {
|
||||
@@ -49,19 +47,14 @@ func LoadResourcesFromHelmCharts(ctx context.Context, basePath string) (map[stri
|
||||
|
||||
sourceToWorkloads := map[string][]workloadinterface.IMetadata{}
|
||||
sourceToChart := make(map[string]Chart, 0)
|
||||
sourceToNodes := map[string]MappingNodes{}
|
||||
for _, helmDir := range helmDirectories {
|
||||
chart, err := NewHelmChart(helmDir)
|
||||
if err == nil {
|
||||
wls, templateToNodes, errs := chart.GetWorkloadsWithDefaultValues()
|
||||
wls, errs := chart.GetWorkloadsWithDefaultValues()
|
||||
if len(errs) > 0 {
|
||||
logger.L().Ctx(ctx).Warning(fmt.Sprintf("Rendering of Helm chart template '%s', failed: %v", chart.GetName(), errs))
|
||||
continue
|
||||
}
|
||||
for k, v := range templateToNodes {
|
||||
sourceToNodes[k] = v
|
||||
}
|
||||
|
||||
chartName := chart.GetName()
|
||||
for k, v := range wls {
|
||||
sourceToWorkloads[k] = v
|
||||
@@ -72,7 +65,7 @@ func LoadResourcesFromHelmCharts(ctx context.Context, basePath string) (map[stri
|
||||
}
|
||||
}
|
||||
}
|
||||
return sourceToWorkloads, sourceToChart, sourceToNodes
|
||||
return sourceToWorkloads, sourceToChart
|
||||
}
|
||||
|
||||
// If the contents at given path is a Kustomize Directory, LoadResourcesFromKustomizeDirectory will
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestLoadResourcesFromFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadResourcesFromHelmCharts(t *testing.T) {
|
||||
sourceToWorkloads, sourceToChartName, _ := LoadResourcesFromHelmCharts(context.TODO(), helmChartPath())
|
||||
sourceToWorkloads, sourceToChartName := LoadResourcesFromHelmCharts(context.TODO(), helmChartPath())
|
||||
assert.Equal(t, 6, len(sourceToWorkloads))
|
||||
|
||||
for file, workloads := range sourceToWorkloads {
|
||||
|
||||
@@ -5,10 +5,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
|
||||
"github.com/kubescape/regolibrary/v2/gitregostore"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
||||
|
||||
helmchart "helm.sh/helm/v3/pkg/chart"
|
||||
helmloader "helm.sh/helm/v3/pkg/chart/loader"
|
||||
helmchartutil "helm.sh/helm/v3/pkg/chartutil"
|
||||
@@ -45,35 +44,24 @@ func (hc *HelmChart) GetDefaultValues() map[string]interface{} {
|
||||
return hc.chart.Values
|
||||
}
|
||||
|
||||
// GetWorkloads renders chart template using the default values and returns a map of source file to its workloads
|
||||
func (hc *HelmChart) GetWorkloadsWithDefaultValues() (map[string][]workloadinterface.IMetadata, map[string]MappingNodes, []error) {
|
||||
// GetWorkloadsWithDefaultValues renders chart template using the default values and returns a map of source file to its workloads
|
||||
func (hc *HelmChart) GetWorkloadsWithDefaultValues() (map[string][]workloadinterface.IMetadata, []error) {
|
||||
return hc.GetWorkloads(hc.GetDefaultValues())
|
||||
}
|
||||
|
||||
// GetWorkloads renders chart template using the provided values and returns a map of source (absolute) file path to its workloads
|
||||
func (hc *HelmChart) GetWorkloads(values map[string]interface{}) (map[string][]workloadinterface.IMetadata, map[string]MappingNodes, []error) {
|
||||
func (hc *HelmChart) GetWorkloads(values map[string]interface{}) (map[string][]workloadinterface.IMetadata, []error) {
|
||||
vals, err := helmchartutil.ToRenderValues(hc.chart, values, helmchartutil.ReleaseOptions{}, nil)
|
||||
if err != nil {
|
||||
return nil, nil, []error{err}
|
||||
return nil, []error{err}
|
||||
}
|
||||
|
||||
// change the chart to template with comment, only is template(.yaml added otherwise no)
|
||||
hc.AddCommentToTemplate()
|
||||
|
||||
sourceToFile, err := helmengine.Render(hc.chart, vals)
|
||||
if err != nil {
|
||||
return nil, nil, []error{err}
|
||||
return nil, []error{err}
|
||||
}
|
||||
|
||||
// get the resouse and analysis and store it to the struct
|
||||
fileMapping := make(map[string]MappingNodes)
|
||||
GetTemplateMapping(sourceToFile, fileMapping)
|
||||
|
||||
// delete the comment from chart and from sourceToFile
|
||||
RemoveComment(sourceToFile)
|
||||
|
||||
workloads := make(map[string][]workloadinterface.IMetadata, 0)
|
||||
errs := []error{}
|
||||
workloads := make(map[string][]workloadinterface.IMetadata)
|
||||
var errs []error
|
||||
|
||||
for path, renderedYaml := range sourceToFile {
|
||||
if !IsYaml(strings.ToLower(path)) {
|
||||
@@ -87,13 +75,9 @@ func (hc *HelmChart) GetWorkloads(values map[string]interface{}) (map[string][]w
|
||||
if len(wls) == 0 {
|
||||
continue
|
||||
}
|
||||
if firstPathSeparatorIndex := strings.Index(path, string("/")); firstPathSeparatorIndex != -1 {
|
||||
if firstPathSeparatorIndex := strings.Index(path, "/"); firstPathSeparatorIndex != -1 {
|
||||
absPath := filepath.Join(hc.path, path[firstPathSeparatorIndex:])
|
||||
|
||||
if nodes, ok := fileMapping[path]; ok {
|
||||
fileMapping[absPath] = nodes
|
||||
delete(fileMapping, path)
|
||||
}
|
||||
workloads[absPath] = []workloadinterface.IMetadata{}
|
||||
for i := range wls {
|
||||
lw := localworkload.NewLocalWorkload(wls[i].GetObject())
|
||||
@@ -102,7 +86,7 @@ func (hc *HelmChart) GetWorkloads(values map[string]interface{}) (map[string][]w
|
||||
}
|
||||
}
|
||||
}
|
||||
return workloads, fileMapping, errs
|
||||
return workloads, errs
|
||||
}
|
||||
|
||||
func (hc *HelmChart) AddCommentToTemplate() {
|
||||
@@ -121,27 +105,3 @@ func (hc *HelmChart) AddCommentToTemplate() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveComment(sourceToFile map[string]string) {
|
||||
// commentRe := regexp.MustCompile(CommentFormat)
|
||||
for fileName, file := range sourceToFile {
|
||||
if !IsYaml(strings.ToLower((fileName))) {
|
||||
continue
|
||||
}
|
||||
sourceToFile[fileName] = commentRe.ReplaceAllLiteralString(file, "")
|
||||
}
|
||||
}
|
||||
|
||||
func GetTemplateMapping(sourceToFile map[string]string, fileMapping map[string]MappingNodes) {
|
||||
for fileName, fileContent := range sourceToFile {
|
||||
mappingNodes, err := GetMapping(fileName, fileContent)
|
||||
if err != nil {
|
||||
// if one file cannot get mapping nodes, generate error, then ignore it
|
||||
logger.L().Warning("Failed to get File Mapping nodes", helpers.String("file name", fileName), helpers.Error(err))
|
||||
continue
|
||||
}
|
||||
if len(mappingNodes.Nodes) != 0 {
|
||||
fileMapping[fileName] = *mappingNodes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func (s *HelmChartTestSuite) TestGetWorkloadsWithOverride() {
|
||||
// Override default value
|
||||
values["image"].(map[string]interface{})["pullPolicy"] = "Never"
|
||||
|
||||
fileToWorkloads, _, errs := chart.GetWorkloads(values)
|
||||
fileToWorkloads, errs := chart.GetWorkloads(values)
|
||||
s.Len(errs, 0)
|
||||
|
||||
s.Lenf(fileToWorkloads, len(s.expectedFiles), "Expected %d files", len(s.expectedFiles))
|
||||
@@ -111,7 +111,7 @@ func (s *HelmChartTestSuite) TestGetWorkloadsMissingValue() {
|
||||
values := chart.GetDefaultValues()
|
||||
delete(values, "image")
|
||||
|
||||
fileToWorkloads, _, errs := chart.GetWorkloads(values)
|
||||
fileToWorkloads, errs := chart.GetWorkloads(values)
|
||||
s.Nil(fileToWorkloads)
|
||||
s.Len(errs, 1, "Expected an error due to missing value")
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/krusty"
|
||||
"sigs.k8s.io/kustomize/kyaml/filesys"
|
||||
)
|
||||
|
||||
@@ -4,10 +4,9 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
"github.com/kubescape/rbac-utils/rbacscanner"
|
||||
"github.com/kubescape/rbac-utils/rbacutils"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kubescape/backend/pkg/versioncheck"
|
||||
giturl "github.com/kubescape/go-git-url"
|
||||
"github.com/kubescape/go-logger"
|
||||
@@ -18,8 +19,6 @@ import (
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ScanningContext string
|
||||
@@ -132,8 +131,10 @@ type ScanInfo struct {
|
||||
ScanAll bool // true if scan all frameworks
|
||||
OmitRawResources bool // true if omit raw resources from the output
|
||||
PrintAttackTree bool // true if print attack tree
|
||||
EnableRegoPrint bool // true if print rego
|
||||
ScanObject *objectsenvelopes.ScanObject // identifies a single resource (k8s object) to be scanned
|
||||
IsDeletedScanObject bool // indicates whether the ScanObject is a deleted K8S resource
|
||||
TriggeredByCLI bool // indicates whether the scan was triggered by the CLI
|
||||
ScanType ScanTypes
|
||||
ScanImages bool
|
||||
ChartPath string
|
||||
|
||||
@@ -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,13 +1,11 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/pkg/fixhandler"
|
||||
)
|
||||
|
||||
@@ -17,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)
|
||||
@@ -43,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")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/grype/grype/presenter/models"
|
||||
"github.com/kubescape/go-logger"
|
||||
@@ -12,7 +15,153 @@ 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 {
|
||||
return regexStringMatch(target.Attributes.Registry, attributes.Registry) && regexStringMatch(target.Attributes.Organization, attributes.Organization) && regexStringMatch(target.Attributes.ImageName, attributes.ImageName) && regexStringMatch(target.Attributes.ImageTag, attributes.ImageTag)
|
||||
}
|
||||
|
||||
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) (*models.PresenterConfig, error) {
|
||||
logger.L().Start(fmt.Sprintf("Scanning image %s...", imgScanInfo.Image))
|
||||
|
||||
dbCfg, _ := imagescan.NewDefaultDBConfig()
|
||||
@@ -23,7 +172,19 @@ func (ks *Kubescape) ScanImage(ctx context.Context, imgScanInfo *ksmetav1.ImageS
|
||||
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 nil, err
|
||||
}
|
||||
|
||||
vulnerabilityExceptions, severityExceptions = getUniqueVulnerabilitiesAndSeverities(exceptionPolicies, imgScanInfo.Image)
|
||||
}
|
||||
|
||||
scanResults, 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
|
||||
@@ -33,9 +194,9 @@ func (ks *Kubescape) ScanImage(ctx context.Context, imgScanInfo *ksmetav1.ImageS
|
||||
|
||||
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)
|
||||
|
||||
@@ -46,5 +207,5 @@ func (ks *Kubescape) ScanImage(ctx context.Context, imgScanInfo *ksmetav1.ImageS
|
||||
},
|
||||
}
|
||||
|
||||
return scanResults, resultsHandler.HandleResults(ctx)
|
||||
return scanResults, resultsHandler.HandleResults(ks.Context())
|
||||
}
|
||||
|
||||
420
core/core/image_scan_test.go
Normal file
420
core/core/image_scan_test.go
Normal file
@@ -0,0 +1,420 @@
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
@@ -16,11 +17,8 @@ import (
|
||||
printerv2 "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2"
|
||||
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/reporter"
|
||||
reporterv2 "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/reporter/v2"
|
||||
"go.opentelemetry.io/otel"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/kubescape/rbac-utils/rbacscanner"
|
||||
"go.opentelemetry.io/otel"
|
||||
)
|
||||
|
||||
// getKubernetesApi
|
||||
@@ -277,7 +275,7 @@ func getAttackTracksGetter(ctx context.Context, attackTracks, accountID string,
|
||||
return downloadReleasedPolicy
|
||||
}
|
||||
|
||||
// getUIPrinter returns a printer that will be used to print to the program’s UI (terminal)
|
||||
// GetUIPrinter returns a printer that will be used to print to the program’s UI (terminal)
|
||||
func GetUIPrinter(ctx context.Context, scanInfo *cautils.ScanInfo, clusterName string) printer.IPrinter {
|
||||
var p printer.IPrinter
|
||||
if helpers.ToLevel(logger.L().GetLevel()) >= helpers.WarningLevel {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -38,16 +38,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)
|
||||
}
|
||||
|
||||
@@ -9,25 +9,22 @@ import (
|
||||
|
||||
"github.com/anchore/grype/grype/presenter"
|
||||
"github.com/anchore/grype/grype/presenter/models"
|
||||
copaGrype "github.com/anubhav06/copa-grype/grype"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
ksmetav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"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/kubescape/kubescape/v3/core/pkg/resultshandling"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
copaGrype "github.com/anubhav06/copa-grype/grype"
|
||||
"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"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
|
||||
func (ks *Kubescape) Patch(patchInfo *ksmetav1.PatchInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
|
||||
|
||||
// ===================== Scan the image =====================
|
||||
logger.L().Start(fmt.Sprintf("Scanning image: %s", patchInfo.Image))
|
||||
@@ -40,7 +37,7 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo, s
|
||||
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
|
||||
}
|
||||
@@ -55,7 +52,7 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo, s
|
||||
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
|
||||
@@ -71,7 +68,7 @@ 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 {
|
||||
if err = copaPatch(ks.Context(), patchInfo.Timeout, patchInfo.BuildkitAddress, patchInfo.Image, fileName, patchedImageName, "", patchInfo.IgnoreError, patchInfo.BuildKitOpts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -84,7 +81,7 @@ 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
|
||||
}
|
||||
@@ -99,8 +96,8 @@ 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{
|
||||
{
|
||||
@@ -109,7 +106,7 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo, s
|
||||
},
|
||||
}
|
||||
|
||||
return scanResultsPatched, resultsHandler.HandleResults(ctx)
|
||||
return scanResultsPatched, resultsHandler.HandleResults(ks.Context())
|
||||
}
|
||||
|
||||
func disableCopaLogger() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package core
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/kubescape/backend/pkg/versioncheck"
|
||||
"github.com/kubescape/go-logger"
|
||||
@@ -21,11 +22,9 @@ import (
|
||||
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/reporter"
|
||||
"github.com/kubescape/kubescape/v3/pkg/imagescan"
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"go.opentelemetry.io/otel"
|
||||
"golang.org/x/exp/slices"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/kubescape/opa-utils/resources"
|
||||
"go.opentelemetry.io/otel"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type componentInterfaces struct {
|
||||
@@ -122,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 =====================
|
||||
@@ -149,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))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -178,11 +177,11 @@ 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())
|
||||
reportResults := opaprocessor.NewOPAProcessor(scanData, deps, interfaces.tenantConfig.GetContextName(), scanInfo.ExcludedNamespaces, scanInfo.IncludeNamespaces)
|
||||
reportResults := opaprocessor.NewOPAProcessor(scanData, deps, interfaces.tenantConfig.GetContextName(), scanInfo.ExcludedNamespaces, scanInfo.IncludeNamespaces, scanInfo.EnableRegoPrint)
|
||||
if err = reportResults.ProcessRulesListener(ctxOpa, cautils.NewProgressHandler("")); err != nil {
|
||||
// TODO - do something
|
||||
return resultsHandling, fmt.Errorf("%w", err)
|
||||
@@ -192,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)
|
||||
}
|
||||
@@ -203,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)
|
||||
}
|
||||
// ========================= results handling =====================
|
||||
resultsHandling.SetData(scanData)
|
||||
@@ -258,7 +257,7 @@ func scanImages(scanType cautils.ScanTypes, scanData *cautils.OPASessionObj, ctx
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -8,10 +8,8 @@ type SetConfig struct {
|
||||
CloudReportURL string
|
||||
CloudAPIURL string
|
||||
}
|
||||
|
||||
type ViewConfig struct {
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
type DeleteConfig struct {
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package v1
|
||||
|
||||
type ImageScanInfo struct {
|
||||
Username string
|
||||
Password string
|
||||
Image string
|
||||
Username string
|
||||
Password string
|
||||
Image string
|
||||
Exceptions string
|
||||
}
|
||||
|
||||
@@ -10,23 +10,25 @@ import (
|
||||
)
|
||||
|
||||
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) (*models.PresenterConfig, error)
|
||||
|
||||
// scan image
|
||||
ScanImage(ctx context.Context, imgScanInfo *metav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error)
|
||||
ScanImage(imgScanInfo *metav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error)
|
||||
}
|
||||
|
||||
@@ -11,15 +11,19 @@ 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(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) List(ctx context.Context, listPolicies *metav1.ListPolicies) error {
|
||||
func (m *MockIKubescape) List(listPolicies *metav1.ListPolicies) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) Download(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
|
||||
func (m *MockIKubescape) Download(downloadInfo *metav1.DownloadInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -31,18 +35,18 @@ func (m *MockIKubescape) ViewCachedConfig(viewConfig *metav1.ViewConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) DeleteCachedConfig(ctx context.Context, deleteConfig *metav1.DeleteConfig) error {
|
||||
func (m *MockIKubescape) DeleteCachedConfig(deleteConfig *metav1.DeleteConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) Fix(ctx context.Context, fixInfo *metav1.FixInfo) error {
|
||||
func (m *MockIKubescape) Fix(fixInfo *metav1.FixInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) Patch(ctx context.Context, patchInfo *metav1.PatchInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
|
||||
func (m *MockIKubescape) Patch(patchInfo *metav1.PatchInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) ScanImage(ctx context.Context, imgScanInfo *metav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
|
||||
func (m *MockIKubescape) ScanImage(imgScanInfo *metav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -13,9 +13,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes/hostsensor"
|
||||
)
|
||||
|
||||
@@ -27,18 +28,19 @@ type workerPool struct {
|
||||
|
||||
func newWorkerPool() workerPool {
|
||||
wp := workerPool{}
|
||||
wp.noOfWorkers = noOfWorkers
|
||||
wp.noOfWorkers, _ = cautils.ParseIntEnvVar("KUBESCAPE_WORKERS", noOfWorkers)
|
||||
wp.init()
|
||||
return wp
|
||||
}
|
||||
|
||||
func (wp *workerPool) init(noOfPods ...int) {
|
||||
if len(noOfPods) > 0 && noOfPods[0] < noOfWorkers {
|
||||
if len(noOfPods) > 0 && noOfPods[0] < wp.noOfWorkers {
|
||||
wp.noOfWorkers = noOfPods[0]
|
||||
}
|
||||
logger.L().Debug("Initializing worker pool", helpers.Int("noOfWorkers", wp.noOfWorkers))
|
||||
// init the channels
|
||||
wp.jobs = make(chan job, noOfWorkers)
|
||||
wp.results = make(chan hostsensor.HostSensorDataEnvelope, noOfWorkers)
|
||||
wp.jobs = make(chan job, wp.noOfWorkers)
|
||||
wp.results = make(chan hostsensor.HostSensorDataEnvelope, wp.noOfWorkers)
|
||||
wp.done = make(chan bool)
|
||||
}
|
||||
|
||||
@@ -57,7 +59,7 @@ func (wp *workerPool) hostSensorWorker(ctx context.Context, hsh *HostSensorHandl
|
||||
}
|
||||
|
||||
func (wp *workerPool) createWorkerPool(ctx context.Context, hsh *HostSensorHandler, wg *sync.WaitGroup, log *LogsMap) {
|
||||
for i := 0; i < noOfWorkers; i++ {
|
||||
for i := 0; i < wp.noOfWorkers; i++ {
|
||||
wg.Add(1)
|
||||
go wp.hostSensorWorker(ctx, hsh, wg, log)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
|
||||
"github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
|
||||
|
||||
@@ -2,8 +2,9 @@ package opaprocessor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_verify(t *testing.T) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package opaprocessor
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -21,8 +22,8 @@ import (
|
||||
"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"
|
||||
"go.opentelemetry.io/otel"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const ScoreConfigPath = "/resources/config"
|
||||
@@ -41,9 +42,10 @@ type OPAProcessor struct {
|
||||
opaRegisterOnce sync.Once
|
||||
excludeNamespaces []string
|
||||
includeNamespaces []string
|
||||
printEnabled bool
|
||||
}
|
||||
|
||||
func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *resources.RegoDependenciesData, clusterName string, excludeNamespaces string, includeNamespaces string) *OPAProcessor {
|
||||
func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *resources.RegoDependenciesData, clusterName string, excludeNamespaces string, includeNamespaces string, enableRegoPrint bool) *OPAProcessor {
|
||||
if regoDependenciesData != nil && sessionObj != nil {
|
||||
regoDependenciesData.PostureControlInputs = sessionObj.RegoInputData.PostureControlInputs
|
||||
regoDependenciesData.DataControlInputs = sessionObj.RegoInputData.DataControlInputs
|
||||
@@ -55,6 +57,7 @@ func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *re
|
||||
clusterName: clusterName,
|
||||
excludeNamespaces: split(excludeNamespaces),
|
||||
includeNamespaces: split(includeNamespaces),
|
||||
printEnabled: enableRegoPrint,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +322,9 @@ func (opap *OPAProcessor) runRegoOnK8s(ctx context.Context, rule *reporthandling
|
||||
modules[rule.Name] = getRuleData(rule)
|
||||
|
||||
// NOTE: OPA module compilation is the most resource-intensive operation.
|
||||
compiled, err := ast.CompileModules(modules)
|
||||
compiled, err := ast.CompileModulesWithOpt(modules, ast.CompileOpts{
|
||||
EnablePrintStatements: opap.printEnabled,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("in 'runRegoOnK8s', failed to compile rule, name: %s, reason: %w", rule.Name, err)
|
||||
}
|
||||
@@ -338,12 +343,20 @@ func (opap *OPAProcessor) runRegoOnK8s(ctx context.Context, rule *reporthandling
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) Print(ctx opaprint.Context, str string) error {
|
||||
msg := fmt.Sprintf("opa-print: {%v} - %s", ctx.Location, str)
|
||||
logger.L().Ctx(ctx.Context).Debug(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) regoEval(ctx context.Context, inputObj []map[string]interface{}, compiledRego *ast.Compiler, store *storage.Store) ([]reporthandling.RuleResponse, error) {
|
||||
rego := rego.New(
|
||||
rego.Query("data.armo_builtins"), // get package name from rule
|
||||
rego.Compiler(compiledRego),
|
||||
rego.Input(inputObj),
|
||||
rego.Store(*store),
|
||||
rego.EnablePrintStatements(opap.printEnabled),
|
||||
rego.PrintHook(opap),
|
||||
)
|
||||
|
||||
// Run evaluation
|
||||
|
||||
@@ -14,14 +14,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
"github.com/kubescape/opa-utils/resources"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -197,7 +196,7 @@ func TestProcessResourcesResult(t *testing.T) {
|
||||
opaSessionObj.K8SResources = k8sResources
|
||||
opaSessionObj.AllResources[deployment.GetID()] = deployment
|
||||
|
||||
opap := NewOPAProcessor(opaSessionObj, resources.NewRegoDependenciesDataMock(), "test", "", "")
|
||||
opap := NewOPAProcessor(opaSessionObj, resources.NewRegoDependenciesDataMock(), "test", "", "", false)
|
||||
opap.AllPolicies = policies
|
||||
opap.Process(context.TODO(), policies, nil)
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ package opaprocessor
|
||||
import (
|
||||
"context"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
@@ -16,6 +14,7 @@ import (
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
resources "github.com/kubescape/opa-utils/resources"
|
||||
"go.opentelemetry.io/otel"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const clusterScope = "clusterScope"
|
||||
|
||||
@@ -3,11 +3,9 @@ package opaprocessor
|
||||
import (
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestRemoveData(t *testing.T) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package opaprocessor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// convertFrameworksToPolicies convert list of frameworks to list of policies
|
||||
|
||||
@@ -3,13 +3,12 @@ package opaprocessor
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
v2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestConvertFrameworksToPolicies(t *testing.T) {
|
||||
|
||||
@@ -158,8 +158,11 @@ func (policyHandler *PolicyHandler) downloadScanPolicies(ctx context.Context, po
|
||||
if receivedFramework != nil {
|
||||
frameworks = append(frameworks, *receivedFramework)
|
||||
cache := getter.GetDefaultPath(rule.Identifier + ".json")
|
||||
if _, ok := policyHandler.getters.PolicyGetter.(*getter.LoadPolicy); ok {
|
||||
continue // skip caching for local files
|
||||
}
|
||||
if err := getter.SaveInFile(receivedFramework, cache); err != nil {
|
||||
logger.L().Ctx(ctx).Warning("failed to cache file", helpers.String("file", cache), helpers.Error(err))
|
||||
logger.L().Ctx(ctx).Warning("failed to cache framework", helpers.String("file", cache), helpers.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,7 +181,7 @@ func (policyHandler *PolicyHandler) downloadScanPolicies(ctx context.Context, po
|
||||
|
||||
cache := getter.GetDefaultPath(policy.Identifier + ".json")
|
||||
if err := getter.SaveInFile(receivedControl, cache); err != nil {
|
||||
logger.L().Ctx(ctx).Warning("failed to cache file", helpers.String("file", cache), helpers.Error(err))
|
||||
logger.L().Ctx(ctx).Warning("failed to cache control", helpers.String("file", cache), helpers.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
)
|
||||
|
||||
func getScanKind(policyIdentifier []cautils.PolicyIdentifier) apisv1.NotificationPolicyKind {
|
||||
|
||||
@@ -6,14 +6,13 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
)
|
||||
|
||||
// FileResourceHandler handle resources from files and URLs
|
||||
@@ -39,13 +38,12 @@ func (fileHandler *FileResourceHandler) GetResources(ctx context.Context, sessio
|
||||
for path := range scanInfo.InputPatterns {
|
||||
var workloadIDToSource map[string]reporthandling.Source
|
||||
var workloads []workloadinterface.IMetadata
|
||||
var workloadIDToMappingNodes map[string]cautils.MappingNodes
|
||||
var err error
|
||||
|
||||
if scanInfo.ChartPath != "" && scanInfo.FilePath != "" {
|
||||
workloadIDToSource, workloads, workloadIDToMappingNodes, _ = getWorkloadFromHelmChart(ctx, scanInfo.InputPatterns[path], scanInfo.ChartPath, scanInfo.FilePath)
|
||||
workloadIDToSource, workloads, _ = getWorkloadFromHelmChart(ctx, scanInfo.InputPatterns[path], scanInfo.ChartPath, scanInfo.FilePath)
|
||||
} else {
|
||||
workloadIDToSource, workloads, workloadIDToMappingNodes, err = getResourcesFromPath(ctx, scanInfo.InputPatterns[path])
|
||||
workloadIDToSource, workloads, err = getResourcesFromPath(ctx, scanInfo.InputPatterns[path])
|
||||
if err != nil {
|
||||
return nil, allResources, nil, nil, err
|
||||
}
|
||||
@@ -56,7 +54,7 @@ func (fileHandler *FileResourceHandler) GetResources(ctx context.Context, sessio
|
||||
|
||||
for k, v := range workloadIDToSource {
|
||||
sessionObj.ResourceSource[k] = v
|
||||
sessionObj.TemplateMapping[k] = workloadIDToMappingNodes[k]
|
||||
sessionObj.TemplateMapping[k] = cautils.MappingNodes{}
|
||||
}
|
||||
|
||||
// map all resources: map["/apiVersion/version/kind"][]<k8s workloads>
|
||||
@@ -102,7 +100,7 @@ func (fileHandler *FileResourceHandler) GetResources(ctx context.Context, sessio
|
||||
func (fileHandler *FileResourceHandler) GetCloudProvider() string {
|
||||
return ""
|
||||
}
|
||||
func getWorkloadFromHelmChart(ctx context.Context, path, helmPath, workloadPath string) (map[string]reporthandling.Source, []workloadinterface.IMetadata, map[string]cautils.MappingNodes, error) {
|
||||
func getWorkloadFromHelmChart(ctx context.Context, path, helmPath, workloadPath string) (map[string]reporthandling.Source, []workloadinterface.IMetadata, error) {
|
||||
clonedRepo := cautils.GetClonedPath(path)
|
||||
|
||||
if clonedRepo != "" {
|
||||
@@ -116,38 +114,31 @@ func getWorkloadFromHelmChart(ctx context.Context, path, helmPath, workloadPath
|
||||
// Get repo root
|
||||
repoRoot, gitRepo := extractGitRepo(clonedRepo)
|
||||
|
||||
helmSourceToWorkloads, helmSourceToChart, helmSourceToNodes := cautils.LoadResourcesFromHelmCharts(ctx, helmPath)
|
||||
helmSourceToWorkloads, helmSourceToChart := cautils.LoadResourcesFromHelmCharts(ctx, helmPath)
|
||||
|
||||
wlSource, ok := helmSourceToWorkloads[workloadPath]
|
||||
if !ok {
|
||||
return nil, nil, nil, fmt.Errorf("workload %s not found in chart %s", workloadPath, helmPath)
|
||||
return nil, nil, fmt.Errorf("workload %s not found in chart %s", workloadPath, helmPath)
|
||||
}
|
||||
|
||||
if len(wlSource) != 1 {
|
||||
return nil, nil, nil, fmt.Errorf("workload %s found multiple times in chart %s", workloadPath, helmPath)
|
||||
return nil, nil, fmt.Errorf("workload %s found multiple times in chart %s", workloadPath, helmPath)
|
||||
}
|
||||
|
||||
helmChart, ok := helmSourceToChart[workloadPath]
|
||||
if !ok {
|
||||
return nil, nil, nil, fmt.Errorf("helmChart not found for workload %s", workloadPath)
|
||||
}
|
||||
|
||||
templatesNodes, ok := helmSourceToNodes[workloadPath]
|
||||
if !ok {
|
||||
return nil, nil, nil, fmt.Errorf("templatesNodes not found for workload %s", workloadPath)
|
||||
return nil, nil, fmt.Errorf("helmChart not found for workload %s", workloadPath)
|
||||
}
|
||||
|
||||
workloadSource := getWorkloadSourceHelmChart(repoRoot, helmPath, gitRepo, helmChart)
|
||||
|
||||
workloadIDToSource := make(map[string]reporthandling.Source, 1)
|
||||
workloadIDToNodes := make(map[string]cautils.MappingNodes, 1)
|
||||
workloadIDToSource[wlSource[0].GetID()] = workloadSource
|
||||
workloadIDToNodes[wlSource[0].GetID()] = templatesNodes
|
||||
|
||||
var workloads []workloadinterface.IMetadata
|
||||
workloads = append(workloads, wlSource...)
|
||||
|
||||
return workloadIDToSource, workloads, workloadIDToNodes, nil
|
||||
return workloadIDToSource, workloads, nil
|
||||
|
||||
}
|
||||
|
||||
@@ -181,9 +172,8 @@ func getWorkloadSourceHelmChart(repoRoot string, source string, gitRepo *cautils
|
||||
}
|
||||
}
|
||||
|
||||
func getResourcesFromPath(ctx context.Context, path string) (map[string]reporthandling.Source, []workloadinterface.IMetadata, map[string]cautils.MappingNodes, error) {
|
||||
func getResourcesFromPath(ctx context.Context, path string) (map[string]reporthandling.Source, []workloadinterface.IMetadata, error) {
|
||||
workloadIDToSource := make(map[string]reporthandling.Source)
|
||||
workloadIDToNodes := make(map[string]cautils.MappingNodes)
|
||||
var workloads []workloadinterface.IMetadata
|
||||
|
||||
clonedRepo := cautils.GetClonedPath(path)
|
||||
@@ -270,14 +260,10 @@ func getResourcesFromPath(ctx context.Context, path string) (map[string]reportha
|
||||
}
|
||||
|
||||
// load resources from helm charts
|
||||
helmSourceToWorkloads, helmSourceToChart, helmSourceToNodes := cautils.LoadResourcesFromHelmCharts(ctx, path)
|
||||
helmSourceToWorkloads, helmSourceToChart := cautils.LoadResourcesFromHelmCharts(ctx, path)
|
||||
for source, ws := range helmSourceToWorkloads {
|
||||
workloads = append(workloads, ws...)
|
||||
helmChart := helmSourceToChart[source]
|
||||
var templatesNodes cautils.MappingNodes
|
||||
if nodes, ok := helmSourceToNodes[source]; ok {
|
||||
templatesNodes = nodes
|
||||
}
|
||||
|
||||
if clonedRepo != "" && gitRepo != nil {
|
||||
url, err := gitRepo.GetRemoteUrl()
|
||||
@@ -288,14 +274,12 @@ func getResourcesFromPath(ctx context.Context, path string) (map[string]reportha
|
||||
helmChart.Path = strings.TrimSuffix(url, ".git")
|
||||
repoRoot = ""
|
||||
source = strings.TrimPrefix(source, fmt.Sprintf("%s/", clonedRepo))
|
||||
templatesNodes.TemplateFileName = source
|
||||
}
|
||||
|
||||
workloadSource := getWorkloadSourceHelmChart(repoRoot, source, gitRepo, helmChart)
|
||||
|
||||
for i := range ws {
|
||||
workloadIDToSource[ws[i].GetID()] = workloadSource
|
||||
workloadIDToNodes[ws[i].GetID()] = templatesNodes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +326,7 @@ func getResourcesFromPath(ctx context.Context, path string) (map[string]reportha
|
||||
}
|
||||
}
|
||||
|
||||
return workloadIDToSource, workloads, workloadIDToNodes, nil
|
||||
return workloadIDToSource, workloads, nil
|
||||
}
|
||||
|
||||
func extractGitRepo(path string) (string, *cautils.LocalGitRepository) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
helpersv1 "github.com/kubescape/opa-utils/reporthandling/helpers/v1"
|
||||
reportv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
|
||||
@@ -7,26 +7,24 @@ import (
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/metrics"
|
||||
"github.com/kubescape/kubescape/v3/core/pkg/hostsensorutils"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/pager"
|
||||
|
||||
"github.com/kubescape/k8s-interface/cloudsupport"
|
||||
cloudapis "github.com/kubescape/k8s-interface/cloudsupport/apis"
|
||||
cloudv1 "github.com/kubescape/k8s-interface/cloudsupport/v1"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/metrics"
|
||||
"github.com/kubescape/kubescape/v3/core/pkg/hostsensorutils"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
k8slabels "k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/tools/pager"
|
||||
)
|
||||
|
||||
type cloudResourceGetter func(string, string) (workloadinterface.IMetadata, error)
|
||||
@@ -377,12 +375,13 @@ func (k8sHandler *K8sResourceHandler) pullSingleResource(resource *schema.GroupV
|
||||
clientResource := k8sHandler.k8s.DynamicClient.Resource(*resource)
|
||||
|
||||
// list resources
|
||||
lenBefore := len(resourceList)
|
||||
if err := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) {
|
||||
return clientResource.List(ctx, opts)
|
||||
}).EachListItem(context.Background(), listOptions, func(obj runtime.Object) error {
|
||||
uObject := obj.(*unstructured.Unstructured)
|
||||
if k8sinterface.IsTypeWorkload(uObject.Object) && k8sinterface.WorkloadHasParent(workloadinterface.NewWorkloadObj(uObject.Object)) {
|
||||
logger.L().Debug("Skipping resource with parent", helpers.String("kind", uObject.GetKind()), helpers.String("name", uObject.GetName()))
|
||||
logger.L().Debug("Skipping resource with parent", helpers.String("resource", resource.String()), helpers.String("namespace", uObject.GetNamespace()), helpers.String("name", uObject.GetName()))
|
||||
return nil
|
||||
}
|
||||
resourceList = append(resourceList, *obj.(*unstructured.Unstructured))
|
||||
@@ -390,7 +389,7 @@ func (k8sHandler *K8sResourceHandler) pullSingleResource(resource *schema.GroupV
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to get resource: %v, labelSelector: %v, fieldSelector: %v, reason: %w", resource, listOptions.LabelSelector, listOptions.FieldSelector, err)
|
||||
}
|
||||
|
||||
logger.L().Debug("Pulled resources", helpers.String("resource", resource.String()), helpers.String("fieldSelector", listOptions.FieldSelector), helpers.String("labelSelector", listOptions.LabelSelector), helpers.Int("count", len(resourceList)-lenBefore))
|
||||
}
|
||||
|
||||
return resourceList, nil
|
||||
|
||||
@@ -4,13 +4,12 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"k8s.io/utils/strings/slices"
|
||||
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package resourcehandler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSsEmptyImgVulns(t *testing.T) {
|
||||
|
||||
@@ -229,8 +229,14 @@ func (g *GitHubRepository) getFilesFromTree(filesExtensions []string) []string {
|
||||
return []string{}
|
||||
}
|
||||
}
|
||||
|
||||
basePath := g.path
|
||||
if basePath != "" && !strings.HasSuffix(basePath, "/") {
|
||||
basePath += "/"
|
||||
}
|
||||
|
||||
for _, path := range g.tree.InnerTrees {
|
||||
if g.path != "" && !strings.HasPrefix(path.Path, g.path) {
|
||||
if basePath != "" && !strings.HasPrefix(path.Path, basePath) {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(filesExtensions, getFileExtension(path.Path)) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package resourcehandler
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -13,6 +14,27 @@ var (
|
||||
// urlD = "https://raw.githubusercontent.com/kubescape/kubescape/master/examples/online-boutique/adservice.yaml"
|
||||
)
|
||||
|
||||
var mockTree = tree{
|
||||
InnerTrees: []innerTree{
|
||||
{Path: "charts/fluent-bit/values.yaml"},
|
||||
{Path: "charts/fluent-bit/templates/configmap.yaml"},
|
||||
{Path: "charts/other-chart/templates/deployment.yaml"},
|
||||
{Path: "README.md"},
|
||||
},
|
||||
}
|
||||
|
||||
func newMockGitHubRepository(path string, isFile bool) *GitHubRepository {
|
||||
return &GitHubRepository{
|
||||
host: "github.com",
|
||||
owner: "grafana",
|
||||
repo: "helm-charts",
|
||||
branch: "main",
|
||||
path: path,
|
||||
isFile: isFile,
|
||||
tree: mockTree,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
TODO: tests were commented out due to actual http calls ; http calls should be mocked.
|
||||
@@ -143,3 +165,60 @@ func TestGithubParse(t *testing.T) {
|
||||
assert.False(t, gh.isFile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFilesFromTree(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repo *GitHubRepository
|
||||
extensions []string
|
||||
expectedResults []string
|
||||
}{
|
||||
{
|
||||
name: "Scan entire repo for YAML files",
|
||||
repo: newMockGitHubRepository("", false),
|
||||
extensions: []string{"yaml", "yml"},
|
||||
expectedResults: []string{
|
||||
"https://raw.githubusercontent.com/grafana/helm-charts/main/charts/fluent-bit/values.yaml",
|
||||
"https://raw.githubusercontent.com/grafana/helm-charts/main/charts/fluent-bit/templates/configmap.yaml",
|
||||
"https://raw.githubusercontent.com/grafana/helm-charts/main/charts/other-chart/templates/deployment.yaml",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Scan specific folder (fluent-bit) for YAML files",
|
||||
repo: newMockGitHubRepository("charts/fluent-bit", false),
|
||||
extensions: []string{"yaml", "yml"},
|
||||
expectedResults: []string{
|
||||
"https://raw.githubusercontent.com/grafana/helm-charts/main/charts/fluent-bit/values.yaml",
|
||||
"https://raw.githubusercontent.com/grafana/helm-charts/main/charts/fluent-bit/templates/configmap.yaml",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Scan root with non-matching extension (JSON)",
|
||||
repo: newMockGitHubRepository("", false),
|
||||
extensions: []string{"json"},
|
||||
expectedResults: []string{},
|
||||
},
|
||||
{
|
||||
name: "Scan specific file",
|
||||
repo: newMockGitHubRepository("charts/fluent-bit/values.yaml", true),
|
||||
extensions: []string{"yaml"},
|
||||
expectedResults: []string{
|
||||
"https://raw.githubusercontent.com/grafana/helm-charts/main/charts/fluent-bit/values.yaml",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.repo.getFilesFromTree(tt.extensions)
|
||||
|
||||
if len(got) == 0 && len(tt.expectedResults) == 0 {
|
||||
return // both are empty, so this test case passes
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tt.expectedResults) {
|
||||
t.Errorf("getFilesFromTree() = %v, want %v", got, tt.expectedResults)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func NewResourcesPrioritizationHandler(ctx context.Context, attackTracksGetter g
|
||||
// Store attack tracks in cache
|
||||
cache := getter.GetDefaultPath(cautils.LocalAttackTracksFilename)
|
||||
if err := getter.SaveInFile(tracks, cache); err != nil {
|
||||
logger.L().Ctx(ctx).Warning("failed to cache file", helpers.String("file", cache), helpers.Error(err))
|
||||
logger.L().Ctx(ctx).Warning("failed to cache attack track", helpers.String("file", cache), helpers.Error(err))
|
||||
}
|
||||
|
||||
return handler, nil
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||
|
||||
"gopkg.in/op/go-logging.v1"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/kubescape/kubescape/v3/internal/testutils"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -24,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")
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3,24 +3,20 @@ package printer
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
|
||||
"github.com/johnfercher/maroto/pkg/color"
|
||||
"github.com/johnfercher/maroto/pkg/consts"
|
||||
"github.com/johnfercher/maroto/pkg/pdf"
|
||||
"github.com/johnfercher/maroto/pkg/props"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,11 +24,6 @@ const (
|
||||
pdfOutputExt = ".pdf"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed pdf/logo.png
|
||||
kubescapeLogo []byte
|
||||
)
|
||||
|
||||
var _ printer.IPrinter = &PdfPrinter{}
|
||||
|
||||
type PdfPrinter struct {
|
||||
@@ -67,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")
|
||||
})
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user