mirror of
https://github.com/kubescape/kubescape.git
synced 2026-04-15 06:58:11 +00:00
Compare commits
219 Commits
v3.0.0
...
fix-backsl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
945516f840 | ||
|
|
6a6e4181bd | ||
|
|
5e41d7db1e | ||
|
|
5e9fbf05f1 | ||
|
|
b4f58f3a6d | ||
|
|
2ec3e47f0a | ||
|
|
b6030c0bc5 | ||
|
|
4b8786bcaa | ||
|
|
bdefcd2442 | ||
|
|
4e4a642673 | ||
|
|
3634499e11 | ||
|
|
9f5d4f97df | ||
|
|
a0e6ebe0e0 | ||
|
|
65a557db90 | ||
|
|
d15a837139 | ||
|
|
d4cb97905e | ||
|
|
4208ed1ca6 | ||
|
|
4adb1da4d2 | ||
|
|
3ba1e9c187 | ||
|
|
81eec99b51 | ||
|
|
3ba3af8244 | ||
|
|
8ca6f71d57 | ||
|
|
10278a9088 | ||
|
|
155889a479 | ||
|
|
06d9c9d31c | ||
|
|
f309d54b08 | ||
|
|
0d2a667299 | ||
|
|
1dd8022d6a | ||
|
|
e29e6a5e8a | ||
|
|
e27237047e | ||
|
|
1b260f60cc | ||
|
|
882a8404d3 | ||
|
|
b797413ef0 | ||
|
|
298e30e857 | ||
|
|
6ba727b721 | ||
|
|
362557a964 | ||
|
|
c119911914 | ||
|
|
de2e86dc6e | ||
|
|
a2aa52f4cb | ||
|
|
a382a2c5a3 | ||
|
|
dd5a18c8fa | ||
|
|
e4e795c6dc | ||
|
|
2c65d92d69 | ||
|
|
d1b78856ca | ||
|
|
289e055014 | ||
|
|
81ba550043 | ||
|
|
8e5d8e5c96 | ||
|
|
5789f1f0fa | ||
|
|
9a523f4a01 | ||
|
|
fb3357fba4 | ||
|
|
69362ae415 | ||
|
|
de799d7b75 | ||
|
|
bb17e1de78 | ||
|
|
23013d6fe6 | ||
|
|
27d2fe8f27 | ||
|
|
504891f519 | ||
|
|
d1606c5e39 | ||
|
|
952beddcc3 | ||
|
|
9630adc74b | ||
|
|
27c171d09c | ||
|
|
bd79fe4d8d | ||
|
|
08f3756608 | ||
|
|
7cda7346b7 | ||
|
|
8cd0bddf6f | ||
|
|
8fa7fc922d | ||
|
|
6a2e48ac74 | ||
|
|
b68cfbed67 | ||
|
|
0bfbb87285 | ||
|
|
491ed09f6c | ||
|
|
658855aaee | ||
|
|
5ed8e180b3 | ||
|
|
e4477eaac4 | ||
|
|
64351f93be | ||
|
|
70a010976e | ||
|
|
d653530ba2 | ||
|
|
5242e8c4b0 | ||
|
|
efd2f7e77f | ||
|
|
16c632950d | ||
|
|
070d8544cd | ||
|
|
b562c1d730 | ||
|
|
fdeadda704 | ||
|
|
5ef720bfee | ||
|
|
0bc2b33e99 | ||
|
|
b9bcb6bbbf | ||
|
|
620f9b2717 | ||
|
|
2ff1512ed0 | ||
|
|
ca57f66b8b | ||
|
|
770f660db5 | ||
|
|
f54c2ee822 | ||
|
|
5172ce30d1 | ||
|
|
4e24ad87dd | ||
|
|
c49f9c88e2 | ||
|
|
4bf3783677 | ||
|
|
139a89770f | ||
|
|
c84a8a7dea | ||
|
|
2fb4efa531 | ||
|
|
1d2993e83e | ||
|
|
68f5ae7ed2 | ||
|
|
803b8dc5a4 | ||
|
|
b0913b2a4f | ||
|
|
8f9824a426 | ||
|
|
28baac78fb | ||
|
|
210b5dac33 | ||
|
|
2d31472fa1 | ||
|
|
a3b4d60dfb | ||
|
|
50bd74a173 | ||
|
|
9ea4b0dd93 | ||
|
|
ba3e416eb8 | ||
|
|
0af0f2a229 | ||
|
|
bca14ea369 | ||
|
|
6f1919bbe2 | ||
|
|
11401c755a | ||
|
|
69bbf7f72e | ||
|
|
524b6f2b1d | ||
|
|
2adb72be8e | ||
|
|
43ba550f72 | ||
|
|
3d606245f2 | ||
|
|
85da52ebbd | ||
|
|
9e7eb6243a | ||
|
|
9611fb631b | ||
|
|
44ddbc6ae5 | ||
|
|
f86fa99316 | ||
|
|
2603f04cfc | ||
|
|
029b4c2677 | ||
|
|
84d4ff7cfe | ||
|
|
a76e02cb8a | ||
|
|
f351b3b333 | ||
|
|
dfd13aea6f | ||
|
|
660a9801a4 | ||
|
|
9fda098f70 | ||
|
|
c02c8bf7e2 | ||
|
|
aa45a874b9 | ||
|
|
94f6261055 | ||
|
|
9c38c1a090 | ||
|
|
1d7519c3b7 | ||
|
|
6cf03bd679 | ||
|
|
f3670ca629 | ||
|
|
3ce838e344 | ||
|
|
e8228c149a | ||
|
|
ef3bda9972 | ||
|
|
66df4412b0 | ||
|
|
db1c4afcd6 | ||
|
|
5ea09516ef | ||
|
|
a0911d8752 | ||
|
|
47d81ce721 | ||
|
|
6fe6dbb333 | ||
|
|
53f45e599a | ||
|
|
6b4ef219c9 | ||
|
|
d496485f75 | ||
|
|
d6bb70ba4a | ||
|
|
1f0cbad800 | ||
|
|
362375a733 | ||
|
|
92d39c5abc | ||
|
|
c4f0e6e46b | ||
|
|
6fcfe7f4e5 | ||
|
|
633024f8c5 | ||
|
|
92a4c1f64a | ||
|
|
1f43de06f8 | ||
|
|
5d5ac5c5d5 | ||
|
|
04b06d875b | ||
|
|
5d795edd31 | ||
|
|
fd390bbd37 | ||
|
|
3b78169f8c | ||
|
|
ba7317b4eb | ||
|
|
85b8648724 | ||
|
|
55162829e7 | ||
|
|
27590f623f | ||
|
|
bc2fc83599 | ||
|
|
cb78723a96 | ||
|
|
a513c27dce | ||
|
|
f814d1df19 | ||
|
|
5455855e65 | ||
|
|
eff7f36866 | ||
|
|
12056f4cad | ||
|
|
d96ab483a4 | ||
|
|
43dbb4ac70 | ||
|
|
dc6c379aa2 | ||
|
|
8cacd4d984 | ||
|
|
1342a06f43 | ||
|
|
55da8c1ce2 | ||
|
|
6adfef2a48 | ||
|
|
beb6d9535c | ||
|
|
8827434cce | ||
|
|
9845175d29 | ||
|
|
ddf01648b4 | ||
|
|
16f4849323 | ||
|
|
4ae45cd727 | ||
|
|
b0a376aa2b | ||
|
|
d45c97cef0 | ||
|
|
ec40320a2d | ||
|
|
7eb97fcba0 | ||
|
|
73d1805ce6 | ||
|
|
c7f9a6ebc4 | ||
|
|
a2f632beb4 | ||
|
|
567698356e | ||
|
|
887f6a0d0e | ||
|
|
0191135b10 | ||
|
|
8b596ec951 | ||
|
|
4863edc042 | ||
|
|
dc6d85bc34 | ||
|
|
1c48636155 | ||
|
|
bd5f8a9439 | ||
|
|
18850b8d41 | ||
|
|
47bab2a9ed | ||
|
|
9e8b11c34f | ||
|
|
74bfb57d3a | ||
|
|
9fb56a2856 | ||
|
|
9a098c59df | ||
|
|
c781bc3166 | ||
|
|
a027a3d3d5 | ||
|
|
ee37dc499b | ||
|
|
450df679cd | ||
|
|
c9ccef90f3 | ||
|
|
3b2feca0dd | ||
|
|
edfc5d5949 | ||
|
|
e00c7722f1 | ||
|
|
fd2fc3db34 | ||
|
|
5111bb541a | ||
|
|
1d25415c21 |
7
.github/workflows/00-pr-scanner.yaml
vendored
7
.github/workflows/00-pr-scanner.yaml
vendored
@@ -41,6 +41,7 @@ jobs:
|
||||
secrets: inherit
|
||||
|
||||
binary-build:
|
||||
if: ${{ github.repository_owner == 'kubescape' }}
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
@@ -58,11 +59,9 @@ jobs:
|
||||
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
|
||||
with:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 1
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.20"
|
||||
GO_VERSION: "1.21"
|
||||
RELEASE: "latest"
|
||||
CLIENT: test
|
||||
ARCH_MATRIX: '[ "" ]'
|
||||
OS_MATRIX: '[ "ubuntu-20.04" ]'
|
||||
secrets: inherit
|
||||
|
||||
4
.github/workflows/02-release.yaml
vendored
4
.github/workflows/02-release.yaml
vendored
@@ -34,9 +34,9 @@ jobs:
|
||||
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
|
||||
with:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 1
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.20"
|
||||
GO_VERSION: "1.21"
|
||||
RELEASE: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
CLIENT: release
|
||||
secrets: inherit
|
||||
|
||||
8
.github/workflows/a-pr-scanner.yaml
vendored
8
.github/workflows/a-pr-scanner.yaml
vendored
@@ -23,14 +23,14 @@ jobs:
|
||||
name: PR Scanner
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # Install go because go-licenses use it ratchet:actions/setup-go@v3
|
||||
- uses: actions/setup-go@v4
|
||||
name: Installing go
|
||||
with:
|
||||
go-version: '1.20'
|
||||
go-version: '1.21'
|
||||
cache: true
|
||||
- name: Scanning - Forbidden Licenses (go-licenses)
|
||||
id: licenses-scan
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
uses: coverallsapp/github-action@v1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
path-to-lcov: coverage.lcov
|
||||
|
||||
- name: Comment results to PR
|
||||
|
||||
162
.github/workflows/b-binary-build-and-e2e-tests.yaml
vendored
162
.github/workflows/b-binary-build-and-e2e-tests.yaml
vendored
@@ -18,7 +18,7 @@ on:
|
||||
GO_VERSION:
|
||||
required: false
|
||||
type: string
|
||||
default: "1.20"
|
||||
default: "1.21"
|
||||
GO111MODULE:
|
||||
required: false
|
||||
type: string
|
||||
@@ -27,14 +27,6 @@ on:
|
||||
type: number
|
||||
default: 1
|
||||
required: false
|
||||
OS_MATRIX:
|
||||
type: string
|
||||
required: false
|
||||
default: '[ "ubuntu-20.04", "macos-latest", "windows-latest"]'
|
||||
ARCH_MATRIX:
|
||||
type: string
|
||||
required: false
|
||||
default: '[ "", "arm64"]'
|
||||
BINARY_TESTS:
|
||||
type: string
|
||||
required: false
|
||||
@@ -53,7 +45,7 @@ on:
|
||||
type: string
|
||||
GO_VERSION:
|
||||
type: string
|
||||
default: "1.20"
|
||||
default: "1.21"
|
||||
GO111MODULE:
|
||||
required: true
|
||||
type: string
|
||||
@@ -62,15 +54,7 @@ on:
|
||||
default: 1
|
||||
BINARY_TESTS:
|
||||
type: string
|
||||
default: '[ "scan_nsa", "scan_mitre", "scan_with_exceptions", "scan_repository", "scan_local_file", "scan_local_glob_files", "scan_local_list_of_files", "scan_nsa_and_submit_to_backend", "scan_mitre_and_submit_to_backend", "scan_local_repository_and_submit_to_backend", "scan_repository_from_url_and_submit_to_backend", "scan_with_exception_to_backend", "scan_with_custom_framework", "scan_customer_configuration", "host_scanner", "scan_compliance_score", "scan_custom_framework_scanning_file_scope_testing", "scan_custom_framework_scanning_cluster_scope_testing", "scan_custom_framework_scanning_cluster_and_file_scope_testing", "unified_configuration_config_view", "unified_configuration_config_set", "unified_configuration_config_delete" ]'
|
||||
OS_MATRIX:
|
||||
type: string
|
||||
required: false
|
||||
default: '[ "ubuntu-20.04", "macos-latest", "windows-latest"]'
|
||||
ARCH_MATRIX:
|
||||
type: string
|
||||
required: false
|
||||
default: '[ "", "arm64"]'
|
||||
default: '[ "scan_nsa", "scan_mitre", "scan_with_exceptions", "scan_repository", "scan_local_file", "scan_local_glob_files", "scan_local_list_of_files", "scan_nsa_and_submit_to_backend", "scan_mitre_and_submit_to_backend", "scan_local_repository_and_submit_to_backend", "scan_repository_from_url_and_submit_to_backend", "scan_with_exception_to_backend", "scan_with_custom_framework", "scan_customer_configuration", "host_scanner", "scan_compliance_score", "scan_custom_framework_scanning_file_scope_testing", "scan_custom_framework_scanning_cluster_scope_testing", "scan_custom_framework_scanning_cluster_and_file_scope_testing" ]'
|
||||
|
||||
jobs:
|
||||
wf-preparation:
|
||||
@@ -78,8 +62,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
TEST_NAMES: ${{ steps.export_tests_to_env.outputs.TEST_NAMES }}
|
||||
OS_MATRIX: ${{ steps.export_os_to_env.outputs.OS_MATRIX }}
|
||||
ARCH_MATRIX: ${{ steps.export_arch_to_env.outputs.ARCH_MATRIX }}
|
||||
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
|
||||
|
||||
steps:
|
||||
@@ -95,13 +77,6 @@ jobs:
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: "echo \"is-secret-set=${{ env.CUSTOMER != '' && \n env.USERNAME != '' &&\n env.PASSWORD != '' &&\n env.CLIENT_ID != '' &&\n env.SECRET_KEY != '' &&\n env.REGISTRY_USERNAME != '' &&\n env.REGISTRY_PASSWORD != ''\n }}\" >> $GITHUB_OUTPUT\n"
|
||||
|
||||
- id: export_os_to_env
|
||||
name: set test name
|
||||
run: |
|
||||
echo "OS_MATRIX=$input" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
input: ${{ inputs.OS_MATRIX }}
|
||||
|
||||
- id: export_tests_to_env
|
||||
name: set test name
|
||||
run: |
|
||||
@@ -109,13 +84,6 @@ jobs:
|
||||
env:
|
||||
input: ${{ inputs.BINARY_TESTS }}
|
||||
|
||||
- id: export_arch_to_env
|
||||
name: set test name
|
||||
run: |
|
||||
echo "ARCH_MATRIX=$input" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
input: ${{ inputs.ARCH_MATRIX }}
|
||||
|
||||
check-secret:
|
||||
name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
|
||||
runs-on: ubuntu-latest
|
||||
@@ -135,124 +103,49 @@ jobs:
|
||||
needs: wf-preparation
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: ${{ fromJson(needs.wf-preparation.outputs.OS_MATRIX) }}
|
||||
arch: ${{ fromJson(needs.wf-preparation.outputs.ARCH_MATRIX) }}
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
arch: arm64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Cache Go modules (Linux)
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # ratchet:actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Cache Go modules (macOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # ratchet:actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/Library/Caches/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Cache Go modules (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # ratchet:actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~\AppData\Local\go-build
|
||||
~\go\pkg\mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # ratchet:actions/setup-go@v3
|
||||
- uses: actions/setup-go@v4
|
||||
name: Installing go
|
||||
with:
|
||||
go-version: ${{ inputs.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: start ${{ matrix.arch }} environment in container
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y binfmt-support qemu-user-static
|
||||
sudo docker run --platform linux/${{ matrix.arch }} -e RELEASE=${{ inputs.RELEASE }} \
|
||||
-e CLIENT=${{ inputs.CLIENT }} -e CGO_ENABLED=${{ inputs.CGO_ENABLED }} \
|
||||
-e KUBESCAPE_SKIP_UPDATE_CHECK=true -e GOARCH=${{ matrix.arch }} -v ${PWD}:/work \
|
||||
-w /work -v ~/go/pkg/mod:/root/go/pkg/mod -v ~/.cache/go-build:/root/.cache/go-build \
|
||||
-d --name build golang:${{ inputs.GO_VERSION }}-bullseye sleep 21600
|
||||
sudo docker ps
|
||||
DOCKER_CMD="sudo docker exec build"
|
||||
${DOCKER_CMD} apt update
|
||||
${DOCKER_CMD} apt install -y cmake python3
|
||||
${DOCKER_CMD} git config --global --add safe.directory '*'
|
||||
echo "DOCKER_CMD=${DOCKER_CMD}" >> $GITHUB_ENV;
|
||||
if: matrix.os == 'ubuntu-20.04' && matrix.arch != ''
|
||||
|
||||
- name: Install pkg-config (macOS)
|
||||
run: brew install pkg-config
|
||||
if: matrix.os == 'macos-latest'
|
||||
|
||||
- name: Install libgit2 (Linux/macOS)
|
||||
run: ${{ env.DOCKER_CMD }} make libgit2${{ matrix.arch }}
|
||||
if: matrix.os != 'windows-latest'
|
||||
|
||||
- name: Test core pkg
|
||||
run: ${{ env.DOCKER_CMD }} go test -v ./...
|
||||
if: "!startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-20.04' && matrix.arch == '' || startsWith(github.ref, 'refs/tags') && (matrix.os != 'macos-latest' || matrix.arch != 'arm64')"
|
||||
if: startsWith(github.ref, 'refs/tags')
|
||||
|
||||
- name: Test httphandler pkg
|
||||
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test -v ./...'
|
||||
if: "!startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-20.04' && matrix.arch == '' || startsWith(github.ref, 'refs/tags') && (matrix.os != 'macos-latest' || matrix.arch != 'arm64')"
|
||||
if: startsWith(github.ref, 'refs/tags')
|
||||
|
||||
- name: Build
|
||||
- uses: anchore/sbom-action/download-syft@v0.15.2
|
||||
name: Setup Syft
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v5
|
||||
name: Build
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean --snapshot
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
CLIENT: ${{ inputs.CLIENT }}
|
||||
CGO_ENABLED: ${{ inputs.CGO_ENABLED }}
|
||||
run: ${{ env.DOCKER_CMD }} python3 --version && ${{ env.DOCKER_CMD }} python3 build.py
|
||||
|
||||
- name: Smoke Testing (Windows / MacOS)
|
||||
- name: Smoke Testing
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/kubescape-${{ matrix.os }}
|
||||
if: startsWith(github.ref, 'refs/tags') && matrix.os != 'ubuntu-20.04' && matrix.arch == ''
|
||||
|
||||
- name: Smoke Testing (Linux amd64)
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ${PWD}/build/kubescape-ubuntu-latest
|
||||
if: matrix.os == 'ubuntu-20.04' && matrix.arch == ''
|
||||
|
||||
- name: Smoke Testing (Linux ${{ matrix.arch }})
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ./build/kubescape-${{ matrix.arch }}-ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-20.04' && matrix.arch != ''
|
||||
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ${PWD}/dist/kubescape-ubuntu-latest
|
||||
|
||||
- name: golangci-lint
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
continue-on-error: true
|
||||
uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5 # ratchet:golangci/golangci-lint-action@v3
|
||||
with:
|
||||
@@ -261,19 +154,10 @@ jobs:
|
||||
only-new-issues: true
|
||||
|
||||
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # ratchet:actions/upload-artifact@v3.1.1
|
||||
name: Upload artifact (Linux)
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
name: Upload artifacts
|
||||
with:
|
||||
name: kubescape${{ matrix.arch }}-ubuntu-latest
|
||||
path: build/
|
||||
if-no-files-found: error
|
||||
|
||||
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # ratchet:actions/upload-artifact@v3.1.1
|
||||
name: Upload artifact (MacOS, Win)
|
||||
if: matrix.os != 'ubuntu-20.04'
|
||||
with:
|
||||
name: kubescape${{ matrix.arch }}-${{ matrix.os }}
|
||||
path: build/
|
||||
name: kubescape
|
||||
path: dist/kubescape*
|
||||
if-no-files-found: error
|
||||
|
||||
build-http-image:
|
||||
@@ -291,7 +175,7 @@ jobs:
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: "on"
|
||||
BUILD_PLATFORM: linux/amd64,linux/arm64
|
||||
GO_VERSION: "1.20"
|
||||
GO_VERSION: "1.21"
|
||||
REQUIRED_TESTS: '[
|
||||
"ks_microservice_create_2_cronjob_mitre_and_nsa_proxy",
|
||||
"ks_microservice_triggering_with_cron_job",
|
||||
|
||||
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.20"
|
||||
GO_VERSION: "1.21"
|
||||
REQUIRED_TESTS: '[]'
|
||||
COSIGN: ${{ inputs.CO_SIGN }}
|
||||
HELM_E2E_TEST: false
|
||||
|
||||
32
.github/workflows/d-publish-image.yaml
vendored
32
.github/workflows/d-publish-image.yaml
vendored
@@ -62,19 +62,35 @@ jobs:
|
||||
id: download-artifact
|
||||
with:
|
||||
path: .
|
||||
- name: mv kubescape amd64 binary
|
||||
run: mv kubescape-ubuntu-latest/kubescape-ubuntu-latest kubescape-amd64-ubuntu-latest
|
||||
- name: mv kubescape arm64 binary
|
||||
run: mv kubescape-ubuntu-latest/kubescape-arm64-ubuntu-latest kubescape-arm64-ubuntu-latest
|
||||
- name: chmod +x
|
||||
run: chmod +x -v kubescape-*/kubescape-*
|
||||
- name: Build and push image for linux/amd64
|
||||
run: docker buildx build . --file build/kubescape-cli.Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --build-arg ks_binary=kubescape-ubuntu-latest/kubescape-ubuntu-latest --push --platform linux/amd64
|
||||
- name: Build and push image for linux/arm64
|
||||
run: docker buildx build . --file build/kubescape-cli.Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --build-arg ks_binary=kubescape-arm64-ubuntu-latest/kubescape-arm64-ubuntu-latest --push --platform linux/arm64
|
||||
run: chmod +x -v kubescape-a*
|
||||
- name: Build and push images
|
||||
run: docker buildx build . --file build/kubescape-cli.Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --push --platform linux/amd64,linux/arm64
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@4079ad3567a89f68395480299c77e40170430341 # ratchet:sigstore/cosign-installer@main
|
||||
uses: sigstore/cosign-installer@main
|
||||
with:
|
||||
cosign-release: 'v1.12.0'
|
||||
cosign-release: 'v2.2.2'
|
||||
- name: sign kubescape container image
|
||||
if: ${{ inputs.cosign }}
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: "true"
|
||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY_V1 }}
|
||||
COSIGN_PRIVATE_KEY_PASSWORD: ${{ secrets.COSIGN_PRIVATE_KEY_V1_PASSWORD }}
|
||||
COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY_V1 }}
|
||||
run: |
|
||||
cosign sign --force ${{ inputs.image_name }}
|
||||
# Sign the image with keyless mode
|
||||
cosign sign -y ${{ inputs.image_name }}:${{ inputs.image_tag }}
|
||||
|
||||
# Sign the image with key for verifier clients without keyless support
|
||||
# Put the key from environment variable to a file
|
||||
echo "$COSIGN_PRIVATE_KEY" > cosign.key
|
||||
printf "$COSIGN_PRIVATE_KEY_PASSWORD" | cosign sign -key cosign.key -y ${{ inputs.image_name }}:${{ inputs.image_tag }}
|
||||
rm cosign.key
|
||||
# Verify the image
|
||||
echo "$COSIGN_PUBLIC_KEY" > cosign.pub
|
||||
cosign verify -key cosign.pub ${{ inputs.image_name }}:${{ inputs.image_tag }}
|
||||
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,3 +9,5 @@
|
||||
ca.srl
|
||||
*.out
|
||||
ks
|
||||
|
||||
dist/
|
||||
|
||||
46
.goreleaser.yaml
Normal file
46
.goreleaser.yaml
Normal file
@@ -0,0 +1,46 @@
|
||||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
|
||||
# The lines bellow are called `modelines`. See `:help modeline`
|
||||
# Feel free to remove those if you don't want/need to use them.
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- go mod tidy
|
||||
|
||||
builds:
|
||||
- id: "kubescape-cli"
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
binary: >-
|
||||
{{ .ProjectName }}-
|
||||
{{- if eq .Arch "amd64" }}
|
||||
{{- else }}{{ .Arch }}-{{ end }}
|
||||
{{- if eq .Os "darwin" }}macos
|
||||
{{- else if eq .Os "linux" }}ubuntu
|
||||
{{- else }}{{ .Os }}{{ end }}-latest
|
||||
no_unique_dist_dir: true
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
# this name template makes the OS and Arch compatible with the results of `uname`.
|
||||
name_template: >-
|
||||
{{ .Binary }}
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
|
||||
sboms:
|
||||
- artifacts: archive
|
||||
@@ -18,6 +18,5 @@ If you want to be listed here and share with others your experience, open a PR a
|
||||
| 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 |
|
||||
|
||||
@@ -5,7 +5,7 @@ The following table lists the Kubescape project core maintainers:
|
||||
| Name | GitHub | Organization | Added/Renewed On |
|
||||
| --- | --- | --- | --- |
|
||||
| [Matthias Bertschy](https://www.linkedin.com/in/matthias-bertschy-b427b815/) | [@matthyx](https://github.com/matthyx) | [ARMO](https://www.armosec.io/) | 2023-01-01 |
|
||||
| [Craig Box](https://www.linkedin.com/in/crbnz/) | [@craigbox](https://github.com/craigbox) | [ARMO](https://www.armosec.io/) | 2022-10-31 |
|
||||
| [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 |
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fkubescape%2Fkubescape?ref=badge_shield&issueType=license)
|
||||
[](https://www.bestpractices.dev/projects/6944)
|
||||
[](https://securityscorecards.dev/viewer/?uri=github.com/kubescape/kubescape)
|
||||
[](https://github.com/kubescape/kubescape/stargazers)
|
||||
[](https://twitter.com/kubescape)
|
||||
[](https://cloud-native.slack.com/archives/C04EY3ZF9GE)
|
||||
|
||||
# Kubescape
|
||||
|
||||
@@ -18,9 +20,11 @@
|
||||
<img alt="Kubescape logo" align="right" src="https://raw.githubusercontent.com/cncf/artwork/master/projects/kubescape/stacked/color/kubescape-stacked-color.svg" width="150">
|
||||
</picture>
|
||||
|
||||
_An open-source Kubernetes security platform for your IDE, CI/CD pipelines, and clusters_
|
||||
_An open-source Kubernetes security platform for your clusters, CI/CD pipelines, and IDE that seperates out the security signal from the scanner noise_
|
||||
|
||||
Kubescape is an open-source Kubernetes security platform. It includes risk analysis, security compliance, and misconfiguration scanning. Targeted at the DevSecOps practitioner or platform engineer, it offers an easy-to-use CLI interface, flexible output formats, and automated scanning capabilities. It saves Kubernetes users and admins precious time, effort, and resources.
|
||||
Kubescape is an open-source Kubernetes security platform, built for use in your day-to-day workflow, by fitting into your clusters, CI/CD pipelines and IDE. It serves as a one-stop-shop for Kuberenetes security and includes vulnerability and misconfiguration scanning. You can run scans via the CLI, or add the Kubescape Helm chart, which gives an in-depth view of what is going on in the cluster.
|
||||
|
||||
Kubescape includes misconfiguration and vulnerability scanning as well as risk analysis and security compliance indicators. All results are presented in context and users get many cues on what to do based on scan results.Targeted at the DevSecOps practitioner or platform engineer, it offers an easy-to-use CLI interface, flexible output formats, and automated scanning capabilities. It saves Kubernetes users and admins precious time, effort, and resources.
|
||||
|
||||
Kubescape scans clusters, YAML files, and Helm charts. It detects misconfigurations according to multiple frameworks (including [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo/?utm_source=github&utm_medium=repository), [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) and the [CIS Benchmark](https://www.armosec.io/blog/cis-kubernetes-benchmark-framework-scanning-tools-comparison/?utm_source=github&utm_medium=repository)).
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:1.20-bullseye as builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.21-bullseye as builder
|
||||
|
||||
ENV GO111MODULE=on CGO_ENABLED=0
|
||||
WORKDIR /work
|
||||
|
||||
@@ -3,10 +3,10 @@ FROM gcr.io/distroless/base-debian11:debug-nonroot
|
||||
USER nonroot
|
||||
WORKDIR /home/nonroot/
|
||||
|
||||
ARG image_version client ks_binary
|
||||
ARG image_version client TARGETARCH
|
||||
ENV RELEASE=$image_version CLIENT=$client
|
||||
|
||||
COPY $ks_binary /usr/bin/kubescape
|
||||
COPY kubescape-${TARGETARCH}-ubuntu-latest /usr/bin/kubescape
|
||||
RUN ["kubescape", "download", "artifacts"]
|
||||
|
||||
ENTRYPOINT ["kubescape"]
|
||||
|
||||
@@ -29,6 +29,12 @@ func GetCompletionCmd() *cobra.Command {
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Check if args array is not empty
|
||||
if len(args) == 0 {
|
||||
fmt.Println("No arguements provided.")
|
||||
return
|
||||
}
|
||||
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "bash":
|
||||
cmd.Root().GenBashCompletion(os.Stdout)
|
||||
@@ -38,6 +44,8 @@ func GetCompletionCmd() *cobra.Command {
|
||||
cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
case "powershell":
|
||||
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||
default:
|
||||
fmt.Printf("Invalid arguement %s", args[0])
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
187
cmd/completion/completion_test.go
Normal file
187
cmd/completion/completion_test.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package completion
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Generates autocompletion script for valid shell types
|
||||
func TestGetCompletionCmd(t *testing.T) {
|
||||
// Arrange
|
||||
completionCmd := GetCompletionCmd()
|
||||
assert.Equal(t, "completion [bash|zsh|fish|powershell]", completionCmd.Use)
|
||||
assert.Equal(t, "Generate autocompletion script", completionCmd.Short)
|
||||
assert.Equal(t, "To load completions", completionCmd.Long)
|
||||
assert.Equal(t, completionCmdExamples, completionCmd.Example)
|
||||
assert.Equal(t, true, completionCmd.DisableFlagsInUseLine)
|
||||
assert.Equal(t, []string{"bash", "zsh", "fish", "powershell"}, completionCmd.ValidArgs)
|
||||
}
|
||||
|
||||
func TestGetCompletionCmd_RunExpectedOutputs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Unknown completion",
|
||||
args: []string{"unknown"},
|
||||
want: "Invalid arguement unknown",
|
||||
},
|
||||
{
|
||||
name: "Empty arguements",
|
||||
args: []string{},
|
||||
want: "No arguements provided.\n",
|
||||
},
|
||||
}
|
||||
|
||||
completionCmd := GetCompletionCmd()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
completionCmd.Run(&cobra.Command{}, tt.args)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCompletionCmd_RunNotExpectedOutputs(t *testing.T) {
|
||||
notExpectedOutput1 := "No arguments provided."
|
||||
notExpectedOutput2 := "No arguments provided."
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
}{
|
||||
{
|
||||
name: "Bash completion",
|
||||
args: []string{"bash"},
|
||||
},
|
||||
{
|
||||
name: "Zsh completion",
|
||||
args: []string{"zsh"},
|
||||
},
|
||||
{
|
||||
name: "Fish completion",
|
||||
args: []string{"fish"},
|
||||
},
|
||||
{
|
||||
name: "PowerShell completion",
|
||||
args: []string{"powershell"},
|
||||
},
|
||||
}
|
||||
|
||||
completionCmd := GetCompletionCmd()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
completionCmd.Run(&cobra.Command{}, tt.args)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.NotEqual(t, notExpectedOutput1, string(got))
|
||||
assert.NotEqual(t, notExpectedOutput2, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCompletionCmd_RunBashCompletionNotExpectedOutputs(t *testing.T) {
|
||||
notExpectedOutput1 := "Unexpected output for bash completion test 1."
|
||||
notExpectedOutput2 := "Unexpected output for bash completion test 2."
|
||||
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
completionCmd := GetCompletionCmd()
|
||||
completionCmd.Run(&cobra.Command{}, []string{"bash"})
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.NotEqual(t, notExpectedOutput1, string(got))
|
||||
assert.NotEqual(t, notExpectedOutput2, string(got))
|
||||
}
|
||||
|
||||
func TestGetCompletionCmd_RunZshCompletionNotExpectedOutputs(t *testing.T) {
|
||||
notExpectedOutput1 := "Unexpected output for zsh completion test 1."
|
||||
notExpectedOutput2 := "Unexpected output for zsh completion test 2."
|
||||
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
completionCmd := GetCompletionCmd()
|
||||
completionCmd.Run(&cobra.Command{}, []string{"zsh"})
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.NotEqual(t, notExpectedOutput1, string(got))
|
||||
assert.NotEqual(t, notExpectedOutput2, string(got))
|
||||
}
|
||||
|
||||
func TestGetCompletionCmd_RunFishCompletionNotExpectedOutputs(t *testing.T) {
|
||||
notExpectedOutput1 := "Unexpected output for fish completion test 1."
|
||||
notExpectedOutput2 := "Unexpected output for fish completion test 2."
|
||||
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
completionCmd := GetCompletionCmd()
|
||||
completionCmd.Run(&cobra.Command{}, []string{"fish"})
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.NotEqual(t, notExpectedOutput1, string(got))
|
||||
assert.NotEqual(t, notExpectedOutput2, string(got))
|
||||
}
|
||||
|
||||
func TestGetCompletionCmd_RunPowerShellCompletionNotExpectedOutputs(t *testing.T) {
|
||||
notExpectedOutput1 := "Unexpected output for powershell completion test 1."
|
||||
notExpectedOutput2 := "Unexpected output for powershell completion test 2."
|
||||
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
completionCmd := GetCompletionCmd()
|
||||
completionCmd.Run(&cobra.Command{}, []string{"powershell"})
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.NotEqual(t, notExpectedOutput1, string(got))
|
||||
assert.NotEqual(t, notExpectedOutput2, string(got))
|
||||
}
|
||||
44
cmd/config/config_test.go
Normal file
44
cmd/config/config_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetConfigCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetConfigCmd function
|
||||
configCmd := GetConfigCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "config", configCmd.Use)
|
||||
assert.Equal(t, "Handle cached configurations", configCmd.Short)
|
||||
assert.Equal(t, configExample, configCmd.Example)
|
||||
|
||||
// Verify that the subcommands are added correctly
|
||||
assert.Equal(t, 3, len(configCmd.Commands()))
|
||||
|
||||
for _, subcmd := range configCmd.Commands() {
|
||||
switch subcmd.Name() {
|
||||
case "delete":
|
||||
// Verify that the delete subcommand is added correctly
|
||||
assert.Equal(t, "delete", subcmd.Use)
|
||||
assert.Equal(t, "Delete cached configurations", subcmd.Short)
|
||||
case "set":
|
||||
// Verify that the set subcommand is added correctly
|
||||
assert.Equal(t, "set", subcmd.Use)
|
||||
assert.Equal(t, "Set configurations, supported: "+strings.Join(stringKeysToSlice(supportConfigSet), "/"), subcmd.Short)
|
||||
case "view":
|
||||
// Verify that the view subcommand is added correctly
|
||||
assert.Equal(t, "view", subcmd.Use)
|
||||
assert.Equal(t, "View cached configurations", subcmd.Short)
|
||||
default:
|
||||
t.Errorf("Unexpected subcommand name: %s", subcmd.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
21
cmd/config/delete_test.go
Normal file
21
cmd/config/delete_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetDeleteCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetConfigCmd function
|
||||
configCmd := getDeleteCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "delete", configCmd.Use)
|
||||
assert.Equal(t, "Delete cached configurations", configCmd.Short)
|
||||
assert.Equal(t, "", configCmd.Long)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
@@ -40,10 +41,16 @@ var supportConfigSet = map[string]func(*metav1.SetConfig, string){
|
||||
}
|
||||
|
||||
func stringKeysToSlice(m map[string]func(*metav1.SetConfig, string)) []string {
|
||||
l := []string{}
|
||||
for i := range m {
|
||||
l = append(l, i)
|
||||
keys := []string{}
|
||||
for key := range m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
// Sort the keys of the map
|
||||
sort.Strings(keys)
|
||||
|
||||
l := []string{}
|
||||
l = append(l, keys...)
|
||||
return l
|
||||
}
|
||||
|
||||
|
||||
81
cmd/config/set_test.go
Normal file
81
cmd/config/set_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetSetCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetConfigCmd function
|
||||
configSetCmd := getSetCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "set", configSetCmd.Use)
|
||||
assert.Equal(t, "Set configurations, supported: "+strings.Join(stringKeysToSlice(supportConfigSet), "/"), configSetCmd.Short)
|
||||
assert.Equal(t, setConfigExample, configSetCmd.Example)
|
||||
assert.Equal(t, stringKeysToSlice(supportConfigSet), configSetCmd.ValidArgs)
|
||||
|
||||
err := configSetCmd.RunE(&cobra.Command{}, []string{"accountID=value1"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = configSetCmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "key '' unknown . supported: accessKey/accountID/cloudAPIURL/cloudReportURL"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
// Should return a slice of keys when given a non-empty map
|
||||
func TestStringKeysToSlice(t *testing.T) {
|
||||
m := map[string]func(*metav1.SetConfig, string){
|
||||
"key1": nil,
|
||||
"key2": nil,
|
||||
"key3": nil,
|
||||
}
|
||||
result := stringKeysToSlice(m)
|
||||
expected := []string{"key1", "key2", "key3"}
|
||||
assert.ElementsMatch(t, expected, result)
|
||||
}
|
||||
|
||||
func TestParseSetArgs_InvalidFormat(t *testing.T) {
|
||||
args := []string{"key"}
|
||||
setConfig, err := parseSetArgs(args)
|
||||
assert.Equal(t, "", setConfig.Account)
|
||||
assert.Equal(t, "", setConfig.AccessKey)
|
||||
assert.Equal(t, "", setConfig.CloudReportURL)
|
||||
assert.Equal(t, "", setConfig.CloudAPIURL)
|
||||
|
||||
expectedErrorMessage := fmt.Sprintf("key '' unknown . supported: %s", strings.Join(stringKeysToSlice(supportConfigSet), "/"))
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
func TestParseSetArgs_AccessKey(t *testing.T) {
|
||||
args := []string{"accessKey", "value1"}
|
||||
setConfig, _ := parseSetArgs(args)
|
||||
assert.Equal(t, "", setConfig.Account)
|
||||
assert.Equal(t, "value1", setConfig.AccessKey)
|
||||
assert.Equal(t, "", setConfig.CloudReportURL)
|
||||
assert.Equal(t, "", setConfig.CloudAPIURL)
|
||||
}
|
||||
|
||||
func TestParseSetArgs_Single(t *testing.T) {
|
||||
args := []string{"accountID=value1"}
|
||||
setConfig, _ := parseSetArgs(args)
|
||||
assert.Equal(t, "value1", setConfig.Account)
|
||||
assert.Equal(t, "", setConfig.AccessKey)
|
||||
assert.Equal(t, "", setConfig.CloudReportURL)
|
||||
assert.Equal(t, "", setConfig.CloudAPIURL)
|
||||
}
|
||||
|
||||
func TestParseSetArgs_InvalidKey(t *testing.T) {
|
||||
args := []string{"invalidKey=value1"}
|
||||
_, err := parseSetArgs(args)
|
||||
assert.Equal(t, "key 'invalidKey' unknown . supported: accessKey/accountID/cloudAPIURL/cloudReportURL", err.Error())
|
||||
}
|
||||
21
cmd/config/view_test.go
Normal file
21
cmd/config/view_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetViewCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetConfigCmd function
|
||||
configCmd := getViewCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "view", configCmd.Use)
|
||||
assert.Equal(t, "View cached configurations", configCmd.Short)
|
||||
assert.Equal(t, "", configCmd.Long)
|
||||
}
|
||||
@@ -37,9 +37,6 @@ var (
|
||||
|
||||
# Download the configured controls-inputs
|
||||
%[1]s download controls-inputs
|
||||
|
||||
# Download the attack tracks
|
||||
%[1]s download attack-tracks
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
@@ -70,6 +67,11 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
if filepath.Ext(downloadInfo.Path) == ".json" {
|
||||
downloadInfo.Path, downloadInfo.FileName = filepath.Split(downloadInfo.Path)
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("no arguements provided")
|
||||
}
|
||||
|
||||
downloadInfo.Target = args[0]
|
||||
if len(args) >= 2 {
|
||||
|
||||
@@ -84,9 +86,7 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
}
|
||||
|
||||
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.AccessKey, "accessKey", "", "", "Kubescape SaaS access key. Default will load access key from cache")
|
||||
downloadCmd.PersistentFlags().MarkDeprecated("client-id", "Client ID is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
|
||||
downloadCmd.PersistentFlags().MarkDeprecated("secret-key", "Secret Key is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
|
||||
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.AccessKey, "access-key", "", "", "Kubescape SaaS access key. Default will load access key from cache")
|
||||
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
|
||||
|
||||
return downloadCmd
|
||||
|
||||
102
cmd/download/download_test.go
Normal file
102
cmd/download/download_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package download
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetViewCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetConfigCmd function
|
||||
configCmd := GetDownloadCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "download <policy> <policy name>", configCmd.Use)
|
||||
assert.Equal(t, fmt.Sprintf("Download %s", strings.Join(core.DownloadSupportCommands(), ",")), configCmd.Short)
|
||||
assert.Equal(t, "", configCmd.Long)
|
||||
assert.Equal(t, downloadExample, configCmd.Example)
|
||||
}
|
||||
|
||||
func TestGetViewCmd_Args(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetConfigCmd function
|
||||
downloadCmd := GetDownloadCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "download <policy> <policy name>", downloadCmd.Use)
|
||||
assert.Equal(t, fmt.Sprintf("Download %s", strings.Join(core.DownloadSupportCommands(), ",")), downloadCmd.Short)
|
||||
assert.Equal(t, "", downloadCmd.Long)
|
||||
assert.Equal(t, downloadExample, downloadCmd.Example)
|
||||
|
||||
err := downloadCmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "no arguements provided"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = downloadCmd.RunE(&cobra.Command{}, []string{"config"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = downloadCmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage = "policy type required, supported: artifacts,attack-tracks,control,controls-inputs,exceptions,framework"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = downloadCmd.Args(&cobra.Command{}, []string{"invalid"})
|
||||
expectedErrorMessage = "invalid parameter 'invalid'. Supported parameters: artifacts,attack-tracks,control,controls-inputs,exceptions,framework"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = downloadCmd.Args(&cobra.Command{}, []string{"attack-tracks"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = downloadCmd.Args(&cobra.Command{}, []string{"control", "random.json"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = downloadCmd.Args(&cobra.Command{}, []string{"control", "C-0001"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = downloadCmd.Args(&cobra.Command{}, []string{"control", "C-0001", "C-0002"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = downloadCmd.RunE(&cobra.Command{}, []string{"control", "C-0001", "C-0002"})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestFlagValidationDownload_NoError(t *testing.T) {
|
||||
downloadInfo := v1.DownloadInfo{
|
||||
AccessKey: "",
|
||||
AccountID: "",
|
||||
}
|
||||
assert.Equal(t, nil, flagValidationDownload(&downloadInfo))
|
||||
}
|
||||
|
||||
func TestFlagValidationDownload_Error(t *testing.T) {
|
||||
tests := []struct {
|
||||
downloadInfo v1.DownloadInfo
|
||||
}{
|
||||
{
|
||||
downloadInfo: v1.DownloadInfo{
|
||||
AccountID: "12345678",
|
||||
},
|
||||
},
|
||||
{
|
||||
downloadInfo: v1.DownloadInfo{
|
||||
AccountID: "New",
|
||||
},
|
||||
},
|
||||
}
|
||||
want := "bad argument: accound ID must be a valid UUID"
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.downloadInfo.AccountID, func(t *testing.T) {
|
||||
assert.Equal(t, want, flagValidationDownload(&tt.downloadInfo).Error())
|
||||
})
|
||||
}
|
||||
}
|
||||
30
cmd/fix/fix_test.go
Normal file
30
cmd/fix/fix_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetFixCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetFixCmd function
|
||||
fixCmd := GetFixCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "fix <report output file>", fixCmd.Use)
|
||||
assert.Equal(t, "Propose a fix for the misconfiguration found when scanning Kubernetes manifest files", fixCmd.Short)
|
||||
assert.Equal(t, "", fixCmd.Long)
|
||||
assert.Equal(t, fixCmdExamples, fixCmd.Example)
|
||||
|
||||
err := fixCmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "report output file is required"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = fixCmd.RunE(&cobra.Command{}, []string{"random-file.json"})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package list
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -55,6 +56,10 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("no arguements provided")
|
||||
}
|
||||
|
||||
listPolicies.Target = args[0]
|
||||
|
||||
if err := ks.List(context.TODO(), &listPolicies); err != nil {
|
||||
@@ -64,7 +69,7 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
||||
},
|
||||
}
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.AccessKey, "accessKey", "", "", "Kubescape SaaS access key. Default will load access key from cache")
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.AccessKey, "access-key", "", "", "Kubescape SaaS access key. Default will load access key from cache")
|
||||
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-print'/'json'")
|
||||
listCmd.PersistentFlags().MarkDeprecated("id", "Control ID's are included in list outputs")
|
||||
|
||||
|
||||
44
cmd/list/list_test.go
Normal file
44
cmd/list/list_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/core"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetListCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetListCmd function
|
||||
listCmd := GetListCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "list <policy> [flags]", listCmd.Use)
|
||||
assert.Equal(t, "List frameworks/controls will list the supported frameworks and controls", listCmd.Short)
|
||||
assert.Equal(t, "", listCmd.Long)
|
||||
assert.Equal(t, listExample, listCmd.Example)
|
||||
supported := strings.Join(core.ListSupportActions(), ",")
|
||||
|
||||
err := listCmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "policy type requeued, supported: " + supported
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = listCmd.Args(&cobra.Command{}, []string{"not-frameworks"})
|
||||
expectedErrorMessage = "invalid parameter 'not-frameworks'. Supported parameters: " + supported
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = listCmd.Args(&cobra.Command{}, []string{"frameworks"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = listCmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage = "no arguements provided"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = listCmd.RunE(&cobra.Command{}, []string{"some-value"})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
@@ -21,7 +21,7 @@ var operatorScanConfigExamples = fmt.Sprintf(`
|
||||
func getOperatorScanConfigCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command {
|
||||
configCmd := &cobra.Command{
|
||||
Use: "configurations",
|
||||
Short: "Trigger configuration scanning from the Kubescape-Operator microservice",
|
||||
Short: "Trigger configuration scanning from the Kubescape Operator microservice",
|
||||
Long: ``,
|
||||
Example: operatorScanConfigExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -29,17 +29,17 @@ func getOperatorScanConfigCmd(ks meta.IKubescape, operatorInfo cautils.OperatorI
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo)
|
||||
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo, operatorInfo.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Start("Kubescape-Operator Triggering for configuration scanning")
|
||||
logger.L().Start("Kubescape Operator Triggering for configuration scanning")
|
||||
_, err = operatorAdapter.OperatorScan()
|
||||
if err != nil {
|
||||
logger.L().StopError("Failed to triggering Kubescape-Operator for configuration scanning", helpers.Error(err))
|
||||
logger.L().StopError("Failed to triggering Kubescape Operator for configuration scanning", helpers.Error(err))
|
||||
return err
|
||||
}
|
||||
logger.L().StopSuccess("Triggered Kubescape-Operator for configuration scanning")
|
||||
logger.L().StopSuccess("Triggered Kubescape Operator for configuration scanning")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
32
cmd/operator/configscan_test.go
Normal file
32
cmd/operator/configscan_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetOperatorScanConfigCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
operatorInfo := cautils.OperatorInfo{
|
||||
Namespace: "namespace",
|
||||
}
|
||||
|
||||
cmd := getOperatorScanConfigCmd(mockKubescape, operatorInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "configurations", cmd.Use)
|
||||
assert.Equal(t, "Trigger configuration scanning from the Kubescape Operator microservice", cmd.Short)
|
||||
assert.Equal(t, "", cmd.Long)
|
||||
assert.Equal(t, operatorScanConfigExamples, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"configurations"})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func GetOperatorCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
operatorCmd := &cobra.Command{
|
||||
Use: "operator",
|
||||
Short: "The operator is used to communicate with the Kubescape-Operator within the cluster components.",
|
||||
Short: "The operator is used to communicate with the Kubescape Operator within the cluster components.",
|
||||
Long: ``,
|
||||
Example: operatorExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
42
cmd/operator/operator_test.go
Normal file
42
cmd/operator/operator_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetOperatorCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
cmd := GetOperatorCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "operator", cmd.Use)
|
||||
assert.Equal(t, "The operator is used to communicate with the Kubescape Operator within the cluster components.", cmd.Short)
|
||||
assert.Equal(t, "", cmd.Long)
|
||||
assert.Equal(t, operatorExamples, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "For the operator sub-command, you need to provide at least one additional sub-command. Refer to the examples above."
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"scan", "configurations"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{})
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"scan", "configurations"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"scan"})
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"random-subcommand", "random-config"})
|
||||
expectedErrorMessage = "For the operator sub-command, only " + scanSubCommand + " is supported. Refer to the examples above."
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
@@ -38,6 +38,7 @@ func getOperatorScanCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *
|
||||
},
|
||||
}
|
||||
|
||||
operatorCmd.PersistentFlags().StringVar(&operatorInfo.Namespace, "namespace", "kubescape", "namespace of the Kubescape Operator")
|
||||
operatorCmd.AddCommand(getOperatorScanConfigCmd(ks, operatorInfo))
|
||||
operatorCmd.AddCommand(getOperatorScanVulnerabilitiesCmd(ks, operatorInfo))
|
||||
|
||||
|
||||
46
cmd/operator/scan_test.go
Normal file
46
cmd/operator/scan_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetOperatorScanCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
operatorInfo := cautils.OperatorInfo{
|
||||
Namespace: "namespace",
|
||||
}
|
||||
|
||||
cmd := getOperatorScanCmd(mockKubescape, operatorInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "scan", cmd.Use)
|
||||
assert.Equal(t, "Scan your cluster using the Kubescape-operator within the cluster components", cmd.Short)
|
||||
assert.Equal(t, "", cmd.Long)
|
||||
assert.Equal(t, operatorExamples, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "for operator scan sub command, you must pass at least 1 more sub commands, see above examples"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"operator"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{})
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"configurations"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"vulnerabilities"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"random"})
|
||||
expectedErrorMessage = "For the operator sub-command, only " + vulnerabilitiesSubCommand + " and " + configurationsSubCommand + " are supported. Refer to the examples above."
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
@@ -30,17 +30,17 @@ func getOperatorScanVulnerabilitiesCmd(ks meta.IKubescape, operatorInfo cautils.
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo)
|
||||
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo, operatorInfo.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Start("Triggering the Kubescape-Operator for vulnerability scanning")
|
||||
logger.L().Start("Triggering the Kubescape Operator for vulnerability scanning")
|
||||
_, err = operatorAdapter.OperatorScan()
|
||||
if err != nil {
|
||||
logger.L().StopError("Failed to trigger the Kubescape-Operator for vulnerability scanning", helpers.Error(err))
|
||||
logger.L().StopError("Failed to trigger the Kubescape Operator for vulnerability scanning", helpers.Error(err))
|
||||
return err
|
||||
}
|
||||
logger.L().StopSuccess("Triggered Kubescape-Operator for vulnerability scanning. View the scanning results once they are ready using the following command: \"kubectl get vulnerabilitysummaries\"")
|
||||
logger.L().StopSuccess("Triggered Kubescape Operator for vulnerability scanning. View the scanning results once they are ready using the following command: \"kubectl get vulnerabilitysummaries\"")
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
29
cmd/operator/vulnerabilitiesscan_test.go
Normal file
29
cmd/operator/vulnerabilitiesscan_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetOperatorScanVulnerabilitiesCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
operatorInfo := cautils.OperatorInfo{
|
||||
Namespace: "namespace",
|
||||
}
|
||||
|
||||
cmd := getOperatorScanVulnerabilitiesCmd(mockKubescape, operatorInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "vulnerabilities", cmd.Use)
|
||||
assert.Equal(t, "Vulnerabilities use for scan your cluster vulnerabilities using Kubescape operator in the in cluster components", cmd.Short)
|
||||
assert.Equal(t, "", cmd.Long)
|
||||
assert.Equal(t, operatorScanVulnerabilitiesExamples, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{"random-arg"})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
patchCmd := &cobra.Command{
|
||||
Use: "patch --image <image>:<tag> [flags]",
|
||||
Short: "Patch container images with vulnerabilities ",
|
||||
Short: "Patch container images with vulnerabilities",
|
||||
Long: `Patch command is for automatically patching images with vulnerabilities.`,
|
||||
Example: patchCmdExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
52
cmd/patch/patch_test.go
Normal file
52
cmd/patch/patch_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package patch
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetPatchCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
cmd := GetPatchCmd(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "patch --image <image>:<tag> [flags]", cmd.Use)
|
||||
assert.Equal(t, "Patch container images with vulnerabilities", cmd.Short)
|
||||
assert.Equal(t, "Patch command is for automatically patching images with vulnerabilities.", cmd.Long)
|
||||
assert.Equal(t, patchCmdExamples, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"test"})
|
||||
expectedErrorMessage := "the command takes no arguments"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage = "image tag is required"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{"patch", "--image", "docker.io/library/nginx:1.22"})
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
func TestGetPatchCmdWithNonExistentImage(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
// Call the GetPatchCmd function
|
||||
cmd := GetPatchCmd(mockKubescape)
|
||||
|
||||
// Run the command with a non-existent image argument
|
||||
err := cmd.RunE(&cobra.Command{}, []string{"patch", "--image", "non-existent-image"})
|
||||
|
||||
// Check that there is an error and the error message is as expected
|
||||
expectedErrorMessage := "image tag is required"
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
v1 "github.com/kubescape/backend/pkg/client/v1"
|
||||
"github.com/kubescape/backend/pkg/servicediscovery"
|
||||
sdClientV1 "github.com/kubescape/backend/pkg/servicediscovery/v1"
|
||||
sdClientV2 "github.com/kubescape/backend/pkg/servicediscovery/v2"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/go-logger/iconlogger"
|
||||
@@ -66,7 +66,7 @@ func initEnvironment() {
|
||||
|
||||
logger.L().Debug("fetching URLs from service discovery server", helpers.String("server", rootInfo.DiscoveryServerURL))
|
||||
|
||||
client, err := sdClientV1.NewServiceDiscoveryClientV1(rootInfo.DiscoveryServerURL)
|
||||
client, err := sdClientV2.NewServiceDiscoveryClientV2(rootInfo.DiscoveryServerURL)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to create service discovery client", helpers.Error(err), helpers.String("server", rootInfo.DiscoveryServerURL))
|
||||
return
|
||||
|
||||
60
cmd/scan/control_test.go
Normal file
60
cmd/scan/control_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetControlCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
scanInfo := cautils.ScanInfo{
|
||||
AccountID: "new",
|
||||
}
|
||||
|
||||
cmd := getControlCmd(mockKubescape, &scanInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "control <control names list>/<control ids list>", cmd.Use)
|
||||
assert.Equal(t, fmt.Sprintf("The controls you wish to use. Run '%[1]s list controls' for the list of supported controls", cautils.ExecName()), cmd.Short)
|
||||
assert.Equal(t, controlExample, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "requires at least one control name"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"C-0001,C-0002"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"C-0001,C-0002,"})
|
||||
expectedErrorMessage = "usage: <control-0>,<control-1>"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage = "bad argument: accound ID must be a valid UUID"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
func TestGetControlCmdWithNonExistentControl(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
scanInfo := cautils.ScanInfo{
|
||||
AccountID: "new",
|
||||
}
|
||||
|
||||
// Call the GetControlCmd function
|
||||
cmd := getControlCmd(mockKubescape, &scanInfo)
|
||||
|
||||
// Run the command with a non-existent control argument
|
||||
err := cmd.RunE(&cobra.Command{}, []string{"control", "C-0001,C-0002"})
|
||||
|
||||
// Check that there is an error and the error message is as expected
|
||||
expectedErrorMessage := "bad argument: accound ID must be a valid UUID"
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
@@ -126,9 +126,6 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
if !scanInfo.VerboseMode && scanInfo.ScanType == cautils.ScanTypeFramework {
|
||||
logger.L().Info("Run with '--verbose'/'-v' flag for detailed resources view\n")
|
||||
}
|
||||
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
|
||||
logger.L().Fatal("scan risk-score is above permitted threshold", helpers.String("risk-score", fmt.Sprintf("%.2f", results.GetRiskScore())), helpers.String("fail-threshold", fmt.Sprintf("%.2f", scanInfo.FailThreshold)))
|
||||
}
|
||||
|
||||
60
cmd/scan/framework_test.go
Normal file
60
cmd/scan/framework_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetFrameworkCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
scanInfo := cautils.ScanInfo{
|
||||
AccountID: "new",
|
||||
}
|
||||
|
||||
cmd := getFrameworkCmd(mockKubescape, &scanInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "framework <framework names list> [`<glob pattern>`/`-`] [flags]", cmd.Use)
|
||||
assert.Equal(t, fmt.Sprintf("The framework you wish to use. Run '%[1]s list frameworks' for the list of supported frameworks", cautils.ExecName()), cmd.Short)
|
||||
assert.Equal(t, frameworkExample, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "requires at least one framework name"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"nsa,mitre"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"nsa,mitre,"})
|
||||
expectedErrorMessage = "usage: <framework-0>,<framework-1>"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage = "bad argument: accound ID must be a valid UUID"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
func TestGetFrameworkCmdWithNonExistentFramework(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
scanInfo := cautils.ScanInfo{
|
||||
AccountID: "new",
|
||||
}
|
||||
|
||||
// Call the GetFrameworkCmd function
|
||||
cmd := getFrameworkCmd(mockKubescape, &scanInfo)
|
||||
|
||||
// Run the command with a non-existent framework argument
|
||||
err := cmd.RunE(&cobra.Command{}, []string{"framework", "nsa,mitre"})
|
||||
|
||||
// Check that there is an error and the error message is as expected
|
||||
expectedErrorMessage := "bad argument: accound ID must be a valid UUID"
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
@@ -42,6 +42,10 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("the command takes exactly one image name as an argument")
|
||||
}
|
||||
|
||||
if err := shared.ValidateImageScanInfo(scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
35
cmd/scan/image_test.go
Normal file
35
cmd/scan/image_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/mocks"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetImageCmd(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
scanInfo := cautils.ScanInfo{
|
||||
AccountID: "new",
|
||||
}
|
||||
|
||||
cmd := getImageCmd(mockKubescape, &scanInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "image <image>:<tag> [flags]", cmd.Use)
|
||||
assert.Equal(t, "Scan an image for vulnerabilities", cmd.Short)
|
||||
assert.Equal(t, imageExample, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "the command takes exactly one image name as an argument"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"nginx"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = cmd.RunE(&cobra.Command{}, []string{})
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
@@ -59,6 +59,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
}
|
||||
|
||||
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")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
|
||||
@@ -68,7 +69,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
scanCmd.PersistentFlags().Float32VarP(&scanInfo.ComplianceThreshold, "compliance-threshold", "", 0, "Compliance threshold is the percent below which the command fails and returns exit code 1")
|
||||
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.FailThresholdSeverity, "severity-threshold", "", "Severity threshold is the severity of failed controls at which the command fails and returns exit code 1")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "", `Output file format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html", "sarif"`)
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output file format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html", "sarif"`)
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to configured backend.")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
|
||||
@@ -84,16 +85,13 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.PrintAttackTree, "print-attack-tree", "", false, "Print attack tree")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.ScanImages, "scan-images", "", false, "Scan resources images")
|
||||
|
||||
scanCmd.PersistentFlags().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("fail-threshold", "use '--compliance-threshold' flag instead. Flag will be removed at 1.Dec.2023")
|
||||
|
||||
scanCmd.PersistentFlags().MarkDeprecated("client-id", "Client ID is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("create-account", "Create account is no longer supported. In case of a missing Account ID and a configured backend server, a new account id will be generated automatically by Kubescape. Feel free to contact the Kubescape maintainers for more information.")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("secret-key", "Secret Key is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
|
||||
|
||||
// hidden flags
|
||||
scanCmd.PersistentFlags().MarkHidden("omit-raw-resources")
|
||||
scanCmd.PersistentFlags().MarkHidden("print-attack-tree")
|
||||
scanCmd.PersistentFlags().MarkHidden("format-version")
|
||||
|
||||
// Retrieve --kubeconfig flag from https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/cmd.go
|
||||
scanCmd.PersistentFlags().AddGoFlag(flag.Lookup("kubeconfig"))
|
||||
|
||||
@@ -4,9 +4,11 @@ import (
|
||||
"context"
|
||||
|
||||
"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"
|
||||
@@ -362,3 +364,16 @@ func TestSetSecurityViewScanInfo(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetScanCommand(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
|
||||
cmd := GetScanCommand(mockKubescape)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "scan", cmd.Use)
|
||||
assert.Equal(t, "Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations", cmd.Short)
|
||||
assert.Equal(t, "The action you want to perform", cmd.Long)
|
||||
assert.Equal(t, scanCmdExamples, cmd.Example)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ func getWorkloadCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comma
|
||||
return fmt.Errorf("usage: <kind>/<name> [`<glob pattern>`/`-`] [flags]")
|
||||
}
|
||||
|
||||
// Looks strange, a bug maybe????
|
||||
if scanInfo.ChartPath != "" && scanInfo.FilePath == "" {
|
||||
return fmt.Errorf("usage: --chart-path <chart path> --file-path <file path>")
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"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/objectsenvelopes"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSetWorkloadScanInfo(t *testing.T) {
|
||||
@@ -67,3 +70,27 @@ func TestSetWorkloadScanInfo(t *testing.T) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWorkloadCmd_ChartPathAndFilePathEmpty(t *testing.T) {
|
||||
// Create a mock Kubescape interface
|
||||
mockKubescape := &mocks.MockIKubescape{}
|
||||
scanInfo := cautils.ScanInfo{
|
||||
ChartPath: "temp",
|
||||
FilePath: "",
|
||||
}
|
||||
|
||||
cmd := getWorkloadCmd(mockKubescape, &scanInfo)
|
||||
|
||||
// Verify the command name and short description
|
||||
assert.Equal(t, "workload <kind>/<name> [`<glob pattern>`/`-`] [flags]", cmd.Use)
|
||||
assert.Equal(t, "Scan a workload for misconfigurations and image vulnerabilities", cmd.Short)
|
||||
assert.Equal(t, workloadExample, cmd.Example)
|
||||
|
||||
err := cmd.Args(&cobra.Command{}, []string{})
|
||||
expectedErrorMessage := "usage: <kind>/<name> [`<glob pattern>`/`-`] [flags]"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
|
||||
err = cmd.Args(&cobra.Command{}, []string{"nginx"})
|
||||
expectedErrorMessage = "invalid workload identifier"
|
||||
assert.Equal(t, expectedErrorMessage, err.Error())
|
||||
}
|
||||
|
||||
61
cmd/shared/image_scan_test.go
Normal file
61
cmd/shared/image_scan_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Validate a scanInfo struct with a valid fail threshold severity
|
||||
func TestValidateImageScanInfo(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Description string
|
||||
ScanInfo *cautils.ScanInfo
|
||||
Want error
|
||||
}{
|
||||
{
|
||||
"Empty scanInfo is valid",
|
||||
&cautils.ScanInfo{},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Empty severity is valid",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: ""},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"High severity is valid",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "High"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"HIGH severity is valid",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "HIGH"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"high severity is valid",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "high"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Unknown severity is invalid",
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "unknown"},
|
||||
ErrUnknownSeverity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(
|
||||
tc.Description,
|
||||
func(t *testing.T) {
|
||||
var want error = tc.Want
|
||||
|
||||
got := ValidateImageScanInfo(tc.ScanInfo)
|
||||
|
||||
assert.Equal(t, want, got)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ package update
|
||||
// kubescape update
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
@@ -14,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
installationLink string = "https://github.com/kubescape/kubescape/blob/master/docs/installation.md"
|
||||
installationLink string = "https://kubescape.io/docs/install-cli/"
|
||||
)
|
||||
|
||||
var updateCmdExamples = fmt.Sprintf(`
|
||||
@@ -29,12 +31,23 @@ func GetUpdateCmd() *cobra.Command {
|
||||
Long: ``,
|
||||
Example: updateCmdExamples,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
ctx := context.TODO()
|
||||
v := cautils.NewVersionCheckHandler()
|
||||
versionCheckRequest := cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "update")
|
||||
v.CheckLatestVersion(ctx, versionCheckRequest)
|
||||
|
||||
//Checking the user's version of kubescape to the latest release
|
||||
if cautils.BuildNumber == cautils.LatestReleaseVersion {
|
||||
if cautils.BuildNumber == "" || strings.Contains(cautils.BuildNumber, "rc") {
|
||||
//your version is unknown
|
||||
fmt.Printf("Nothing to update: you are running the development version\n")
|
||||
} else if cautils.LatestReleaseVersion == "" {
|
||||
//Failed to check for updates
|
||||
logger.L().Info(("Failed to check for updates"))
|
||||
} else if cautils.BuildNumber == cautils.LatestReleaseVersion {
|
||||
//your version == latest version
|
||||
logger.L().Info(("Nothing to update, you are running the latest version"), helpers.String("Version", cautils.BuildNumber))
|
||||
logger.L().Info(("Nothing to update: you are running the latest version"), helpers.String("Version", cautils.BuildNumber))
|
||||
} else {
|
||||
fmt.Printf("Please refer to our installation docs in the following link: %s\n", installationLink)
|
||||
fmt.Printf("Version %s is available. Please refer to our installation documentation: %s\n", cautils.LatestReleaseVersion, installationLink)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -3,7 +3,6 @@ package version
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
@@ -18,10 +17,11 @@ func GetVersionCmd() *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.TODO()
|
||||
v := cautils.NewIVersionCheckHandler(ctx)
|
||||
v.CheckLatestVersion(ctx, cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
|
||||
fmt.Fprintf(os.Stdout,
|
||||
versionCheckRequest := cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version")
|
||||
v.CheckLatestVersion(ctx, versionCheckRequest)
|
||||
fmt.Fprintf(cmd.OutOrStdout(),
|
||||
"Your current version is: %s\n",
|
||||
cautils.BuildNumber,
|
||||
versionCheckRequest.ClientVersion,
|
||||
)
|
||||
logger.L().Debug(fmt.Sprintf("git enabled in build: %t", isGitEnabled()))
|
||||
return nil
|
||||
|
||||
45
cmd/version/version_test.go
Normal file
45
cmd/version/version_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetVersionCmd(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
buildNumber string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Undefined Build Number",
|
||||
buildNumber: "",
|
||||
want: "Your current version is: unknown\n",
|
||||
},
|
||||
{
|
||||
name: "Defined Build Number: v3.0.1",
|
||||
buildNumber: "v3.0.1",
|
||||
want: "Your current version is: v3.0.1\n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cautils.BuildNumber = tt.buildNumber
|
||||
|
||||
if cmd := GetVersionCmd(); cmd != nil {
|
||||
buf := bytes.NewBufferString("")
|
||||
cmd.SetOut(buf)
|
||||
cmd.Execute()
|
||||
out, err := io.ReadAll(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, tt.want, string(out))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
53
core/cautils/controllink_test.go
Normal file
53
core/cautils/controllink_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Returns a valid URL when given a valid control ID.
|
||||
func TestGetControlLink_ValidControlID(t *testing.T) {
|
||||
controlID := "cis-1.1.3"
|
||||
expectedURL := "https://hub.armosec.io/docs/cis-1-1-3"
|
||||
|
||||
result := GetControlLink(controlID)
|
||||
|
||||
if result != expectedURL {
|
||||
t.Errorf("Expected URL: %s, but got: %s", expectedURL, result)
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces dots with hyphens in the control ID to generate the correct documentation link.
|
||||
func TestGetControlLink_DotsInControlID(t *testing.T) {
|
||||
controlID := "cis.1.1.3"
|
||||
expectedURL := "https://hub.armosec.io/docs/cis-1-1-3"
|
||||
|
||||
result := GetControlLink(controlID)
|
||||
|
||||
if result != expectedURL {
|
||||
t.Errorf("Expected URL: %s, but got: %s", expectedURL, result)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a lowercase URL.
|
||||
func TestGetControlLink_LowercaseURL(t *testing.T) {
|
||||
controlID := "CIS-1.1.3"
|
||||
expectedURL := "https://hub.armosec.io/docs/cis-1-1-3"
|
||||
|
||||
result := GetControlLink(controlID)
|
||||
|
||||
if result != expectedURL {
|
||||
t.Errorf("Expected URL: %s, but got: %s", expectedURL, result)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns URL to armosec docs when given an empty control ID.
|
||||
func TestGetControlLink_EmptyControlID(t *testing.T) {
|
||||
controlID := ""
|
||||
expectedURL := "https://hub.armosec.io/docs/"
|
||||
|
||||
result := GetControlLink(controlID)
|
||||
|
||||
if result != expectedURL {
|
||||
t.Errorf("Expected URL: %s, but got: %s", expectedURL, result)
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
v1 "github.com/kubescape/backend/pkg/client/v1"
|
||||
"github.com/kubescape/backend/pkg/servicediscovery"
|
||||
servicediscoveryv1 "github.com/kubescape/backend/pkg/servicediscovery/v1"
|
||||
servicediscoveryv2 "github.com/kubescape/backend/pkg/servicediscovery/v2"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
@@ -294,10 +295,16 @@ func (c *ClusterConfig) updateConfigEmptyFieldsFromKubescapeConfigMap() error {
|
||||
if urlsConfigMap != nil {
|
||||
if jsonConf, ok := urlsConfigMap.Data["services"]; ok {
|
||||
services, err := servicediscovery.GetServices(
|
||||
servicediscoveryv1.NewServiceDiscoveryStreamV1([]byte(jsonConf)),
|
||||
servicediscoveryv2.NewServiceDiscoveryStreamV2([]byte(jsonConf)),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
// try to parse as v1
|
||||
services, err = servicediscovery.GetServices(
|
||||
servicediscoveryv1.NewServiceDiscoveryStreamV1([]byte(jsonConf)),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if services.GetApiServerUrl() != "" {
|
||||
@@ -473,7 +480,6 @@ func updateCloudURLs(configObj *ConfigObj) {
|
||||
|
||||
func initializeCloudAPI(c ITenantConfig) *v1.KSCloudAPI {
|
||||
if ksCloud := getter.GetKSCloudAPIConnector(); ksCloud != nil {
|
||||
logger.L().Debug("KS Cloud API already initialized")
|
||||
|
||||
if val := c.GetCloudAPIURL(); val != "" && val != ksCloud.GetCloudAPIURL() {
|
||||
logger.L().Debug("updating KS Cloud API from config", helpers.String("old", ksCloud.GetCloudAPIURL()), helpers.String("new", val))
|
||||
|
||||
@@ -3,9 +3,9 @@ package cautils
|
||||
import (
|
||||
"golang.org/x/mod/semver"
|
||||
|
||||
"github.com/armosec/utils-go/boolutils"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
)
|
||||
|
||||
func NewPolicies() *Policies {
|
||||
@@ -15,7 +15,7 @@ func NewPolicies() *Policies {
|
||||
}
|
||||
}
|
||||
|
||||
func (policies *Policies) Set(frameworks []reporthandling.Framework, version string, excludedRules map[string]bool, scanningScope reporthandling.ScanningScopeType) {
|
||||
func (policies *Policies) Set(frameworks []reporthandling.Framework, excludedRules map[string]bool, scanningScope reporthandling.ScanningScopeType) {
|
||||
for i := range frameworks {
|
||||
if !isFrameworkFitToScanScope(frameworks[i], scanningScope) {
|
||||
continue
|
||||
@@ -33,9 +33,12 @@ func (policies *Policies) Set(frameworks []reporthandling.Framework, version str
|
||||
}
|
||||
}
|
||||
|
||||
if !ruleWithKSOpaDependency(frameworks[i].Controls[j].Rules[r].Attributes) && isRuleKubescapeVersionCompatible(frameworks[i].Controls[j].Rules[r].Attributes, version) && isControlFitToScanScope(frameworks[i].Controls[j], scanningScope) {
|
||||
compatibleRules = append(compatibleRules, frameworks[i].Controls[j].Rules[r])
|
||||
if ShouldSkipRule(frameworks[i].Controls[j], frameworks[i].Controls[j].Rules[r], scanningScope) {
|
||||
continue
|
||||
}
|
||||
// if isRuleKubescapeVersionCompatible(frameworks[i].Controls[j].Rules[r].Attributes, version) && isControlFitToScanScope(frameworks[i].Controls[j], scanningScope) {
|
||||
compatibleRules = append(compatibleRules, frameworks[i].Controls[j].Rules[r])
|
||||
// }
|
||||
}
|
||||
if len(compatibleRules) > 0 {
|
||||
frameworks[i].Controls[j].Rules = compatibleRules
|
||||
@@ -55,12 +58,16 @@ func (policies *Policies) Set(frameworks []reporthandling.Framework, version str
|
||||
}
|
||||
}
|
||||
|
||||
func ruleWithKSOpaDependency(attributes map[string]interface{}) bool {
|
||||
if attributes == nil {
|
||||
return false
|
||||
// ShouldSkipRule checks if the rule should be skipped
|
||||
// It checks the following:
|
||||
// 1. Rule is compatible with the current kubescape version
|
||||
// 2. Rule fits the current scanning scope
|
||||
func ShouldSkipRule(control reporthandling.Control, rule reporthandling.PolicyRule, scanningScope reporthandling.ScanningScopeType) bool {
|
||||
if !isRuleKubescapeVersionCompatible(rule.Attributes, BuildNumber) {
|
||||
return true
|
||||
}
|
||||
if s, ok := attributes["armoOpa"]; ok { // TODO - make global
|
||||
return boolutils.StringToBool(s.(string))
|
||||
if !isControlFitToScanScope(control, scanningScope) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -70,17 +77,25 @@ func ruleWithKSOpaDependency(attributes map[string]interface{}) bool {
|
||||
// returns true only if rule doesn't have the "until" attribute
|
||||
func isRuleKubescapeVersionCompatible(attributes map[string]interface{}, version string) bool {
|
||||
if from, ok := attributes["useFromKubescapeVersion"]; ok && from != nil {
|
||||
if version != "" {
|
||||
if semver.Compare(version, from.(string)) == -1 {
|
||||
switch sfrom := from.(type) {
|
||||
case string:
|
||||
if version != "" && semver.Compare(version, sfrom) == -1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if until, ok := attributes["useUntilKubescapeVersion"]; ok && until != nil {
|
||||
if version == "" {
|
||||
default:
|
||||
// Handle case where useFromKubescapeVersion is not a string
|
||||
return false
|
||||
}
|
||||
if semver.Compare(version, until.(string)) >= 0 {
|
||||
}
|
||||
|
||||
if until, ok := attributes["useUntilKubescapeVersion"]; ok && until != nil {
|
||||
switch suntil := until.(type) {
|
||||
case string:
|
||||
if version == "" || semver.Compare(version, suntil) >= 0 {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
// Handle case where useUntilKubescapeVersion is not a string
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -138,3 +153,13 @@ func isFrameworkFitToScanScope(framework reporthandling.Framework, scanScopeMatc
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetScanningScope(ContextMetadata reporthandlingv2.ContextMetadata) reporthandling.ScanningScopeType {
|
||||
if ContextMetadata.ClusterContextMetadata != nil {
|
||||
if ContextMetadata.ClusterContextMetadata.CloudMetadata != nil && ContextMetadata.ClusterContextMetadata.CloudMetadata.CloudProvider != "" {
|
||||
return reporthandling.ScanningScopeType(ContextMetadata.ClusterContextMetadata.CloudMetadata.CloudProvider)
|
||||
}
|
||||
return reporthandling.ScopeCluster
|
||||
}
|
||||
return reporthandling.ScopeFile
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
spinnerpkg "github.com/briandowns/spinner"
|
||||
@@ -27,7 +28,7 @@ func FailureTextDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
}
|
||||
|
||||
func InfoDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
fmt.Fprintf(w, gchalk.WithCyan().Bold(format), a...)
|
||||
fmt.Fprintf(w, gchalk.WithBrightWhite().Bold(format), a...)
|
||||
}
|
||||
|
||||
func InfoTextDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
@@ -50,6 +51,20 @@ func BoldDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
fmt.Fprintf(w, gchalk.Bold(format), a...)
|
||||
}
|
||||
|
||||
func LineDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
fmt.Fprintf(w, gchalk.WithAnsi256(238).Bold(format), a...)
|
||||
}
|
||||
|
||||
func SectionHeadingDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
fmt.Fprintf(w, "\n"+
|
||||
gchalk.WithBrightWhite().Bold(format)+
|
||||
gchalk.WithAnsi256(238).Bold(fmt.Sprintf("\n%s\n\n", strings.Repeat("─", len(format)))), a...)
|
||||
}
|
||||
|
||||
func StarDisplay(w io.Writer, format string, a ...interface{}) {
|
||||
fmt.Fprintf(w, gchalk.WithAnsi256(238).Bold("* ")+gchalk.White(format), a...)
|
||||
}
|
||||
|
||||
var spinner *spinnerpkg.Spinner
|
||||
|
||||
func StartSpinner() {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStartSpinner(t *testing.T) {
|
||||
@@ -30,3 +33,422 @@ func TestStartSpinner(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailureDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
FailureDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarningDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
WarningDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailureTextDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
FailureTextDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfoDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
InfoDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfoTextDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
InfoTextDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
SimpleDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSuccessDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
SuccessDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescriptionDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
DescriptionDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoldDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
BoldDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLineDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
LineDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSectionHeadingDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test Section",
|
||||
want: "\nTest Section\n────────────\n\n",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "\n\n\n\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
SectionHeadingDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "Test",
|
||||
want: "* Test",
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
want: "* ",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
StarDisplay(os.Stdout, tt.text)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a new instance of ProgressHandler with the given title.
|
||||
func TestNewProgressHandler_(t *testing.T) {
|
||||
tests := []struct {
|
||||
title string
|
||||
}{
|
||||
{
|
||||
title: "Test title",
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.title, func(t *testing.T) {
|
||||
progressHandler := NewProgressHandler(tt.title)
|
||||
assert.NotNil(t, progressHandler)
|
||||
|
||||
assert.Equal(t, tt.title, progressHandler.title)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func LoadResourcesFromHelmCharts(ctx context.Context, basePath string) (map[stri
|
||||
// If the contents at given path is a Kustomize Directory, LoadResourcesFromKustomizeDirectory will
|
||||
// generate yaml files using "Kustomize" & renders a map of workloads from those yaml files
|
||||
func LoadResourcesFromKustomizeDirectory(ctx context.Context, basePath string) (map[string][]workloadinterface.IMetadata, string) {
|
||||
isKustomizeDirectory := IsKustomizeDirectory(basePath)
|
||||
isKustomizeDirectory := isKustomizeDirectory(basePath)
|
||||
isKustomizeFile := IsKustomizeFile(basePath)
|
||||
if ok := isKustomizeDirectory || isKustomizeFile; !ok {
|
||||
return nil, ""
|
||||
@@ -94,7 +94,7 @@ func LoadResourcesFromKustomizeDirectory(ctx context.Context, basePath string) (
|
||||
}
|
||||
|
||||
wls, errs := kustomizeDirectory.GetWorkloads(newBasePath)
|
||||
kustomizeDirectoryName := GetKustomizeDirectoryName(newBasePath)
|
||||
kustomizeDirectoryName := getKustomizeDirectoryName(newBasePath)
|
||||
|
||||
if len(errs) > 0 {
|
||||
logger.L().Ctx(ctx).Warning(fmt.Sprintf("Rendering yaml from Kustomize failed: %v", errs))
|
||||
@@ -137,7 +137,7 @@ func loadFiles(rootPath string, filePaths []string) (map[string][]workloadinterf
|
||||
continue // empty file
|
||||
}
|
||||
|
||||
w, e := ReadFile(f, GetFileFormat(filePaths[i]))
|
||||
w, e := ReadFile(f, getFileFormat(filePaths[i]))
|
||||
if e != nil {
|
||||
logger.L().Debug("failed to read file", helpers.String("file", filePaths[i]), helpers.Error(e))
|
||||
}
|
||||
@@ -196,14 +196,14 @@ func listFilesOrDirectories(pattern string, onlyDirectories bool) ([]string, []e
|
||||
pattern = filepath.Join(o, pattern)
|
||||
}
|
||||
|
||||
if !onlyDirectories && IsFile(pattern) {
|
||||
if !onlyDirectories && isFile(pattern) {
|
||||
paths = append(paths, pattern)
|
||||
return paths, errs
|
||||
}
|
||||
|
||||
root, shouldMatch := filepath.Split(pattern)
|
||||
|
||||
if IsDir(pattern) {
|
||||
if isDir(pattern) {
|
||||
root = pattern
|
||||
shouldMatch = "*"
|
||||
}
|
||||
@@ -324,7 +324,7 @@ func glob(root, pattern string, onlyDirectories bool) ([]string, error) {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
fileFormat := GetFileFormat(path)
|
||||
fileFormat := getFileFormat(path)
|
||||
if !(fileFormat == JSON_FILE_FORMAT || fileFormat == YAML_FILE_FORMAT) {
|
||||
return nil
|
||||
}
|
||||
@@ -342,8 +342,8 @@ func glob(root, pattern string, onlyDirectories bool) ([]string, error) {
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// IsFile checks if a given path is a file
|
||||
func IsFile(name string) bool {
|
||||
// isFile checks if a given path is a file
|
||||
func isFile(name string) bool {
|
||||
if fi, err := os.Stat(name); err == nil {
|
||||
if fi.Mode().IsRegular() {
|
||||
return true
|
||||
@@ -352,8 +352,8 @@ func IsFile(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDir checks if a given path is a directory
|
||||
func IsDir(name string) bool {
|
||||
// isDir checks if a given path is a directory
|
||||
func isDir(name string) bool {
|
||||
if info, err := os.Stat(name); err == nil {
|
||||
if info.IsDir() {
|
||||
return true
|
||||
@@ -362,7 +362,7 @@ func IsDir(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func GetFileFormat(filePath string) FileFormat {
|
||||
func getFileFormat(filePath string) FileFormat {
|
||||
if IsYaml(filePath) {
|
||||
return YAML_FILE_FORMAT
|
||||
} else if IsJson(filePath) {
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestListFiles(t *testing.T) {
|
||||
|
||||
files, errs := listFiles(filesPath)
|
||||
assert.Equal(t, 0, len(errs))
|
||||
assert.Equal(t, 12, len(files))
|
||||
assert.Equal(t, 13, len(files))
|
||||
}
|
||||
|
||||
func TestLoadResourcesFromFiles(t *testing.T) {
|
||||
@@ -105,3 +105,110 @@ func getRelativePath(p string) string {
|
||||
pp := strings.SplitAfter(p, "api=")
|
||||
return pp[1]
|
||||
}
|
||||
|
||||
// Converts a YAML object to a JSON object
|
||||
func TestConvertYamlToJson(t *testing.T) {
|
||||
tests := []struct {
|
||||
yamlObj map[interface{}]interface{}
|
||||
jsonObj map[string]interface{}
|
||||
}{
|
||||
{
|
||||
yamlObj: map[interface{}]interface{}{
|
||||
"name": "John",
|
||||
"age": 30,
|
||||
"city": "New York",
|
||||
},
|
||||
jsonObj: map[string]interface{}{
|
||||
"name": "John",
|
||||
"age": 30,
|
||||
"city": "New York",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
assert.Equal(t, tt.jsonObj, convertYamlToJson(tt.yamlObj))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsYaml(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
path: "temp.yaml",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
path: "temp.json",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
path: "random.txt",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.path, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, IsYaml(tt.path))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJson(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
path: "temp.yaml",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
path: "temp.json",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
path: "random.txt",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.path, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, IsJson(tt.path))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetFileFormat(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
want FileFormat
|
||||
}{
|
||||
{
|
||||
path: "temp.yaml",
|
||||
want: YAML_FILE_FORMAT,
|
||||
},
|
||||
{
|
||||
path: "temp.json",
|
||||
want: JSON_FILE_FORMAT,
|
||||
},
|
||||
{
|
||||
path: "random.txt",
|
||||
want: "random.txt",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.path, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, getFileFormat(tt.path))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -86,26 +85,6 @@ func HttpGetter(httpClient *http.Client, fullURL string, headers map[string]stri
|
||||
return respStr, nil
|
||||
}
|
||||
|
||||
// HttpPost provides a low-level capability to send a HTTP POST request and serialize the response as a string.
|
||||
//
|
||||
// Deprecated: use methods of the KSCloudAPI client instead.
|
||||
func HttpPost(httpClient *http.Client, fullURL string, headers map[string]string, body []byte) (string, error) {
|
||||
req, err := http.NewRequest("POST", fullURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
setHeaders(req, headers)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
respStr, err := httpRespToString(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return respStr, nil
|
||||
}
|
||||
|
||||
func setHeaders(req *http.Request, headers map[string]string) {
|
||||
if len(headers) >= 0 { // might be nil
|
||||
for k, v := range headers {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
beClient "github.com/kubescape/backend/pkg/client/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -82,17 +85,51 @@ func TestHttpMethods(t *testing.T) {
|
||||
require.EqualValues(t, "body-get", resp)
|
||||
})
|
||||
|
||||
t.Run("HttpPost should POST", func(t *testing.T) {
|
||||
body := []byte("body-post")
|
||||
|
||||
resp, err := HttpPost(client, srv.URL(pathTestPost), hdrs, body)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, string(body), resp)
|
||||
})
|
||||
|
||||
t.Run("HttpDelete should DELETE", func(t *testing.T) {
|
||||
resp, err := HttpDelete(client, srv.URL(pathTestDelete), hdrs)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, "body-delete", resp)
|
||||
})
|
||||
}
|
||||
|
||||
// Returns an empty string and nil error when given a nil response or nil response body.
|
||||
func TestHttpRespToString_NilResponse(t *testing.T) {
|
||||
resp := &http.Response{}
|
||||
result, err := httpRespToString(resp)
|
||||
assert.Equal(t, "", result)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestHttpRespToString_ValidResponse(t *testing.T) {
|
||||
resp := &http.Response{
|
||||
Body: ioutil.NopCloser(strings.NewReader("test response")),
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
}
|
||||
result, err := httpRespToString(resp)
|
||||
assert.Equal(t, "test response", result)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
// Returns an error with status and reason when unable to read response body.
|
||||
func TestHttpRespToString_ReadError(t *testing.T) {
|
||||
resp := &http.Response{
|
||||
Body: ioutil.NopCloser(strings.NewReader("test response")),
|
||||
}
|
||||
resp.Body.Close()
|
||||
result, err := httpRespToString(resp)
|
||||
assert.EqualError(t, err, "http-error: '', reason: 'test response'")
|
||||
assert.Equal(t, "test response", result)
|
||||
}
|
||||
|
||||
// Returns an error with status and reason when unable to read response body.
|
||||
func TestHttpRespToString_ErrorCodeLessThan200(t *testing.T) {
|
||||
resp := &http.Response{
|
||||
Body: ioutil.NopCloser(strings.NewReader("test response")),
|
||||
StatusCode: 100,
|
||||
}
|
||||
resp.Body.Close()
|
||||
result, err := httpRespToString(resp)
|
||||
assert.EqualError(t, err, "http-error: '', reason: 'test response'")
|
||||
assert.Equal(t, "test response", result)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
@@ -14,25 +11,3 @@ func init() {
|
||||
// For finer-grained config, see: https://pkg.go.dev/github.com/json-iterator/go#section-readme
|
||||
json = jsoniter.ConfigFastest
|
||||
}
|
||||
|
||||
// JSONDecoder provides a low-level utility that returns a JSON decoder for given string.
|
||||
//
|
||||
// Deprecated: use higher level methods from the KSCloudAPI client instead.
|
||||
func JSONDecoder(origin string) *jsoniter.Decoder {
|
||||
dec := jsoniter.NewDecoder(strings.NewReader(origin))
|
||||
dec.UseNumber()
|
||||
|
||||
return dec
|
||||
}
|
||||
|
||||
func decode[T any](rdr io.Reader) (T, error) {
|
||||
var receiver T
|
||||
dec := newDecoder(rdr)
|
||||
err := dec.Decode(&receiver)
|
||||
|
||||
return receiver, err
|
||||
}
|
||||
|
||||
func newDecoder(rdr io.Reader) *jsoniter.Decoder {
|
||||
return json.NewDecoder(rdr)
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestJSONDecoder(t *testing.T) {
|
||||
t.Run("should decode json string", func(t *testing.T) {
|
||||
const input = `"xyz"`
|
||||
d := JSONDecoder(input)
|
||||
var receiver string
|
||||
require.NoError(t, d.Decode(&receiver))
|
||||
require.Equal(t, "xyz", receiver)
|
||||
})
|
||||
|
||||
t.Run("should decode json number", func(t *testing.T) {
|
||||
const input = `123.01`
|
||||
d := JSONDecoder(input)
|
||||
var receiver float64
|
||||
require.NoError(t, d.Decode(&receiver))
|
||||
require.Equal(t, 123.01, receiver)
|
||||
})
|
||||
|
||||
t.Run("requires json quotes", func(t *testing.T) {
|
||||
const input = `xyz`
|
||||
d := JSONDecoder(input)
|
||||
var receiver string
|
||||
require.Error(t, d.Decode(&receiver))
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
v1 "github.com/kubescape/backend/pkg/client/v1"
|
||||
utils "github.com/kubescape/backend/pkg/utils"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
)
|
||||
@@ -21,13 +26,11 @@ var (
|
||||
//
|
||||
// NOTE: cannot be used concurrently.
|
||||
func SetKSCloudAPIConnector(ksCloudAPI *v1.KSCloudAPI) {
|
||||
if ksCloudAPI != nil {
|
||||
if ksCloudAPI != nil && ksCloudAPI.GetCloudAPIURL() != "" {
|
||||
logger.L().Debug("setting global KS Cloud API connector",
|
||||
helpers.String("accountID", ksCloudAPI.GetAccountID()),
|
||||
helpers.String("cloudAPIURL", ksCloudAPI.GetCloudAPIURL()),
|
||||
helpers.String("cloudReportURL", ksCloudAPI.GetCloudReportURL()))
|
||||
} else {
|
||||
logger.L().Debug("setting global KS Cloud API connector (nil)")
|
||||
}
|
||||
globalKSCloudAPIConnector = ksCloudAPI
|
||||
}
|
||||
@@ -47,3 +50,25 @@ func GetKSCloudAPIConnector() *v1.KSCloudAPI {
|
||||
|
||||
return &client
|
||||
}
|
||||
|
||||
// HTTPPost provides a low-level utility that sends a POST request to a given url
|
||||
func HTTPPost(client *http.Client, fullURL string, body []byte, headers map[string]string) (io.ReadCloser, int64, error) {
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, fullURL, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
setHeaders(req, headers)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return nil, 0, utils.ErrAPI(resp)
|
||||
}
|
||||
|
||||
return resp.Body, resp.ContentLength, err
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
v1 "github.com/kubescape/backend/pkg/client/v1"
|
||||
utils "github.com/kubescape/backend/pkg/utils"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -47,3 +53,69 @@ func TestGlobalKSCloudAPIConnector(t *testing.T) {
|
||||
require.Equal(t, client, GetKSCloudAPIConnector())
|
||||
})
|
||||
}
|
||||
|
||||
func TestHttpPost(t *testing.T) {
|
||||
client := http.DefaultClient
|
||||
hdrs := map[string]string{"key": "value"}
|
||||
|
||||
srv := mockAPIServer(t)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
t.Run("HttpPost should POST", func(t *testing.T) {
|
||||
type VersionCheckResponse struct {
|
||||
Client string `json:"client"` // kubescape
|
||||
ClientUpdate string `json:"clientUpdate"` // kubescape latest version
|
||||
Framework float32 `json:"framework"` // framework name
|
||||
FrameworkUpdate int64 `json:"frameworkUpdate"` // framework latest version
|
||||
Message string `json:"message"` // alert message
|
||||
}
|
||||
body := &VersionCheckResponse{
|
||||
Client: "kubescape",
|
||||
ClientUpdate: "v3.0.0",
|
||||
Framework: 45.3,
|
||||
FrameworkUpdate: 29,
|
||||
Message: "",
|
||||
}
|
||||
|
||||
reqBody, err := json.Marshal(*body)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, _, err := HTTPPost(client, srv.URL(pathTestPost), reqBody, hdrs)
|
||||
require.NoError(t, err)
|
||||
|
||||
respString, err := utils.Decode[*VersionCheckResponse](resp)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, body, respString)
|
||||
})
|
||||
}
|
||||
|
||||
type testServer struct {
|
||||
*httptest.Server
|
||||
}
|
||||
|
||||
func (s *testServer) URL(pth string) string {
|
||||
pth = strings.TrimLeft(pth, "/")
|
||||
|
||||
return fmt.Sprintf("%s/%s", s.Server.URL, pth)
|
||||
}
|
||||
|
||||
func mockAPIServer(t testing.TB) *testServer {
|
||||
h := http.NewServeMux()
|
||||
|
||||
// test options: regular mock (default), error or garbled JSON output
|
||||
server := &testServer{
|
||||
Server: httptest.NewServer(h),
|
||||
}
|
||||
|
||||
h.HandleFunc(pathTestPost, func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Truef(t, strings.EqualFold(http.MethodPost, r.Method), "expected a POST method called, but got %q", r.Method)
|
||||
// write a json response here
|
||||
defer func() { _ = r.Body.Close() }()
|
||||
_, _ = io.Copy(w, r.Body)
|
||||
|
||||
return
|
||||
|
||||
})
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ var (
|
||||
ErrNameRequired = errors.New("missing required input framework name")
|
||||
ErrIDRequired = errors.New("missing required input control ID")
|
||||
ErrFrameworkNotMatching = errors.New("framework from file not matching")
|
||||
ErrControlNotMatching = errors.New("framework from file not matching")
|
||||
ErrControlNotMatching = errors.New("control from file not matching")
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
4
core/cautils/getter/testdata/C-0001.json
vendored
4
core/cautils/getter/testdata/C-0001.json
vendored
@@ -2,7 +2,6 @@
|
||||
"guid": "",
|
||||
"name": "Forbidden Container Registries",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "container",
|
||||
@@ -29,7 +28,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-identify-blocklisted-image-registries",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Initial Access::Compromised images in registry"
|
||||
},
|
||||
"creationTime": "",
|
||||
@@ -82,4 +80,4 @@
|
||||
""
|
||||
],
|
||||
"baseScore": 7
|
||||
}
|
||||
}
|
||||
|
||||
72
core/cautils/getter/testdata/MITRE.json
vendored
72
core/cautils/getter/testdata/MITRE.json
vendored
@@ -2,7 +2,6 @@
|
||||
"guid": "",
|
||||
"name": "MITRE",
|
||||
"attributes": {
|
||||
"armoBuiltin": true
|
||||
},
|
||||
"creationTime": "",
|
||||
"description": "Testing MITRE for Kubernetes as suggested by microsoft in https://www.microsoft.com/security/blog/wp-content/uploads/2020/04/k8s-matrix.png",
|
||||
@@ -12,7 +11,6 @@
|
||||
"guid": "",
|
||||
"name": "Access container service account",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "container",
|
||||
@@ -41,7 +39,6 @@
|
||||
"guid": "",
|
||||
"name": "access-container-service-account",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Credential Access::Access container service account, Lateral Movement::Container service account",
|
||||
"useUntilKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -115,7 +112,6 @@
|
||||
"guid": "",
|
||||
"name": "access-container-service-account-v1",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Credential Access::Access container service account, Lateral Movement::Container service account",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -196,7 +192,6 @@
|
||||
"guid": "",
|
||||
"name": "Access Kubernetes dashboard",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"controlTypeTags": [
|
||||
"compliance"
|
||||
],
|
||||
@@ -216,7 +211,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-access-dashboard",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Lateral Movement::Access Kubernetes dashboard, Discovery::Access Kubernetes dashboard",
|
||||
"useUntilKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -250,7 +244,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-access-dashboard-subject-v1",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Lateral Movement::Access Kubernetes dashboard, Discovery::Access Kubernetes dashboard",
|
||||
"resourcesAggregator": "subject-role-rolebinding",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
@@ -287,7 +280,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-access-dashboard-wl-v1",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Lateral Movement::Access Kubernetes dashboard, Discovery::Access Kubernetes dashboard",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -354,7 +346,6 @@
|
||||
"guid": "",
|
||||
"name": "Applications credentials in configuration files",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "kubeapi",
|
||||
@@ -389,7 +380,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-credentials-in-env-var",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Credential access::Applications credentials in configuration files, Lateral Movement::Applications credentials in configuration files"
|
||||
},
|
||||
"creationTime": "",
|
||||
@@ -461,7 +451,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-credentials-configmap",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Credential access::Applications credentials in configuration files, Lateral Movement::Applications credentials in configuration files"
|
||||
},
|
||||
"creationTime": "",
|
||||
@@ -520,7 +509,6 @@
|
||||
"guid": "",
|
||||
"name": "Cluster-admin binding",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "kubeapi",
|
||||
@@ -549,7 +537,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-list-all-cluster-admins",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Privilege Escalation::Cluster-admin binding",
|
||||
"useUntilKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -589,7 +576,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-list-all-cluster-admins-v1",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Privilege Escalation::Cluster-admin binding",
|
||||
"resourcesAggregator": "subject-role-rolebinding",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
@@ -633,7 +619,6 @@
|
||||
"guid": "",
|
||||
"name": "Cluster internal networking",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "container",
|
||||
@@ -661,7 +646,6 @@
|
||||
"guid": "",
|
||||
"name": "internal-networking",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Lateral Movement::Container internal networking, Discovery::Network mapping"
|
||||
},
|
||||
"creationTime": "",
|
||||
@@ -710,7 +694,6 @@
|
||||
"guid": "",
|
||||
"name": "Exec into container",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"controlTypeTags": [
|
||||
"compliance",
|
||||
"security-impact"
|
||||
@@ -730,7 +713,6 @@
|
||||
"guid": "",
|
||||
"name": "exec-into-container",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Privilege Escalation::Exec into container",
|
||||
"useUntilKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -770,7 +752,6 @@
|
||||
"guid": "",
|
||||
"name": "exec-into-container-v1",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Privilege Escalation::Exec into container",
|
||||
"resourcesAggregator": "subject-role-rolebinding",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
@@ -814,7 +795,6 @@
|
||||
"guid": "",
|
||||
"name": "Exposed sensitive interfaces",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"controlTypeTags": [
|
||||
"compliance"
|
||||
],
|
||||
@@ -832,7 +812,6 @@
|
||||
"guid": "",
|
||||
"name": "exposed-sensitive-interfaces",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"microsoftK8sThreatMatrix": "Initial access::Exposed sensitive interfaces",
|
||||
"useUntilKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -904,7 +883,6 @@
|
||||
"guid": "",
|
||||
"name": "exposed-sensitive-interfaces-v1",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"microsoftK8sThreatMatrix": "Initial access::Exposed sensitive interfaces",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -983,7 +961,6 @@
|
||||
"guid": "",
|
||||
"name": "HostPath mount",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "container",
|
||||
@@ -1010,7 +987,6 @@
|
||||
"guid": "",
|
||||
"name": "alert-any-hostpath",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Privilege Escalation::hostPath mount"
|
||||
},
|
||||
"creationTime": "",
|
||||
@@ -1074,7 +1050,6 @@
|
||||
"guid": "",
|
||||
"name": "Instance Metadata API",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "container",
|
||||
@@ -1103,7 +1078,6 @@
|
||||
"guid": "",
|
||||
"name": "instance-metadata-api-access",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"hostSensorRule": "true",
|
||||
"m$K8sThreatMatrix": "Credential Access::Instance Metadata API"
|
||||
},
|
||||
@@ -1143,7 +1117,6 @@
|
||||
"guid": "",
|
||||
"name": "Kubernetes CronJob",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"controlTypeTags": [
|
||||
"compliance"
|
||||
],
|
||||
@@ -1161,7 +1134,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-deny-cronjobs",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Persistence::Kubernetes Cronjob"
|
||||
},
|
||||
"creationTime": "",
|
||||
@@ -1199,7 +1171,6 @@
|
||||
"guid": "",
|
||||
"name": "List Kubernetes secrets",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "kubeapi",
|
||||
@@ -1227,7 +1198,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-can-list-get-secrets",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"microsoftK8sThreatMatrix": "Discovery::Access the K8s API server",
|
||||
"useUntilKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -1267,7 +1237,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-can-list-get-secrets-v1",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"microsoftK8sThreatMatrix": "Discovery::Access the K8s API server",
|
||||
"resourcesAggregator": "subject-role-rolebinding",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
@@ -1311,7 +1280,6 @@
|
||||
"guid": "",
|
||||
"name": "Mount service principal",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"controlTypeTags": [
|
||||
"compliance"
|
||||
],
|
||||
@@ -1329,7 +1297,6 @@
|
||||
"guid": "",
|
||||
"name": "alert-mount-potential-credentials-paths",
|
||||
"attributes": {
|
||||
"armoBuiltin": true
|
||||
},
|
||||
"creationTime": "",
|
||||
"rule": "package armo_builtins\nimport future.keywords.if\n\n\ndeny[msga] {\n\tprovider := data.dataControlInputs.cloudProvider\n\tprovider != \"\"\n\tresources := input[_]\n\tvolumes_data := get_volumes(resources)\n volumes := volumes_data[\"volumes\"]\n volume := volumes[i]\n\tbeggining_of_path := volumes_data[\"beggining_of_path\"]\n result := is_unsafe_paths(volume, beggining_of_path, provider,i)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"%v: %v has: %v as volume with potential credentials access.\", [resources.kind, resources.metadata.name, volume.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": [result],\n\t\t\"fixPaths\":[],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [resources]\n\t\t}\n\t}\t\n}\n\n\t\n# get_volume - get resource volumes paths for {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\nget_volumes(resources) := result {\n\tresources_kinds := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tresources_kinds[resources.kind]\n\tresult = {\"volumes\": resources.spec.template.spec.volumes, \"beggining_of_path\": \"spec.template.spec.\"}\n}\n\n# get_volume - get resource volumes paths for \"Pod\"\nget_volumes(resources) := result {\n\tresources.kind == \"Pod\"\n\tresult = {\"volumes\": resources.spec.volumes, \"beggining_of_path\": \"spec.\"}\n}\n\n# get_volume - get resource volumes paths for \"CronJob\"\nget_volumes(resources) := result {\n\tresources.kind == \"CronJob\"\n\tresult = {\"volumes\": resources.spec.jobTemplate.spec.template.spec.volumes, \"beggining_of_path\": \"spec.jobTemplate.spec.template.spec.\"}\n}\n\n\n# is_unsafe_paths - looking for cloud provider (eks/gke/aks) paths that have the potential of accessing credentials\nis_unsafe_paths(volume, beggining_of_path, provider, i) = result {\n\tunsafe := unsafe_paths(provider)\n\tunsafe[_] == fix_path(volume.hostPath.path)\n\tresult= sprintf(\"%vvolumes[%d].hostPath.path\", [beggining_of_path, i])\n}\n\n\n# fix_path - adding \"/\" at the end of the path if doesn't exist and if not a file path.\nfix_path(path) := result if {\n\n\t# filter file path\n not regex.match(`[\\\\w-]+\\\\.`, path)\n\n\t# filter path that doesn't end with \"/\"\n not endswith(path, \"/\")\n\n\t# adding \"/\" to the end of the path\n result = sprintf(\"%v/\", [path])\n} else := path\n\n\n\n# eks unsafe paths\nunsafe_paths(x) := [\"/.aws/\", \n\t\t\t\t\t\"/.aws/config/\", \n\t\t\t\t\t\"/.aws/credentials/\"] if {x==\"eks\"}\n\n# aks unsafe paths\nunsafe_paths(x) := [\"/etc/\",\n\t\t\t\t\t\"/etc/kubernetes/\",\n\t\t\t\t\t\"/etc/kubernetes/azure.json\", \n\t\t\t\t\t\"/.azure/\",\n\t\t\t\t\t\"/.azure/credentials/\", \n\t\t\t\t\t\"/etc/kubernetes/azure.json\"] if {x==\"aks\"}\n\n# gke unsafe paths\nunsafe_paths(x) := [\"/.config/gcloud/\", \n\t\t\t\t\t\"/.config/\", \n\t\t\t\t\t\"/gcloud/\", \n\t\t\t\t\t\"/.config/gcloud/application_default_credentials.json\",\n\t\t\t\t\t\"/gcloud/application_default_credentials.json\"] if {x==\"gke\"}\n\n",
|
||||
@@ -1396,7 +1363,6 @@
|
||||
"guid": "",
|
||||
"name": "Privileged container",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "container",
|
||||
@@ -1422,7 +1388,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-privilege-escalation",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Privilege Escalation::privileged container",
|
||||
"mitre": "Privilege Escalation",
|
||||
"mitreCode": "TA0004"
|
||||
@@ -1488,7 +1453,6 @@
|
||||
"guid": "",
|
||||
"name": "SSH server running inside container",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"controlTypeTags": [
|
||||
"compliance"
|
||||
],
|
||||
@@ -1506,7 +1470,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-can-ssh-to-pod",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"microsoftK8sThreatMatrix": "Execution::SSH server running inside container",
|
||||
"useUntilKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -1566,7 +1529,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-can-ssh-to-pod-v1",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"microsoftK8sThreatMatrix": "Execution::SSH server running inside container",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -1633,7 +1595,6 @@
|
||||
"guid": "",
|
||||
"name": "Writable hostPath mount",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "container",
|
||||
@@ -1664,7 +1625,6 @@
|
||||
"guid": "",
|
||||
"name": "alert-rw-hostpath",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Persistence::Writable hostPath mount, Lateral Movement::Writable volume mounts on the host"
|
||||
},
|
||||
"creationTime": "",
|
||||
@@ -1735,7 +1695,6 @@
|
||||
"guid": "",
|
||||
"name": "Malicious admission controller (mutating)",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "kubeapi",
|
||||
@@ -1762,7 +1721,6 @@
|
||||
"guid": "",
|
||||
"name": "list-all-mutating-webhooks",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Persistence::Malicious admission controller"
|
||||
},
|
||||
"creationTime": "",
|
||||
@@ -1800,7 +1758,6 @@
|
||||
"guid": "",
|
||||
"name": "Malicious admission controller (validating)",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "kubeapi",
|
||||
@@ -1828,7 +1785,6 @@
|
||||
"guid": "",
|
||||
"name": "list-all-validating-webhooks",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Credential Access::Malicious admission controller"
|
||||
},
|
||||
"creationTime": "",
|
||||
@@ -1866,7 +1822,6 @@
|
||||
"guid": "",
|
||||
"name": "Delete Kubernetes events",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "kubeapi",
|
||||
@@ -1894,7 +1849,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-can-delete-k8s-events",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"microsoftK8sThreatMatrix": "Defense Evasion::Delete K8S events",
|
||||
"useUntilKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -1934,7 +1888,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-can-delete-k8s-events-v1",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"microsoftK8sThreatMatrix": "Defense Evasion::Delete K8S events",
|
||||
"resourcesAggregator": "subject-role-rolebinding",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
@@ -1978,7 +1931,6 @@
|
||||
"guid": "",
|
||||
"name": "CoreDNS poisoning",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "kubeapi",
|
||||
@@ -2004,7 +1956,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-can-update-configmap",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"microsoftK8sThreatMatrix": "Lateral Movement::CoreDNS poisoning",
|
||||
"useUntilKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -2045,7 +1996,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-can-update-configmap-v1",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"microsoftK8sThreatMatrix": "Lateral Movement::CoreDNS poisoning",
|
||||
"resourcesAggregator": "subject-role-rolebinding",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
@@ -2089,7 +2039,6 @@
|
||||
"guid": "",
|
||||
"name": "Data Destruction",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"controlTypeTags": [
|
||||
"compliance"
|
||||
],
|
||||
@@ -2108,7 +2057,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-excessive-delete-rights",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Impact::Data Destruction",
|
||||
"useUntilKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -2144,7 +2092,6 @@
|
||||
"guid": "",
|
||||
"name": "rule-excessive-delete-rights-v1",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"m$K8sThreatMatrix": "Impact::Data Destruction",
|
||||
"resourcesAggregator": "subject-role-rolebinding",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
@@ -2188,7 +2135,6 @@
|
||||
"guid": "",
|
||||
"name": "CVE-2021-25741 - Using symlink for arbitrary host file system access.",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "container",
|
||||
@@ -2213,7 +2159,6 @@
|
||||
"guid": "",
|
||||
"name": "Symlink-Exchange-Can-Allow-Host-Filesystem-Access",
|
||||
"attributes": {
|
||||
"armoBuiltin": true
|
||||
},
|
||||
"creationTime": "",
|
||||
"rule": "package armo_builtins\n\n\ndeny[msga] {\n\tnodes := input[_]\n\tcurrent_version := nodes.status.nodeInfo.kubeletVersion\n is_vulnerable_version(current_version)\n pod := input[_]\n pod.kind == \"Pod\"\n\tcontainer := pod.spec.containers[i]\n\tbeggining_of_path := \"spec.\"\n final_path := is_sub_path_container(container, i, beggining_of_path)\n\n\tmsga := {\n\t\t\t\"alertMessage\": sprintf(\"You may be vulnerable to CVE-2021-25741. You have a Node with a vulnerable version and the following container : %v in pod : %v with subPath/subPathExpr\", [container.name, pod.metadata.name]),\n\t\t\t\"alertObject\": {\"k8SApiObjects\": [pod]},\n\t\t\t\"failedPaths\": final_path,\n\t\t\t\"fixPaths\": [],\n\t\t}\n}\n\n\ndeny[msga] {\n\tnodes := input[_]\n\tcurrent_version := nodes.status.nodeInfo.kubeletVersion\n is_vulnerable_version(current_version)\n wl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n container := wl.spec.template.spec.containers[i]\n\tbeggining_of_path := \"spec.template.spec.\"\n final_path := is_sub_path_container(container, i, beggining_of_path)\n \n\tmsga := {\n\t\"alertMessage\": sprintf(\"You may be vulnerable to CVE-2021-25741. You have a Node with a vulnerable version and the following container : %v in %v : %v with subPath/subPathExpr\", [container.name, wl.kind, wl.metadata.name]),\n\t\t\t\"alertObject\": {\"k8SApiObjects\": [wl]},\n\t\t\t\"failedPaths\": final_path,\n\t\t\t\"fixPaths\": [],\n\t\t}\n}\n\n\n\ndeny[msga] {\n\tnodes := input[_]\n\tcurrent_version := nodes.status.nodeInfo.kubeletVersion\n is_vulnerable_version(current_version)\n wl := input[_]\n\twl.kind == \"CronJob\"\n\tcontainer = wl.spec.jobTemplate.spec.template.spec.containers[i]\n\tbeggining_of_path := \"spec.jobTemplate.spec.template.spec.\"\n final_path := is_sub_path_container(container, i, beggining_of_path)\n \n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"You may be vulnerable to CVE-2021-25741. You have a Node with a vulnerable version and the following container : %v in %v : %v with subPath/subPathExpr\", [container.name, wl.kind, wl.metadata.name]),\n\t\t\t\"alertObject\": {\"k8SApiObjects\": [wl]},\n\t\t\t\"failedPaths\": final_path,\n\t\t\t\"fixPaths\": [],\n\t\t}\n}\n\n\n\nis_sub_path_container(container, i, beggining_of_path) = path {\n\tpath = [sprintf(\"%vcontainers[%v].volumeMounts[%v].subPath\" ,[beggining_of_path, format_int(i, 10), format_int(j, 10)]) | volume_mount = container.volumeMounts[j]; volume_mount.subPath]\n\tcount(path) \u003e 0\n}\n\nis_vulnerable_version(version) {\n version \u003c= \"v1.19.14\"\n}\n\nis_vulnerable_version(version){\n version \u003e= \"v1.22.0\"\n version \u003c= \"v1.22.1\"\n}\n\n\nis_vulnerable_version(version){\n version \u003e= \"v1.21.0\"\n version \u003c= \"v1.21.4\"\n}\n\n\nis_vulnerable_version(version){\n version \u003e= \"v1.20.0\"\n version \u003c= \"v1.20.9\"\n}\n\nis_vulnerable_version(version){\n\tversion == \"v1.20.10\"\n}\n\n\n",
|
||||
@@ -2277,7 +2222,6 @@
|
||||
"guid": "",
|
||||
"name": "CVE-2021-25742-nginx-ingress-snippet-annotation-vulnerability",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "container",
|
||||
@@ -2302,7 +2246,6 @@
|
||||
"guid": "",
|
||||
"name": "nginx-ingress-snippet-annotation-vulnerability",
|
||||
"attributes": {
|
||||
"armoBuiltin": true
|
||||
},
|
||||
"creationTime": "",
|
||||
"rule": "package armo_builtins\n\ndeny[msga] {\n\tdeployment := input[_]\n\tdeployment.kind == \"Deployment\"\n\timage := deployment.spec.template.spec.containers[i].image\n\tis_nginx_image(image)\n\tis_tag_image(image)\n\n\t# Extracting version from image tag\n\ttag_version_match := regex.find_all_string_submatch_n(\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\", image, -1)[0][0]\n image_version_str_arr := split(tag_version_match,\".\")\n\timage_version_arr := [to_number(image_version_str_arr[0]),to_number(image_version_str_arr[1]),to_number(image_version_str_arr[2])]\n\n\t# Check if vulnerable \n\tis_vulnerable(image_version_arr, deployment.metadata.namespace)\n\n\tpath := sprintf(\"spec.template.spec.containers[%v].image\", [format_int(i, 10)])\n\tmsga := {\n\t\t\t\"alertMessage\": sprintf(\"You may be vulnerable to CVE-2021-25742. Deployment %v\", [deployment.metadata.name]),\n\t\t\t\"failedPaths\": [path],\n\t\t\t\"fixPaths\":[],\n\t\t\t\"alertObject\": {\"k8SApiObjects\": [deployment]},\n\t\t}\n}\n\n\t\nis_nginx_image(image) {\n\tcontains(image, \"nginx-controller\")\n}\n\nis_nginx_image(image) {\n\tcontains(image, \"ingress-controller\")\n}\n\nis_nginx_image(image) {\n\tcontains(image, \"ingress-nginx\")\n}\n\nis_allow_snippet_annotation_on(namespace) {\n configmaps := [configmap | configmap = input[_]; configmap.kind == \"ConfigMap\"]\n\tconfigmap_on_ingress_namespace := [configmap | configmap= configmaps[_]; configmap.metadata.namespace == namespace]\n\tconfig_maps_with_snippet := [configmap | configmap= configmap_on_ingress_namespace[_]; configmap.data[\"allow-snippet-annotations\"] == \"false\"]\n\tcount(config_maps_with_snippet) \u003c 1\n}\n\nis_vulnerable(image_version, namespace) {\n\timage_version[0] == 0\n\timage_version[1] \u003c 49\n\tis_allow_snippet_annotation_on(namespace)\n}\n\nis_vulnerable(image_version, namespace) {\n\timage_version[0] == 0\n\timage_version[1] == 49\n\timage_version[2] == 0\n\tis_allow_snippet_annotation_on(namespace)\n}\n\t\nis_vulnerable(image_version, namespace) {\n\timage_version[0] == 1\n\timage_version[1] == 0\n\timage_version[2] == 0\n\tis_allow_snippet_annotation_on(namespace)\n}\n\nis_tag_image(image) {\n reg := \":[\\\\w][\\\\w.-]{0,127}(\\/)?\"\n version := regex.find_all_string_submatch_n(reg, image, -1)\n v := version[_]\n img := v[_]\n not endswith(img, \"/\")\n}",
|
||||
@@ -2340,7 +2283,6 @@
|
||||
"guid": "",
|
||||
"name": "Audit logs enabled",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "container",
|
||||
@@ -2364,7 +2306,6 @@
|
||||
"guid": "",
|
||||
"name": "k8s-audit-logs-enabled-cloud",
|
||||
"attributes": {
|
||||
"armoBuiltin": true
|
||||
},
|
||||
"creationTime": "",
|
||||
"rule": "package armo_builtins\nimport data.cautils as cautils\n\n# Check if audit logs is enabled for GKE\ndeny[msga] {\n\tcluster_config := input[_]\n\tcluster_config.apiVersion == \"container.googleapis.com/v1\"\n\tcluster_config.kind == \"ClusterDescribe\"\n cluster_config.metadata.provider == \"gke\"\t\n\tconfig := cluster_config.data\n\t\n # If enableComponents is empty, it will disable logging\n # https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1beta1/projects.locations.clusters#loggingcomponentconfig\n\tis_logging_disabled(config)\n\tmsga := {\n\t\t\"alertMessage\": \"audit logs is disabled\",\n\t\t\"alertScore\": 3,\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"failedPaths\": [],\n\t\t\"fixPaths\": [],\n\t\t\"fixCommand\":\"\",\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [],\n \"externalObjects\": cluster_config\n\t\t}\n\t}\n}\n\n\n# Check if audit logs is enabled for EKS\ndeny[msga] {\n\tcluster_config := input[_]\n\tcluster_config.apiVersion == \"eks.amazonaws.com/v1\"\n\tcluster_config.kind == \"ClusterDescribe\"\n cluster_config.metadata.provider == \"eks\"\t\n\tconfig := cluster_config.data\n # logSetup is an object representing the enabled or disabled Kubernetes control plane logs for your cluster.\n # types - available cluster control plane log types\n # https://docs.aws.amazon.com/eks/latest/APIReference/API_LogSetup.html\n goodTypes := [logSetup | logSetup = config.Cluster.Logging.ClusterLogging[_]; isAuditLogs(logSetup)]\n count(goodTypes) == 0\n\t\n\tmsga := {\n\t\t\"alertMessage\": \"audit logs is disabled\",\n\t\t\"alertScore\": 3,\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"failedPaths\": [],\n\t\t\"fixCommand\":\"aws eks update-cluster-config --region \u003cregion_code\u003e --name \u003ccluster_name\u003e --logging '{'clusterLogging':[{'types':['\u003capi/audit/authenticator\u003e'],'enabled':true}]}'\",\n\t\t\"fixPaths\": [],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [],\n\t\t\t\"externalObjects\": cluster_config\n\t\t}\n\t}\n}\n\n\nis_logging_disabled(cluster_config) {\n\tnot cluster_config.logging_config.component_config.enable_components\n}\nis_logging_disabled(cluster_config) {\n\tcluster_config.logging_config.component_config.enable_components\n\tcount(cluster_config.logging_config.component_config.enable_components) == 0\n}\n\nisAuditLogs(logSetup) {\n logSetup.Enabled == true\n cautils.list_contains(logSetup.Types, \"api\")\n}\n\nisAuditLogs(logSetup) {\n logSetup.Enabled == true\n cautils.list_contains(logSetup.Types, \"audit\")\n}\n\nisAuditLogs(logSetup) {\n logSetup.enabled == true\n cautils.list_contains(logSetup.Types, \"authenticator\")\n}",
|
||||
@@ -2406,7 +2347,6 @@
|
||||
"guid": "",
|
||||
"name": "k8s-audit-logs-enabled-native",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"resourcesAggregator": "apiserver-pod",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -2446,7 +2386,6 @@
|
||||
"guid": "",
|
||||
"name": "Secret/ETCD encryption enabled",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "node",
|
||||
@@ -2470,7 +2409,6 @@
|
||||
"guid": "",
|
||||
"name": "secret-etcd-encryption-cloud",
|
||||
"attributes": {
|
||||
"armoBuiltin": true
|
||||
},
|
||||
"creationTime": "",
|
||||
"rule": "package armo_builtins\n\n\n# Check if encryption in etcd in enabled for EKS\ndeny[msga] {\n\tcluster_config := input[_]\n\tcluster_config.apiVersion == \"eks.amazonaws.com/v1\"\n\tcluster_config.kind == \"ClusterDescribe\"\n cluster_config.metadata.provider == \"eks\"\t\n\tconfig = cluster_config.data\n\n\tis_not_encrypted_EKS(config)\n \n\t\n\tmsga := {\n\t\t\"alertMessage\": \"etcd/secret encryption is not enabled\",\n\t\t\"alertScore\": 3,\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"failedPaths\": [],\n\t\t\"fixPaths\": [],\n\t\t\"fixCommand\": \"eksctl utils enable-secrets-encryption --cluster=\u003ccluster\u003e --key-arn=arn:aws:kms:\u003ccluster_region\u003e:\u003caccount\u003e:key/\u003ckey\u003e --region=\u003cregion\u003e\",\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [],\n \"externalObjects\": cluster_config\n\t\t}\n\t}\n}\n\n\n\n# Check if encryption in etcd in enabled for GKE\ndeny[msga] {\n\tcluster_config := input[_]\n\tcluster_config.apiVersion == \"container.googleapis.com/v1\"\n\tcluster_config.kind == \"ClusterDescribe\"\n cluster_config.metadata.provider == \"gke\"\t\n\tconfig := cluster_config.data\n\n\tnot is_encrypted_GKE(config)\n \n\t\n\tmsga := {\n\t\t\"alertMessage\": \"etcd/secret encryption is not enabled\",\n\t\t\"alertScore\": 3,\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"failedPaths\": [\"data.database_encryption.state\"],\n\t\t\"fixPaths\": [],\n\t\t\"fixCommand\": \"gcloud container clusters update \u003ccluster_name\u003e --region=\u003ccompute_region\u003e --database-encryption-key=\u003ckey_project_id\u003e/locations/\u003clocation\u003e/keyRings/\u003cring_name\u003e/cryptoKeys/\u003ckey_name\u003e --project=\u003ccluster_project_id\u003e\",\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [],\n \"externalObjects\": cluster_config\n\t\t}\n\t}\n}\n\nis_encrypted_GKE(config) {\n\t config.database_encryption.state == \"1\"\n}\nis_encrypted_GKE(config) {\n\t config.database_encryption.state == \"ENCRYPTED\"\n}\n\nis_not_encrypted_EKS(cluster_config) {\n\tencryptionConfig := cluster_config.Cluster.EncryptionConfig[_]\n goodResources := [resource | resource = cluster_config.Cluster.EncryptionConfig.Resources[_]; resource == \"secrets\"]\n\tcount(goodResources) == 0\n}\n\nis_not_encrypted_EKS(cluster_config) {\n\tcluster_config.Cluster.EncryptionConfig == null\n}\n\nis_not_encrypted_EKS(cluster_config) {\n\tcount(cluster_config.Cluster.EncryptionConfig) == 0\n}\n\nis_not_encrypted_EKS(cluster_config) {\n\tencryptionConfig := cluster_config.Cluster.EncryptionConfig[_]\n count(encryptionConfig.Resources) == 0\n}",
|
||||
@@ -2512,7 +2450,6 @@
|
||||
"guid": "",
|
||||
"name": "etcd-encryption-native",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"resourcesAggregator": "apiserver-pod",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -2552,7 +2489,6 @@
|
||||
"guid": "",
|
||||
"name": "PSP enabled",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "kubeapi",
|
||||
@@ -2576,7 +2512,6 @@
|
||||
"guid": "",
|
||||
"name": "psp-enabled-cloud",
|
||||
"attributes": {
|
||||
"armoBuiltin": true
|
||||
},
|
||||
"creationTime": "",
|
||||
"rule": "package armo_builtins\n\n\n# Check if PSP is enabled for GKE\ndeny[msga] {\n\tcluster_config := input[_]\n\tcluster_config.apiVersion == \"container.googleapis.com/v1\"\n\tcluster_config.kind == \"ClusterDescribe\"\n cluster_config.metadata.provider == \"gke\"\t\n\tconfig := cluster_config.data\n not config.pod_security_policy_config.enabled == true\n\n\t\n\tmsga := {\n\t\t\"alertMessage\": \"pod security policy configuration is not enabled\",\n\t\t\"alertScore\": 3,\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"failedPaths\": [],\n\t\t\"fixPaths\": [],\n\t\t\"fixCommand\": \"gcloud beta container clusters update \u003ccluster_name\u003e --enable-pod-security-policy\",\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [],\n \"externalObjects\": cluster_config\n\t\t}\n\t}\n}",
|
||||
@@ -2618,7 +2553,6 @@
|
||||
"guid": "",
|
||||
"name": "psp-enabled-native",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"resourcesAggregator": "apiserver-pod",
|
||||
"useFromKubescapeVersion": "v1.0.133"
|
||||
},
|
||||
@@ -2658,7 +2592,6 @@
|
||||
"guid": "",
|
||||
"name": "Disable anonymous access to Kubelet service",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "kubeapi",
|
||||
@@ -2682,7 +2615,6 @@
|
||||
"guid": "",
|
||||
"name": "anonymous-requests-to-kubelet-service-updated",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"hostSensorRule": "true"
|
||||
},
|
||||
"creationTime": "",
|
||||
@@ -2727,7 +2659,6 @@
|
||||
"guid": "",
|
||||
"name": "Enforce Kubelet client TLS authentication",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"attackTracks": [
|
||||
{
|
||||
"attackTrack": "node",
|
||||
@@ -2751,7 +2682,6 @@
|
||||
"guid": "",
|
||||
"name": "enforce-kubelet-client-tls-authentication",
|
||||
"attributes": {
|
||||
"armoBuiltin": true,
|
||||
"hostSensorRule": "true"
|
||||
},
|
||||
"creationTime": "",
|
||||
@@ -2830,4 +2760,4 @@
|
||||
"C-0069",
|
||||
"C-0070"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
59
core/cautils/getter/testdata/NSA.json
vendored
59
core/cautils/getter/testdata/NSA.json
vendored
File diff suppressed because one or more lines are too long
43
core/cautils/getter/utils_test.go
Normal file
43
core/cautils/getter/utils_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// should return true if the string is present in the slice
|
||||
func TestContains(t *testing.T) {
|
||||
tests := []struct {
|
||||
str []string
|
||||
key string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
str: []string{"apple", "banana", "orange"},
|
||||
key: "banana",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
str: []string{"apple", "banana", "orange"},
|
||||
key: "mango",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
str: []string{"", "banana", "banana"},
|
||||
key: "banana",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
str: []string{"", "", ""},
|
||||
key: "grape",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.key, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, contains(tt.str, tt.key))
|
||||
})
|
||||
}
|
||||
}
|
||||
89
core/cautils/jsonutils_test.go
Normal file
89
core/cautils/jsonutils_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPrettyJson(t *testing.T) {
|
||||
type args struct {
|
||||
data interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "SimpleJson",
|
||||
args: args{
|
||||
data: map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
want: []byte(`{
|
||||
"key": "value"
|
||||
}
|
||||
`),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "NestedJson",
|
||||
args: args{
|
||||
data: map[string]interface{}{
|
||||
"key": map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []byte(`{
|
||||
"key": {
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
`),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ComplexJson",
|
||||
args: args{
|
||||
data: map[string]interface{}{
|
||||
"A": "B",
|
||||
"C": map[string]interface{}{
|
||||
"D": "E",
|
||||
"F": map[string]interface{}{
|
||||
"G": "H",
|
||||
"I": "J",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []byte(`{
|
||||
"A": "B",
|
||||
"C": {
|
||||
"D": "E",
|
||||
"F": {
|
||||
"G": "H",
|
||||
"I": "J"
|
||||
}
|
||||
}
|
||||
}
|
||||
`),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := PrettyJson(tt.args.data)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("PrettyJson() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("PrettyJson() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
66
core/cautils/krewutils_test.go
Normal file
66
core/cautils/krewutils_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsKrewPlugin(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "krew plugin",
|
||||
arg: "kubectl-kubescape",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "not krew plugin",
|
||||
arg: "kubescape",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
arg: "",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
os.Args = []string{tt.arg}
|
||||
result := IsKrewPlugin()
|
||||
if result != tt.want {
|
||||
t.Errorf("IsKrewPlugin() = %v, want %v", result, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "krew plugin",
|
||||
arg: "kubectl-kubescape",
|
||||
want: "kubectl kubescape",
|
||||
},
|
||||
{
|
||||
name: "not krew plugin",
|
||||
arg: "kubescape",
|
||||
want: "kubescape",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
os.Args = []string{tt.arg}
|
||||
if got := ExecName(); got != tt.want {
|
||||
t.Errorf("ExecName() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -20,18 +20,14 @@ type KustomizeDirectory struct {
|
||||
// Used for checking if there is "Kustomization" file in the given Directory
|
||||
var kustomizationFileMatchers = [3]string{"kustomization.yml", "kustomization.yaml", "Kustomization"}
|
||||
|
||||
func IsKustomizeDirectory(path string) bool {
|
||||
if isDir := IsDir(path); !isDir {
|
||||
func isKustomizeDirectory(path string) bool {
|
||||
if ok := isDir(path); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if lastChar := path[len(path)-1:]; lastChar != "/" {
|
||||
path += "/"
|
||||
}
|
||||
|
||||
matches := 0
|
||||
for _, kustomizationFileMatcher := range kustomizationFileMatchers {
|
||||
checkPath := path + kustomizationFileMatcher
|
||||
checkPath := filepath.Join(path, kustomizationFileMatcher)
|
||||
if _, err := os.Stat(checkPath); err == nil {
|
||||
matches++
|
||||
}
|
||||
@@ -43,7 +39,7 @@ func IsKustomizeDirectory(path string) bool {
|
||||
case 1:
|
||||
return true
|
||||
default:
|
||||
logger.L().Info("Multiple kustomize files found while checking Kustomize Directory")
|
||||
logger.L().Info("Multiple kustomize files found while checking the Kustomize Directory")
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -67,11 +63,12 @@ func NewKustomizeDirectory(path string) *KustomizeDirectory {
|
||||
}
|
||||
}
|
||||
|
||||
func GetKustomizeDirectoryName(path string) string {
|
||||
if isKustomizeDirectory := IsKustomizeDirectory(path); !isKustomizeDirectory {
|
||||
func getKustomizeDirectoryName(path string) string {
|
||||
if ok := isKustomizeDirectory(path); !ok {
|
||||
return ""
|
||||
}
|
||||
return filepath.Dir(path)
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// Get Workloads, creates the yaml files(K8s resources) using Kustomize and
|
||||
|
||||
63
core/cautils/kustomizedirectory_test.go
Normal file
63
core/cautils/kustomizedirectory_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetKustomizeDirectoryName(t *testing.T) {
|
||||
type args struct {
|
||||
path string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
createKustomization bool
|
||||
}{
|
||||
{
|
||||
name: "kustomize directory",
|
||||
args: args{
|
||||
path: os.TempDir(),
|
||||
},
|
||||
createKustomization: true,
|
||||
want: os.TempDir(),
|
||||
},
|
||||
{
|
||||
name: "not kustomize directory",
|
||||
args: args{
|
||||
path: os.TempDir(),
|
||||
},
|
||||
createKustomization: false,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "inexistent directory",
|
||||
args: args{
|
||||
path: filepath.Join(os.TempDir(), "bla"),
|
||||
},
|
||||
createKustomization: false,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
args: args{
|
||||
path: "",
|
||||
},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tempFile := filepath.Join(tt.args.path, "kustomization.yaml")
|
||||
if tt.createKustomization {
|
||||
_ = os.WriteFile(tempFile, []byte(""), 0644)
|
||||
}
|
||||
if got := getKustomizeDirectoryName(tt.args.path); got != tt.want {
|
||||
t.Errorf("GetKustomizeDirectoryName() = %v, want %v", got, tt.want)
|
||||
}
|
||||
os.Remove(tempFile)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func Test_GetRequestPayload(t *testing.T) {
|
||||
Commands: []apis.Command{
|
||||
{
|
||||
CommandName: apis.TypeScanImages,
|
||||
WildWlid: "wlid://cluster-any",
|
||||
WildWlid: "wlid://cluster-any/namespace-",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -19,20 +19,21 @@ const (
|
||||
)
|
||||
|
||||
type VulnerabilitiesScanInfo struct {
|
||||
IncludeNamespaces []string
|
||||
ClusterName string
|
||||
IncludeNamespaces []string
|
||||
}
|
||||
|
||||
type ConfigScanInfo struct {
|
||||
ExcludedNamespaces []string
|
||||
IncludedNamespaces []string
|
||||
Frameworks []string
|
||||
HostScanner bool
|
||||
Frameworks []string // Load frameworks for config scan
|
||||
}
|
||||
|
||||
type OperatorInfo struct {
|
||||
Subcommands []OperatorSubCommand
|
||||
Namespace string
|
||||
OperatorScanInfo
|
||||
Subcommands []OperatorSubCommand
|
||||
}
|
||||
|
||||
type OperatorConnector interface {
|
||||
|
||||
@@ -326,7 +326,7 @@ func GetScanningContext(input string) ScanningContext {
|
||||
}
|
||||
|
||||
// single file
|
||||
if IsFile(input) {
|
||||
if isFile(input) {
|
||||
return ContextFile
|
||||
}
|
||||
|
||||
|
||||
@@ -5,36 +5,8 @@ import (
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ConvertLabelsToString(labels map[string]string) string {
|
||||
labelsStr := ""
|
||||
delimiter := ""
|
||||
for k, v := range labels {
|
||||
labelsStr += fmt.Sprintf("%s%s=%s", delimiter, k, v)
|
||||
delimiter = ";"
|
||||
}
|
||||
return labelsStr
|
||||
}
|
||||
|
||||
// ConvertStringToLabels convert a string "a=b;c=d" to map: {"a":"b", "c":"d"}
|
||||
func ConvertStringToLabels(labelsStr string) map[string]string {
|
||||
labels := make(map[string]string)
|
||||
labelsSlice := strings.Split(labelsStr, ";")
|
||||
if len(labelsSlice)%2 != 0 {
|
||||
return labels
|
||||
}
|
||||
for i := range labelsSlice {
|
||||
kvSlice := strings.Split(labelsSlice[i], "=")
|
||||
if len(kvSlice) != 2 {
|
||||
continue
|
||||
}
|
||||
labels[kvSlice[0]] = kvSlice[1]
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
func StringSlicesAreEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
|
||||
@@ -1,41 +1,11 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestConvertLabelsToString(t *testing.T) {
|
||||
str := "a=b;c=d"
|
||||
strMap := map[string]string{"a": "b", "c": "d"}
|
||||
rsrt := ConvertLabelsToString(strMap)
|
||||
spilltedA := strings.Split(rsrt, ";")
|
||||
spilltedB := strings.Split(str, ";")
|
||||
for i := range spilltedA {
|
||||
exists := false
|
||||
for j := range spilltedB {
|
||||
if spilltedB[j] == spilltedA[i] {
|
||||
exists = true
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
t.Errorf("%s != %s", spilltedA[i], spilltedB[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertStringToLabels(t *testing.T) {
|
||||
str := "a=b;c=d"
|
||||
strMap := map[string]string{"a": "b", "c": "d"}
|
||||
rstrMap := ConvertStringToLabels(str)
|
||||
if fmt.Sprintf("%v", rstrMap) != fmt.Sprintf("%v", strMap) {
|
||||
t.Errorf("%s != %s", fmt.Sprintf("%v", rstrMap), fmt.Sprintf("%v", strMap))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseIntEnvVar(t *testing.T) {
|
||||
testCases := []struct {
|
||||
expectedErr string
|
||||
|
||||
@@ -6,11 +6,14 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/utils-go/boolutils"
|
||||
utils "github.com/kubescape/backend/pkg/utils"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
"github.com/mattn/go-isatty"
|
||||
"go.opentelemetry.io/otel"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
@@ -31,7 +34,7 @@ type IVersionCheckHandler interface {
|
||||
|
||||
func NewIVersionCheckHandler(ctx context.Context) IVersionCheckHandler {
|
||||
if BuildNumber == "" {
|
||||
logger.L().Ctx(ctx).Warning("Unknown build number, this might affect your scan results. Please make sure that you are running the latest version")
|
||||
logger.L().Ctx(ctx).Warning("Unknown build number: this might affect your scan results. Please ensure that you are running the latest version.")
|
||||
}
|
||||
|
||||
if v, ok := os.LookupEnv(CLIENT_ENV); ok && v != "" {
|
||||
@@ -64,6 +67,7 @@ type VersionCheckRequest struct {
|
||||
FrameworkVersion string `json:"frameworkVersion"` // framework version
|
||||
ScanningTarget string `json:"target"` // Deprecated
|
||||
ScanningContext string `json:"context"` // scanning context- cluster/file/gitURL/localGit/dir
|
||||
TriggeredBy string `json:"triggeredBy"` // triggered by - cli/ ci / microservice
|
||||
}
|
||||
|
||||
type VersionCheckResponse struct {
|
||||
@@ -79,16 +83,37 @@ func NewVersionCheckHandler() *VersionCheckHandler {
|
||||
versionURL: "https://us-central1-elated-pottery-310110.cloudfunctions.net/ksgf1v1",
|
||||
}
|
||||
}
|
||||
|
||||
func getTriggerSource() string {
|
||||
if strings.Contains(os.Args[0], "ksserver") {
|
||||
return "microservice"
|
||||
}
|
||||
|
||||
if !isatty.IsTerminal(os.Stdin.Fd()) && !isatty.IsCygwinTerminal(os.Stdin.Fd()) {
|
||||
// non-interactive shell
|
||||
return "pipeline"
|
||||
}
|
||||
|
||||
if os.Getenv("GITHUB_ACTIONS") == "true" {
|
||||
return "pipeline"
|
||||
}
|
||||
|
||||
return "cli"
|
||||
}
|
||||
|
||||
func NewVersionCheckRequest(buildNumber, frameworkName, frameworkVersion, scanningTarget string) *VersionCheckRequest {
|
||||
if buildNumber == "" {
|
||||
buildNumber = UnknownBuildNumber
|
||||
}
|
||||
|
||||
if scanningTarget == "" {
|
||||
scanningTarget = "unknown"
|
||||
}
|
||||
|
||||
if Client == "" {
|
||||
Client = "local-build"
|
||||
}
|
||||
|
||||
return &VersionCheckRequest{
|
||||
Client: "kubescape",
|
||||
ClientBuild: Client,
|
||||
@@ -96,6 +121,7 @@ func NewVersionCheckRequest(buildNumber, frameworkName, frameworkVersion, scanni
|
||||
Framework: frameworkName,
|
||||
FrameworkVersion: frameworkVersion,
|
||||
ScanningTarget: scanningTarget,
|
||||
TriggeredBy: getTriggerSource(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +144,7 @@ func (v *VersionCheckHandler) CheckLatestVersion(ctx context.Context, versionDat
|
||||
return fmt.Errorf("failed to get latest version")
|
||||
}
|
||||
|
||||
LatestReleaseVersion := latestVersion.ClientUpdate
|
||||
LatestReleaseVersion = latestVersion.ClientUpdate
|
||||
|
||||
if latestVersion.ClientUpdate != "" {
|
||||
if BuildNumber != "" && semver.Compare(BuildNumber, LatestReleaseVersion) == -1 {
|
||||
@@ -145,15 +171,13 @@ func (v *VersionCheckHandler) getLatestVersion(versionData *VersionCheckRequest)
|
||||
return nil, fmt.Errorf("in 'CheckLatestVersion' failed to json.Marshal, reason: %s", err.Error())
|
||||
}
|
||||
|
||||
resp, err := getter.HttpPost(http.DefaultClient, v.versionURL, map[string]string{"Content-Type": "application/json"}, reqBody)
|
||||
rdr, _, err := getter.HTTPPost(http.DefaultClient, v.versionURL, reqBody, map[string]string{"Content-Type": "application/json"})
|
||||
|
||||
vResp, err := utils.Decode[*VersionCheckResponse](rdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vResp := &VersionCheckResponse{}
|
||||
if err = getter.JSONDecoder(resp).Decode(vResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vResp, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
@@ -20,11 +24,20 @@ var rule_v1_0_133 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
|
||||
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.133", "useUntilKubescapeVersion": "v1.0.134"}}}
|
||||
var rule_v1_0_134 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
|
||||
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.134"}}}
|
||||
var rule_invalid_from = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
|
||||
Attributes: map[string]interface{}{"useFromKubescapeVersion": 1.0135, "useUntilKubescapeVersion": "v1.0.135"}}}
|
||||
var rule_invalid_until = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
|
||||
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.135", "useUntilKubescapeVersion": 1.0135}}}
|
||||
|
||||
func TestIsRuleKubescapeVersionCompatible(t *testing.T) {
|
||||
// local build- no build number
|
||||
|
||||
// should not crash when the value of useUntilKubescapeVersion is not a string
|
||||
buildNumberMock := "v1.0.135"
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_invalid_from.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_invalid_until.Attributes, buildNumberMock))
|
||||
// should use only rules that don't have "until"
|
||||
buildNumberMock := ""
|
||||
buildNumberMock = ""
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_131.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_132.Attributes, buildNumberMock))
|
||||
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_133.Attributes, buildNumberMock))
|
||||
@@ -59,10 +72,122 @@ func TestIsRuleKubescapeVersionCompatible(t *testing.T) {
|
||||
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
|
||||
}
|
||||
|
||||
func TestCheckLatestVersion(t *testing.T) {
|
||||
func TestCheckLatestVersion_Semver_Compare(t *testing.T) {
|
||||
assert.Equal(t, -1, semver.Compare("v2.0.150", "v2.0.151"))
|
||||
assert.Equal(t, 0, semver.Compare("v2.0.150", "v2.0.150"))
|
||||
assert.Equal(t, 1, semver.Compare("v2.0.150", "v2.0.149"))
|
||||
assert.Equal(t, -1, semver.Compare("v2.0.150", "v3.0.150"))
|
||||
|
||||
}
|
||||
|
||||
func TestCheckLatestVersion(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
versionData *VersionCheckRequest
|
||||
versionURL string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Get latest version",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
versionData: &VersionCheckRequest{},
|
||||
versionURL: "https://us-central1-elated-pottery-310110.cloudfunctions.net/ksgf1v1",
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "Failed to get latest version",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
versionData: &VersionCheckRequest{},
|
||||
versionURL: "https://example.com",
|
||||
},
|
||||
err: fmt.Errorf("failed to get latest version"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := &VersionCheckHandler{
|
||||
versionURL: tt.args.versionURL,
|
||||
}
|
||||
err := v.CheckLatestVersion(tt.args.ctx, tt.args.versionData)
|
||||
|
||||
assert.Equal(t, tt.err, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionCheckHandler_getLatestVersion(t *testing.T) {
|
||||
type fields struct {
|
||||
versionURL string
|
||||
}
|
||||
type args struct {
|
||||
versionData *VersionCheckRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want *VersionCheckResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Get latest version",
|
||||
fields: fields{
|
||||
versionURL: "https://us-central1-elated-pottery-310110.cloudfunctions.net/ksgf1v1",
|
||||
},
|
||||
args: args{
|
||||
versionData: &VersionCheckRequest{
|
||||
Client: "kubescape",
|
||||
},
|
||||
},
|
||||
want: &VersionCheckResponse{
|
||||
Client: "kubescape",
|
||||
ClientUpdate: "v3.0.0",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Failed to get latest version",
|
||||
fields: fields{
|
||||
versionURL: "https://example.com",
|
||||
},
|
||||
args: args{
|
||||
versionData: &VersionCheckRequest{},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := &VersionCheckHandler{
|
||||
versionURL: tt.fields.versionURL,
|
||||
}
|
||||
got, err := v.getLatestVersion(tt.args.versionData)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("VersionCheckHandler.getLatestVersion() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("VersionCheckHandler.getLatestVersion() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTriggerSource(t *testing.T) {
|
||||
// Running in github actions pipeline
|
||||
os.Setenv("GITHUB_ACTIONS", "true")
|
||||
source := getTriggerSource()
|
||||
assert.Equal(t, "pipeline", source)
|
||||
|
||||
os.Args[0] = "ksserver"
|
||||
source = getTriggerSource()
|
||||
assert.Equal(t, "microservice", source)
|
||||
}
|
||||
|
||||
234
core/cautils/workloadmappingutils_test.go
Normal file
234
core/cautils/workloadmappingutils_test.go
Normal file
@@ -0,0 +1,234 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMapExternalResource(t *testing.T) {
|
||||
type args struct {
|
||||
externalResourceMap ExternalResources
|
||||
resources []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "One resource",
|
||||
args: args{
|
||||
externalResourceMap: ExternalResources{
|
||||
"ImageVulnerabilities": {"ImageVulnerabilities"},
|
||||
},
|
||||
resources: []string{"ImageVulnerabilities"},
|
||||
},
|
||||
want: []string{"ImageVulnerabilities"},
|
||||
},
|
||||
{
|
||||
name: "Two resources",
|
||||
args: args{
|
||||
externalResourceMap: ExternalResources{
|
||||
"ImageVulnerabilities": {"ImageVulnerabilities"},
|
||||
"KubeletConfiguration": {"KubeletConfiguration"},
|
||||
},
|
||||
resources: []string{"ImageVulnerabilities", "KubeletConfiguration"},
|
||||
},
|
||||
want: []string{"ImageVulnerabilities", "KubeletConfiguration"},
|
||||
},
|
||||
{
|
||||
name: "No resources",
|
||||
args: args{
|
||||
externalResourceMap: make(ExternalResources),
|
||||
resources: []string{},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := MapExternalResource(tt.args.externalResourceMap, tt.args.resources)
|
||||
sort.Strings(got)
|
||||
sort.Strings(tt.want)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("MapExternalResource() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapHostResources(t *testing.T) {
|
||||
type args struct {
|
||||
externalResourceMap ExternalResources
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "Host Sensor Resource",
|
||||
args: args{
|
||||
externalResourceMap: ExternalResources{
|
||||
"KernelVersion": {"KubeletConfiguration"},
|
||||
},
|
||||
},
|
||||
want: []string{"KernelVersion"},
|
||||
},
|
||||
{
|
||||
name: "Not Host Sensor Resource",
|
||||
args: args{
|
||||
externalResourceMap: ExternalResources{
|
||||
"ImageVulnerabilities": {"ImageVulnerabilities"},
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "Mixed resources",
|
||||
args: args{
|
||||
externalResourceMap: ExternalResources{
|
||||
"ImageVulnerabilities": {"ImageVulnerabilities"},
|
||||
"KernelVersion": {"KubeletConfiguration"},
|
||||
},
|
||||
},
|
||||
want: []string{"KernelVersion"},
|
||||
},
|
||||
{
|
||||
name: "No resources",
|
||||
args: args{
|
||||
externalResourceMap: make(ExternalResources),
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := MapHostResources(tt.args.externalResourceMap)
|
||||
sort.Strings(got)
|
||||
sort.Strings(tt.want)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("MapHostResources() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapImageVulnResources(t *testing.T) {
|
||||
type args struct {
|
||||
externalResourceMap ExternalResources
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "Image Vulnerability Resource",
|
||||
args: args{
|
||||
externalResourceMap: ExternalResources{
|
||||
"ImageVulnerabilities": {"ImageVulnerabilities"},
|
||||
},
|
||||
},
|
||||
want: []string{"ImageVulnerabilities"},
|
||||
},
|
||||
{
|
||||
name: "Not Image Vulnerability Resource",
|
||||
args: args{
|
||||
externalResourceMap: ExternalResources{
|
||||
"KernelVersion": {"KubeletConfiguration"},
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "Mixed resources",
|
||||
args: args{
|
||||
externalResourceMap: ExternalResources{
|
||||
"ImageVulnerabilities": {"ImageVulnerabilities"},
|
||||
"KernelVersion": {"KubeletConfiguration"},
|
||||
},
|
||||
},
|
||||
want: []string{"ImageVulnerabilities"},
|
||||
},
|
||||
{
|
||||
name: "No resources",
|
||||
args: args{
|
||||
externalResourceMap: make(ExternalResources),
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := MapImageVulnResources(tt.args.externalResourceMap)
|
||||
sort.Strings(got)
|
||||
sort.Strings(tt.want)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("MapImageVulnResources() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapCloudResources(t *testing.T) {
|
||||
type args struct {
|
||||
externalResourceMap ExternalResources
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "Cloud Resource",
|
||||
args: args{
|
||||
externalResourceMap: ExternalResources{
|
||||
"ClusterDescribe": {"CloudProviderInfo"},
|
||||
},
|
||||
},
|
||||
want: []string{"ClusterDescribe"},
|
||||
},
|
||||
{
|
||||
name: "Not Cloud Resource",
|
||||
args: args{
|
||||
externalResourceMap: ExternalResources{
|
||||
"KernelVersion": {"KubeletConfiguration"},
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "Mixed resources",
|
||||
args: args{
|
||||
externalResourceMap: ExternalResources{
|
||||
"ClusterDescribe": {"CloudProviderInfo"},
|
||||
"KernelVersion": {"KubeletConfiguration"},
|
||||
},
|
||||
},
|
||||
want: []string{"ClusterDescribe"},
|
||||
},
|
||||
{
|
||||
name: "No resources",
|
||||
args: args{
|
||||
externalResourceMap: make(ExternalResources),
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := MapCloudResources(tt.args.externalResourceMap)
|
||||
sort.Strings(got)
|
||||
sort.Strings(tt.want)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("MapCloudResources() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -27,28 +27,28 @@ type OperatorAdapter struct {
|
||||
cautils.OperatorConnector
|
||||
}
|
||||
|
||||
func getOperatorPod(k8sClient *k8sinterface.KubernetesApi) (*v1.Pod, error) {
|
||||
func getOperatorPod(k8sClient *k8sinterface.KubernetesApi, ns string) (*v1.Pod, error) {
|
||||
listOptions := metav1.ListOptions{
|
||||
LabelSelector: "app=operator",
|
||||
}
|
||||
pods, err := k8sClient.KubernetesClient.CoreV1().Pods(kubescapeNamespace).List(k8sClient.Context, listOptions)
|
||||
pods, err := k8sClient.KubernetesClient.CoreV1().Pods(ns).List(k8sClient.Context, listOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pods.Items) != 1 {
|
||||
return nil, errors.New("Could not find the Kubescape-Operator chart, please validate that the Kubescape-Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts")
|
||||
return nil, errors.New("Could not find the Kubescape Operator chart, please validate that the Kubescape Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts")
|
||||
}
|
||||
|
||||
return &pods.Items[0], nil
|
||||
}
|
||||
|
||||
func NewOperatorAdapter(scanInfo cautils.OperatorScanInfo) (*OperatorAdapter, error) {
|
||||
func NewOperatorAdapter(scanInfo cautils.OperatorScanInfo, ns string) (*OperatorAdapter, error) {
|
||||
k8sClient := getKubernetesApi()
|
||||
pod, err := getOperatorPod(k8sClient)
|
||||
pod, err := getOperatorPod(k8sClient, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
operatorConnector, err := cautils.CreatePortForwarder(k8sClient, pod, operatorServicePort, kubescapeNamespace)
|
||||
operatorConnector, err := cautils.CreatePortForwarder(k8sClient, pod, operatorServicePort, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -23,13 +23,13 @@ func Test_getOperatorPod(t *testing.T) {
|
||||
name: "test error no operator exist",
|
||||
createOperatorPod: false,
|
||||
createAnotherOperatorPodWithSameLabel: false,
|
||||
expectedError: fmt.Errorf("Could not find the Kubescape-Operator chart, please validate that the Kubescape-Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"),
|
||||
expectedError: fmt.Errorf("Could not find the Kubescape Operator chart, please validate that the Kubescape Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"),
|
||||
},
|
||||
{
|
||||
name: "test error several operators exist",
|
||||
createOperatorPod: true,
|
||||
createAnotherOperatorPodWithSameLabel: true,
|
||||
expectedError: fmt.Errorf("Could not find the Kubescape-Operator chart, please validate that the Kubescape-Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"),
|
||||
expectedError: fmt.Errorf("Could not find the Kubescape Operator chart, please validate that the Kubescape Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"),
|
||||
},
|
||||
{
|
||||
name: "test no error",
|
||||
@@ -73,7 +73,7 @@ func Test_getOperatorPod(t *testing.T) {
|
||||
assert.Equal(t, nil, err)
|
||||
}
|
||||
|
||||
pod, err := getOperatorPod(&k8sClient)
|
||||
pod, err := getOperatorPod(&k8sClient, kubescapeNamespace)
|
||||
assert.Equal(t, err, tc.expectedError)
|
||||
if tc.expectedError == nil {
|
||||
assert.Equal(t, pod, createdOperatorPod)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
@@ -34,14 +35,17 @@ var downloadFunc = map[string]func(context.Context, *metav1.DownloadInfo) error{
|
||||
|
||||
func DownloadSupportCommands() []string {
|
||||
commands := []string{}
|
||||
for k := range downloadFunc {
|
||||
commands = append(commands, k)
|
||||
for key := range downloadFunc {
|
||||
commands = append(commands, key)
|
||||
}
|
||||
|
||||
// Sort the keys of the map
|
||||
sort.Strings(commands)
|
||||
return commands
|
||||
}
|
||||
|
||||
func (ks *Kubescape) Download(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
|
||||
setPathandFilename(downloadInfo)
|
||||
setPathAndFilename(downloadInfo)
|
||||
if err := os.MkdirAll(downloadInfo.Path, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -61,17 +65,19 @@ func downloadArtifact(ctx context.Context, downloadInfo *metav1.DownloadInfo, do
|
||||
return fmt.Errorf("unknown command to download")
|
||||
}
|
||||
|
||||
func setPathandFilename(downloadInfo *metav1.DownloadInfo) {
|
||||
func setPathAndFilename(downloadInfo *metav1.DownloadInfo) {
|
||||
if downloadInfo.Path == "" {
|
||||
downloadInfo.Path = getter.GetDefaultPath("")
|
||||
} else {
|
||||
dir, file := filepath.Split(downloadInfo.Path)
|
||||
if dir == "" {
|
||||
downloadInfo.Path = file
|
||||
} else if strings.Contains(file, ".json") {
|
||||
downloadInfo.Path = dir
|
||||
downloadInfo.FileName = file
|
||||
}
|
||||
return
|
||||
}
|
||||
dir, file := filepath.Split(downloadInfo.Path)
|
||||
if dir == "" {
|
||||
downloadInfo.Path = file
|
||||
return
|
||||
}
|
||||
if strings.Contains(file, ".json") {
|
||||
downloadInfo.Path = filepath.Clean(dir)
|
||||
downloadInfo.FileName = file
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
338
core/core/download_test.go
Normal file
338
core/core/download_test.go
Normal file
@@ -0,0 +1,338 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Returns a list of all available download commands when 'DownloadSupportCommands' is called.
|
||||
func TestDownloadSupportCommands_ReturnsListOfAllAvailableDownloadCommands(t *testing.T) {
|
||||
result := DownloadSupportCommands()
|
||||
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, len(downloadFunc), len(result))
|
||||
}
|
||||
|
||||
// Returns a non-empty list of download commands when 'DownloadSupportCommands' is called and 'downloadFunc' is not empty.
|
||||
func TestDownloadSupportCommands_ReturnsNonEmptyListOfDownloadCommandsWhenDownloadFuncNotEmpty(t *testing.T) {
|
||||
// Arrange
|
||||
downloadFunc = map[string]func(context.Context, *metav1.DownloadInfo) error{
|
||||
"controls-inputs": downloadConfigInputs,
|
||||
"exceptions": downloadExceptions,
|
||||
"framework": downloadFramework,
|
||||
"attack-tracks": downloadAttackTracks,
|
||||
}
|
||||
|
||||
// Act
|
||||
result := DownloadSupportCommands()
|
||||
|
||||
// Assert
|
||||
assert.NotNil(t, result)
|
||||
assert.NotEmpty(t, result)
|
||||
}
|
||||
|
||||
// Returns a list of strings when 'DownloadSupportCommands' is called.
|
||||
func TestDownloadSupportCommands_ReturnsListOfStrings(t *testing.T) {
|
||||
result := DownloadSupportCommands()
|
||||
|
||||
// Assert
|
||||
assert.NotNil(t, result)
|
||||
for _, command := range result {
|
||||
assert.IsType(t, "", command)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an empty list when 'DownloadSupportCommands' is called and 'downloadFunc' is empty.
|
||||
func TestDownloadSupportCommands_ReturnsEmptyListWhenDownloadFuncEmpty(t *testing.T) {
|
||||
// Arrange
|
||||
downloadFunc = map[string]func(context.Context, *metav1.DownloadInfo) error{}
|
||||
|
||||
// Act
|
||||
result := DownloadSupportCommands()
|
||||
|
||||
// Assert
|
||||
assert.NotNil(t, result)
|
||||
assert.Empty(t, result)
|
||||
}
|
||||
|
||||
// Returns an empty list when 'DownloadSupportCommands' is called and 'downloadFunc' is nil.
|
||||
func TestDownloadSupportCommands_ReturnsEmptyListWhenDownloadFuncNil(t *testing.T) {
|
||||
// Arrange
|
||||
downloadFunc = nil
|
||||
|
||||
// Act
|
||||
result := DownloadSupportCommands()
|
||||
|
||||
// Assert
|
||||
assert.NotNil(t, result)
|
||||
assert.Empty(t, result)
|
||||
}
|
||||
|
||||
func TestDownloadArtifact(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
tests := []struct {
|
||||
downloadInfo *metav1.DownloadInfo
|
||||
downloadArtifactFunc map[string]func(context.Context, *metav1.DownloadInfo) error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
downloadInfo: &metav1.DownloadInfo{
|
||||
Target: "controls-inputs",
|
||||
Path: filepath.Join("path", "to", "download"),
|
||||
},
|
||||
downloadArtifactFunc: map[string]func(context.Context, *metav1.DownloadInfo) error{
|
||||
"controls-inputs": func(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
downloadInfo: &metav1.DownloadInfo{
|
||||
Target: "unknown",
|
||||
Path: filepath.Join("path", "to", "download"),
|
||||
},
|
||||
downloadArtifactFunc: map[string]func(context.Context, *metav1.DownloadInfo) error{},
|
||||
err: fmt.Errorf("unknown command to download"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
err := downloadArtifact(ctx, tt.downloadInfo, tt.downloadArtifactFunc)
|
||||
assert.Equal(t, tt.err, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetPathAndFilename(t *testing.T) {
|
||||
tests := []struct {
|
||||
downloadInfo *metav1.DownloadInfo
|
||||
expectedPath string
|
||||
expectedFilename string
|
||||
}{
|
||||
{
|
||||
downloadInfo: &metav1.DownloadInfo{
|
||||
Path: filepath.Join("test-path", "to", "file.txt"),
|
||||
},
|
||||
expectedPath: filepath.Join("test-path", "to", "file.txt"),
|
||||
expectedFilename: "",
|
||||
},
|
||||
{
|
||||
downloadInfo: &metav1.DownloadInfo{
|
||||
Path: filepath.Join("path", "to", "path.json"),
|
||||
},
|
||||
expectedPath: filepath.Join("path", "to"),
|
||||
expectedFilename: "path.json",
|
||||
},
|
||||
{
|
||||
downloadInfo: &metav1.DownloadInfo{
|
||||
Path: filepath.Join("path", "to"),
|
||||
},
|
||||
expectedPath: filepath.Join("path", "to"),
|
||||
expectedFilename: "",
|
||||
},
|
||||
{
|
||||
downloadInfo: &metav1.DownloadInfo{
|
||||
Path: "",
|
||||
},
|
||||
expectedPath: getter.GetDefaultPath(""),
|
||||
expectedFilename: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.expectedFilename, func(t *testing.T) {
|
||||
setPathAndFilename(tt.downloadInfo)
|
||||
assert.Equal(t, tt.expectedPath, tt.downloadInfo.Path)
|
||||
assert.Equal(t, tt.expectedFilename, tt.downloadInfo.FileName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ========================= Unstable tests =========================
|
||||
|
||||
// func TestDownloadConfigInputs(t *testing.T) {
|
||||
// ctx := context.Background()
|
||||
// tests := []struct {
|
||||
// downloadInfo *metav1.DownloadInfo
|
||||
// }{
|
||||
// {
|
||||
// downloadInfo: &metav1.DownloadInfo{
|
||||
// AccountID: "Test-Id",
|
||||
// AccessKey: "Random-value",
|
||||
// Identifier: "Unique-Id",
|
||||
// FileName: "",
|
||||
// Target: "Temp",
|
||||
// Path: filepath.Join("path", "to"),
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.downloadInfo.Path, func(t *testing.T) {
|
||||
// err := downloadConfigInputs(ctx, tt.downloadInfo)
|
||||
// assert.NotNil(t, err)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestDownloadExceptions(t *testing.T) {
|
||||
// ctx := context.Background()
|
||||
// tests := []struct {
|
||||
// downloadInfo *metav1.DownloadInfo
|
||||
// }{
|
||||
// {
|
||||
// downloadInfo: &metav1.DownloadInfo{
|
||||
// AccountID: "Test-Id",
|
||||
// AccessKey: "Random-value",
|
||||
// Identifier: "Unique-Id",
|
||||
// FileName: "",
|
||||
// Target: "Temp",
|
||||
// Path: filepath.Join("path", "to"),
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.downloadInfo.Path, func(t *testing.T) {
|
||||
// err := downloadExceptions(ctx, tt.downloadInfo)
|
||||
// assert.NotNil(t, err)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestDownloadAttackTracks(t *testing.T) {
|
||||
// ctx := context.Background()
|
||||
// tests := []struct {
|
||||
// downloadInfo *metav1.DownloadInfo
|
||||
// isErrNil bool
|
||||
// }{
|
||||
// {
|
||||
// downloadInfo: &metav1.DownloadInfo{
|
||||
// AccountID: "00000000-0000-0000-0000-000000000000",
|
||||
// AccessKey: "00000000-0000-0000-0000-000000000000",
|
||||
// Identifier: "id",
|
||||
// FileName: "",
|
||||
// Target: "temp",
|
||||
// Path: filepath.Join("path", "to"),
|
||||
// },
|
||||
// isErrNil: false,
|
||||
// },
|
||||
// {
|
||||
// downloadInfo: &metav1.DownloadInfo{
|
||||
// AccountID: "",
|
||||
// AccessKey: "",
|
||||
// Identifier: "",
|
||||
// FileName: "",
|
||||
// Target: "temp",
|
||||
// Path: filepath.Join("path", "to"),
|
||||
// },
|
||||
// isErrNil: false,
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.downloadInfo.Path, func(t *testing.T) {
|
||||
// err := downloadAttackTracks(ctx, tt.downloadInfo)
|
||||
// if tt.isErrNil {
|
||||
// assert.Nil(t, err)
|
||||
// } else {
|
||||
// assert.NotNil(t, err)
|
||||
// t.Error(err)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestDownloadFramework(t *testing.T) {
|
||||
// ctx := context.Background()
|
||||
// tests := []struct {
|
||||
// downloadInfo *metav1.DownloadInfo
|
||||
// isErrNil bool
|
||||
// }{
|
||||
// {
|
||||
// downloadInfo: &metav1.DownloadInfo{
|
||||
// AccountID: "Test-Id",
|
||||
// AccessKey: "Random-value",
|
||||
// Identifier: "Id",
|
||||
// FileName: "",
|
||||
// Target: "Temp",
|
||||
// Path: filepath.Join("path", "to"),
|
||||
// },
|
||||
// isErrNil: false,
|
||||
// },
|
||||
// {
|
||||
// downloadInfo: &metav1.DownloadInfo{
|
||||
// AccountID: "",
|
||||
// AccessKey: "",
|
||||
// Identifier: "",
|
||||
// FileName: "",
|
||||
// Target: "Temp",
|
||||
// Path: filepath.Join("path", "to"),
|
||||
// },
|
||||
// isErrNil: false,
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.downloadInfo.Path, func(t *testing.T) {
|
||||
// err := downloadFramework(ctx, tt.downloadInfo)
|
||||
// if tt.isErrNil {
|
||||
// assert.Nil(t, err)
|
||||
// } else {
|
||||
|
||||
// assert.NotNil(t, err)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestDownloadControl(t *testing.T) {
|
||||
// ctx := context.Background()
|
||||
// tests := []struct {
|
||||
// downloadInfo *metav1.DownloadInfo
|
||||
// isErrNil bool
|
||||
// }{
|
||||
// {
|
||||
// downloadInfo: &metav1.DownloadInfo{
|
||||
// AccountID: "Test-Id",
|
||||
// AccessKey: "Random-value",
|
||||
// Identifier: "Id",
|
||||
// FileName: "",
|
||||
// Target: "Temp",
|
||||
// Path: filepath.Join("path", "to"),
|
||||
// },
|
||||
// isErrNil: false,
|
||||
// },
|
||||
// {
|
||||
// downloadInfo: &metav1.DownloadInfo{
|
||||
// AccountID: "",
|
||||
// AccessKey: "",
|
||||
// Identifier: "",
|
||||
// FileName: "",
|
||||
// Target: "Temp",
|
||||
// Path: filepath.Join("path", "to"),
|
||||
// },
|
||||
// isErrNil: false,
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.downloadInfo.Path, func(t *testing.T) {
|
||||
// err := downloadControl(ctx, tt.downloadInfo)
|
||||
// if tt.isErrNil {
|
||||
// assert.Nil(t, err)
|
||||
// } else {
|
||||
|
||||
// assert.NotNil(t, err)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
51
core/core/fix_test.go
Normal file
51
core/core/fix_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUserConfirmed(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
input: "yes",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: "y",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: "no",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
input: "n",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.input), func(t *testing.T) {
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdin = r
|
||||
defer func() {
|
||||
os.Stdin = os.Stdin
|
||||
}()
|
||||
|
||||
go func() {
|
||||
fmt.Fprintln(w, tt.input)
|
||||
}()
|
||||
|
||||
got := userConfirmed()
|
||||
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func (ks *Kubescape) ScanImage(ctx context.Context, imgScanInfo *ksmetav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
|
||||
logger.L().Start(fmt.Sprintf("Scanning image: %s", imgScanInfo.Image))
|
||||
logger.L().Start(fmt.Sprintf("Scanning image %s...", imgScanInfo.Image))
|
||||
|
||||
dbCfg, _ := imagescan.NewDefaultDBConfig()
|
||||
svc := imagescan.NewScanService(dbCfg)
|
||||
|
||||
@@ -167,7 +167,7 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
|
||||
return
|
||||
}
|
||||
|
||||
if getter.GetKSCloudAPIConnector().GetCloudReportURL() == "" {
|
||||
if tenantConfig.GetCloudReportURL() == "" {
|
||||
scanInfo.Submit = false
|
||||
return
|
||||
}
|
||||
@@ -202,7 +202,10 @@ func getPolicyGetter(ctx context.Context, loadPoliciesFromFile []string, account
|
||||
if accountID != "" && getter.GetKSCloudAPIConnector().GetCloudAPIURL() != "" && frameworkScope {
|
||||
g := getter.GetKSCloudAPIConnector() // download policy from Kubescape Cloud backend
|
||||
return g
|
||||
} else if accountID != "" && getter.GetKSCloudAPIConnector().GetCloudAPIURL() == "" && frameworkScope {
|
||||
logger.L().Ctx(ctx).Warning("Kubescape Cloud API URL is not set, loading policies from cache")
|
||||
}
|
||||
|
||||
if downloadReleasedPolicy == nil {
|
||||
downloadReleasedPolicy = getter.NewDownloadReleasedPolicy()
|
||||
}
|
||||
@@ -240,7 +243,7 @@ func getDownloadReleasedPolicy(ctx context.Context, downloadReleasedPolicy *gett
|
||||
func getDefaultFrameworksPaths() []string {
|
||||
fwPaths := []string{}
|
||||
for i := range getter.NativeFrameworks {
|
||||
fwPaths = append(fwPaths, getter.GetDefaultPath(getter.NativeFrameworks[i]))
|
||||
fwPaths = append(fwPaths, getter.GetDefaultPath(getter.NativeFrameworks[i]+".json")) // GetDefaultPath expects a filename, not just the framework name
|
||||
}
|
||||
return fwPaths
|
||||
}
|
||||
@@ -249,6 +252,8 @@ func listFrameworksNames(policyGetter getter.IPolicyGetter) []string {
|
||||
fw, err := policyGetter.ListFrameworks()
|
||||
if err == nil {
|
||||
return fw
|
||||
} else {
|
||||
logger.L().Warning("failed to list frameworks", helpers.Error(err))
|
||||
}
|
||||
return getter.NativeFrameworks
|
||||
}
|
||||
|
||||
@@ -3,17 +3,185 @@ package core
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v3/core/pkg/hostsensorutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type TenantConfigMock struct {
|
||||
clusterName string
|
||||
accountID string
|
||||
accessKey string
|
||||
cloudReportURL string
|
||||
}
|
||||
|
||||
func (tcm *TenantConfigMock) UpdateCachedConfig() error {
|
||||
return nil
|
||||
}
|
||||
func (tcm *TenantConfigMock) DeleteCachedConfig(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
func (tcm *TenantConfigMock) GetContextName() string {
|
||||
return tcm.clusterName
|
||||
}
|
||||
func (tcm *TenantConfigMock) GetAccountID() string {
|
||||
return tcm.accountID
|
||||
}
|
||||
func (tcm *TenantConfigMock) IsStorageEnabled() bool {
|
||||
return true
|
||||
}
|
||||
func (tcm *TenantConfigMock) GetConfigObj() *cautils.ConfigObj {
|
||||
return &cautils.ConfigObj{
|
||||
AccountID: tcm.accountID,
|
||||
ClusterName: tcm.clusterName,
|
||||
}
|
||||
}
|
||||
func (tcm *TenantConfigMock) GetCloudReportURL() string {
|
||||
return tcm.cloudReportURL
|
||||
}
|
||||
func (tcm *TenantConfigMock) GetCloudAPIURL() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (tcm *TenantConfigMock) GenerateAccountID() (string, error) {
|
||||
//tcm.accountID = "6a1ff233-5297-4193-bb51-5d67bc841cbf"
|
||||
return tcm.accountID, nil
|
||||
}
|
||||
|
||||
func (tcm *TenantConfigMock) DeleteCredentials() error {
|
||||
tcm.accountID = ""
|
||||
tcm.accessKey = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tcm *TenantConfigMock) GetAccessKey() string {
|
||||
return tcm.accessKey
|
||||
}
|
||||
|
||||
func TestGetExceptionsGetter(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
useExceptions string
|
||||
accountID string
|
||||
downloadReleasedPolicy *getter.DownloadReleasedPolicy
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Test GetExceptionsGetter all empty",
|
||||
args: args{
|
||||
ctx: context.TODO(),
|
||||
useExceptions: "",
|
||||
accountID: "",
|
||||
downloadReleasedPolicy: nil,
|
||||
},
|
||||
want: "*getter.DownloadReleasedPolicy",
|
||||
},
|
||||
{
|
||||
name: "Test GetExceptionsGetter empty useExceptions",
|
||||
args: args{
|
||||
ctx: context.TODO(),
|
||||
useExceptions: "",
|
||||
accountID: "",
|
||||
downloadReleasedPolicy: getter.NewDownloadReleasedPolicy(),
|
||||
},
|
||||
want: "*getter.DownloadReleasedPolicy",
|
||||
},
|
||||
{
|
||||
name: "Test GetExceptionsGetter with useExceptions and empty accountID",
|
||||
args: args{
|
||||
ctx: context.TODO(),
|
||||
useExceptions: "true",
|
||||
accountID: "",
|
||||
downloadReleasedPolicy: getter.NewDownloadReleasedPolicy(),
|
||||
},
|
||||
want: "*getter.LoadPolicy",
|
||||
},
|
||||
{
|
||||
name: "Test GetExceptionsGetter with useExceptions and filled accountID",
|
||||
args: args{
|
||||
ctx: context.TODO(),
|
||||
useExceptions: "true",
|
||||
accountID: "123456789012",
|
||||
downloadReleasedPolicy: getter.NewDownloadReleasedPolicy(),
|
||||
},
|
||||
want: "*getter.LoadPolicy",
|
||||
},
|
||||
{
|
||||
name: "Test GetExceptionsGetter with accountID",
|
||||
args: args{
|
||||
ctx: context.TODO(),
|
||||
useExceptions: "",
|
||||
accountID: "123456789012",
|
||||
downloadReleasedPolicy: getter.NewDownloadReleasedPolicy(),
|
||||
},
|
||||
want: "*v1.KSCloudAPI",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := getExceptionsGetter(tt.args.ctx, tt.args.useExceptions, tt.args.accountID, tt.args.downloadReleasedPolicy)
|
||||
assert.Equal(t, tt.want, reflect.TypeOf(got).String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPolicyIdentifierIdentities(t *testing.T) {
|
||||
type args struct {
|
||||
pi []cautils.PolicyIdentifier
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Test PolicyIdentifierIdentities",
|
||||
args: args{
|
||||
pi: []cautils.PolicyIdentifier{
|
||||
{Identifier: "policy1"},
|
||||
{Identifier: "policy2"},
|
||||
{Identifier: "policy3"},
|
||||
},
|
||||
},
|
||||
want: "policy1,policy2,policy3",
|
||||
},
|
||||
{
|
||||
name: "Test PolicyIdentifierIdentities Empty",
|
||||
args: args{
|
||||
pi: []cautils.PolicyIdentifier{},
|
||||
},
|
||||
want: "all",
|
||||
},
|
||||
{
|
||||
name: "Test PolicyIdentifierIdentities nil",
|
||||
args: args{
|
||||
pi: nil,
|
||||
},
|
||||
want: "all",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := policyIdentifierIdentities(tt.args.pi)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getUIPrinter(t *testing.T) {
|
||||
scanInfo := &cautils.ScanInfo{
|
||||
FormatVersion: "v2",
|
||||
@@ -184,6 +352,154 @@ func TestGetSensorHandler(t *testing.T) {
|
||||
// TODO(fredbi): need to share the k8s client mock to test a happy path / deployment failure path
|
||||
}
|
||||
|
||||
func TestSetSubmitBehavior(t *testing.T) {
|
||||
type args struct {
|
||||
scanInfo *cautils.ScanInfo
|
||||
tenantConfig *TenantConfigMock
|
||||
isScanTypeForSubmission bool
|
||||
isLocal bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Test SetSubmitBehavior !setSubmitBehavior and keep-local",
|
||||
args: args{
|
||||
scanInfo: &cautils.ScanInfo{
|
||||
ScanType: cautils.ScanTypeControl,
|
||||
Local: true,
|
||||
},
|
||||
tenantConfig: &TenantConfigMock{
|
||||
clusterName: "test",
|
||||
accountID: "",
|
||||
accessKey: "",
|
||||
},
|
||||
isScanTypeForSubmission: false,
|
||||
isLocal: true,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Test SetSubmitBehavior !setSubmitBehavior and !keep-local",
|
||||
args: args{
|
||||
scanInfo: &cautils.ScanInfo{
|
||||
ScanType: cautils.ScanTypeControl,
|
||||
Local: false,
|
||||
},
|
||||
tenantConfig: &TenantConfigMock{
|
||||
clusterName: "test",
|
||||
accountID: "",
|
||||
accessKey: "",
|
||||
},
|
||||
isScanTypeForSubmission: false,
|
||||
isLocal: false,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Test SetSubmitBehavior setSubmitBehavior and keep-local",
|
||||
args: args{
|
||||
scanInfo: &cautils.ScanInfo{
|
||||
ScanType: cautils.ScanTypeCluster,
|
||||
Local: true,
|
||||
},
|
||||
tenantConfig: &TenantConfigMock{
|
||||
clusterName: "test",
|
||||
accountID: "",
|
||||
accessKey: "",
|
||||
},
|
||||
isScanTypeForSubmission: true,
|
||||
isLocal: true,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Test SetSubmitBehavior !keep-local and setSubmitBehavior",
|
||||
args: args{
|
||||
scanInfo: &cautils.ScanInfo{
|
||||
ScanType: cautils.ScanTypeCluster,
|
||||
Local: false,
|
||||
},
|
||||
tenantConfig: &TenantConfigMock{
|
||||
clusterName: "test",
|
||||
accountID: "",
|
||||
accessKey: "",
|
||||
},
|
||||
isScanTypeForSubmission: true,
|
||||
isLocal: false,
|
||||
},
|
||||
want: false,
|
||||
}, // TODO: Add test "If CloudReportURL is set"
|
||||
{
|
||||
name: "Test SetSubmitBehavior CloudReportURL is set, no AccountID",
|
||||
args: args{
|
||||
scanInfo: &cautils.ScanInfo{
|
||||
ScanType: cautils.ScanTypeCluster,
|
||||
Local: false,
|
||||
},
|
||||
tenantConfig: &TenantConfigMock{
|
||||
clusterName: "test",
|
||||
accountID: "",
|
||||
accessKey: "",
|
||||
cloudReportURL: "https://example.kubescape.com",
|
||||
},
|
||||
isScanTypeForSubmission: true,
|
||||
isLocal: false,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test SetSubmitBehavior CloudReportURL is set, Invalid AccountID",
|
||||
args: args{
|
||||
scanInfo: &cautils.ScanInfo{
|
||||
ScanType: cautils.ScanTypeCluster,
|
||||
Local: false,
|
||||
},
|
||||
tenantConfig: &TenantConfigMock{
|
||||
clusterName: "test",
|
||||
accountID: "123456789012",
|
||||
accessKey: "",
|
||||
cloudReportURL: "https://example.kubescape.com",
|
||||
},
|
||||
isScanTypeForSubmission: true,
|
||||
isLocal: false,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Test SetSubmitBehavior CloudReportURL is set, Valid AccountID",
|
||||
args: args{
|
||||
scanInfo: &cautils.ScanInfo{
|
||||
ScanType: cautils.ScanTypeCluster,
|
||||
Local: false,
|
||||
},
|
||||
tenantConfig: &TenantConfigMock{
|
||||
clusterName: "test",
|
||||
accountID: "6a1ff233-5297-4193-bb51-5d67bc841cbf",
|
||||
accessKey: "",
|
||||
cloudReportURL: "https://example.kubescape.com",
|
||||
},
|
||||
isScanTypeForSubmission: true,
|
||||
isLocal: false,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.args.isScanTypeForSubmission, isScanTypeForSubmission(tt.args.scanInfo.ScanType))
|
||||
require.Equal(t, tt.args.isLocal, tt.args.scanInfo.Local)
|
||||
|
||||
setSubmitBehavior(tt.args.scanInfo, tt.args.tenantConfig)
|
||||
|
||||
assert.Equal(t, tt.want, tt.args.scanInfo.Submit)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsScanTypeForSubmission(t *testing.T) {
|
||||
test := []struct {
|
||||
name string
|
||||
@@ -229,3 +545,25 @@ func TestIsScanTypeForSubmission(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDefaultFrameworksPaths(t *testing.T) {
|
||||
result := getDefaultFrameworksPaths()
|
||||
|
||||
assert.NotEmpty(t, result)
|
||||
for _, path := range result {
|
||||
assert.NotEmpty(t, path)
|
||||
assert.True(t, strings.HasSuffix(path, ".json"))
|
||||
}
|
||||
}
|
||||
|
||||
// getDownloadReleasedPolicy should always have a non-nil result
|
||||
func TestGetDownloadReleasedPolicy(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
downloadReleasedPolicy := getter.NewDownloadReleasedPolicy()
|
||||
|
||||
require.NoError(t, downloadReleasedPolicy.SetRegoObjects())
|
||||
|
||||
result := getDownloadReleasedPolicy(ctx, downloadReleasedPolicy)
|
||||
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
|
||||
23
core/core/kscore_test.go
Normal file
23
core/core/kscore_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// The function should return a non-nil pointer.
|
||||
func TestNewKubescape_ReturnsNonNilPointer(t *testing.T) {
|
||||
k := NewKubescape()
|
||||
assert.NotNil(t, k)
|
||||
}
|
||||
|
||||
// The function should not panic.
|
||||
func TestNewKubescape_DoesNotPanic(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Function panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
NewKubescape()
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer"
|
||||
v2 "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2"
|
||||
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
|
||||
"github.com/maruel/natural"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
@@ -29,9 +30,12 @@ var listFormatFunc = map[string]func(context.Context, string, []string){
|
||||
|
||||
func ListSupportActions() []string {
|
||||
commands := []string{}
|
||||
for k := range listFunc {
|
||||
commands = append(commands, k)
|
||||
for key := range listFunc {
|
||||
commands = append(commands, key)
|
||||
}
|
||||
|
||||
// Sort the keys
|
||||
sort.Strings(commands)
|
||||
return commands
|
||||
}
|
||||
func (ks *Kubescape) List(ctx context.Context, listPolicies *metav1.ListPolicies) error {
|
||||
@@ -40,7 +44,7 @@ func (ks *Kubescape) List(ctx context.Context, listPolicies *metav1.ListPolicies
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Strings(policies)
|
||||
policies = naturalSortPolicies(policies)
|
||||
|
||||
if listFormatFunction, ok := listFormatFunc[listPolicies.Format]; ok {
|
||||
listFormatFunction(ctx, listPolicies.Target, policies)
|
||||
@@ -53,6 +57,13 @@ func (ks *Kubescape) List(ctx context.Context, listPolicies *metav1.ListPolicies
|
||||
return fmt.Errorf("unknown command to download")
|
||||
}
|
||||
|
||||
func naturalSortPolicies(policies []string) []string {
|
||||
sort.Slice(policies, func(i, j int) bool {
|
||||
return natural.Less(policies[i], policies[j])
|
||||
})
|
||||
return policies
|
||||
}
|
||||
|
||||
func listFrameworks(ctx context.Context, listPolicies *metav1.ListPolicies) ([]string, error) {
|
||||
tenant := cautils.GetTenantConfig(listPolicies.AccountID, listPolicies.AccessKey, "", "", getKubernetesApi()) // change k8sinterface
|
||||
policyGetter := getPolicyGetter(ctx, nil, tenant.GetAccountID(), true, nil)
|
||||
@@ -134,13 +145,13 @@ func prettyPrintControls(ctx context.Context, policies []string) {
|
||||
|
||||
controlRows := generateControlRows(policies)
|
||||
|
||||
short := utils.CheckShortTerminalWidth(controlRows, []string{"Control ID", "Control Name", "Docs", "Frameworks"})
|
||||
short := utils.CheckShortTerminalWidth(controlRows, []string{"Control ID", "Control name", "Docs", "Frameworks"})
|
||||
if short {
|
||||
controlsTable.SetAutoWrapText(false)
|
||||
controlsTable.SetHeader([]string{"Controls"})
|
||||
controlRows = shortFormatControlRows(controlRows)
|
||||
} else {
|
||||
controlsTable.SetHeader([]string{"Control ID", "Control Name", "Docs", "Frameworks"})
|
||||
controlsTable.SetHeader([]string{"Control ID", "Control name", "Docs", "Frameworks"})
|
||||
}
|
||||
var headerColors []tablewriter.Colors
|
||||
for range controlRows[0] {
|
||||
@@ -159,8 +170,21 @@ func generateControlRows(policies []string) [][]string {
|
||||
rows := [][]string{}
|
||||
|
||||
for _, control := range policies {
|
||||
|
||||
idAndControlAndFrameworks := strings.Split(control, "|")
|
||||
id, control, framework := idAndControlAndFrameworks[0], idAndControlAndFrameworks[1], idAndControlAndFrameworks[2]
|
||||
|
||||
var id, control, framework string
|
||||
|
||||
switch len(idAndControlAndFrameworks) {
|
||||
case 0:
|
||||
continue
|
||||
case 1:
|
||||
id = idAndControlAndFrameworks[0]
|
||||
case 2:
|
||||
id, control = idAndControlAndFrameworks[0], idAndControlAndFrameworks[1]
|
||||
default:
|
||||
id, control, framework = idAndControlAndFrameworks[0], idAndControlAndFrameworks[1], idAndControlAndFrameworks[2]
|
||||
}
|
||||
|
||||
docs := cautils.GetControlLink(id)
|
||||
|
||||
|
||||
428
core/core/list_test.go
Normal file
428
core/core/list_test.go
Normal file
@@ -0,0 +1,428 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Function receives a non-empty list of policies
|
||||
func TestNonEmptyListOfPolicies(t *testing.T) {
|
||||
policies := []string{"policy1", "policy2", "policy3"}
|
||||
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
jsonListFormat(context.Background(), "", policies)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
// got := buf.String()
|
||||
want := `[
|
||||
"policy1",
|
||||
"policy2",
|
||||
"policy3"
|
||||
]
|
||||
`
|
||||
assert.Equal(t, want, string(got))
|
||||
}
|
||||
|
||||
// Function returns a valid JSON string
|
||||
func TestValidJsonString(t *testing.T) {
|
||||
policies := []string{"policy1", "policy2", "policy3"}
|
||||
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
jsonListFormat(context.Background(), "", policies)
|
||||
|
||||
w.Close()
|
||||
out, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
var result []string
|
||||
err := json.Unmarshal(out, &result)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// Function receives an empty list of policies
|
||||
func TestEmptyListOfPolicies(t *testing.T) {
|
||||
policies := []string{}
|
||||
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
jsonListFormat(context.Background(), "", policies)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
want := "[]\n"
|
||||
|
||||
assert.Equal(t, want, string(got))
|
||||
}
|
||||
|
||||
// Function receives a nil list of policies
|
||||
func TestNilListOfPolicies(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
jsonListFormat(context.Background(), "", nil)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
want := "null\n"
|
||||
|
||||
assert.Equal(t, want, string(got))
|
||||
}
|
||||
|
||||
// Returns a 2D slice with one row for each policy in the input slice.
|
||||
func TestGeneratePolicyRows_NonEmptyPolicyList(t *testing.T) {
|
||||
// Arrange
|
||||
policies := []string{"policy1", "policy2", "policy3"}
|
||||
|
||||
// Act
|
||||
result := generatePolicyRows(policies)
|
||||
|
||||
// Assert
|
||||
assert.Equal(t, [][]string{{"policy1"}, {"policy2"}, {"policy3"}}, result)
|
||||
}
|
||||
|
||||
// Returns an empty 2D slice for an empty list of policies.
|
||||
func TestGeneratePolicyRows_EmptyPolicyList(t *testing.T) {
|
||||
// Arrange
|
||||
policies := []string{}
|
||||
|
||||
// Act
|
||||
got := generatePolicyRows(policies)
|
||||
|
||||
// Assert
|
||||
assert.Empty(t, got)
|
||||
}
|
||||
|
||||
// The function returns a list of rows, each containing a formatted string with control ID, control name, docs, and frameworks.
|
||||
func TestShortFormatControlRows_ReturnsListOfRowsWithFormattedString(t *testing.T) {
|
||||
controlRows := [][]string{
|
||||
{"ID1", "Control 1", "Docs 1", "Framework 1"},
|
||||
{"ID2", "Control 2", "Docs 2", "Framework 2"},
|
||||
}
|
||||
|
||||
want := [][]string{
|
||||
{"Control ID : ID1\nControl Name : Control 1\nDocs : Docs 1\nFrameworks : Framework 1"},
|
||||
{"Control ID : ID2\nControl Name : Control 2\nDocs : Docs 2\nFrameworks : Framework 2"},
|
||||
}
|
||||
|
||||
got := shortFormatControlRows(controlRows)
|
||||
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
// The function formats the control rows correctly, replacing newlines in the frameworks column with line breaks.
|
||||
func TestShortFormatControlRows_FormatsControlRowsCorrectly(t *testing.T) {
|
||||
controlRows := [][]string{
|
||||
{"ID1", "Control 1", "Docs 1", "Framework\n1"},
|
||||
{"ID2", "Control 2", "Docs 2", "Framework\n2"},
|
||||
}
|
||||
|
||||
want := [][]string{
|
||||
{"Control ID : ID1\nControl Name : Control 1\nDocs : Docs 1\nFrameworks : Framework 1"},
|
||||
{"Control ID : ID2\nControl Name : Control 2\nDocs : Docs 2\nFrameworks : Framework 2"},
|
||||
}
|
||||
|
||||
result := shortFormatControlRows(controlRows)
|
||||
|
||||
assert.Equal(t, want, result)
|
||||
}
|
||||
|
||||
// The function handles a control row with an empty control ID.
|
||||
func TestShortFormatControlRows_HandlesControlRowWithEmptyControlID(t *testing.T) {
|
||||
controlRows := [][]string{
|
||||
{"", "Control 1", "Docs 1", "Framework 1"},
|
||||
}
|
||||
|
||||
want := [][]string{
|
||||
{"Control ID : \nControl Name : Control 1\nDocs : Docs 1\nFrameworks : Framework 1"},
|
||||
}
|
||||
|
||||
got := shortFormatControlRows(controlRows)
|
||||
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
// The function handles a control row with an empty control name.
|
||||
func TestShortFormatControlRows_HandlesControlRowWithEmptyControlName(t *testing.T) {
|
||||
controlRows := [][]string{
|
||||
{"ID1", "", "Docs 1", "Framework 1"},
|
||||
}
|
||||
|
||||
want := [][]string{
|
||||
{"Control ID : ID1\nControl Name : \nDocs : Docs 1\nFrameworks : Framework 1"},
|
||||
}
|
||||
|
||||
got := shortFormatControlRows(controlRows)
|
||||
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
// Generates rows for each policy with ID, control, documentation, and framework
|
||||
func TestGenerateControlRowsWithAllFields(t *testing.T) {
|
||||
policies := []string{
|
||||
"1|Control 1|Framework 1",
|
||||
"2|Control 2|Framework 2",
|
||||
"3|Control 3|Framework 3",
|
||||
}
|
||||
|
||||
want := [][]string{
|
||||
{"1", "Control 1", "https://hub.armosec.io/docs/1", "Framework\n1"},
|
||||
{"2", "Control 2", "https://hub.armosec.io/docs/2", "Framework\n2"},
|
||||
{"3", "Control 3", "https://hub.armosec.io/docs/3", "Framework\n3"},
|
||||
}
|
||||
|
||||
got := generateControlRows(policies)
|
||||
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
// Handles policies with no '|' characters in the string
|
||||
func TestGenerateControlRowsHandlesPoliciesWithEmptyStringOrNoPipesOrOnePipeMissing(t *testing.T) {
|
||||
policies := []string{
|
||||
"",
|
||||
"1",
|
||||
"2|Control 2|Framework 2",
|
||||
"3|Control 3|Framework 3|Extra 3",
|
||||
"4||Framework 4",
|
||||
"|",
|
||||
"5|Control 5||Extra 5",
|
||||
}
|
||||
|
||||
expectedRows := [][]string{
|
||||
{"", "", "https://hub.armosec.io/docs/", ""},
|
||||
{"1", "", "https://hub.armosec.io/docs/1", ""},
|
||||
{"2", "Control 2", "https://hub.armosec.io/docs/2", "Framework\n2"},
|
||||
{"3", "Control 3", "https://hub.armosec.io/docs/3", "Framework\n3"},
|
||||
{"4", "", "https://hub.armosec.io/docs/4", "Framework\n4"},
|
||||
{"", "", "https://hub.armosec.io/docs/", ""},
|
||||
{"5", "Control 5", "https://hub.armosec.io/docs/5", ""},
|
||||
}
|
||||
|
||||
rows := generateControlRows(policies)
|
||||
|
||||
assert.Equal(t, expectedRows, rows)
|
||||
}
|
||||
|
||||
// The function generates a table with the correct headers and rows based on the input policies.
|
||||
func TestGenerateTableWithCorrectHeadersAndRows(t *testing.T) {
|
||||
// Arrange
|
||||
ctx := context.Background()
|
||||
policies := []string{
|
||||
"1|Control 1|Framework 1",
|
||||
"2|Control 2|Framework 2",
|
||||
"3|Control 3|Framework 3",
|
||||
}
|
||||
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
prettyPrintControls(ctx, policies)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
// got := buf.String()
|
||||
want := `┌────────────┬──────────────┬───────────────────────────────┬────────────┐
|
||||
│ Control ID │ Control name │ Docs │ Frameworks │
|
||||
├────────────┼──────────────┼───────────────────────────────┼────────────┤
|
||||
│ 1 │ Control 1 │ https://hub.armosec.io/docs/1 │ Framework │
|
||||
│ │ │ │ 1 │
|
||||
├────────────┼──────────────┼───────────────────────────────┼────────────┤
|
||||
│ 2 │ Control 2 │ https://hub.armosec.io/docs/2 │ Framework │
|
||||
│ │ │ │ 2 │
|
||||
├────────────┼──────────────┼───────────────────────────────┼────────────┤
|
||||
│ 3 │ Control 3 │ https://hub.armosec.io/docs/3 │ Framework │
|
||||
│ │ │ │ 3 │
|
||||
└────────────┴──────────────┴───────────────────────────────┴────────────┘
|
||||
`
|
||||
|
||||
assert.Equal(t, want, string(got))
|
||||
}
|
||||
|
||||
func TestGenerateTableWithMalformedPoliciesAndPrettyPrintHeadersAndRows(t *testing.T) {
|
||||
// Arrange
|
||||
ctx := context.Background()
|
||||
policies := []string{
|
||||
"",
|
||||
"1",
|
||||
"2|Control 2|Framework 2",
|
||||
"3|Control 3|Framework 3|Extra 3",
|
||||
"4||Framework 4",
|
||||
"|",
|
||||
"5|Control 5||Extra 5",
|
||||
}
|
||||
|
||||
// Redirect stdout to a buffer
|
||||
rescueStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
prettyPrintControls(ctx, policies)
|
||||
|
||||
w.Close()
|
||||
got, _ := io.ReadAll(r)
|
||||
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
want := `┌────────────┬──────────────┬───────────────────────────────┬────────────┐
|
||||
│ Control ID │ Control name │ Docs │ Frameworks │
|
||||
├────────────┼──────────────┼───────────────────────────────┼────────────┤
|
||||
│ │ │ https://hub.armosec.io/docs/ │ │
|
||||
├────────────┼──────────────┼───────────────────────────────┼────────────┤
|
||||
│ 1 │ │ https://hub.armosec.io/docs/1 │ │
|
||||
├────────────┼──────────────┼───────────────────────────────┼────────────┤
|
||||
│ 2 │ Control 2 │ https://hub.armosec.io/docs/2 │ Framework │
|
||||
│ │ │ │ 2 │
|
||||
├────────────┼──────────────┼───────────────────────────────┼────────────┤
|
||||
│ 3 │ Control 3 │ https://hub.armosec.io/docs/3 │ Framework │
|
||||
│ │ │ │ 3 │
|
||||
├────────────┼──────────────┼───────────────────────────────┼────────────┤
|
||||
│ 4 │ │ https://hub.armosec.io/docs/4 │ Framework │
|
||||
│ │ │ │ 4 │
|
||||
├────────────┼──────────────┼───────────────────────────────┼────────────┤
|
||||
│ │ │ https://hub.armosec.io/docs/ │ │
|
||||
├────────────┼──────────────┼───────────────────────────────┼────────────┤
|
||||
│ 5 │ Control 5 │ https://hub.armosec.io/docs/5 │ │
|
||||
└────────────┴──────────────┴───────────────────────────────┴────────────┘
|
||||
`
|
||||
|
||||
assert.Equal(t, want, string(got))
|
||||
}
|
||||
|
||||
// Returns a non-empty list of supported actions when 'ListSupportActions' is called.
|
||||
func TestListSupportActionsNotEmpty(t *testing.T) {
|
||||
actions := ListSupportActions()
|
||||
assert.NotEmpty(t, actions)
|
||||
}
|
||||
|
||||
func TestListSupportActionsReturnsSupportedActions(t *testing.T) {
|
||||
got := ListSupportActions()
|
||||
want := []string{"controls", "exceptions", "frameworks"}
|
||||
sort.Strings(got)
|
||||
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func TestListFrameworks(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
listPolicies := &metav1.ListPolicies{
|
||||
Target: "all",
|
||||
Format: "json",
|
||||
AccountID: "1234567890",
|
||||
AccessKey: "myaccesskey",
|
||||
}
|
||||
|
||||
frameworks, err := listFrameworks(ctx, listPolicies)
|
||||
|
||||
assert.NotEmpty(t, frameworks)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestListControls(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
listPolicies := &metav1.ListPolicies{
|
||||
Target: "all",
|
||||
Format: "json",
|
||||
AccountID: "1234567890",
|
||||
AccessKey: "myaccesskey",
|
||||
}
|
||||
|
||||
controls, err := listControls(ctx, listPolicies)
|
||||
|
||||
assert.NotNil(t, controls)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestListExceptions(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
listPolicies := &metav1.ListPolicies{
|
||||
Target: "all",
|
||||
Format: "json",
|
||||
AccountID: "1234567890",
|
||||
AccessKey: "myaccesskey",
|
||||
}
|
||||
|
||||
controls, err := listExceptions(ctx, listPolicies)
|
||||
|
||||
assert.Nil(t, controls)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestNaturalSortPolicies(t *testing.T) {
|
||||
type args struct {
|
||||
policies []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
args: args{
|
||||
policies: []string{},
|
||||
},
|
||||
want: []string{},
|
||||
},
|
||||
{
|
||||
name: "one element",
|
||||
args: args{
|
||||
policies: []string{"policy-1"},
|
||||
},
|
||||
want: []string{"policy-1"},
|
||||
},
|
||||
{
|
||||
name: "Natural sort",
|
||||
args: args{
|
||||
policies: []string{"policy-1", "policy-11", "policy-12", "policy-2"},
|
||||
},
|
||||
want: []string{"policy-1", "policy-2", "policy-11", "policy-12"},
|
||||
},
|
||||
{
|
||||
name: "Natural sort 2",
|
||||
args: args{
|
||||
policies: []string{"exclude-aks-kube-system-daemonsets-10", "exclude-aks-kube-system-daemonsets-4", "exclude-aks-kube-system-daemonsets-1",
|
||||
"exclude-gke-kube-public-resources", "exclude-kubescape-otel",
|
||||
},
|
||||
},
|
||||
want: []string{"exclude-aks-kube-system-daemonsets-1", "exclude-aks-kube-system-daemonsets-4", "exclude-aks-kube-system-daemonsets-10", "exclude-gke-kube-public-resources", "exclude-kubescape-otel"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := naturalSortPolicies(tt.args.policies); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("sortPolicies() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo, s
|
||||
|
||||
// ===================== Re-scan the image =====================
|
||||
|
||||
logger.L().Start(fmt.Sprintf("Re-Scanning image: %s", patchedImageName))
|
||||
logger.L().Start(fmt.Sprintf("Re-scanning image: %s", patchedImageName))
|
||||
|
||||
scanResultsPatched, err := svc.Scan(ctx, patchedImageName, creds)
|
||||
if err != nil {
|
||||
|
||||
@@ -49,7 +49,7 @@ func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInt
|
||||
}
|
||||
|
||||
// ================== setup tenant object ======================================
|
||||
tenantConfig := cautils.GetTenantConfig(scanInfo.AccountID, scanInfo.AccessKey, k8sinterface.GetContextName(), scanInfo.CustomClusterName, k8s)
|
||||
tenantConfig := cautils.GetTenantConfig(scanInfo.AccountID, scanInfo.AccessKey, k8sinterface.GetContextName(), scanInfo.CustomClusterName, getKubernetesApi())
|
||||
|
||||
// Set submit behavior AFTER loading tenant config
|
||||
setSubmitBehavior(scanInfo, tenantConfig)
|
||||
@@ -110,7 +110,7 @@ func GetOutputPrinters(scanInfo *cautils.ScanInfo, ctx context.Context, clusterN
|
||||
logger.L().Ctx(ctx).Fatal(err.Error())
|
||||
}
|
||||
|
||||
printerHandler := resultshandling.NewPrinter(ctx, format, scanInfo.FormatVersion, scanInfo.PrintAttackTree, scanInfo.VerboseMode, cautils.ViewTypes(scanInfo.View), clusterName)
|
||||
printerHandler := resultshandling.NewPrinter(ctx, format, scanInfo, clusterName)
|
||||
printerHandler.SetWriter(ctx, scanInfo.Output)
|
||||
outputPrinters = append(outputPrinters, printerHandler)
|
||||
}
|
||||
@@ -119,7 +119,7 @@ func GetOutputPrinters(scanInfo *cautils.ScanInfo, ctx context.Context, clusterN
|
||||
|
||||
func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
|
||||
ctxInit, spanInit := otel.Tracer("").Start(ctx, "initialization")
|
||||
logger.L().Start("Kubescape scanner initializing")
|
||||
logger.L().Start("Kubescape scanner initializing...")
|
||||
|
||||
// ===================== Initialization =====================
|
||||
scanInfo.Init(ctxInit) // initialize scan info
|
||||
@@ -246,7 +246,7 @@ func scanImages(scanType cautils.ScanTypes, scanData *cautils.OPASessionObj, ctx
|
||||
if err := scanSingleImage(ctx, img, svc, resultsHandling); err != nil {
|
||||
logger.L().StopError("failed to scan", helpers.String("image", img), helpers.Error(err))
|
||||
}
|
||||
logger.L().StopSuccess("Scanned successfully", helpers.String("image", img))
|
||||
logger.L().StopSuccess("Scan successful: ", helpers.String("image", img))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
60
core/core/scan_test.go
Normal file
60
core/core/scan_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetOutputPrinters(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
scanInfo := &cautils.ScanInfo{
|
||||
ScanType: "control",
|
||||
Format: "json,junit,html",
|
||||
}
|
||||
|
||||
outputPrinters := GetOutputPrinters(scanInfo, ctx, "test-cluster")
|
||||
|
||||
assert.NotNil(t, outputPrinters)
|
||||
assert.Equal(t, 3, len(outputPrinters))
|
||||
}
|
||||
|
||||
func TestIsPrioritizationScanType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name cautils.ScanTypes
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: cautils.ScanTypeCluster,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: cautils.ScanTypeRepo,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: cautils.ScanTypeImage,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: cautils.ScanTypeWorkload,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: cautils.ScanTypeFramework,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: cautils.ScanTypeControl,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.name), func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, isPrioritizationScanType(tt.name))
|
||||
})
|
||||
}
|
||||
}
|
||||
48
core/mocks/cmd_mocks.go
Normal file
48
core/mocks/cmd_mocks.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anchore/grype/grype/presenter/models"
|
||||
"github.com/kubescape/kubescape/v3/core/cautils"
|
||||
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling"
|
||||
)
|
||||
|
||||
type MockIKubescape struct{}
|
||||
|
||||
func (m *MockIKubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) List(ctx context.Context, listPolicies *metav1.ListPolicies) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) Download(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) SetCachedConfig(setConfig *metav1.SetConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) ViewCachedConfig(viewConfig *metav1.ViewConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) DeleteCachedConfig(ctx context.Context, deleteConfig *metav1.DeleteConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) Fix(ctx context.Context, fixInfo *metav1.FixInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) Patch(ctx context.Context, patchInfo *metav1.PatchInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockIKubescape) ScanImage(ctx context.Context, imgScanInfo *metav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
|
||||
return nil, nil
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -3,11 +3,12 @@ package containerscan
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/francoispqt/gojay"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecodeScanWIthDangearousArtifacts(t *testing.T) {
|
||||
@@ -61,30 +62,243 @@ func TestUnmarshalScanReport1(t *testing.T) {
|
||||
|
||||
func TestGetByPkgNameSuccess(t *testing.T) {
|
||||
ds := GenerateContainerScanReportMock()
|
||||
a := ds.Layers[0].GetFilesByPackage("coreutils")
|
||||
if a != nil {
|
||||
|
||||
fmt.Printf("%+v\n", *a)
|
||||
}
|
||||
a := ds.Layers[0].GetPackagesNames()
|
||||
require.Equal(t, 1, len(a))
|
||||
assert.Equal(t, []string{"coreutils"}, a)
|
||||
|
||||
}
|
||||
|
||||
func TestGetByPkgNameMissing(t *testing.T) {
|
||||
ds := GenerateContainerScanReportMock()
|
||||
a := ds.Layers[0].GetFilesByPackage("s")
|
||||
if a != nil && len(*a) > 0 {
|
||||
t.Errorf("expected - no such package should be in that layer %v\n\n; found - %v", ds, a)
|
||||
func TestScanResultReportValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in ScanResultReport
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "empty report should return false",
|
||||
in: ScanResultReport{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "report with empty CustomerGUID should return false",
|
||||
in: ScanResultReport{
|
||||
CustomerGUID: "",
|
||||
ImgHash: "aaa",
|
||||
ImgTag: "bbb",
|
||||
Timestamp: 1,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "report with empty ImgHash and ImgTag should return false",
|
||||
in: ScanResultReport{
|
||||
CustomerGUID: "aaa",
|
||||
ImgHash: "",
|
||||
ImgTag: "",
|
||||
Timestamp: 1,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "report with empty ImageHash and non-empty ImgTag should return true",
|
||||
in: ScanResultReport{
|
||||
CustomerGUID: "aaa",
|
||||
ImgHash: "",
|
||||
ImgTag: "bbb",
|
||||
Timestamp: 1,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "report with non-empty ImageHash and empty ImgTag should return true",
|
||||
in: ScanResultReport{
|
||||
CustomerGUID: "aaa",
|
||||
ImgHash: "bbb",
|
||||
ImgTag: "",
|
||||
Timestamp: 1,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "report with non-empty ImageHash and non-empty ImgTag should return true",
|
||||
in: ScanResultReport{
|
||||
CustomerGUID: "aaa",
|
||||
ImgHash: "bbb",
|
||||
ImgTag: "ccc",
|
||||
Timestamp: 1,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "report with Timestamp <= 0 should return false",
|
||||
in: ScanResultReport{
|
||||
CustomerGUID: "aaa",
|
||||
ImgHash: "bbb",
|
||||
ImgTag: "ccc",
|
||||
Timestamp: 0,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res := test.in.Validate()
|
||||
assert.Equal(t, test.expected, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestScanElasticContainerScanSummaryResultValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in ElasticContainerScanSummaryResult
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "empty summary should return false",
|
||||
in: ElasticContainerScanSummaryResult{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "summary with empty CustomerGUID should return false",
|
||||
in: ElasticContainerScanSummaryResult{
|
||||
CustomerGUID: "",
|
||||
ContainerScanID: "aaa",
|
||||
ImgHash: "bbb",
|
||||
ImgTag: "ccc",
|
||||
Timestamp: 1,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "summary with empty ContainerScanID should return false",
|
||||
in: ElasticContainerScanSummaryResult{
|
||||
CustomerGUID: "aaa",
|
||||
ContainerScanID: "",
|
||||
ImgHash: "bbb",
|
||||
ImgTag: "ccc",
|
||||
Timestamp: 1,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "summary with empty ImgHash and ImgTag should return false",
|
||||
in: ElasticContainerScanSummaryResult{
|
||||
CustomerGUID: "aaa",
|
||||
ContainerScanID: "bbb",
|
||||
ImgHash: "",
|
||||
ImgTag: "",
|
||||
Timestamp: 1,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "summary with empty ImageHash and non-empty ImgTag should return true",
|
||||
in: ElasticContainerScanSummaryResult{
|
||||
CustomerGUID: "aaa",
|
||||
ContainerScanID: "bbb",
|
||||
ImgHash: "",
|
||||
ImgTag: "ccc",
|
||||
Timestamp: 1,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "summary with non-empty ImageHash and empty ImgTag should return true",
|
||||
in: ElasticContainerScanSummaryResult{
|
||||
CustomerGUID: "aaa",
|
||||
ContainerScanID: "bbb",
|
||||
ImgHash: "ccc",
|
||||
ImgTag: "",
|
||||
Timestamp: 1,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "summary with non-empty ImageHash and non-empty ImgTag should return true",
|
||||
in: ElasticContainerScanSummaryResult{
|
||||
CustomerGUID: "aaa",
|
||||
ContainerScanID: "bbb",
|
||||
ImgHash: "ccc",
|
||||
ImgTag: "ddd",
|
||||
Timestamp: 1,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "summary with Timestamp < 0 should return false",
|
||||
in: ElasticContainerScanSummaryResult{
|
||||
CustomerGUID: "aaa",
|
||||
ContainerScanID: "bbb",
|
||||
ImgHash: "ccc",
|
||||
ImgTag: "ddd",
|
||||
Timestamp: -1,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res := test.in.Validate()
|
||||
assert.Equal(t, test.expected, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateFixed(t *testing.T) {
|
||||
res := CalculateFixed([]FixedIn{{
|
||||
Name: "",
|
||||
ImgTag: "",
|
||||
Version: "",
|
||||
}})
|
||||
if 0 != res {
|
||||
t.Errorf("wrong fix status: %v", res)
|
||||
tests := []struct {
|
||||
name string
|
||||
in []FixedIn
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "empty list should return 0",
|
||||
in: []FixedIn{},
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "None Version value should return 0",
|
||||
in: []FixedIn{
|
||||
{Version: "None"},
|
||||
{Version: "None"},
|
||||
{Version: "None"},
|
||||
},
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "empty Version value should return 0",
|
||||
in: []FixedIn{
|
||||
{Version: ""},
|
||||
{Version: ""},
|
||||
{Version: ""},
|
||||
},
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "non empty or non None Version value should return 1",
|
||||
in: []FixedIn{
|
||||
{Version: "1.23"},
|
||||
{Version: ""},
|
||||
{Version: ""},
|
||||
},
|
||||
expected: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res := CalculateFixed(test.in)
|
||||
assert.Equal(t, test.expected, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReturnsHashValueForValidInputValues(t *testing.T) {
|
||||
report := ScanResultReport{}
|
||||
expectedHash := "7416232187745851261"
|
||||
actualHash := report.AsFNVHash()
|
||||
if actualHash != expectedHash {
|
||||
t.Errorf("Expected hash value %s, but got %s", expectedHash, actualHash)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,30 @@ var KnownSeverities = map[string]bool{
|
||||
CriticalSeverity: true,
|
||||
}
|
||||
|
||||
// CalculateFixed calculates the number of fixes in a given list of FixedIn objects.
|
||||
//
|
||||
// Example Usage:
|
||||
//
|
||||
// fixes := []FixedIn{
|
||||
// {Version: "None"},
|
||||
// {Version: "1.2.3"},
|
||||
// {Version: ""},
|
||||
// }
|
||||
//
|
||||
// result := CalculateFixed(fixes)
|
||||
// fmt.Println(result) // Output: 1
|
||||
//
|
||||
// Inputs:
|
||||
// - Fixes: a slice of FixedIn objects representing the fixes for a vulnerability.
|
||||
//
|
||||
// Flow:
|
||||
// 1. Iterate over each FixedIn object in the Fixes slice.
|
||||
// 2. Check if the Version field of the current FixedIn object is not equal to "None" and not empty.
|
||||
// 3. If the condition is true for any FixedIn object, return 1.
|
||||
// 4. If the loop completes without returning, return 0.
|
||||
//
|
||||
// Outputs:
|
||||
// - An integer representing the number of fixes found in the Fixes slice.
|
||||
func CalculateFixed(Fixes []FixedIn) int {
|
||||
for _, fix := range Fixes {
|
||||
if fix.Version != "None" && fix.Version != "" {
|
||||
|
||||
62
core/pkg/containerscan/datastructures_test.go
Normal file
62
core/pkg/containerscan/datastructures_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package containerscan
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCalculateFixed_All(t *testing.T) {
|
||||
tests := []struct {
|
||||
Fixes []FixedIn
|
||||
want int
|
||||
}{
|
||||
{
|
||||
Fixes: []FixedIn{
|
||||
{Version: "None"},
|
||||
{Version: ""},
|
||||
{Version: "1.0.0"},
|
||||
},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
Fixes: []FixedIn{
|
||||
{Version: "None"},
|
||||
{Version: ""},
|
||||
{Version: ""},
|
||||
},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
Fixes: []FixedIn{
|
||||
{Version: "None"},
|
||||
{Version: ""},
|
||||
{Version: "None"},
|
||||
},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
Fixes: []FixedIn{
|
||||
{Version: "None"},
|
||||
{Version: ""},
|
||||
{Version: "1.0.0"},
|
||||
{Version: "2.0.0"},
|
||||
},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
Fixes: []FixedIn{
|
||||
{Version: "None"},
|
||||
{Version: ""},
|
||||
{Version: "1.0.0"},
|
||||
{Version: ""},
|
||||
},
|
||||
want: 1,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, CalculateFixed(tt.Fixes))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,7 @@ import (
|
||||
"github.com/armosec/armoapi-go/identifiers"
|
||||
)
|
||||
|
||||
func (layer *ScanResultLayer) GetFilesByPackage(pkgname string) (files *PkgFiles) {
|
||||
for _, pkg := range layer.Packages {
|
||||
if pkg.PackageName == pkgname {
|
||||
return &pkg.Files
|
||||
}
|
||||
}
|
||||
|
||||
return &PkgFiles{}
|
||||
}
|
||||
|
||||
// GetPackagesNames retrieves the names of all the packages stored in the Packages field of the ScanResultLayer object and returns them as a slice of strings.
|
||||
func (layer *ScanResultLayer) GetPackagesNames() []string {
|
||||
pkgsNames := []string{}
|
||||
for _, pkg := range layer.Packages {
|
||||
@@ -24,6 +15,7 @@ func (layer *ScanResultLayer) GetPackagesNames() []string {
|
||||
return pkgsNames
|
||||
}
|
||||
|
||||
// GetDesignatorsNContext retrieves the designators and context information from the ScanResultReport object and returns them as a pair of objects.
|
||||
func (scanresult *ScanResultReport) GetDesignatorsNContext() (*identifiers.PortalDesignator, []identifiers.ArmoContext) {
|
||||
designatorsObj := identifiers.AttributesDesignatorsFromWLID(scanresult.WLID)
|
||||
designatorsObj.Attributes["containerName"] = scanresult.ContainerName
|
||||
@@ -32,6 +24,7 @@ func (scanresult *ScanResultReport) GetDesignatorsNContext() (*identifiers.Porta
|
||||
return designatorsObj, contextObj
|
||||
}
|
||||
|
||||
// Validate checks if the scan result report is valid.
|
||||
func (scanresult *ScanResultReport) Validate() bool {
|
||||
if scanresult.CustomerGUID == "" || (scanresult.ImgHash == "" && scanresult.ImgTag == "") || scanresult.Timestamp <= 0 {
|
||||
return false
|
||||
@@ -42,6 +35,7 @@ func (scanresult *ScanResultReport) Validate() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsRCE checks if a vulnerability description contains any keywords related to remote code execution (RCE) or arbitrary code injection.
|
||||
func (v *Vulnerability) IsRCE() bool {
|
||||
desc := strings.ToLower(v.Description)
|
||||
|
||||
|
||||
@@ -5,55 +5,7 @@ import (
|
||||
cautils "github.com/armosec/utils-k8s-go/armometadata"
|
||||
)
|
||||
|
||||
// ToFlatVulnerabilities - returnsgit p
|
||||
func (scanresult *ScanResultReport) ToFlatVulnerabilities() []*ElasticContainerVulnerabilityResult {
|
||||
vuls := make([]*ElasticContainerVulnerabilityResult, 0)
|
||||
vul2indx := make(map[string]int)
|
||||
scanID := scanresult.AsFNVHash()
|
||||
designatorsObj, ctxList := scanresult.GetDesignatorsNContext()
|
||||
for _, layer := range scanresult.Layers {
|
||||
for _, vul := range layer.Vulnerabilities {
|
||||
esLayer := ESLayer{LayerHash: layer.LayerHash, ParentLayerHash: layer.ParentLayerHash}
|
||||
if indx, isOk := vul2indx[vul.Name]; isOk {
|
||||
vuls[indx].Layers = append(vuls[indx].Layers, esLayer)
|
||||
continue
|
||||
}
|
||||
result := &ElasticContainerVulnerabilityResult{WLID: scanresult.WLID,
|
||||
Timestamp: scanresult.Timestamp,
|
||||
Designators: *designatorsObj,
|
||||
Context: ctxList}
|
||||
|
||||
result.Vulnerability = vul
|
||||
result.Layers = make([]ESLayer, 0)
|
||||
result.Layers = append(result.Layers, esLayer)
|
||||
result.ContainerScanID = scanID
|
||||
|
||||
result.IsFixed = CalculateFixed(vul.Fixes)
|
||||
result.RelevantLinks = append(result.RelevantLinks, "https://nvd.nist.gov/vuln/detail/"+vul.Name)
|
||||
result.RelevantLinks = append(result.RelevantLinks, vul.Link)
|
||||
result.Vulnerability.Link = "https://nvd.nist.gov/vuln/detail/" + vul.Name
|
||||
|
||||
result.Categories.IsRCE = result.IsRCE()
|
||||
vuls = append(vuls, result)
|
||||
vul2indx[vul.Name] = len(vuls) - 1
|
||||
|
||||
}
|
||||
}
|
||||
// find first introduced
|
||||
for i, v := range vuls {
|
||||
earlyLayer := ""
|
||||
for _, layer := range v.Layers {
|
||||
if layer.ParentLayerHash == earlyLayer {
|
||||
earlyLayer = layer.LayerHash
|
||||
}
|
||||
}
|
||||
vuls[i].IntroducedInLayer = earlyLayer
|
||||
|
||||
}
|
||||
|
||||
return vuls
|
||||
}
|
||||
|
||||
// Summarize generates a summary of the scan result report.
|
||||
func (scanresult *ScanResultReport) Summarize() *ElasticContainerScanSummaryResult {
|
||||
designatorsObj, ctxList := scanresult.GetDesignatorsNContext()
|
||||
summary := &ElasticContainerScanSummaryResult{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user