Compare commits

..

21 Commits

Author SHA1 Message Date
Daniel Grunberger
d8286d4c36 go lint
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-05 11:37:43 +03:00
Daniel Grunberger
07d88e098b fix shadowing
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-05 11:34:29 +03:00
Daniel Grunberger
eddbed4363 fix commands
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-05 09:48:15 +03:00
Daniel Grunberger
2606042768 docs
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-05 09:38:51 +03:00
Daniel Grunberger
59b2800c0e support threshold for patch
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-05 09:29:16 +03:00
Daniel Grunberger
77d66af0ad update tests with const
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-05 09:16:50 +03:00
Daniel Grunberger
54e808748c Merge branch 'refactor-img-cmd' of github.com:kubescape/kubescape into refactor-img-cmd
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-05 07:49:05 +03:00
Daniel Grunberger
cc9443d7d1 fix patch info
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-05 07:48:23 +03:00
David Wertenteil
fd2b560979 update readme
Signed-off-by: David Wertenteil <dwertent@armosec.io>
2023-09-04 21:03:55 +03:00
Daniel Grunberger
b3b1b3812b send by address
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-04 18:43:23 +03:00
Daniel Grunberger
7298be68f1 refactor img credentials
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-04 18:36:35 +03:00
Daniel Grunberger
4f1eb58216 add scan info
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-04 18:24:15 +03:00
Daniel Grunberger
5d02a5ba72 readme fixes
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-04 18:11:29 +03:00
Daniel Grunberger
6103b317f6 add image command
Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
2023-09-04 18:02:55 +03:00
Daniel Grunberger
e091269b22 Merge pull request #1332 from anubhav06/patch-cmd
feat: add kubescape patch command
2023-09-04 12:47:58 +03:00
Anubhav Gupta
78ec661452 update docs & minor changes
Signed-off-by: Anubhav Gupta <mail.anubhav06@gmail.com>
2023-09-04 13:02:18 +05:30
Anubhav Gupta
7f889b8423 Merge branch 'master' of https://github.com/kubescape/kubescape into patch-cmd 2023-09-01 10:19:07 +05:30
Anubhav Gupta
53f5667694 fix patch command logic
Signed-off-by: Anubhav Gupta <mail.anubhav06@gmail.com>
2023-09-01 09:43:09 +05:30
Anubhav Gupta
18b4d3cbec update patch command output format
Signed-off-by: Anubhav Gupta <mail.anubhav06@gmail.com>
2023-08-22 01:11:55 +05:30
Anubhav Gupta
deff1c3b2c Merge branch 'master' of https://github.com/kubescape/kubescape into patch-cmd 2023-08-21 20:28:08 +05:30
Anubhav Gupta
38aa8b0dce feat: add kubescape patch command
Signed-off-by: Anubhav Gupta <mail.anubhav06@gmail.com>
2023-08-05 00:18:18 +05:30
392 changed files with 8000 additions and 62719 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
git2go
kubescape

View File

@@ -1,5 +1,4 @@
name: 00-pr_scanner
permissions: read-all
on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review]
@@ -13,7 +12,7 @@ on:
- 'docs/*'
- 'build/*'
- '.github/*'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@@ -21,51 +20,22 @@ concurrency:
jobs:
pr-scanner:
permissions:
actions: read
checks: read
deployments: read
id-token: write
issues: read
discussions: read
packages: read
pages: read
pull-requests: write
repository-projects: read
security-events: read
statuses: read
attestations: read
contents: write
uses: ./.github/workflows/a-pr-scanner.yaml
with:
RELEASE: ""
CLIENT: test
CGO_ENABLED: 0
GO111MODULE: ""
secrets: inherit
binary-build:
if: ${{ github.actor == 'kubescape' }}
permissions:
actions: read
checks: read
contents: write
deployments: read
discussions: read
id-token: write
issues: read
packages: write
pages: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
attestations: read
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
with:
COMPONENT_NAME: kubescape
CGO_ENABLED: 0
CGO_ENABLED: 1
GO111MODULE: ""
GO_VERSION: "1.23"
RELEASE: "latest"
GO_VERSION: "1.20"
RELEASE: ""
CLIENT: test
ARCH_MATRIX: '[ "" ]'
OS_MATRIX: '[ "ubuntu-20.04" ]'
secrets: inherit

View File

@@ -1,5 +1,4 @@
name: 02-create_release
permissions: read-all
on:
push:
tags:
@@ -8,55 +7,27 @@ jobs:
retag:
outputs:
NEW_TAG: ${{ steps.tag-calculator.outputs.NEW_TAG }}
runs-on: ubuntu22-core4-mem16-ssd150
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
- id: tag-calculator
uses: ./.github/actions/tag-action
with:
SUB_STRING: "-rc"
binary-build:
permissions:
actions: read
checks: read
deployments: read
discussions: read
id-token: write
issues: read
packages: write
pages: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
contents: write
attestations: write
needs: [retag]
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
with:
COMPONENT_NAME: kubescape
CGO_ENABLED: 0
CGO_ENABLED: 1
GO111MODULE: ""
GO_VERSION: "1.23"
GO_VERSION: "1.20"
RELEASE: ${{ needs.retag.outputs.NEW_TAG }}
CLIENT: release
secrets: inherit
create-release:
permissions:
actions: read
checks: read
contents: write
deployments: read
discussions: read
id-token: write
issues: read
packages: read
pages: read
pull-requests: read
repository-projects: read
statuses: read
security-events: read
attestations: read
needs: [retag, binary-build]
uses: ./.github/workflows/c-create-release.yaml
with:
@@ -66,47 +37,15 @@ jobs:
secrets: inherit
publish-image:
permissions:
actions: read
checks: read
deployments: read
discussions: read
id-token: write
issues: read
packages: write
pages: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
attestations: read
contents: write
contents: read
uses: ./.github/workflows/d-publish-image.yaml
needs: [create-release, retag]
with:
client: "image-release"
image_name: "quay.io/${{ github.repository_owner }}/kubescape-cli"
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
image_tag: ${{ needs.retag.outputs.NEW_TAG }}
support_platforms: true
cosign: true
secrets: inherit
post-release:
permissions:
actions: read
checks: read
deployments: read
discussions: read
id-token: write
issues: read
packages: write
pages: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
attestations: read
contents: write
uses: ./.github/workflows/e-post-release.yaml
needs: [publish-image]
with:
TAG: ${{ needs.retag.outputs.NEW_TAG }}
secrets: inherit

View File

@@ -1,25 +1,20 @@
name: e-post_release
permissions: read-all
name: 03-post_release
on:
workflow_call:
inputs:
TAG:
description: 'Tag name'
required: true
type: string
release:
types: [published]
branches:
- 'master'
- 'main'
jobs:
post_release:
name: Post release jobs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Digest
uses: MCJack123/ghaction-generate-release-hashes@c03f3111b39432dde3edebe401c5a8d1ffbbf917 # ratchet:MCJack123/ghaction-generate-release-hashes@v1
with:
submodules: recursive
- name: Update new version in krew-index
uses: rajatjindal/krew-release-bot@v0.0.47
if: github.repository_owner == 'kubescape'
env:
GITHUB_REF: ${{ inputs.TAG }}
hash-type: sha1
file-name: kubescape-release-digests
- name: Invoke workflow to update packaging
uses: benc-uk/workflow-dispatch@v1
if: github.repository_owner == 'kubescape'

View File

@@ -0,0 +1,16 @@
name: 04-publish_krew_plugin
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
publish_krew_plugin:
name: Publish Krew plugin
runs-on: ubuntu-latest
if: github.repository_owner == 'kubescape'
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
with:
submodules: recursive
- name: Update new version in krew-index
uses: rajatjindal/krew-release-bot@92da038bbf995803124a8e50ebd438b2f37bbbb0 # ratchet:rajatjindal/krew-release-bot@v0.0.43

View File

@@ -1,5 +1,4 @@
name: a-pr-scanner
permissions: read-all
on:
workflow_call:
inputs:
@@ -15,84 +14,23 @@ on:
required: false
type: string
default: "./..."
GO111MODULE:
required: true
type: string
CGO_ENABLED:
type: number
default: 1
jobs:
unit-tests:
if: ${{ github.actor != 'kubescape' }}
name: Create cross-platform build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: ubuntu22-core4-mem16-ssd150
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- uses: actions/setup-go@v4
name: Installing go
with:
go-version: ${{ inputs.GO_VERSION }}
- name: Test core pkg
run: ${{ env.DOCKER_CMD }} go test -v ./...
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')
- 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 }}
- name: Smoke Testing
env:
RELEASE: ${{ inputs.RELEASE }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ${PWD}/dist/kubescape-ubuntu-latest
- name: golangci-lint
continue-on-error: false
uses: golangci/golangci-lint-action@v3
with:
version: latest
args: --timeout 10m
only-new-issues: true
skip-pkg-cache: true
skip-build-cache: true
scanners:
env:
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
name: PR Scanner
runs-on: ubuntu22-core4-mem16-ssd150
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
with:
fetch-depth: 0
submodules: recursive
- uses: actions/setup-go@v4
- uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # Install go because go-licenses use it ratchet:actions/setup-go@v3
name: Installing go
with:
go-version: "1.23"
go-version: '1.20'
cache: true
- name: Scanning - Forbidden Licenses (go-licenses)
id: licenses-scan
continue-on-error: true
@@ -105,7 +43,7 @@ jobs:
if: ${{ env.GITGUARDIAN_API_KEY }}
continue-on-error: true
id: credentials-scan
uses: GitGuardian/ggshield-action@master
uses: GitGuardian/ggshield-action@4ab2994172fadab959240525e6b833d9ae3aca61 # ratchet:GitGuardian/ggshield-action@master
with:
args: -v --all-policies
env:
@@ -118,7 +56,7 @@ jobs:
if: ${{ env.SNYK_TOKEN }}
id: vulnerabilities-scan
continue-on-error: true
uses: snyk/actions/golang@master
uses: snyk/actions/golang@806182742461562b67788a64410098c9d9b96adb # ratchet:snyk/actions/golang@master
with:
command: test --all-projects
env:
@@ -130,17 +68,17 @@ jobs:
- name: Convert coverage count to lcov format
uses: jandelgado/gcov2lcov-action@v1
- name: Submit coverage tests to Coveralls
continue-on-error: true
uses: coverallsapp/github-action@v1
with:
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: coverage.lcov
- name: Comment results to PR
continue-on-error: true # Warning: This might break opening PRs from forks
uses: peter-evans/create-or-update-comment@v4
uses: peter-evans/create-or-update-comment@5adcb0bb0f9fb3f95ef05400558bdb3f329ee808 # ratchet:peter-evans/create-or-update-comment@v2.1.0
with:
issue-number: ${{ github.event.pull_request.number }}
body: |

View File

@@ -1,5 +1,5 @@
name: b-binary-build-and-e2e-tests
permissions: read-all
on:
workflow_dispatch:
inputs:
@@ -18,7 +18,7 @@ on:
GO_VERSION:
required: false
type: string
default: "1.23"
default: "1.20"
GO111MODULE:
required: false
type: string
@@ -27,35 +27,18 @@ 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
default: '[
"ks_microservice_create_2_cronjob_mitre_and_nsa_proxy",
"ks_microservice_triggering_with_cron_job",
"ks_microservice_update_cronjob_schedule",
"ks_microservice_delete_cronjob",
"ks_microservice_create_2_cronjob_mitre_and_nsa",
"ks_microservice_ns_creation",
"ks_microservice_on_demand",
"ks_microservice_mitre_framework_on_demand",
"ks_microservice_nsa_and_mitre_framework_demand",
"scan_nsa",
"scan_mitre",
"scan_with_exceptions",
"scan_repository",
"scan_local_file",
"scan_local_glob_files",
"scan_local_list_of_files",
"scan_with_exception_to_backend",
"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_custom_framework",
"scan_customer_configuration",
"scan_compliance_score"
]'
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" ]'
workflow_call:
inputs:
@@ -70,7 +53,7 @@ on:
type: string
GO_VERSION:
type: string
default: "1.23"
default: "1.20"
GO111MODULE:
required: true
type: string
@@ -79,32 +62,24 @@ 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_custom_framework",
"scan_customer_configuration",
"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"
]'
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"]'
jobs:
wf-preparation:
name: secret-validator
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:
@@ -118,7 +93,14 @@ jobs:
SECRET_KEY: ${{ secrets.SECRET_KEY_PROD }}
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
run: "echo \"is-secret-set=${{ env.CUSTOMER != '' && env.USERNAME != '' && env.PASSWORD != '' && env.CLIENT_ID != '' && env.SECRET_KEY != '' && env.REGISTRY_USERNAME != '' && env.REGISTRY_PASSWORD != '' }}\" >> $GITHUB_OUTPUT\n"
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
@@ -127,158 +109,164 @@ jobs:
env:
input: ${{ inputs.BINARY_TESTS }}
check-secret:
name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
runs-on: ubuntu-latest
outputs:
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
steps:
- name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
id: check-secret-set
env:
QUAYIO_REGISTRY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
- id: export_arch_to_env
name: set test name
run: |
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
echo "ARCH_MATRIX=$input" >> $GITHUB_OUTPUT
env:
input: ${{ inputs.ARCH_MATRIX }}
binary-build:
name: Create cross-platform build
needs: wf-preparation
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: ubuntu-large
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
steps:
- name: (debug) Step 1 - Check disk space before checkout
run: df -h
- uses: actions/checkout@v4
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
with:
fetch-depth: 0
submodules: recursive
- name: (debug) Step 2 - Check disk space before installing Go
run: df -h
- 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-
- uses: actions/setup-go@v4
- 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
name: Installing go
with:
go-version: ${{ inputs.GO_VERSION }}
cache: true
- name: (debug) Step 3 - Check disk space before build
run: df -h
- 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 MSYS2 & libgit2 (Windows)
shell: pwsh
run: .\build.ps1 all
if: matrix.os == 'windows-latest'
- 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')
- name: (debug) Step 4 - Check disk space before testing httphandler pkg
run: df -h
run: ${{ env.DOCKER_CMD }} go test "-tags=static,gitenabled" -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')"
- name: Test httphandler pkg
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test -v ./...'
if: startsWith(github.ref, 'refs/tags')
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test "-tags=static,gitenabled" -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')"
- name: (debug) Step 5 - Check disk space before setting up Syft
run: df -h
- uses: anchore/sbom-action/download-syft@v0
name: Setup Syft
- name: (debug) Step 6 - Check disk space before goreleaser
run: df -h
- uses: goreleaser/goreleaser-action@v5
name: Build
with:
distribution: goreleaser
version: latest
args: release --clean --snapshot
- name: Build
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: (debug) Step 7 - Check disk space before smoke testing
run: df -h
- name: Smoke Testing
- name: Smoke Testing (Windows / MacOS)
env:
RELEASE: ${{ inputs.RELEASE }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ${PWD}/dist/kubescape-ubuntu-latest
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: (debug) Step 8 - Check disk space before golangci-lint
run: df -h
- 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 != ''
- name: golangci-lint
if: matrix.os == 'ubuntu-20.04'
continue-on-error: true
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5 # ratchet:golangci/golangci-lint-action@v3
with:
version: latest
args: --timeout 10m
args: --timeout 10m --build-tags=static
only-new-issues: true
skip-pkg-cache: true
skip-build-cache: true
- name: (debug) Step 9 - Check disk space before uploading artifacts
run: df -h
- uses: actions/upload-artifact@v4
name: Upload artifacts
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # ratchet:actions/upload-artifact@v3.1.1
name: Upload artifact (Linux)
if: matrix.os == 'ubuntu-20.04'
with:
name: kubescape
path: dist/kubescape*
name: kubescape${{ matrix.arch }}-ubuntu-latest
path: build/
if-no-files-found: error
- name: (debug) Step 10 - Check disk space after uploading artifacts
run: df -h
build-http-image:
permissions:
contents: write
id-token: write
packages: write
pull-requests: read
needs: [check-secret]
uses: kubescape/workflows/.github/workflows/incluster-comp-pr-merged.yaml@main
with:
IMAGE_NAME: quay.io/${{ github.repository_owner }}/kubescape
IMAGE_TAG: ${{ inputs.RELEASE }}
COMPONENT_NAME: kubescape
CGO_ENABLED: 0
GO111MODULE: "on"
BUILD_PLATFORM: linux/amd64,linux/arm64
GO_VERSION: "1.23"
REQUIRED_TESTS: '[
"ks_microservice_create_2_cronjob_mitre_and_nsa_proxy",
"ks_microservice_triggering_with_cron_job",
"ks_microservice_update_cronjob_schedule",
"ks_microservice_delete_cronjob",
"ks_microservice_create_2_cronjob_mitre_and_nsa",
"ks_microservice_ns_creation",
"ks_microservice_on_demand",
"ks_microservice_mitre_framework_on_demand",
"ks_microservice_nsa_and_mitre_framework_demand",
"scan_nsa",
"scan_mitre",
"scan_with_exceptions",
"scan_repository",
"scan_local_file",
"scan_local_glob_files",
"scan_local_list_of_files",
"scan_with_exception_to_backend",
"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_custom_framework",
"scan_customer_configuration",
"scan_compliance_score"
]'
COSIGN: true
HELM_E2E_TEST: true
FORCE: true
secrets: inherit
- 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/
if-no-files-found: error
run-tests:
strategy:
@@ -289,10 +277,10 @@ jobs:
if: ${{ (needs.wf-preparation.outputs.is-secret-set == 'true') && (always() && (contains(needs.*.result, 'success') || contains(needs.*.result, 'skipped')) && !(contains(needs.*.result, 'failure')) && !(contains(needs.*.result, 'cancelled'))) }}
runs-on: ubuntu-latest # This cannot change
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3.0.2
id: download-artifact
with:
name: kubescape
name: kubescape-ubuntu-latest
path: "~"
- run: ls -laR
@@ -301,12 +289,13 @@ jobs:
run: chmod +x -R ${{steps.download-artifact.outputs.download-path}}/kubescape-ubuntu-latest
- name: Checkout systests repo
uses: actions/checkout@v4
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
with:
repository: armosec/system-tests
ref: remove-urls
path: .
- uses: actions/setup-python@v4
- uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # ratchet:actions/setup-python@v4
with:
python-version: '3.8.13'
cache: 'pip'
@@ -321,7 +310,7 @@ jobs:
- name: Create k8s Kind Cluster
id: kind-cluster-install
uses: helm/kind-action@v1.10.0
uses: helm/kind-action@d08cf6ff1575077dee99962540d77ce91c62387d # ratchet:helm/kind-action@v1.3.0
with:
cluster_name: ${{ steps.uuid.outputs.RANDOM_UUID }}
@@ -351,9 +340,8 @@ jobs:
deactivate
- name: Test Report
uses: mikepenz/action-junit-report@v5
uses: mikepenz/action-junit-report@6e9933f4a97f4d2b99acef4d7b97924466037882 # ratchet:mikepenz/action-junit-report@v3.6.1
if: always() # always run even if the previous step fails
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
report_paths: '**/results_xml_format/**.xml'
commit: ${{github.event.workflow_run.head_sha}}

View File

@@ -1,5 +1,5 @@
name: build-image
permissions: read-all
on:
workflow_dispatch:
inputs:
@@ -19,23 +19,16 @@ on:
required: false
default: false
jobs:
build-http-image:
publish-image:
permissions:
id-token: write
packages: write
contents: write
pull-requests: read
uses: kubescape/workflows/.github/workflows/incluster-comp-pr-merged.yaml@main
contents: read
uses: ./.github/workflows/d-publish-image.yaml
with:
IMAGE_NAME: quay.io/${{ github.repository_owner }}/kubescape
IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
COMPONENT_NAME: kubescape
CGO_ENABLED: 0
GO111MODULE: "on"
BUILD_PLATFORM: ${{ inputs.PLATFORMS && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
GO_VERSION: "1.23"
REQUIRED_TESTS: '[]'
COSIGN: ${{ inputs.CO_SIGN }}
HELM_E2E_TEST: false
FORCE: true
secrets: inherit
client: ${{ inputs.CLIENT }}
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
image_tag: ${{ inputs.IMAGE_TAG }}
support_platforms: ${{ inputs.PLATFORMS }}
cosign: ${{ inputs.CO_SIGN }}
secrets: inherit

View File

@@ -1,5 +1,4 @@
name: c-create_release
permissions: read-all
on:
workflow_call:
inputs:
@@ -24,69 +23,50 @@ jobs:
MAC_OS: macos-latest
UBUNTU_OS: ubuntu-latest
WINDOWS_OS: windows-latest
permissions:
contents: write
# permissions:
# contents: write
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3.0.2
id: download-artifact
with:
name: kubescape
path: .
# TODO: kubescape-windows-latest is deprecated and should be removed
- name: Get kubescape.exe from kubescape-windows-latest.exe
run: cp ${{steps.download-artifact.outputs.download-path}}/kubescape-${{ env.WINDOWS_OS }}.exe ${{steps.download-artifact.outputs.download-path}}/kubescape.exe
- name: Get kubescape.exe from kubescape-windows-latest
run: cp ./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }} ./kubescape-${{ env.WINDOWS_OS }}/kubescape.exe
- name: Set release token
id: set-token
run: |
if [ "${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" != "" ]; then
echo "token=${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" >> $GITHUB_OUTPUT;
echo "TOKEN=${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" >> $GITHUB_ENV;
else
echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT;
echo "TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV;
fi
- name: List artifacts
run: |
find . -type f -print
- name: Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # ratchet:softprops/action-gh-release@v1
with:
token: ${{ steps.set-token.outputs.token }}
token: ${{ env.TOKEN }}
name: ${{ inputs.RELEASE_NAME }}
tag_name: ${{ inputs.TAG }}
body: ${{ github.event.pull_request.body }}
draft: ${{ inputs.DRAFT }}
prerelease: false
fail_on_unmatched_files: true
prerelease: false
# TODO: kubescape-windows-latest is deprecated and should be removed
files: |
./kubescape-${{ env.MAC_OS }}
./kubescape-${{ env.MAC_OS }}.sbom
./kubescape-${{ env.MAC_OS }}.sha256
./kubescape-${{ env.MAC_OS }}.tar.gz
./kubescape-${{ env.UBUNTU_OS }}
./kubescape-${{ env.UBUNTU_OS }}.sbom
./kubescape-${{ env.UBUNTU_OS }}.sha256
./kubescape-${{ env.UBUNTU_OS }}.tar.gz
./kubescape-${{ env.WINDOWS_OS }}.exe
./kubescape-${{ env.WINDOWS_OS }}.exe.sbom
./kubescape-${{ env.WINDOWS_OS }}.exe.sha256
./kubescape-${{ env.WINDOWS_OS }}.tar.gz
./kubescape-arm64-${{ env.MAC_OS }}
./kubescape-arm64-${{ env.MAC_OS }}.sbom
./kubescape-arm64-${{ env.MAC_OS }}.sha256
./kubescape-arm64-${{ env.MAC_OS }}.tar.gz
./kubescape-arm64-${{ env.UBUNTU_OS }}
./kubescape-arm64-${{ env.UBUNTU_OS }}.sbom
./kubescape-arm64-${{ env.UBUNTU_OS }}.sha256
./kubescape-arm64-${{ env.UBUNTU_OS }}.tar.gz
./kubescape-arm64-${{ env.WINDOWS_OS }}.exe
./kubescape-arm64-${{ env.WINDOWS_OS }}.exe.sbom
./kubescape-arm64-${{ env.WINDOWS_OS }}.exe.sha256
./kubescape-arm64-${{ env.WINDOWS_OS }}.tar.gz
./kubescape-riscv64-${{ env.UBUNTU_OS }}
./kubescape-riscv64-${{ env.UBUNTU_OS }}.sbom
./kubescape-riscv64-${{ env.UBUNTU_OS }}.sha256
./kubescape-riscv64-${{ env.UBUNTU_OS }}.tar.gz
./kubescape.exe
./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }}
./kubescape-${{ env.MAC_OS }}/kubescape-${{ env.MAC_OS }}
./kubescape-${{ env.MAC_OS }}/kubescape-${{ env.MAC_OS }}.sha256
./kubescape-${{ env.MAC_OS }}/kubescape-${{ env.MAC_OS }}.tar.gz
./kubescape-${{ env.UBUNTU_OS }}/kubescape-${{ env.UBUNTU_OS }}
./kubescape-${{ env.UBUNTU_OS }}/kubescape-${{ env.UBUNTU_OS }}.sha256
./kubescape-${{ env.UBUNTU_OS }}/kubescape-${{ env.UBUNTU_OS }}.tar.gz
./kubescape-${{ env.WINDOWS_OS }}/kubescape.exe
./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }}.sha256
./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }}.tar.gz
./kubescapearm64-${{ env.MAC_OS }}/kubescape-arm64-${{ env.MAC_OS }}
./kubescapearm64-${{ env.MAC_OS }}/kubescape-arm64-${{ env.MAC_OS }}.sha256
./kubescapearm64-${{ env.MAC_OS }}/kubescape-arm64-${{ env.MAC_OS }}.tar.gz
./kubescapearm64-${{ env.UBUNTU_OS }}/kubescape-arm64-${{ env.UBUNTU_OS }}
./kubescapearm64-${{ env.UBUNTU_OS }}/kubescape-arm64-${{ env.UBUNTU_OS }}.sha256
./kubescapearm64-${{ env.UBUNTU_OS }}/kubescape-arm64-${{ env.UBUNTU_OS }}.tar.gz

View File

@@ -0,0 +1,30 @@
# This workflow was added by CodeSee. Learn more at https://codesee.io/
# This is v2.0 of this workflow file
on:
pull_request_target:
types: [opened, synchronize, reopened]
paths-ignore:
- '**.yaml'
- '**.yml'
- '**.md'
- '**.sh'
- 'website/*'
- 'examples/*'
- 'docs/*'
- 'build/*'
- '.github/*'
name: CodeSee
permissions: read-all
jobs:
codesee:
runs-on: ubuntu-latest
continue-on-error: true
name: Analyze the repo with CodeSee
steps:
- uses: Codesee-io/codesee-action@v2
with:
codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }}
codesee-url: https://app.codesee.io

View File

@@ -1,13 +1,14 @@
name: pr-agent
permissions: read-all
on:
issue_comment:
permissions:
issues: write
pull-requests: write
jobs:
pr_agent:
permissions:
issues: write
pull-requests: write
runs-on: ubuntu-latest
name: Run pr agent on every pull request, respond to user comments
steps:
@@ -18,3 +19,5 @@ jobs:
env:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,18 +1,4 @@
name: d-publish-image
permissions:
actions: read
checks: read
contents: write
deployments: read
discussions: read
id-token: write
issues: read
packages: read
pages: read
pull-requests: read
repository-projects: read
statuses: read
security-events: read
on:
workflow_call:
inputs:
@@ -52,56 +38,37 @@ jobs:
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
run: |
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
build-cli-image:
build-image:
needs: [check-secret]
if: needs.check-secret.outputs.is-secret-set == 'true'
name: Build image and upload to registry
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
with:
submodules: recursive
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # ratchet:docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # ratchet:docker/setup-buildx-action@v2
- name: Login to Quay.io
env:
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
- uses: actions/download-artifact@v4
id: download-artifact
with:
name: kubescape
path: .
- name: mv kubescape amd64 binary
run: mv kubescape-ubuntu-latest kubescape-amd64-ubuntu-latest
- name: chmod +x
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: Build and push image
if: ${{ inputs.support_platforms }}
run: docker buildx build . --file build/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: Build and push image without amd64/arm64 support
if: ${{ !inputs.support_platforms }}
run: docker buildx build . --file build/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
- name: Install cosign
uses: sigstore/cosign-installer@main
uses: sigstore/cosign-installer@4079ad3567a89f68395480299c77e40170430341 # ratchet:sigstore/cosign-installer@main
with:
cosign-release: 'v2.2.2'
cosign-release: 'v1.12.0'
- 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: |
# 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 }}
cosign sign --force ${{ inputs.image_name }}

View File

@@ -1,72 +0,0 @@
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '0 00 * * 1'
push:
branches: [ "master" ]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
# Uncomment the permissions below if installing in a private repository.
# contents: read
# actions: read
steps:
- name: "Checkout code"
uses: actions/checkout@v4
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@v2.4.0
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecard on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@v4
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif

View File

@@ -1,4 +1,3 @@
permissions: read-all
on:
issues:
types: [opened, labeled]
@@ -7,14 +6,14 @@ jobs:
if: github.event.label.name == 'typo'
runs-on: ubuntu-latest
steps:
- uses: ben-z/actions-comment-on-issue@1.0.2
- uses: ben-z/actions-comment-on-issue@10be23f9c43ac792663043420fda29dde07e2f0f # ratchet:ben-z/actions-comment-on-issue@1.0.2
with:
message: "Hello! :wave:\n\nThis issue is being automatically closed, Please open a PR with a relevant fix."
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
auto_close_issues:
runs-on: ubuntu-latest
steps:
- uses: lee-dohm/close-matching-issues@v2
- uses: lee-dohm/close-matching-issues@e9e43aad2fa6f06a058cedfd8fb975fd93b56d8f # ratchet:lee-dohm/close-matching-issues@v2
with:
query: 'label:typo'
token: ${{ secrets.GITHUB_TOKEN }}

5
.gitignore vendored
View File

@@ -1,6 +1,5 @@
*.vs*
*kubescape*
!*Dockerfile*
*debug*
*vendor*
*.pyc*
@@ -8,6 +7,4 @@
.history
ca.srl
*.out
ks
dist/
ks

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "git2go"]
path = git2go
url = https://github.com/libgit2/git2go.git

View File

@@ -1,6 +1,6 @@
linters-settings:
govet:
shadow: true
check-shadowing: true
dupl:
threshold: 200
goconst:
@@ -24,6 +24,7 @@ linters:
- gosimple
disable:
# temporarily disabled
- varcheck
- errcheck
- dupl
- gocritic
@@ -35,6 +36,8 @@ linters:
- unparam
#- forbidigo # <- see later
# should remain disabled
- deadcode # deprecated linter
- maligned
- lll
- gochecknoinits
- gochecknoglobals
@@ -49,3 +52,6 @@ issues:
- linters:
- stylecheck
text: "ST1003"
run:
skip-dirs:
- git2go

View File

@@ -1,60 +0,0 @@
# 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:
- goos:
- linux
- windows
- darwin
goarch:
- amd64
- arm64
- riscv64
ldflags:
- -s -w
- -X "github.com/kubescape/kubescape/v3/core/cautils.BuildNumber={{.Env.RELEASE}}"
- -X "github.com/kubescape/kubescape/v3/core/cautils.Client={{.Env.CLIENT}}"
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: binary
id: binaries
name_template: >-
{{ .Binary }}
- format: tar.gz
name_template: >-
{{ .Binary }}
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
checksum:
ids:
- binaries
split: true
sboms:
- artifacts: binary
documents:
- "{{ .Binary }}.sbom"

View File

@@ -1,5 +0,0 @@
# Adopters
The Kubescape project manages this document in the central project repository.
Go to the [centralized ADOPTERS.md](https://github.com/kubescape/project-governance/blob/main/ADOPTERS.md)

View File

@@ -1,5 +1,3 @@
# Code of Conduct
## Code of Conduct
The Kubescape project manages this document in the central project repository.
Go to the [centralized CODE_OF_CONDUCT.md](https://github.com/kubescape/project-governance/blob/main/CODE_OF_CONDUCT.md)
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).

View File

@@ -1,5 +0,0 @@
# Community
The Kubescape project manages this document in the central project repository.
Go to the [centralized COMMUNITY.md](https://github.com/kubescape/project-governance/blob/main/COMMUNITY.md)

View File

@@ -1,5 +1,98 @@
# Contributing
The Kubescape project manages this document in the central project repository.
First, it is awesome that you are considering contributing to Kubescape! Contributing is important and fun and we welcome your efforts.
Go to the [centralized CONTRIBUTING.md](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md)
When contributing, we categorize contributions into two:
* Small code changes or fixes, whose scope is limited to a single or two files
* Complex features and improvements, with potentially unlimited scope
If you have a small change, feel free to fire up a Pull Request.
When planning a bigger change, please first discuss the change you wish to make via an issue,
so the maintainers are able to help guide you and let you know if you are going in the right direction.
## Code of Conduct
Please follow our [code of conduct](CODE_OF_CONDUCT.md) in all of your interactions within the project.
## Build and test locally
Please follow the [instructions here](https://github.com/kubescape/kubescape/wiki/Building).
## Pull Request Process
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
build.
2. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. Open Pull Request to the `master` branch.
4. We will merge the Pull Request once you have the sign-off.
## Developer Certificate of Origin
All commits to the project must be "signed off", which states that you agree to the terms of the [Developer Certificate of Origin](https://developercertificate.org/). This is done by adding a "Signed-off-by:" line in the commit message, with your name and email address.
Commits made through the GitHub web application are automatically signed off.
### Configuring Git to sign off commits
First, configure your name and email address in Git global settings:
```
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
```
You can now sign off per-commit, or configure Git to always sign off commits per repository.
### Sign off per-commit
Add [`-s`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s) to your Git command line. For example:
```git commit -s -m "Fix issue 64738"```
This is tedious, and if you forget, you'll have to [amend your commit](#fixing-a-commit-where-the-dco-failed).
### Configure a repository to always include sign off
There are many ways to achieve this with Git hooks, but the simplest is to do the following:
```
cd your-repo
curl -Ls https://gist.githubusercontent.com/dixudx/7d7edea35b4d91e1a2a8fbf41d0954fa/raw/prepare-commit-msg -o .git/hooks/prepare-commit-msg
chmod +x .git/hooks/prepare-commit-msg
```
### Use semantic commit messages (optional)
When contributing, you could consider using [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/), in order to improve logs readability and help us to automatically generate `CHANGELOG`s.
Format: `<type>(<scope>): <subject>`
`<scope>` is optional
#### Example
```
feat(cmd): add kubectl plugin
^--^ ^-^ ^----------------^
| | |
| | +-> subject: summary in present tense.
| |
| +-------> scope: point of interest
|
+-------> type: chore, docs, feat, fix, refactor, style, or test.
```
More Examples:
* `feat`: new feature for the user, not a new feature for build script
* `fix`: bug fix for the user, not a fix to a build script
* `docs`: changes to the documentation
* `style`: formatting, missing semi colons, etc; no production code change
* `refactor`: refactoring production code, eg. renaming a variable
* `test`: adding missing tests, refactoring tests; no production code change
* `chore`: updating grunt tasks etc; no production code change
## Fixing a commit where the DCO failed
Check out [this guide](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md).

View File

@@ -1,5 +0,0 @@
# Governance
The Kubescape project manages this document in the central project repository.
Go to the [centralized GOVERNANCE.md](https://github.com/kubescape/project-governance/blob/main/GOVERNANCE.md)

View File

@@ -1,5 +1,11 @@
# Maintainers
The Kubescape project manages this document in the central project repository.
The following table lists the Kubescape project maintainers:
Go to the [centralized MAINTAINERS.md](https://github.com/kubescape/project-governance/blob/main/MAINTAINERS.md)
| Name | GitHub | Organization | Added/Renewed On |
| --- | --- | --- | --- |
| [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 |
| [Bezalel Brandwine](https://www.linkedin.com/in/bezalel-brandwine) | [@Bezbran](https://github.com/Bezbran) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
| [Craig Box](https://www.linkedin.com/in/crbnz/) | [@craigbox](https://github.com/craigbox) | [ARMO](https://www.armosec.io/) | 2022-10-31 |

View File

@@ -1,12 +1,28 @@
.PHONY: test all build
.PHONY: test all build libgit2
# default task invoked while running make
all: build
all: libgit2 build
export CGO_ENABLED=0
export CGO_ENABLED=1
# build and install libgit2
libgit2:
-git submodule update --init --recursive
cd git2go; make install-static
# build and install libgit2 for macOS m1
libgit2arm64:
git submodule update --init --recursive
if [ "$(shell uname -s)" = "Darwin" ]; then \
sed -i '' 's/cmake -D/cmake -DCMAKE_OSX_ARCHITECTURES="arm64" -D/' git2go/script/build-libgit2.sh; \
fi
cd git2go; make install-static
# go build tags
TAGS = "gitenabled,static"
build:
go build -v .
go build -v -tags=$(TAGS) .
test:
go test -v ./...
go test -v -tags=$(TAGS) ./...

View File

@@ -3,14 +3,8 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/kubescape/kubescape)](https://goreportcard.com/report/github.com/kubescape/kubescape)
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/kubescape/kubescape)
[![GitHub](https://img.shields.io/github/license/kubescape/kubescape)](https://github.com/kubescape/kubescape/blob/master/LICENSE)
[![CNCF](https://shields.io/badge/CNCF-Incubating%20project-blue?logo=linux-foundation&style=flat)](https://landscape.cncf.io/?item=provisioning--security-compliance--kubescape)
[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/kubescape)](https://artifacthub.io/packages/search?repo=kubescape)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fkubescape%2Fkubescape.svg?type=shield&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2Fkubescape%2Fkubescape?ref=badge_shield&issueType=license)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/6944/badge)](https://www.bestpractices.dev/projects/6944)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/kubescape/kubescape/badge)](https://securityscorecards.dev/viewer/?uri=github.com/kubescape/kubescape)
[![Stars](https://img.shields.io/github/stars/kubescape/kubescape?style=social)](https://github.com/kubescape/kubescape/stargazers)
[![CNCF](https://shields.io/badge/CNCF-Sandbox%20project-blue?logo=linux-foundation&style=flat)](https://landscape.cncf.io/card-mode?project=sandbox&selected=kubescape)
[![Twitter Follow](https://img.shields.io/twitter/follow/kubescape?style=social)](https://twitter.com/kubescape)
[![Slack](https://img.shields.io/badge/slack-kubescape-blueviolet?logo=slack)](https://cloud-native.slack.com/archives/C04EY3ZF9GE)
# Kubescape
@@ -20,21 +14,18 @@
<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>
_Comprehensive Kubernetes Security from Development to Runtime_
_An open-source Kubernetes security platform for your IDE, CI/CD pipelines, and clusters_
Kubescape is an open-source Kubernetes security platform that provides comprehensive security coverage, from left to right across the entire development and deployment lifecycle. It offers hardening, posture management, and runtime security capabilities to ensure robust protection for Kubernetes environments. It saves Kubernetes users and admins precious time, effort, and resources.
Kubescape 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 scans clusters, YAML files, and Helm charts. It detects misconfigurations according to multiple frameworks (including [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo/?utm_source=github&utm_medium=repository), [MITRE ATT&CK®](https://www.armosec.io/glossary/mitre-attck-framework/?utm_source=github&utm_medium=repository) and the [CIS Benchmark](https://www.armosec.io/blog/cis-kubernetes-benchmark-framework-scanning-tools-comparison/?utm_source=github&utm_medium=repository)).
Kubescape 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)).
Kubescape was created by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository) and is a [Cloud Native Computing Foundation (CNCF) incubating project](https://www.cncf.io/projects/).
_Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape! 😀_
Kubescape was created by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository) and is a [Cloud Native Computing Foundation (CNCF) sandbox project](https://www.cncf.io/sandbox-projects/).
## Demo
<img src="docs/img/demo.gif">
Kubescape has a command line tool that you can use to quickly get a report on the security posture of a Kubernetes cluster:
<img src="docs/img/demo-v3.gif">
_Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape! 😀_
## Getting started
@@ -44,13 +35,13 @@ Experimenting with Kubescape is as easy as:
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
```
This script will automatically download the latest Kubescape CLI release and scan the Kubernetes cluster in your current kubectl context.
Learn more about:
* [Installing the Kubescape CLI](https://kubescape.io/docs/install-cli/)
* [Running your first scan](https://kubescape.io/docs/scanning/)
* [Accepting risk with exceptions](https://kubescape.io/docs/accepting-risk/)
* [Installing Kubescape](docs/installation.md)
* [Running your first scan](docs/getting-started.md#run-your-first-scan)
* [Usage](docs/getting-started.md#examples)
* [Architecture](docs/architecture.md)
* [Building Kubescape from source](https://github.com/kubescape/kubescape/wiki/Building)
_Did you know you can use Kubescape in all these places?_
@@ -58,47 +49,33 @@ _Did you know you can use Kubescape in all these places?_
<img src="docs/img/ksfromcodetodeploy.png" alt="Places you can use Kubescape: in your IDE, CI, CD, or against a running cluster.">
</div>
### Continuous security monitoring with the Kubescape Operator
As well as a CLI, Kubescape provides an in-cluster mode, which is installed via a Helm chart. Kubescape in-cluster provides extensive features such as continuous scanning, image vulnerability scanning, runtime analysis, network policy generation, and more. [Learn more about the Kubescape operator](https://kubescape.io/docs/operator/).
### Using Kubescape as a GitHub Action
Kubescape can be used as a GitHub Action. This is a great way to integrate Kubescape into your CI/CD pipeline. You can find the Kubescape GitHub Action in the [GitHub Action marketplace](https://github.com/marketplace/actions/kubescape).
## Under the hood
Kubescape uses [Open Policy Agent](https://github.com/open-policy-agent/opa) to verify Kubernetes objects against [a library of posture controls](https://github.com/kubescape/regolibrary).
For image scanning, it uses [Grype](https://github.com/anchore/grype).
For image patching, it uses [Copacetic](https://github.com/project-copacetic/copacetic).
For eBPF, it uses [Inspektor Gadget](https://github.com/inspektor-gadget)
By default, CLI scan results are printed in a console-friendly manner, but they can be:
By default, the results are printed in a console-friendly manner, but they can be:
* exported to JSON, junit XML or SARIF
* exported to JSON or junit XML
* rendered to HTML or PDF
* submitted to a [cloud service](docs/providers.md)
### In-cluster architecture
![Architecture diagram](docs/img/architecture-diagram.png)
It retrieves Kubernetes objects from the API server and runs a set of [Rego snippets](https://www.openpolicyagent.org/docs/latest/policy-language/) developed by [ARMO](https://www.armosec.io?utm_source=github&utm_medium=repository).
## Community
Kubescape is an open source project. We welcome your feedback and ideas for improvement. We are part of the CNCF community and are evolving Kubescape in sync with the security needs of Kubernetes users. To learn more about where Kubescape is heading, please check out our [ROADMAP](https://github.com/kubescape/project-governance/blob/main/ROADMAP.md).
Kubescape is an open source project, we welcome your feedback and ideas for improvement. We are part of the Kubernetes community and are building more tests and controls as the ecosystem develops.
If you feel inspired to contribute to Kubescape, check out our [CONTRIBUTING](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md) file to learn how. You can find the issues we are working on (triage to development) on the [Kubescaping board](https://github.com/orgs/kubescape/projects/4/views/1)
* Feel free to pick a task from the [board](https://github.com/orgs/kubescape/projects/4) or suggest a feature of your own.
* Open an issue on the board. We aim to respond to all issues within 48 hours.
* [Join the CNCF Slack](https://slack.cncf.io/) and then our [users](https://cloud-native.slack.com/archives/C04EY3ZF9GE) or [developers](https://cloud-native.slack.com/archives/C04GY6H082K) channel.
We hold [community meetings](https://zoom.us/j/95174063585) on Zoom, on the first Tuesday of every month, at 14:00 GMT. ([See that in your local time zone](https://time.is/compare/1400_in_GMT)).
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
For more information about the Kubescape community, please visit [COMMUNITY](https://github.com/kubescape/project-governance/blob/main/COMMUNITY.md).
## Contributions
Thanks to all our contributors! Check out our [CONTRIBUTING](CONTRIBUTING.md) file to learn how to join them.
We would like to take this opportunity to thank all our contibutors to date.
* Feel free to pick a task from the [issues](https://github.com/kubescape/kubescape/issues?q=is%3Aissue+is%3Aopen+label%3A%22open+for+contribution%22), [roadmap](docs/roadmap.md) or suggest a feature of your own.
* [Open an issue](https://github.com/kubescape/kubescape/issues/new/choose): we aim to respond to all issues within 48 hours.
* [Join the CNCF Slack](https://slack.cncf.io/) and then our [users](https://cloud-native.slack.com/archives/C04EY3ZF9GE) or [developers](https://cloud-native.slack.com/archives/C04GY6H082K) channel.
<br>
@@ -106,16 +83,12 @@ We would like to take this opportunity to thank all our contibutors to date.
<img src = "https://contrib.rocks/image?repo=kubescape/kubescape"/>
</a>
## Changelog
Kubescape changes are tracked on the [release](https://github.com/kubescape/kubescape/releases) page.
## License
Copyright 2021-2024, the Kubescape Authors. All rights reserved. Kubescape is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details.
Copyright 2021-2023, the Kubescape Authors. All rights reserved. Kubescape is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details.
Kubescape is a [Cloud Native Computing Foundation (CNCF) incubating project](https://www.cncf.io/projects/kubescape/) and was contributed by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository).
Kubescape is a [Cloud Native Computing Foundation (CNCF) sandbox project](https://www.cncf.io/sandbox-projects/) and was contributed by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository).
<div align="center">
<img src="https://raw.githubusercontent.com/cncf/artwork/refs/heads/main/other/cncf-member/incubating/color/cncf-incubating-color.svg" width="300" alt="CNCF Incubating Project">
<img src="https://raw.githubusercontent.com/cncf/artwork/master/other/cncf-sandbox/horizontal/color/cncf-sandbox-horizontal-color.svg" width="300" alt="CNCF Sandbox Project">
</div>

View File

@@ -1,56 +0,0 @@
header:
schema-version: 1.0.0
last-updated: '2023-10-12'
last-reviewed: '2023-10-12'
expiration-date: '2024-10-12T01:00:00.000Z'
project-url: https://github.com/kubescape/kubescape/
project-release: 1.0.0
project-lifecycle:
status: active
bug-fixes-only: false
core-maintainers:
- github:amirmalka
- github:amitschendel
- github:bezbran
- github:craigbox
- github:dwertent
- github:matthyx
- github:rotemamsa
- github:slashben
contribution-policy:
accepts-pull-requests: true
accepts-automated-pull-requests: false
code-of-conduct: https://github.com/kubescape/kubescape/blob/master/CODE_OF_CONDUCT.md
dependencies:
third-party-packages: true
dependencies-lists:
- https://github.com/kubescape/kubescape/blob/master/go.mod
- https://github.com/kubescape/kubescape/blob/master/httphandler/go.mod
env-dependencies-policy:
policy-url: https://github.com/kubescape/kubescape/blob/master/docs/environment-dependencies-policy.md
documentation:
- https://github.com/kubescape/kubescape/tree/master/docs
distribution-points:
- https://github.com/kubescape/kubescape/
security-artifacts:
threat-model:
threat-model-created: false
security-testing:
- tool-type: sca
tool-name: Dependabot
tool-version: latest
integration:
ad-hoc: false
ci: true
before-release: true
comment: |
Dependabot is enabled for this repo.
security-contacts:
- type: email
value: cncf-kubescape-maintainers@lists.cncf.io
vulnerability-reporting:
accepts-vulnerability-reports: true
security-policy: https://github.com/kubescape/kubescape/security/policy
email-contact: cncf-kubescape-maintainers@lists.cncf.io
comment: |
The first and best way to report a vulnerability is by using private security issues in GitHub.

View File

@@ -1,5 +0,0 @@
# Security
The Kubescape project manages this document in the central project repository.
Go to the [centralized SECURITY.md](https://github.com/kubescape/project-governance/blob/main/SECURITY.md)

78
build.ps1 Normal file
View File

@@ -0,0 +1,78 @@
# Defining input params
param (
[string]$mode = "error"
)
# Function to install MSYS
function Install {
Write-Host "Starting install..." -ForegroundColor Cyan
# Check to see if already installed
if (Test-Path "C:\MSYS64\") {
Write-Host "MSYS2 already installed" -ForegroundColor Green
} else {
# Create a temp directory
New-Item -Path "$PSScriptRoot\temp_install" -ItemType Directory > $null
# Download MSYS
Write-Host "Downloading MSYS2..." -ForegroundColor Cyan
$bitsJobObj = Start-BitsTransfer "https://github.com/msys2/msys2-installer/releases/download/2022-06-03/msys2-x86_64-20220603.exe" -Destination "$PSScriptRoot\temp_install\msys2-x86_64-20220603.exe"
switch ($bitsJobObj.JobState) {
"Transferred" {
Complete-BitsTransfer -BitsJob $bitsJobObj
break
}
"Error" {
throw "Error downloading"
}
}
Write-Host "MSYS2 download complete" -ForegroundColor Green
# Install MSYS
Write-Host "Installing MSYS2..." -ForegroundColor Cyan
Start-Process -Filepath "$PSScriptRoot\temp_install\msys2-x86_64-20220603.exe" -ArgumentList @("install", "--root", "C:\MSYS64", "--confirm-command") -Wait
Write-Host "MSYS2 install complete" -ForegroundColor Green
# Remove temp directory
Remove-Item "$PSScriptRoot\temp_install" -Recurse
}
# Set PATH
$env:Path = "C:\MSYS64\mingw64\bin;C:\MSYS64\usr\bin;" + $env:Path
# Install MSYS packages
Write-Host "Installing MSYS2 packages..." -ForegroundColor Cyan
Start-Process -Filepath "pacman" -ArgumentList @("-S", "--needed", "--noconfirm", "make") -Wait
Start-Process -Filepath "pacman" -ArgumentList @("-S", "--needed", "--noconfirm", "mingw-w64-x86_64-cmake") -Wait
Start-Process -Filepath "pacman" -ArgumentList @("-S", "--needed", "--noconfirm", "mingw-w64-x86_64-gcc") -Wait
Start-Process -Filepath "pacman" -ArgumentList @("-S", "--needed", "--noconfirm", "mingw-w64-x86_64-pkg-config") -Wait
Start-Process -Filepath "pacman" -ArgumentList @("-S", "--needed", "--noconfirm", "msys2-w32api-runtime") -Wait
Write-Host "MSYS2 packages install complete" -ForegroundColor Green
Write-Host "Install complete" -ForegroundColor Green
}
# Function to build libgit2
function Build {
Write-Host "Starting build..." -ForegroundColor Cyan
# Set PATH
$env:Path = "C:\MSYS64\mingw64\bin;C:\MSYS64\usr\bin;" + $env:Path
# Build
Start-Process -Filepath "make" -ArgumentList @("libgit2") -Wait -NoNewWindow
Write-Host "Build complete" -ForegroundColor Green
}
# Check user call mode
if ($mode -eq "all") {
Install
Build
} elseif ($mode -eq "install") {
Install
} elseif ($mode -eq "build") {
Build
} else {
Write-Host "Error: -mode should be one of (all|install|build)" -ForegroundColor Red
}

94
build.py Normal file
View File

@@ -0,0 +1,94 @@
import os
import sys
import hashlib
import platform
import subprocess
import tarfile
BASE_GETTER_CONST = "github.com/kubescape/kubescape/v2/core/cautils/getter"
CURRENT_PLATFORM = platform.system()
platformSuffixes = {
"Windows": "windows-latest",
"Linux": "ubuntu-latest",
"Darwin": "macos-latest",
}
def check_status(status, msg):
if status != 0:
sys.stderr.write(msg)
exit(status)
def get_build_dir():
return "build"
def get_package_name():
if CURRENT_PLATFORM not in platformSuffixes: raise OSError("Platform %s is not supported!" % (CURRENT_PLATFORM))
# # TODO: kubescape-windows-latest is deprecated and should be removed
# if CURRENT_PLATFORM == "Windows": return "kubescape.exe"
package_name = "kubescape-"
if os.getenv("GOARCH"):
package_name += os.getenv("GOARCH") + "-"
return package_name + platformSuffixes[CURRENT_PLATFORM]
def main():
print("Building Kubescape")
# Set some variables
package_name = get_package_name()
build_url = "github.com/kubescape/kubescape/v2/core/cautils.BuildNumber"
release_version = os.getenv("RELEASE")
client_var = "github.com/kubescape/kubescape/v2/core/cautils.Client"
client_name = os.getenv("CLIENT")
# Create build directory
build_dir = get_build_dir()
ks_file = os.path.join(build_dir, package_name)
hash_file = ks_file + ".sha256"
tar_file = ks_file + ".tar.gz"
if not os.path.isdir(build_dir):
os.makedirs(build_dir)
# Build kubescape
ldflags = "-w -s"
if release_version:
ldflags += " -X {}={}".format(build_url, release_version)
if client_name:
ldflags += " -X {}={}".format(client_var, client_name)
build_command = ["go", "build", "-buildmode=pie", "-tags=static,gitenabled", "-o", ks_file, "-ldflags" ,ldflags]
print("Building kubescape and saving here: {}".format(ks_file))
print("Build command: {}".format(" ".join(build_command)))
status = subprocess.call(build_command)
check_status(status, "Failed to build kubescape")
sha256 = hashlib.sha256()
with open(ks_file, "rb") as kube:
sha256.update(kube.read())
with open(hash_file, "w") as kube_sha:
hash = sha256.hexdigest()
print("kubescape hash: {}, file: {}".format(hash, hash_file))
kube_sha.write(sha256.hexdigest())
with tarfile.open(tar_file, 'w:gz') as archive:
name = "kubescape"
if CURRENT_PLATFORM == "Windows":
name += ".exe"
archive.add(ks_file, name)
archive.add("LICENSE", "LICENSE")
print("Build Done")
if __name__ == "__main__":
main()

View File

@@ -1,27 +1,39 @@
FROM --platform=$BUILDPLATFORM golang:1.23-bookworm AS builder
ENV GO111MODULE=on CGO_ENABLED=0
WORKDIR /work
ARG TARGETOS TARGETARCH
RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
cd httphandler && GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/ksserver .
RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
go run downloader/main.go
FROM gcr.io/distroless/static-debian12:nonroot
USER nonroot
WORKDIR /home/nonroot/
COPY --from=builder /out/ksserver /usr/bin/ksserver
COPY --from=builder /root/.kubescape /home/nonroot/.kubescape
FROM golang:1.20-bullseye as builder
ARG image_version client
ENV RELEASE=$image_version CLIENT=$client
ENV GO111MODULE=on CGO_ENABLED=1 PYTHONUNBUFFERED=1 RELEASE=$image_version CLIENT=$client
# Install required python/pip
RUN apt update
RUN apt install -y cmake python3-pip
RUN pip3 install --no-cache --upgrade pip setuptools
WORKDIR /work
ADD . .
# install libgit2
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
make libgit2
# build kubescape server
WORKDIR /work/httphandler
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
python3 build.py
RUN ls -ltr build/
# build kubescape cmd
WORKDIR /work
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
python3 build.py
RUN /work/build/kubescape-ubuntu-latest download artifacts -o /work/artifacts
FROM gcr.io/distroless/base-debian11:nonroot
COPY --from=builder /work/artifacts/ /home/nonroot/.kubescape
COPY --from=builder /work/httphandler/build/kubescape-ubuntu-latest /usr/bin/ksserver
COPY --from=builder /work/build/kubescape-ubuntu-latest /usr/bin/kubescape
ENTRYPOINT ["ksserver"]

View File

@@ -1,2 +0,0 @@
.git
kubescape*

View File

@@ -7,13 +7,7 @@
git clone https://github.com/kubescape/kubescape.git kubescape && cd "$_"
```
2. Build kubescape CLI Docker image
```
make all
docker buildx build -t kubescape-cli -f build/kubescape-cli.Dockerfile --build-arg="ks_binary=kubescape" --load .
```
3. Build kubescape Docker image
```
docker buildx build -t kubescape -f build/Dockerfile --load .
2. Build
```
docker build -t kubescape -f build/Dockerfile .
```

View File

@@ -1,12 +0,0 @@
FROM gcr.io/distroless/static-debian12:debug-nonroot
USER nonroot
WORKDIR /home/nonroot/
ARG image_version client TARGETARCH
ENV RELEASE=$image_version CLIENT=$client
COPY kubescape-${TARGETARCH}-ubuntu-latest /usr/bin/kubescape
RUN ["kubescape", "download", "artifacts"]
ENTRYPOINT ["kubescape"]

View File

@@ -1 +0,0 @@
.git

View File

@@ -5,7 +5,7 @@ import (
"os"
"strings"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/spf13/cobra"
)
@@ -29,12 +29,6 @@ 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)
@@ -44,8 +38,6 @@ 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])
}
},
}

View File

@@ -1,187 +0,0 @@
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))
}

View File

@@ -3,8 +3,8 @@ package config
import (
"fmt"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/spf13/cobra"
)

View File

@@ -1,44 +0,0 @@
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())
}
}
}

View File

@@ -1,9 +1,11 @@
package config
import (
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/core/meta"
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"context"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
@@ -13,7 +15,7 @@ func getDeleteCmd(ks meta.IKubescape) *cobra.Command {
Short: "Delete cached configurations",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
if err := ks.DeleteCachedConfig(&v1.DeleteConfig{}); err != nil {
if err := ks.DeleteCachedConfig(context.TODO(), &v1.DeleteConfig{}); err != nil {
logger.L().Fatal(err.Error())
}
},

View File

@@ -1,21 +0,0 @@
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)
}

View File

@@ -2,12 +2,11 @@ package config
import (
"fmt"
"sort"
"strings"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/core/meta"
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
@@ -34,23 +33,16 @@ func getSetCmd(ks meta.IKubescape) *cobra.Command {
}
var supportConfigSet = map[string]func(*metav1.SetConfig, string){
"accessKey": func(s *metav1.SetConfig, accessKey string) { s.AccessKey = accessKey },
"accountID": func(s *metav1.SetConfig, account string) { s.Account = account },
"cloudAPIURL": func(s *metav1.SetConfig, cloudAPIURL string) { s.CloudAPIURL = cloudAPIURL },
"cloudReportURL": func(s *metav1.SetConfig, cloudReportURL string) { s.CloudReportURL = cloudReportURL },
}
func stringKeysToSlice(m map[string]func(*metav1.SetConfig, string)) []string {
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...)
for i := range m {
l = append(l, i)
}
return l
}

View File

@@ -1,81 +0,0 @@
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())
}

View File

@@ -3,9 +3,9 @@ package config
import (
"os"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/core/meta"
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)

View File

@@ -1,21 +0,0 @@
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)
}

View File

@@ -1,17 +1,18 @@
package download
import (
"context"
"fmt"
"path/filepath"
"slices"
"strings"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/core"
"github.com/kubescape/kubescape/v3/core/meta"
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/core"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)
var (
@@ -36,6 +37,9 @@ var (
# Download the configured controls-inputs
%[1]s download controls-inputs
# Download the attack tracks
%[1]s download attack-tracks
`, cautils.ExecName())
)
@@ -66,16 +70,13 @@ 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 {
downloadInfo.Identifier = args[1]
}
if err := ks.Download(&downloadInfo); err != nil {
if err := ks.Download(context.TODO(), &downloadInfo); err != nil {
logger.L().Fatal(err.Error())
}
return nil
@@ -83,7 +84,8 @@ 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, "access-key", "", "", "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.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
return downloadCmd

View File

@@ -1,102 +0,0 @@
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())
})
}
}

View File

@@ -1,12 +1,14 @@
package fix
import (
"context"
"errors"
"fmt"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
@@ -34,7 +36,7 @@ func GetFixCmd(ks meta.IKubescape) *cobra.Command {
}
fixInfo.ReportFile = args[0]
return ks.Fix(&fixInfo)
return ks.Fix(context.TODO(), &fixInfo)
},
}

View File

@@ -1,30 +0,0 @@
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)
}

View File

@@ -1,13 +1,110 @@
# Scan Command
The scan command is used for scanning container images for vulnerabilities.
It uses [grype](https://github.com/anchore/grype) under the hood for scanning the images.
## Usage
```bash
kubescape image scan <image:tag> [flags]
```
### Flags
| Flag | Description | Required | Default |
| ------------------------- | ------------------------------------------------------ |--------- | --------|
| -u, --username | Username for the image registry login | No | |
| -p, --password | Password for the image registry login | No | |
| -f, --format | Output file format. | No | |
| -o, --output | Output file. Print output to file and not stdout | No | |
| -v, --verbose | Display full report | No | false |
| -s, --severity-threshold | Maximum severity of a vulnerability | No | |
| -h, --help | help for scan | No | |
## Example
We will demonstrate how to use the scan command with an example of [nginx](https://www.nginx.com/) image.
### Steps
1. In a terminal, run the `kubescape image scan` command:
```bash
kubescape image scan nginx:1.22
```
2. You will get an output like below:
```bash
✅ Successfully scanned image: nginx:1.22
| Severity | Vulnerability | Component | Version | Fixed In |
| -------- | -------------- | ------------- | ----------------------- | -------- |
| Critical | CVE-2023-23914 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| Critical | CVE-2019-8457 | libdb5.3 | 5.3.28+dfsg1-0.8 | wont-fix |
| High | CVE-2022-42916 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-1304 | libext2fs2 | 1.46.2-2 | wont-fix |
| High | CVE-2022-42916 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-1304 | e2fsprogs | 1.46.2-2 | wont-fix |
| High | CVE-2022-1304 | libcom-err2 | 1.46.2-2 | wont-fix |
| High | CVE-2023-27533 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2023-27534 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2023-27533 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-43551 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-3715 | bash | 5.1-2+deb11u1 | wont-fix |
| High | CVE-2023-27534 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-43551 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2021-33560 | libgcrypt20 | 1.8.7-6 | wont-fix |
| High | CVE-2023-2953 | libldap-2.4-2 | 2.4.57+dfsg-3+deb11u1 | wont-fix |
| High | CVE-2022-1304 | libss2 | 1.46.2-2 | wont-fix |
| High | CVE-2020-22218 | libssh2-1 | 1.9.0-2 | wont-fix |
| High | CVE-2023-29491 | libtinfo6 | 6.2+20201114-2+deb11u1 | wont-fix |
| High | CVE-2022-2309 | libxml2 | 2.9.10+dfsg-6.7+deb11u4 | wont-fix |
| High | CVE-2022-4899 | libzstd1 | 1.4.8+dfsg-2.1 | wont-fix |
| High | CVE-2022-1304 | logsave | 1.46.2-2 | wont-fix |
| High | CVE-2023-29491 | ncurses-base | 6.2+20201114-2+deb11u1 | wont-fix |
| High | CVE-2023-29491 | ncurses-bin | 6.2+20201114-2+deb11u1 | wont-fix |
| High | CVE-2023-31484 | perl-base | 5.32.1-4+deb11u2 | wont-fix |
| High | CVE-2020-16156 | perl-base | 5.32.1-4+deb11u2 | wont-fix |
Vulnerability summary - 184 vulnerabilities found:
──────────────────────────────────────────────────
Image: nginx:1.22
* 3 Critical
* 35 High
* 43 Medium
* 103 Other
Most vulnerable components:
* curl (7.74.0-1.3+deb11u7) - 1 Critical, 4 High, 5 Medium, 1 Low, 3 Negligible
* libcurl4 (7.74.0-1.3+deb11u7) - 1 Critical, 4 High, 5 Medium, 1 Low, 3 Negligible
* libtiff5 (4.2.0-1+deb11u4) - 7 Medium, 10 Negligible, 2 Unknown
* libssl1.1 (1.1.1n-0+deb11u4) - 1 High, 5 Medium, 2 Negligible
* openssl (1.1.1n-0+deb11u4) - 1 High, 5 Medium, 2 Negligible
What now?
─────────
* Run with '--verbose'/'-v' flag for detailed vulnerabilities view
* Install Kubescape in your cluster for continuous monitoring and a full vulnerability report: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator
```
# Patch Command
The patch command is used for patching container images with vulnerabilities.
It uses [copa](https://github.com/project-copacetic/copacetic) and [buildkit](https://github.com/moby/buildkit) under the hood for patching the container images, and [grype](https://github.com/anchore/grype) as the engine for scanning the images (at the moment).
It uses [copa](https://github.com/project-copacetic/copacetic) and [buildkit](https://github.com/moby/buildkit) under the hood for patching the container images, and [grype](https://github.com/anchore/grype) as the engine for scanning the images.
## Usage
```bash
kubescape patch --image <image-name> [flags]
kubescape image patch <image:tag> [flags]
```
### Pre-requisites
- [docker](https://docs.docker.com/desktop/install/linux-install/#generic-installation-steps) daemon must be installed and running.
- [buildkit](https://github.com/moby/buildkit) daemon must be installed
### Running the patch command
The patch command can be run in 2 ways:
1. **With sudo privileges**
@@ -16,7 +113,7 @@ The patch command can be run in 2 ways:
```bash
sudo buildkitd &
sudo kubescape patch --image <image-name>
sudo kubescape image patch <image>
```
2. **Without sudo privileges**
@@ -34,8 +131,7 @@ The patch command can be run in 2 ways:
"moby/buildkit:$BUILDKIT_VERSION" \
--addr tcp://0.0.0.0:$BUILDKIT_PORT
kubescape patch \
-i <image-name> \
kubescape image patch <image-name> \
-a tcp://0.0.0.0:$BUILDKIT_PORT
```
@@ -43,11 +139,9 @@ The patch command can be run in 2 ways:
| Flag | Description | Required | Default |
| -------------- | ------------------------------------------------------ | -------- | ----------------------------------- |
| -i, --image | Image name to be patched (should be in canonical form) | Yes | |
| -a, --addr | Address of the buildkitd service | No | unix:///run/buildkit/buildkitd.sock |
| -t, --tag | Tag of the resultant patched image | No | image_name-patched |
| --timeout | Timeout for the patching process | No | 5m |
| --ignore-errors| Ignore errors during patching | No | false |
| -u, --username | Username for the image registry login | No | |
| -p, --password | Password for the image registry login | No | |
| -f, --format | Output file format. | No | |
@@ -60,11 +154,6 @@ The patch command can be run in 2 ways:
We will demonstrate how to use the patch command with an example of [nginx](https://www.nginx.com/) image.
### Pre-requisites
- [docker](https://docs.docker.com/desktop/install/linux-install/#generic-installation-steps) daemon must be installed and running.
- [buildkit](https://github.com/moby/buildkit) daemon must be installed
### Steps
1. Run `buildkitd` service:
@@ -73,10 +162,10 @@ We will demonstrate how to use the patch command with an example of [nginx](http
sudo buildkitd
```
2. In a seperate terminal, run the `kubescape patch` command:
2. In a seperate terminal, run the `kubescape image patch` command:
```bash
sudo kubescape patch --image docker.io/library/nginx:1.22
sudo kubescape image patch docker.io/library/nginx:1.22
```
3. You will get an output like below:

61
cmd/image/image.go Normal file
View File

@@ -0,0 +1,61 @@
package image
import (
"fmt"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/spf13/cobra"
)
type imageCredentials struct {
Username string
Password string
}
var imageCmdExamples = fmt.Sprintf(`
# Scan an image
%[1]s image scan <image>:<tag>
# Patch an image
%[1]s image patch <image>:<tag>
`, cautils.ExecName())
func GetImageCmd(ks meta.IKubescape) *cobra.Command {
var scanInfo cautils.ScanInfo
var imgCredentials imageCredentials
imageCmd := &cobra.Command{
Use: "image",
Short: "Scan or patch an image",
Example: imageCmdExamples,
}
imageCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "", `Output file format. Supported formats: "pretty-printer", "json", "sarif"`)
imageCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
imageCmd.PersistentFlags().BoolVarP(&scanInfo.VerboseMode, "verbose", "v", false, "Display full report. Default to false")
imageCmd.PersistentFlags().StringVarP(&scanInfo.FailThresholdSeverity, "severity-threshold", "s", "", "Severity threshold is the severity of a vulnerability at which the command fails and returns exit code 1")
imageCmd.PersistentFlags().StringVarP(&imgCredentials.Username, "username", "u", "", "Username for registry login")
imageCmd.PersistentFlags().StringVarP(&imgCredentials.Password, "password", "p", "", "Password for registry login")
imageCmd.AddCommand(getScanCmd(ks, &scanInfo, &imgCredentials))
imageCmd.AddCommand(getPatchCmd(ks, &scanInfo, &imgCredentials))
imageCmd.SetHelpFunc(func(command *cobra.Command, strings []string) {
// hide kube-context and server flags
command.Flags().MarkHidden("kube-context")
command.Flags().MarkHidden("server")
// this will prevent an infinite recursive call for the sub-commands of 'image'
if parent := command.Parent().Parent(); parent != nil {
parent.HelpFunc()(command, strings)
return
}
command.Parent().HelpFunc()(command, strings)
})
return imageCmd
}

View File

@@ -1,81 +1,78 @@
package patch
package image
import (
"context"
"errors"
"fmt"
"strings"
"time"
ref "github.com/distribution/distribution/reference"
"github.com/docker/distribution/reference"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/pkg/imagescan"
"github.com/kubescape/kubescape/v2/cmd/utils"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v2/pkg/imagescan"
"github.com/spf13/cobra"
)
var patchCmdExamples = fmt.Sprintf(`
# Patch the nginx:1.22 image
1) sudo buildkitd # start buildkitd service, run in seperate terminal
2) sudo %[1]s patch --image docker.io/library/nginx:1.22 # patch the image
2) sudo %[1]s patch nginx:1.22 # patch the image
# The patch command can also be run without sudo privileges
# Documentation: https://github.com/kubescape/kubescape/tree/master/cmd/patch
# Documentation: https://github.com/kubescape/kubescape/tree/master/cmd/image/README.md
`, cautils.ExecName())
func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
var patchInfo metav1.PatchInfo
var scanInfo cautils.ScanInfo
func getPatchCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgCredentials *imageCredentials) *cobra.Command {
var patchInfo metav1.PatchInfo
patchCmd := &cobra.Command{
Use: "patch --image <image>:<tag> [flags]",
Short: "Patch container images with vulnerabilities",
Long: `Patch command is for automatically patching images with vulnerabilities.`,
Use: "patch <image>:<tag> [flags]",
Short: "Patch container images with vulnerabilities ",
Long: `Automatically patch container images with vulnerabilities`,
Example: patchCmdExamples,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return fmt.Errorf("the command takes no arguments")
if len(args) != 1 {
return fmt.Errorf("the command takes exactly one image name as an argument")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := shared.ValidateImageScanInfo(&scanInfo); err != nil {
if err := validateImageScanInfo(scanInfo); err != nil {
return err
}
patchInfo.Username = imgCredentials.Username
patchInfo.Password = imgCredentials.Password
patchInfo.Image = args[0]
if err := validateImagePatchInfo(&patchInfo); err != nil {
return err
}
results, err := ks.Patch(&patchInfo, &scanInfo)
results, err := ks.Patch(context.Background(), &patchInfo, scanInfo)
if err != nil {
return err
}
if imagescan.ExceedsSeverityThreshold(results, imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)) {
shared.TerminateOnExceedingSeverity(&scanInfo, logger.L())
utils.TerminateOnExceedingSeverity(scanInfo, logger.L())
}
return nil
},
}
patchCmd.PersistentFlags().StringVarP(&patchInfo.Image, "image", "i", "", "Application image name and tag to patch")
patchCmd.PersistentFlags().StringVarP(&patchInfo.PatchedImageTag, "tag", "t", "", "Tag for the patched image. Defaults to '<image-tag>-patched' ")
patchCmd.PersistentFlags().StringVarP(&patchInfo.BuildkitAddress, "address", "a", "unix:///run/buildkit/buildkitd.sock", "Address of buildkitd service, defaults to local buildkitd.sock")
patchCmd.PersistentFlags().DurationVar(&patchInfo.Timeout, "timeout", 5*time.Minute, "Timeout for the operation, defaults to '5m'")
patchCmd.PersistentFlags().BoolVar(&patchInfo.IgnoreError, "ignore-errors", false, "Ignore errors and continue patching other images. Default to false")
patchCmd.PersistentFlags().StringVarP(&patchInfo.Username, "username", "u", "", "Username for registry login")
patchCmd.PersistentFlags().StringVarP(&patchInfo.Password, "password", "p", "", "Password for registry login")
patchCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "", `Output file format. Supported formats: "pretty-printer", "json", "sarif"`)
patchCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
patchCmd.PersistentFlags().BoolVarP(&scanInfo.VerboseMode, "verbose", "v", false, "Display full report. Default to false")
patchCmd.PersistentFlags().StringVarP(&scanInfo.FailThresholdSeverity, "severity-threshold", "s", "", "Severity threshold is the severity of a vulnerability at which the command fails and returns exit code 1")
return patchCmd
}
@@ -94,22 +91,22 @@ func validateImagePatchInfo(patchInfo *metav1.PatchInfo) error {
}
// Parse the image full name to get image name and tag
named, err := reference.ParseNamed(patchInfoImage)
named, err := ref.ParseNamed(patchInfoImage)
if err != nil {
return err
}
// If no tag or digest is provided, default to 'latest'
if reference.IsNameOnly(named) {
if ref.IsNameOnly(named) {
logger.L().Warning("Image name has no tag or digest, using latest as tag")
named = reference.TagNameOnly(named)
named = ref.TagNameOnly(named)
}
patchInfo.Image = named.String()
// If no patched image tag is provided, default to '<image-tag>-patched'
if patchInfo.PatchedImageTag == "" {
taggedName, ok := named.(reference.Tagged)
taggedName, ok := named.(ref.Tagged)
if !ok {
return errors.New("unexpected error while parsing image tag")
}

73
cmd/image/scan.go Normal file
View File

@@ -0,0 +1,73 @@
package image
import (
"context"
"fmt"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/cmd/utils"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v2/pkg/imagescan"
"github.com/spf13/cobra"
)
// TODO(vladklokun): document image scanning on the Kubescape Docs Hub?
var (
imageExample = fmt.Sprintf(`
Scan an image for vulnerabilities.
# Scan the 'nginx' image
%[1]s image scan "nginx"
`, cautils.ExecName())
)
// imageCmd represents the image command
func getScanCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgCredentials *imageCredentials) *cobra.Command {
cmd := &cobra.Command{
Use: "scan <image>:<tag> [flags]",
Short: "Scan container images for vulnerabilities",
Example: imageExample,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("the command takes exactly one image name as an argument")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateImageScanInfo(scanInfo); err != nil {
return err
}
imgScanInfo := &metav1.ImageScanInfo{
Image: args[0],
Username: imgCredentials.Username,
Password: imgCredentials.Password,
}
results, err := ks.ScanImage(context.Background(), imgScanInfo, scanInfo)
if err != nil {
return err
}
if imagescan.ExceedsSeverityThreshold(results, imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)) {
utils.TerminateOnExceedingSeverity(scanInfo, logger.L())
}
return nil
},
}
return cmd
}
// validateImageScanInfo validates the ScanInfo struct for the `image` command
func validateImageScanInfo(scanInfo *cautils.ScanInfo) error {
severity := scanInfo.FailThresholdSeverity
if err := utils.ValidateSeverity(severity); severity != "" && err != nil {
return err
}
return nil
}

View File

@@ -1,17 +1,17 @@
package list
import (
"errors"
"context"
"fmt"
"slices"
"strings"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/core"
"github.com/kubescape/kubescape/v3/core/meta"
v1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/core"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)
var (
@@ -55,20 +55,15 @@ 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(&listPolicies); err != nil {
if err := ks.List(context.TODO(), &listPolicies); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
listCmd.PersistentFlags().StringVarP(&listPolicies.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID 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")

View File

@@ -1,44 +0,0 @@
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)
}

View File

@@ -1,56 +0,0 @@
package operator
import (
"fmt"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/core"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/spf13/cobra"
)
var operatorScanConfigExamples = fmt.Sprintf(`
# Run a configuration scan
%[1]s operator scan configurations
`, cautils.ExecName())
func getOperatorScanConfigCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command {
configCmd := &cobra.Command{
Use: "configurations",
Short: "Trigger configuration scanning from the Kubescape Operator microservice",
Long: ``,
Example: operatorScanConfigExamples,
Args: func(cmd *cobra.Command, args []string) error {
operatorInfo.Subcommands = append(operatorInfo.Subcommands, "config")
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo, operatorInfo.Namespace)
if err != nil {
return err
}
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))
return err
}
logger.L().StopSuccess("Triggered Kubescape Operator for configuration scanning")
return nil
},
}
configScanInfo := &cautils.ConfigScanInfo{}
operatorInfo.OperatorScanInfo = configScanInfo
configCmd.PersistentFlags().StringSliceVar(&configScanInfo.IncludedNamespaces, "include-namespaces", nil, "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
configCmd.PersistentFlags().StringSliceVar(&configScanInfo.ExcludedNamespaces, "exclude-namespaces", nil, "Namespaces to exclude from scanning. e.g: --exclude-namespaces ns-a,ns-b. Notice, when running with `exclude-namespace` kubescape does not scan cluster-scoped objects.")
configCmd.PersistentFlags().StringSliceVar(&configScanInfo.Frameworks, "frameworks", nil, "Load frameworks for configuration scanning")
configCmd.PersistentFlags().BoolVarP(&configScanInfo.HostScanner, "enable-host-scan", "", false, "Deploy Kubescape host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valuable data from cluster nodes for certain controls. Yaml file: https://github.com/kubescape/kubescape/blob/master/core/pkg/hostsensorutils/hostsensor.yaml")
return configCmd
}

View File

@@ -1,32 +0,0 @@
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)
}

View File

@@ -1,55 +0,0 @@
package operator
import (
"errors"
"fmt"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/spf13/cobra"
)
const (
scanSubCommand string = "scan"
)
var operatorExamples = fmt.Sprintf(`
# Trigger a configuration scan
%[1]s operator scan configurations
# Trigger a vulnerabilities scan
%[1]s operator scan vulnerabilities
`, cautils.ExecName())
func GetOperatorCmd(ks meta.IKubescape) *cobra.Command {
var operatorInfo cautils.OperatorInfo
operatorCmd := &cobra.Command{
Use: "operator",
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 {
operatorInfo.Subcommands = append(operatorInfo.Subcommands, "operator")
if len(args) < 2 {
return errors.New("For the operator sub-command, you need to provide at least one additional sub-command. Refer to the examples above.")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("For the operator sub-command, you need to provide at least one additional sub-command. Refer to the examples above.")
}
if args[0] != scanSubCommand {
return errors.New(fmt.Sprintf("For the operator sub-command, only %s is supported. Refer to the examples above.", scanSubCommand))
}
return nil
},
}
operatorCmd.AddCommand(getOperatorScanCmd(ks, operatorInfo))
return operatorCmd
}

View File

@@ -1,42 +0,0 @@
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())
}

View File

@@ -1,46 +0,0 @@
package operator
import (
"errors"
"fmt"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/spf13/cobra"
)
const (
vulnerabilitiesSubCommand string = "vulnerabilities"
configurationsSubCommand string = "configurations"
)
func getOperatorScanCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command {
operatorCmd := &cobra.Command{
Use: "scan",
Short: "Scan your cluster using the Kubescape-operator within the cluster components",
Long: ``,
Example: operatorExamples,
Args: func(cmd *cobra.Command, args []string) error {
operatorInfo.Subcommands = append(operatorInfo.Subcommands, "scan")
if len(args) < 1 {
return errors.New("for operator scan sub command, you must pass at least 1 more sub commands, see above examples")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("for operator scan sub command, you must pass at least 1 more sub commands, see above examples")
}
if (args[0] != vulnerabilitiesSubCommand) && (args[0] != configurationsSubCommand) {
return errors.New(fmt.Sprintf("For the operator sub-command, only %s and %s are supported. Refer to the examples above.", vulnerabilitiesSubCommand, configurationsSubCommand))
}
return nil
},
}
operatorCmd.PersistentFlags().StringVar(&operatorInfo.Namespace, "namespace", "kubescape", "namespace of the Kubescape Operator")
operatorCmd.AddCommand(getOperatorScanConfigCmd(ks, operatorInfo))
operatorCmd.AddCommand(getOperatorScanVulnerabilitiesCmd(ks, operatorInfo))
return operatorCmd
}

View File

@@ -1,46 +0,0 @@
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())
}

View File

@@ -1,56 +0,0 @@
package operator
import (
"fmt"
"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/core"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/spf13/cobra"
)
var operatorScanVulnerabilitiesExamples = fmt.Sprintf(`
# Trigger a vulnerabilities scan
%[1]s operator scan vulnerabilities
`, cautils.ExecName())
func getOperatorScanVulnerabilitiesCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command {
configCmd := &cobra.Command{
Use: "vulnerabilities",
Short: "Vulnerabilities use for scan your cluster vulnerabilities using Kubescape operator in the in cluster components",
Long: ``,
Example: operatorScanVulnerabilitiesExamples,
Args: func(cmd *cobra.Command, args []string) error {
operatorInfo.Subcommands = append(operatorInfo.Subcommands, "vulnerabilities")
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo, operatorInfo.Namespace)
if err != nil {
return err
}
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))
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\"")
return err
},
}
vulnerabilitiesScanInfo := &cautils.VulnerabilitiesScanInfo{
ClusterName: k8sinterface.GetContextName(),
}
operatorInfo.OperatorScanInfo = vulnerabilitiesScanInfo
configCmd.PersistentFlags().StringSliceVar(&vulnerabilitiesScanInfo.IncludeNamespaces, "include-namespaces", nil, "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
return configCmd
}

View File

@@ -1,29 +0,0 @@
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)
}

View File

@@ -1,52 +0,0 @@
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())
}

View File

@@ -1,46 +0,0 @@
package prerequisites
import (
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/kubescape/sizing-checker/pkg/checks/connectivitycheck"
"github.com/kubescape/sizing-checker/pkg/checks/ebpfcheck"
"github.com/kubescape/sizing-checker/pkg/checks/pvcheck"
"github.com/kubescape/sizing-checker/pkg/checks/sizing"
"github.com/kubescape/sizing-checker/pkg/common"
"github.com/spf13/cobra"
)
func GetPreReqCmd(ks meta.IKubescape) *cobra.Command {
// preReqCmd represents the prerequisites command
preReqCmd := &cobra.Command{
Use: "prerequisites",
Short: "Check prerequisites for installing Kubescape Operator",
Run: func(cmd *cobra.Command, args []string) {
clientSet, inCluster := common.BuildKubeClient()
if clientSet == nil {
logger.L().Fatal("Could not create kube client. Exiting.")
}
// 1) Collect cluster data
clusterData, err := common.CollectClusterData(ks.Context(), clientSet)
if err != nil {
logger.L().Error("Failed to collect cluster data", helpers.Error(err))
}
// 2) Run checks
sizingResult := sizing.RunSizingChecker(clusterData)
pvResult := pvcheck.RunPVProvisioningCheck(ks.Context(), clientSet, clusterData, inCluster)
connectivityResult := connectivitycheck.RunConnectivityChecks(ks.Context(), clientSet, clusterData, inCluster)
ebpfResult := ebpfcheck.RunEbpfCheck(ks.Context(), clientSet, clusterData, inCluster)
// 3) Build and export the final ReportData
finalReport := common.BuildReportData(clusterData, sizingResult, pvResult, connectivityResult, ebpfResult)
finalReport.InCluster = inCluster
common.GenerateOutput(finalReport, inCluster)
},
}
return preReqCmd
}

View File

@@ -1,29 +1,26 @@
package cmd
import (
"context"
"fmt"
"strings"
"github.com/kubescape/go-logger"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v3/cmd/completion"
"github.com/kubescape/kubescape/v3/cmd/config"
"github.com/kubescape/kubescape/v3/cmd/download"
"github.com/kubescape/kubescape/v3/cmd/fix"
"github.com/kubescape/kubescape/v3/cmd/list"
"github.com/kubescape/kubescape/v3/cmd/operator"
"github.com/kubescape/kubescape/v3/cmd/patch"
"github.com/kubescape/kubescape/v3/cmd/prerequisites"
"github.com/kubescape/kubescape/v3/cmd/scan"
"github.com/kubescape/kubescape/v3/cmd/update"
"github.com/kubescape/kubescape/v3/cmd/vap"
"github.com/kubescape/kubescape/v3/cmd/version"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
"github.com/kubescape/kubescape/v3/core/core"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/kubescape/kubescape/v2/cmd/completion"
"github.com/kubescape/kubescape/v2/cmd/config"
"github.com/kubescape/kubescape/v2/cmd/download"
"github.com/kubescape/kubescape/v2/cmd/fix"
"github.com/kubescape/kubescape/v2/cmd/image"
"github.com/kubescape/kubescape/v2/cmd/list"
"github.com/kubescape/kubescape/v2/cmd/scan"
"github.com/kubescape/kubescape/v2/cmd/update"
"github.com/kubescape/kubescape/v2/cmd/version"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/core"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/spf13/cobra"
)
@@ -43,8 +40,8 @@ var ksExamples = fmt.Sprintf(`
%[1]s config view
`, cautils.ExecName())
func NewDefaultKubescapeCommand(ctx context.Context) *cobra.Command {
ks := core.NewKubescape(ctx)
func NewDefaultKubescapeCommand() *cobra.Command {
ks := core.NewKubescape()
return getRootCmd(ks)
}
@@ -73,7 +70,7 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd.SetUsageTemplate(newUsageTemplate)
}
rootCmd.PersistentFlags().StringVar(&rootInfo.DiscoveryServerURL, "server", "", "Backend discovery server URL")
rootCmd.PersistentFlags().StringVar(&rootInfo.DiscoveryServerURL, "server", "api.armosec.io", "Backend discovery server URL") // TODO: remove default value
rootCmd.PersistentFlags().MarkDeprecated("environment", "'environment' is no longer supported, Use 'server' instead. Feel free to contact the Kubescape maintainers for more information.")
rootCmd.PersistentFlags().MarkDeprecated("env", "'env' is no longer supported, Use 'server' instead. Feel free to contact the Kubescape maintainers for more information.")
@@ -85,6 +82,8 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd.PersistentFlags().StringVarP(&rootInfo.Logger, "logger", "l", helpers.InfoLevel.String(), fmt.Sprintf("Logger level. Supported: %s [$KS_LOGGER]", strings.Join(helpers.SupportedLevels(), "/")))
rootCmd.PersistentFlags().StringVar(&rootInfo.CacheDir, "cache-dir", getter.DefaultLocalStore, "Cache directory [$KS_CACHE_DIR]")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable color output for logging")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.EnableColor, "enable-color", "", false, "Force enable color output for logging")
rootCmd.PersistentFlags().StringVarP(&rootInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
// Supported commands
@@ -92,14 +91,11 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd.AddCommand(download.GetDownloadCmd(ks))
rootCmd.AddCommand(list.GetListCmd(ks))
rootCmd.AddCommand(completion.GetCompletionCmd())
rootCmd.AddCommand(version.GetVersionCmd(ks))
rootCmd.AddCommand(version.GetVersionCmd())
rootCmd.AddCommand(config.GetConfigCmd(ks))
rootCmd.AddCommand(update.GetUpdateCmd(ks))
rootCmd.AddCommand(update.GetUpdateCmd())
rootCmd.AddCommand(fix.GetFixCmd(ks))
rootCmd.AddCommand(patch.GetPatchCmd(ks))
rootCmd.AddCommand(vap.GetVapHelperCmd())
rootCmd.AddCommand(operator.GetOperatorCmd(ks))
rootCmd.AddCommand(prerequisites.GetPreReqCmd(ks))
rootCmd.AddCommand(image.GetImageCmd(ks))
// deprecated commands
rootCmd.AddCommand(&cobra.Command{
@@ -114,7 +110,7 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
return rootCmd
}
func Execute(ctx context.Context) error {
ks := NewDefaultKubescapeCommand(ctx)
func Execute() error {
ks := NewDefaultKubescapeCommand()
return ks.Execute()
}

View File

@@ -7,17 +7,21 @@ import (
v1 "github.com/kubescape/backend/pkg/client/v1"
"github.com/kubescape/backend/pkg/servicediscovery"
sdClientV2 "github.com/kubescape/backend/pkg/servicediscovery/v2"
"github.com/kubescape/go-logger"
sdClientV1 "github.com/kubescape/backend/pkg/servicediscovery/v1"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/go-logger/iconlogger"
"github.com/kubescape/go-logger/zaplogger"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/mattn/go-isatty"
)
func initLogger() {
logger.DisableColor(rootInfo.DisableColor)
logger.EnableColor(rootInfo.EnableColor)
if rootInfo.LoggerName == "" {
if l := os.Getenv("KS_LOGGER_NAME"); l != "" {
rootInfo.LoggerName = l
@@ -31,8 +35,8 @@ func initLogger() {
}
logger.InitLogger(rootInfo.LoggerName)
}
}
func initLoggerLevel() {
if rootInfo.Logger == helpers.InfoLevel.String() {
} else if l := os.Getenv("KS_LOGGER"); l != "" {
@@ -62,7 +66,7 @@ func initEnvironment() {
logger.L().Debug("fetching URLs from service discovery server", helpers.String("server", rootInfo.DiscoveryServerURL))
client, err := sdClientV2.NewServiceDiscoveryClientV2(rootInfo.DiscoveryServerURL)
client, err := sdClientV1.NewServiceDiscoveryClientV1(rootInfo.DiscoveryServerURL)
if err != nil {
logger.L().Fatal("failed to create service discovery client", helpers.Error(err), helpers.String("server", rootInfo.DiscoveryServerURL))
return
@@ -73,13 +77,13 @@ func initEnvironment() {
)
if err != nil {
logger.L().Fatal("failed to get services from server", helpers.Error(err), helpers.String("server", rootInfo.DiscoveryServerURL))
logger.L().Fatal("failed to to get services from server", helpers.Error(err), helpers.String("server", rootInfo.DiscoveryServerURL))
return
}
logger.L().Debug("configuring service discovery URLs", helpers.String("cloudAPIURL", services.GetApiServerUrl()), helpers.String("cloudReportURL", services.GetReportReceiverHttpUrl()))
tenant := cautils.GetTenantConfig("", "", "", "", nil)
tenant := cautils.GetTenantConfig("", "", "", nil)
if services.GetApiServerUrl() != "" {
tenant.GetConfigObj().CloudAPIURL = services.GetApiServerUrl()
}
@@ -95,7 +99,6 @@ func initEnvironment() {
services.GetApiServerUrl(),
services.GetReportReceiverHttpUrl(),
"",
"",
)
if err != nil {
logger.L().Fatal("failed to create KS Cloud client", helpers.Error(err))

View File

@@ -1,17 +1,20 @@
package scan
import (
"context"
"fmt"
"io"
"os"
"strings"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/cmd/utils"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/spf13/cobra"
)
@@ -95,11 +98,12 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
return err
}
results, err := ks.Scan(scanInfo)
ctx := context.TODO()
results, err := ks.Scan(ctx, scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
if err := results.HandleResults(ks.Context()); err != nil {
if err := results.HandleResults(ctx); err != nil {
logger.L().Fatal(err.Error())
}
if !scanInfo.VerboseMode {
@@ -111,7 +115,7 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
if results.GetComplianceScore() < float32(scanInfo.ComplianceThreshold) {
logger.L().Fatal("scan compliance-score is below permitted threshold", helpers.String("compliance score", fmt.Sprintf("%.2f", results.GetComplianceScore())), helpers.String("compliance-threshold", fmt.Sprintf("%.2f", scanInfo.ComplianceThreshold)))
}
enforceSeverityThresholds(results.GetResults().SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
enforceSeverityThresholds(results.GetResults().SummaryDetails.GetResourcesSeverityCounters(), scanInfo, utils.TerminateOnExceedingSeverity)
return nil
},
@@ -126,7 +130,7 @@ func validateControlScanInfo(scanInfo *cautils.ScanInfo) error {
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
}
if err := shared.ValidateSeverity(severity); severity != "" && err != nil {
if err := utils.ValidateSeverity(severity); severity != "" && err != nil {
return err
}
return nil

View File

@@ -1,60 +0,0 @@
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())
}

View File

@@ -1,22 +1,25 @@
package scan
import (
"context"
"errors"
"fmt"
"io"
"os"
"slices"
"strings"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
"github.com/kubescape/kubescape/v3/core/meta"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
reporthandlingapis "github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"golang.org/x/exp/slices"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/cmd/utils"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/spf13/cobra"
)
@@ -91,7 +94,7 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
}
if len(args) > 1 {
if args[1] != "-" {
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
logger.L().Debug("List of input files", helpers.Interface("patterns", scanInfo.InputPatterns))
} else { // store stdin to file - do NOT move to separate function !!
@@ -109,18 +112,23 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
}
}
scanInfo.SetScanType(cautils.ScanTypeFramework)
scanInfo.FrameworkScan = true
scanInfo.SetPolicyIdentifiers(frameworks, apisv1.KindFramework)
results, err := ks.Scan(scanInfo)
ctx := context.TODO()
results, err := ks.Scan(ctx, scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
if err = results.HandleResults(ks.Context()); err != nil {
if err = results.HandleResults(ctx); err != nil {
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)))
}
@@ -128,17 +136,16 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
logger.L().Fatal("scan compliance-score is below permitted threshold", helpers.String("compliance-score", fmt.Sprintf("%.2f", results.GetComplianceScore())), helpers.String("compliance-threshold", fmt.Sprintf("%.2f", scanInfo.ComplianceThreshold)))
}
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), scanInfo, utils.TerminateOnExceedingSeverity)
return nil
},
}
}
// countersExceedSeverityThreshold returns true if severity of failed controls exceed the set severity threshold, else returns false
func countersExceedSeverityThreshold(severityCounters reportsummary.ISeverityCounters, scanInfo *cautils.ScanInfo) (bool, error) {
targetSeverity := scanInfo.FailThresholdSeverity
if err := shared.ValidateSeverity(targetSeverity); err != nil {
if err := utils.ValidateSeverity(targetSeverity); err != nil {
return false, err
}
@@ -171,11 +178,6 @@ func countersExceedSeverityThreshold(severityCounters reportsummary.ISeverityCou
}
// terminateOnExceedingSeverity terminates the application on exceeding severity
func terminateOnExceedingSeverity(scanInfo *cautils.ScanInfo, l helpers.ILogger) {
l.Fatal("compliance result exceeds severity threshold", helpers.String("set severity threshold", scanInfo.FailThresholdSeverity))
}
// enforceSeverityThresholds ensures that the scan results are below the defined severity threshold
//
// The function forces the application to terminate with an exit code 1 if at least one control failed control that exceeds the set severity threshold
@@ -195,7 +197,7 @@ func enforceSeverityThresholds(severityCounters reportsummary.ISeverityCounters,
// validateFrameworkScanInfo validates the scan info struct for the `scan framework` command
func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
if scanInfo.View == string(cautils.SecurityViewType) {
scanInfo.View = string(cautils.ResourceViewType)
return ErrSecurityViewNotSupported
}
if scanInfo.Submit && scanInfo.Local {
@@ -211,7 +213,7 @@ func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
return ErrOmitRawResourcesOrSubmit
}
severity := scanInfo.FailThresholdSeverity
if err := shared.ValidateSeverity(severity); severity != "" && err != nil {
if err := utils.ValidateSeverity(severity); severity != "" && err != nil {
return err
}

View File

@@ -1,60 +0,0 @@
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())
}

View File

@@ -1,82 +0,0 @@
package scan
import (
"fmt"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/pkg/imagescan"
"github.com/spf13/cobra"
)
// TODO(vladklokun): document image scanning on the Kubescape Docs Hub?
var (
imageExample = fmt.Sprintf(`
Scan an image for vulnerabilities.
# Scan the 'nginx' image
%[1]s scan image "nginx"
# Scan the 'nginx' image and see the full report
%[1]s scan image "nginx" -v
# Scan the 'nginx' image and use exceptions
%[1]s scan image "nginx" --exceptions exceptions.json
`, cautils.ExecName())
)
// getImageCmd returns the scan image command
func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
var imgCredentials shared.ImageCredentials
var exceptions string
cmd := &cobra.Command{
Use: "image <image>:<tag> [flags]",
Short: "Scan an image for vulnerabilities",
Example: imageExample,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("the command takes exactly one image name as an argument")
}
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
}
imgScanInfo := &metav1.ImageScanInfo{
Image: args[0],
Username: imgCredentials.Username,
Password: imgCredentials.Password,
Exceptions: exceptions,
}
results, err := ks.ScanImage(imgScanInfo, scanInfo)
if err != nil {
return err
}
if imagescan.ExceedsSeverityThreshold(results, imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)) {
shared.TerminateOnExceedingSeverity(scanInfo, logger.L())
}
return nil
},
}
// The exceptions flag
cmd.PersistentFlags().StringVarP(&exceptions, "exceptions", "", "", "Path to the exceptions file")
cmd.PersistentFlags().StringVarP(&imgCredentials.Username, "username", "u", "", "Username for registry login")
cmd.PersistentFlags().StringVarP(&imgCredentials.Password, "password", "p", "", "Password for registry login")
return cmd
}

View File

@@ -1,35 +0,0 @@
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())
}

View File

@@ -1,25 +1,26 @@
package scan
import (
"context"
"flag"
"fmt"
"strings"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/kubescape/kubescape/v2/cmd/utils"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/spf13/cobra"
)
var scanCmdExamples = fmt.Sprintf(`
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
# Scan current cluster
%[1]s scan
# Scan kubernetes manifest files
# Scan kubernetes manifest files
%[1]s scan .
# Scan and save the results in the JSON format
@@ -28,7 +29,7 @@ var scanCmdExamples = fmt.Sprintf(`
# Display all resources
%[1]s scan --verbose
# Scan different clusters from the kubectl context
# Scan different clusters from the kubectl context
%[1]s scan --kube-context <kubernetes context>
`, cautils.ExecName())
@@ -45,17 +46,12 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
if scanInfo.View == string(cautils.SecurityViewType) {
setSecurityViewScanInfo(args, &scanInfo)
if err := securityScan(scanInfo, ks); err != nil {
logger.L().Fatal(err.Error())
}
} else if len(args) == 0 || (args[0] != "framework" && args[0] != "control") {
if err := getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{strings.Join(getter.NativeFrameworks, ",")}, args...)); err != nil {
logger.L().Fatal(err.Error())
}
} else {
return fmt.Errorf("kubescape did not do anything")
return securityScan(scanInfo, ks)
}
if len(args) == 0 || (args[0] != "framework" && args[0] != "control") {
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{strings.Join(getter.NativeFrameworks, ",")}, args...))
}
return nil
},
PostRun: func(cmd *cobra.Command, args []string) {
@@ -63,10 +59,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
},
}
scanInfo.TriggeredByCLI = true
scanCmd.PersistentFlags().StringVarP(&scanInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
scanCmd.PersistentFlags().StringVarP(&scanInfo.AccessKey, "access-key", "", "", "Kubescape SaaS access key. Default will load access key from cache")
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
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")
@@ -76,12 +69,12 @@ 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", "pretty-printer", `Output file format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html", "sarif"`)
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "", `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")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.VerboseMode, "verbose", "v", false, "Display all of the input resources and not only failed resources")
scanCmd.PersistentFlags().StringVar(&scanInfo.View, "view", string(cautils.SecurityViewType), fmt.Sprintf("View results based on the %s/%s/%s. default is --view=%s", cautils.ResourceViewType, cautils.ControlViewType, cautils.SecurityViewType, cautils.SecurityViewType))
scanCmd.PersistentFlags().StringVar(&scanInfo.View, "view", string(cautils.ResourceViewType), fmt.Sprintf("View results based on the %s/%s/%s. default is --view=%s", cautils.ResourceViewType, cautils.ControlViewType, cautils.SecurityViewType, cautils.ResourceViewType))
scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest")
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest")
scanCmd.PersistentFlags().StringVar(&scanInfo.HostSensorYamlPath, "host-scan-yaml", "", "Override default host scanner DaemonSet. Use this flag cautiously")
@@ -90,16 +83,18 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Submit the scan results to Kubescape SaaS where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not submitted")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.OmitRawResources, "omit-raw-resources", "", false, "Omit raw resources from the output. By default the raw resources are included in the output")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.PrintAttackTree, "print-attack-tree", "", false, "Print attack tree")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.EnableRegoPrint, "enable-rego-prints", "", false, "Enable sending to rego prints to the logs (use with debug log level: -l debug)")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.ScanImages, "scan-images", "", false, "Scan resources images")
scanCmd.PersistentFlags().MarkDeprecated("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"))
@@ -108,17 +103,15 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
hostF.NoOptDefVal = "true"
hostF.DefValue = "false, for no TTY in stdin"
scanCmd.PersistentFlags().MarkHidden("enable-host-scan")
scanCmd.PersistentFlags().MarkDeprecated("enable-host-scan", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator. The flag will be removed at 1.Dec.2023")
scanCmd.PersistentFlags().MarkDeprecated("enable-host-scan", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-cloud-operator. The flag will be removed at 1.Dec.2023")
scanCmd.PersistentFlags().MarkHidden("host-scan-yaml") // this flag should be used very cautiously. We prefer users will not use it at all unless the DaemonSet can not run pods on the nodes
scanCmd.PersistentFlags().MarkDeprecated("host-scan-yaml", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator. The flag will be removed at 1.Dec.2023")
scanCmd.PersistentFlags().MarkDeprecated("host-scan-yaml", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-cloud-operator. The flag will be removed at 1.Dec.2023")
scanCmd.AddCommand(getControlCmd(ks, &scanInfo))
scanCmd.AddCommand(getFrameworkCmd(ks, &scanInfo))
scanCmd.AddCommand(getWorkloadCmd(ks, &scanInfo))
scanCmd.AddCommand(getImageCmd(ks, &scanInfo))
return scanCmd
}
@@ -126,24 +119,26 @@ func setSecurityViewScanInfo(args []string, scanInfo *cautils.ScanInfo) {
if len(args) > 0 {
scanInfo.SetScanType(cautils.ScanTypeRepo)
scanInfo.InputPatterns = args
scanInfo.SetPolicyIdentifiers([]string{"workloadscan", "allcontrols"}, v1.KindFramework)
} else {
scanInfo.SetScanType(cautils.ScanTypeCluster)
scanInfo.SetPolicyIdentifiers([]string{"clusterscan", "mitre", "nsa"}, v1.KindFramework)
}
scanInfo.SetPolicyIdentifiers([]string{"clusterscan", "mitre", "nsa"}, v1.KindFramework)
}
func securityScan(scanInfo cautils.ScanInfo, ks meta.IKubescape) error {
results, err := ks.Scan(&scanInfo)
ctx := context.TODO()
results, err := ks.Scan(ctx, &scanInfo)
if err != nil {
return err
}
if err = results.HandleResults(ks.Context()); err != nil {
if err = results.HandleResults(ctx); err != nil {
return err
}
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), &scanInfo, terminateOnExceedingSeverity)
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), &scanInfo, utils.TerminateOnExceedingSeverity)
return nil
}

View File

@@ -1,19 +1,15 @@
package scan
import (
"context"
"os"
"reflect"
"testing"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/mocks"
"github.com/kubescape/kubescape/v2/cmd/utils"
"github.com/kubescape/kubescape/v2/core/cautils"
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"
"github.com/stretchr/testify/assert"
"testing"
)
func TestExceedsSeverity(t *testing.T) {
@@ -113,7 +109,7 @@ func TestExceedsSeverity(t *testing.T) {
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "unknown"},
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
Want: false,
Error: shared.ErrUnknownSeverity,
Error: utils.ErrUnknownSeverity,
},
}
@@ -177,89 +173,6 @@ func Test_enforceSeverityThresholds(t *testing.T) {
}
}
type spyLogMessage struct {
Message string
Details map[string]string
}
type spyLogger struct {
setItems []spyLogMessage
}
func (l *spyLogger) Error(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Success(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Warning(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Info(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Debug(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) SetLevel(level string) error { return nil }
func (l *spyLogger) GetLevel() string { return "" }
func (l *spyLogger) SetWriter(w *os.File) {}
func (l *spyLogger) GetWriter() *os.File { return &os.File{} }
func (l *spyLogger) LoggerName() string { return "" }
func (l *spyLogger) Ctx(_ context.Context) helpers.ILogger { return l }
func (l *spyLogger) Start(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) StopSuccess(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) StopError(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Fatal(msg string, details ...helpers.IDetails) {
firstDetail := details[0]
detailsMap := map[string]string{firstDetail.Key(): firstDetail.Value().(string)}
newMsg := spyLogMessage{msg, detailsMap}
l.setItems = append(l.setItems, newMsg)
}
func (l *spyLogger) GetSpiedItems() []spyLogMessage {
return l.setItems
}
func Test_terminateOnExceedingSeverity(t *testing.T) {
expectedMessage := "compliance result exceeds severity threshold"
expectedKey := "set severity threshold"
testCases := []struct {
Description string
ExpectedMessage string
ExpectedKey string
ExpectedValue string
Logger *spyLogger
}{
{
"Should log the Critical threshold that was set in scan info",
expectedMessage,
expectedKey,
apis.SeverityCriticalString,
&spyLogger{},
},
{
"Should log the High threshold that was set in scan info",
expectedMessage,
expectedKey,
apis.SeverityHighString,
&spyLogger{},
},
}
for _, tc := range testCases {
t.Run(
tc.Description,
func(t *testing.T) {
want := []spyLogMessage{
{tc.ExpectedMessage, map[string]string{tc.ExpectedKey: tc.ExpectedValue}},
}
scanInfo := &cautils.ScanInfo{FailThresholdSeverity: tc.ExpectedValue}
terminateOnExceedingSeverity(scanInfo, tc.Logger)
got := tc.Logger.GetSpiedItems()
if !reflect.DeepEqual(got, want) {
t.Errorf("got: %v, want: %v", got, want)
}
},
)
}
}
func TestSetSecurityViewScanInfo(t *testing.T) {
tests := []struct {
name string
@@ -303,11 +216,15 @@ func TestSetSecurityViewScanInfo(t *testing.T) {
PolicyIdentifier: []cautils.PolicyIdentifier{
{
Kind: v1.KindFramework,
Identifier: "workloadscan",
Identifier: "clusterscan",
},
{
Kind: v1.KindFramework,
Identifier: "allcontrols",
Identifier: "mitre",
},
{
Kind: v1.KindFramework,
Identifier: "nsa",
},
},
},
@@ -358,16 +275,3 @@ 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)
}

View File

@@ -3,8 +3,8 @@ package scan
import (
"testing"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v2/cmd/utils"
"github.com/kubescape/kubescape/v2/core/cautils"
)
// Test_validateControlScanInfo tests how scan info is validated for the `scan control` command
@@ -27,7 +27,7 @@ func Test_validateControlScanInfo(t *testing.T) {
{
"Unknown severity should be invalid for scan info",
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
shared.ErrUnknownSeverity,
utils.ErrUnknownSeverity,
},
}
@@ -67,12 +67,12 @@ func Test_validateFrameworkScanInfo(t *testing.T) {
{
"Unknown severity should be invalid for scan info",
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
shared.ErrUnknownSeverity,
utils.ErrUnknownSeverity,
},
{
"Security view should be invalid for scan info",
&cautils.ScanInfo{View: string(cautils.SecurityViewType)},
nil,
ErrSecurityViewNotSupported,
},
{
"Empty view should be valid for scan info",

View File

@@ -1,20 +1,24 @@
package scan
import (
"context"
"errors"
"fmt"
"strings"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/opa-utils/objectsenvelopes"
"github.com/spf13/cobra"
)
var (
workloadExample = fmt.Sprintf(`
This command is still in BETA. Feel free to contact the Kubescape maintainers for more information.
Scan a workload for misconfigurations and image vulnerabilities.
# Scan an workload
@@ -48,7 +52,6 @@ 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>")
}
@@ -65,17 +68,16 @@ func getWorkloadCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comma
setWorkloadScanInfo(scanInfo, kind, name)
// todo: add api version if provided
results, err := ks.Scan(scanInfo)
ctx := context.TODO()
results, err := ks.Scan(ctx, scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
if err = results.HandleResults(ks.Context()); err != nil {
if err = results.HandleResults(ctx); err != nil {
logger.L().Fatal(err.Error())
}
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
return nil
},
}

View File

@@ -3,12 +3,9 @@ package scan
import (
"testing"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/mocks"
"github.com/kubescape/kubescape/v2/core/cautils"
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) {
@@ -70,27 +67,3 @@ 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())
}

View File

@@ -1,18 +0,0 @@
package shared
import "github.com/kubescape/kubescape/v3/core/cautils"
type ImageCredentials struct {
Username string
Password string
}
// ValidateImageScanInfo validates the ScanInfo struct for image scanning commands
func ValidateImageScanInfo(scanInfo *cautils.ScanInfo) error {
severity := scanInfo.FailThresholdSeverity
if err := ValidateSeverity(severity); severity != "" && err != nil {
return err
}
return nil
}

View File

@@ -1,61 +0,0 @@
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)
},
)
}
}

View File

@@ -6,19 +6,15 @@ package update
import (
"fmt"
"strings"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/kubescape/backend/pkg/versioncheck"
"github.com/kubescape/go-logger"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/spf13/cobra"
)
const (
installationLink string = "https://kubescape.io/docs/install-cli/"
installationLink string = "https://github.com/kubescape/kubescape/blob/master/docs/installation.md"
)
var updateCmdExamples = fmt.Sprintf(`
@@ -26,31 +22,19 @@ var updateCmdExamples = fmt.Sprintf(`
%[1]s update
`, cautils.ExecName())
func GetUpdateCmd(ks meta.IKubescape) *cobra.Command {
func GetUpdateCmd() *cobra.Command {
updateCmd := &cobra.Command{
Use: "update",
Short: "Update to latest release version",
Long: ``,
Example: updateCmdExamples,
RunE: func(_ *cobra.Command, args []string) error {
v := versioncheck.NewVersionCheckHandler()
versionCheckRequest := versioncheck.NewVersionCheckRequest("", versioncheck.BuildNumber, "", "", "update", nil)
if err := v.CheckLatestVersion(ks.Context(), versionCheckRequest); err != nil {
return err
}
//Checking the user's version of kubescape to the latest release
if versioncheck.BuildNumber == "" || strings.Contains(versioncheck.BuildNumber, "rc") {
//your version is unknown
fmt.Printf("Nothing to update: you are running the development version\n")
} else if versioncheck.LatestReleaseVersion == "" {
//Failed to check for updates
logger.L().Info("Failed to check for updates")
} else if versioncheck.BuildNumber == versioncheck.LatestReleaseVersion {
if cautils.BuildNumber == cautils.LatestReleaseVersion {
//your version == latest version
logger.L().Info("Nothing to update: you are running the latest version", helpers.String("Version", versioncheck.BuildNumber))
logger.L().Info(("Nothing to update, you are running the latest version"), helpers.String("Version", cautils.BuildNumber))
} else {
fmt.Printf("Version %s is available. Please refer to our installation documentation: %s\n", versioncheck.LatestReleaseVersion, installationLink)
fmt.Printf("Please refer to our installation docs in the following link: %s", installationLink)
}
return nil
},

View File

@@ -1,17 +1,17 @@
package shared
package utils
import (
"fmt"
"strings"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils"
reporthandlingapis "github.com/kubescape/opa-utils/reporthandling/apis"
)
var ErrUnknownSeverity = fmt.Errorf("unknown severity. Supported severities are: %s", strings.Join(reporthandlingapis.GetSupportedSeverities(), ", "))
// ValidateSeverity returns an error if a given severity is not known, nil otherwise
// validateSeverity returns an error if a given severity is not known, nil otherwise
func ValidateSeverity(severity string) error {
for _, val := range reporthandlingapis.GetSupportedSeverities() {
if strings.EqualFold(severity, val) {
@@ -22,7 +22,6 @@ func ValidateSeverity(severity string) error {
}
// TerminateOnExceedingSeverity terminates the program if the result exceeds the severity threshold
func TerminateOnExceedingSeverity(scanInfo *cautils.ScanInfo, l helpers.ILogger) {
l.Fatal("result exceeds severity threshold", helpers.String("Set severity threshold", scanInfo.FailThresholdSeverity))
}

View File

@@ -1,4 +1,4 @@
package shared
package utils
import (
"context"
@@ -7,7 +7,7 @@ import (
"testing"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/opa-utils/reporthandling/apis"
)

View File

@@ -1,235 +0,0 @@
package vap
import (
"errors"
"fmt"
"io"
"net/http"
"regexp"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/spf13/cobra"
admissionv1 "k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)
var vapHelperCmdExamples = fmt.Sprintf(`
vap command can be used for managing Validating Admission Policies in a Kubernetes cluster.
This is an experimental feature and it might change.
Examples:
# Install Kubescape CEL admission policy library
%[1]s vap deploy-library | kubectl apply -f -
# Create a policy binding
%[1]s vap create-policy-binding --name my-policy-binding --policy c-0016 --namespace=my-namespace | kubectl apply -f -
`, cautils.ExecName())
func GetVapHelperCmd() *cobra.Command {
vapHelperCmd := &cobra.Command{
Use: "vap",
Short: "Helper commands for managing Validating Admission Policies in a Kubernetes cluster",
Long: ``,
Example: vapHelperCmdExamples,
}
// Create subcommands
vapHelperCmd.AddCommand(getDeployLibraryCmd())
vapHelperCmd.AddCommand(getCreatePolicyBindingCmd())
return vapHelperCmd
}
func getDeployLibraryCmd() *cobra.Command {
return &cobra.Command{
Use: "deploy-library",
Short: "Install Kubescape CEL admission policy library",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
return deployLibrary()
},
}
}
func getCreatePolicyBindingCmd() *cobra.Command {
var policyBindingName string
var policyName string
var namespaceArr []string
var labelArr []string
var action string
var parameterReference string
createPolicyBindingCmd := &cobra.Command{
Use: "create-policy-binding",
Short: "Create a policy binding",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
// Validate the inputs
if err := isValidK8sObjectName(policyBindingName); err != nil {
return fmt.Errorf("invalid policy binding name %s: %w", policyBindingName, err)
}
if err := isValidK8sObjectName(policyName); err != nil {
return fmt.Errorf("invalid policy name %s: %w", policyName, err)
}
for _, namespace := range namespaceArr {
if err := isValidK8sObjectName(namespace); err != nil {
return fmt.Errorf("invalid namespace %s: %w", namespace, err)
}
}
for _, label := range labelArr {
// Label selector must be in the format key=value
if !regexp.MustCompile(`^[a-zA-Z0-9]+=[a-zA-Z0-9]+$`).MatchString(label) {
return fmt.Errorf("invalid label selector: %s", label)
}
}
if action != "Deny" && action != "Audit" && action != "Warn" {
return fmt.Errorf("invalid action: %s", action)
}
if parameterReference != "" {
if err := isValidK8sObjectName(parameterReference); err != nil {
return fmt.Errorf("invalid parameter reference %s: %w", parameterReference, err)
}
}
return createPolicyBinding(policyBindingName, policyName, action, parameterReference, namespaceArr, labelArr)
},
}
// Must specify the name of the policy binding
createPolicyBindingCmd.Flags().StringVarP(&policyBindingName, "name", "n", "", "Name of the policy binding")
createPolicyBindingCmd.MarkFlagRequired("name")
createPolicyBindingCmd.Flags().StringVarP(&policyName, "policy", "p", "", "Name of the policy to bind the resources to")
createPolicyBindingCmd.MarkFlagRequired("policy")
createPolicyBindingCmd.Flags().StringSliceVar(&namespaceArr, "namespace", []string{}, "Resource namespace selector")
createPolicyBindingCmd.Flags().StringSliceVar(&labelArr, "label", []string{}, "Resource label selector")
createPolicyBindingCmd.Flags().StringVarP(&action, "action", "a", "Deny", "Action to take when policy fails")
createPolicyBindingCmd.Flags().StringVarP(&parameterReference, "parameter-reference", "r", "", "Parameter reference object name")
return createPolicyBindingCmd
}
// Implementation of the VAP helper commands
// deploy-library
func deployLibrary() error {
logger.L().Info("Downloading the Kubescape CEL admission policy library")
// Download the policy-configuration-definition.yaml from the latest release URL
policyConfigurationDefinitionURL := "https://github.com/kubescape/cel-admission-library/releases/latest/download/policy-configuration-definition.yaml"
policyConfigurationDefinition, err := downloadFileToString(policyConfigurationDefinitionURL)
if err != nil {
return err
}
// Download the basic-control-configuration.yaml from the latest release URL
basicControlConfigurationURL := "https://github.com/kubescape/cel-admission-library/releases/latest/download/basic-control-configuration.yaml"
basicControlConfiguration, err := downloadFileToString(basicControlConfigurationURL)
if err != nil {
return err
}
// Download the kubescape-validating-admission-policies.yaml from the latest release URL
kubescapeValidatingAdmissionPoliciesURL := "https://github.com/kubescape/cel-admission-library/releases/latest/download/kubescape-validating-admission-policies.yaml"
kubescapeValidatingAdmissionPolicies, err := downloadFileToString(kubescapeValidatingAdmissionPoliciesURL)
if err != nil {
return err
}
logger.L().Info("Successfully downloaded admission policy library")
// Print the downloaded files to the STDOUT for the user to apply connecting them to a single YAML with ---
fmt.Println(policyConfigurationDefinition)
fmt.Println("---")
fmt.Println(basicControlConfiguration)
fmt.Println("---")
fmt.Println(kubescapeValidatingAdmissionPolicies)
return nil
}
func downloadFileToString(url string) (string, error) {
// Send an HTTP GET request to the URL
response, err := http.Get(url) //nolint:gosec
if err != nil {
return "", err // Return an empty string and the error if the request fails
}
defer response.Body.Close()
// Check for a successful response (HTTP 200 OK)
if response.StatusCode != http.StatusOK {
return "", fmt.Errorf("failed to download file: %s", response.Status)
}
// Read the response body
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
return "", err // Return an empty string and the error if reading fails
}
// Convert the byte slice to a string
bodyString := string(bodyBytes)
return bodyString, nil
}
func isValidK8sObjectName(name string) error {
// Kubernetes object names must consist of lower case alphanumeric characters, '-' or '.',
// and must start and end with an alphanumeric character (e.g., 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')
// Max length of 63 characters.
if len(name) > 63 {
return errors.New("name should be less than 63 characters")
}
regex := regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`)
if !regex.MatchString(name) {
return errors.New("name should consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character")
}
return nil
}
// Create a policy binding
func createPolicyBinding(bindingName string, policyName string, action string, paramRefName string, namespaceArr []string, labelMatch []string) error {
// Create a policy binding struct
policyBinding := &admissionv1.ValidatingAdmissionPolicyBinding{}
// Print the policy binding after marshalling it to YAML to the STDOUT
// The user can apply the output to the cluster
policyBinding.APIVersion = "admissionregistration.k8s.io/v1"
policyBinding.Name = bindingName
policyBinding.Kind = "ValidatingAdmissionPolicyBinding"
policyBinding.Spec.PolicyName = policyName
policyBinding.Spec.MatchResources = &admissionv1.MatchResources{}
if len(namespaceArr) > 0 {
policyBinding.Spec.MatchResources.NamespaceSelector = &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "kubernetes.io/metadata.name",
Operator: metav1.LabelSelectorOpIn,
Values: namespaceArr,
},
},
}
}
if len(labelMatch) > 0 {
policyBinding.Spec.MatchResources.ObjectSelector = &metav1.LabelSelector{}
policyBinding.Spec.MatchResources.ObjectSelector.MatchLabels = make(map[string]string)
for _, label := range labelMatch {
labelParts := regexp.MustCompile(`=`).Split(label, 2)
policyBinding.Spec.MatchResources.ObjectSelector.MatchLabels[labelParts[0]] = labelParts[1]
}
}
policyBinding.Spec.ValidationActions = []admissionv1.ValidationAction{admissionv1.ValidationAction(action)}
if paramRefName != "" {
policyBinding.Spec.ParamRef = &admissionv1.ParamRef{
Name: paramRefName,
}
}
// Marshal the policy binding to YAML
out, err := yaml.Marshal(policyBinding)
if err != nil {
return err
}
fmt.Println(string(out))
return nil
}

View File

@@ -1,10 +0,0 @@
package vap
import (
"testing"
)
func TestGetVapHelperCmd(t *testing.T) {
// Call the GetFixCmd function
_ = GetVapHelperCmd()
}

View File

@@ -0,0 +1,7 @@
//go:build !gitenabled
package version
func isGitEnabled() bool {
return false
}

View File

@@ -0,0 +1,7 @@
//go:build gitenabled
package version
func isGitEnabled() bool {
return true
}

View File

@@ -1,30 +1,29 @@
package version
import (
"context"
"fmt"
"os"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/kubescape/backend/pkg/versioncheck"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/spf13/cobra"
)
func GetVersionCmd(ks meta.IKubescape) *cobra.Command {
func GetVersionCmd() *cobra.Command {
versionCmd := &cobra.Command{
Use: "version",
Short: "Get current version",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
v := versioncheck.NewIVersionCheckHandler(ks.Context())
versionCheckRequest := versioncheck.NewVersionCheckRequest("", versioncheck.BuildNumber, "", "", "version", nil)
if err := v.CheckLatestVersion(ks.Context(), versionCheckRequest); err != nil {
return err
}
fmt.Fprintf(cmd.OutOrStdout(),
ctx := context.TODO()
v := cautils.NewIVersionCheckHandler(ctx)
v.CheckLatestVersion(ctx, cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
fmt.Fprintf(os.Stdout,
"Your current version is: %s\n",
versionCheckRequest.ClientVersion,
cautils.BuildNumber,
)
logger.L().Debug(fmt.Sprintf("git enabled in build: %t", isGitEnabled()))
return nil
},
}

View File

@@ -1,49 +0,0 @@
package version
import (
"bytes"
"context"
"io"
"testing"
"github.com/kubescape/kubescape/v3/core/core"
"github.com/kubescape/backend/pkg/versioncheck"
"github.com/stretchr/testify/assert"
)
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) {
versioncheck.BuildNumber = tt.buildNumber
ks := core.NewKubescape(context.TODO())
if cmd := GetVersionCmd(ks); 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))
}
})
}
}

View File

@@ -1,21 +0,0 @@
package cautils
import (
"os"
"github.com/kubescape/backend/pkg/versioncheck"
)
var BuildNumber string
var Client string
func init() {
if BuildNumber != "" {
versioncheck.BuildNumber = BuildNumber
} else {
versioncheck.BuildNumber = os.Getenv("RELEASE")
}
if Client != "" {
versioncheck.Client = Client
}
}

View File

@@ -1,53 +0,0 @@
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)
}
}

View File

@@ -7,33 +7,30 @@ import (
"path/filepath"
"regexp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/google/uuid"
v1 "github.com/kubescape/backend/pkg/client/v1"
"github.com/kubescape/backend/pkg/servicediscovery"
servicediscoveryv1 "github.com/kubescape/backend/pkg/servicediscovery/v1"
servicediscoveryv2 "github.com/kubescape/backend/pkg/servicediscovery/v2"
"github.com/kubescape/go-logger"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
configFileName string = "config"
kubescapeNamespace string = "kubescape"
kubescapeConfigMapName string = "kubescape-config" // deprecated - for backward compatibility
kubescapeCloudConfigMapName string = "ks-cloud-config" // deprecated - for backward compatibility
cloudConfigMapLabelSelector string = "kubescape.io/infra=config"
credsLabelSelectors string = "kubescape.io/infra=credentials" //nolint:gosec
configFileName string = "config"
kubescapeNamespace string = "kubescape"
kubescapeConfigMapName string = "kubescape-config"
kubescapeCloudConfigMapName string = "ks-cloud-config"
// env vars
defaultConfigMapNameEnvVar string = "KS_DEFAULT_CONFIGMAP_NAME"
defaultCloudConfigMapNameEnvVar string = "KS_DEFAULT_CLOUD_CONFIGMAP_NAME"
defaultConfigMapNamespaceEnvVar string = "KS_DEFAULT_CONFIGMAP_NAMESPACE"
accountIdEnvVar string = "KS_ACCOUNT_ID"
accessKeyEnvVar string = "KS_ACCESS_KEY"
cloudApiUrlEnvVar string = "KS_CLOUD_API_URL"
cloudReportUrlEnvVar string = "KS_CLOUD_REPORT_URL"
)
@@ -49,7 +46,6 @@ type ConfigObj struct {
ClusterName string `json:"clusterName,omitempty"`
CloudReportURL string `json:"cloudReportURL,omitempty"`
CloudAPIURL string `json:"cloudAPIURL,omitempty"`
AccessKey string `json:"accessKey,omitempty"`
}
// Config - convert ConfigObj to config file
@@ -94,12 +90,11 @@ type ITenantConfig interface {
UpdateCachedConfig() error
DeleteCachedConfig(ctx context.Context) error
GenerateAccountID() (string, error)
DeleteCredentials() error
DeleteAccountID() error
// getters
GetContextName() string
GetAccountID() string
GetAccessKey() string
GetConfigObj() *ConfigObj
GetCloudReportURL() string
GetCloudAPIURL() string
@@ -116,7 +111,7 @@ type LocalConfig struct {
configObj *ConfigObj
}
func NewLocalConfig(accountID, accessKey, clusterName, customClusterName string) *LocalConfig {
func NewLocalConfig(accountID, clusterName string, customClusterName string) *LocalConfig {
lc := &LocalConfig{
configObj: &ConfigObj{},
}
@@ -125,7 +120,7 @@ func NewLocalConfig(accountID, accessKey, clusterName, customClusterName string)
loadConfigFromFile(lc.configObj)
}
updateCredentials(lc.configObj, accountID, accessKey)
updateAccountID(lc.configObj, accountID)
updateCloudURLs(lc.configObj)
// If a custom cluster name is provided then set that name, else use the cluster's original name
@@ -135,7 +130,8 @@ func NewLocalConfig(accountID, accessKey, clusterName, customClusterName string)
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
}
initializeCloudAPI(lc)
updatedKsCloud := initializeCloudAPI(lc)
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", updatedKsCloud.GetCloudAPIURL()), helpers.String("report", updatedKsCloud.GetCloudReportURL()))
return lc
}
@@ -145,7 +141,6 @@ func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountI
func (lc *LocalConfig) GetContextName() string { return lc.configObj.ClusterName }
func (lc *LocalConfig) GetCloudReportURL() string { return lc.configObj.CloudReportURL }
func (lc *LocalConfig) GetCloudAPIURL() string { return lc.configObj.CloudAPIURL }
func (lc *LocalConfig) GetAccessKey() string { return lc.configObj.AccessKey }
func (lc *LocalConfig) GenerateAccountID() (string, error) {
lc.configObj.AccountID = uuid.NewString()
@@ -153,8 +148,7 @@ func (lc *LocalConfig) GenerateAccountID() (string, error) {
return lc.configObj.AccountID, err
}
func (lc *LocalConfig) DeleteCredentials() error {
lc.configObj.AccessKey = ""
func (lc *LocalConfig) DeleteAccountID() error {
lc.configObj.AccountID = ""
return lc.UpdateCachedConfig()
}
@@ -190,16 +184,20 @@ KS_CACHE // path to cached files
var _ ITenantConfig = &ClusterConfig{}
type ClusterConfig struct {
k8s *k8sinterface.KubernetesApi
configObj *ConfigObj
configMapNamespace string
k8s *k8sinterface.KubernetesApi
configObj *ConfigObj
configMapNamespace string
ksConfigMapName string
ksCloudConfigMapName string
}
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, accessKey, clusterName, customClusterName string) *ClusterConfig {
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, clusterName string, customClusterName string) *ClusterConfig {
c := &ClusterConfig{
k8s: k8s,
configObj: &ConfigObj{},
configMapNamespace: GetConfigMapNamespace(),
k8s: k8s,
configObj: &ConfigObj{},
ksConfigMapName: getKubescapeConfigMapName(),
ksCloudConfigMapName: getKubescapeCloudConfigMapName(),
configMapNamespace: GetConfigMapNamespace(),
}
// first, load from file
@@ -207,13 +205,17 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, accessKey, clu
loadConfigFromFile(c.configObj)
}
// second, load urls from config map
c.updateConfigEmptyFieldsFromKubescapeConfigMap()
// second, load from configMap
if c.existsConfigMap(c.ksConfigMapName) {
c.updateConfigEmptyFieldsFromKubescapeConfigMap()
}
// third, credentials from secret
c.updateConfigEmptyFieldsFromCredentialsSecret()
// third, load urls from cloudConfigMap
if c.existsConfigMap(c.ksCloudConfigMapName) {
c.updateConfigEmptyFieldsFromKubescapeCloudConfigMap()
}
updateCredentials(c.configObj, accountID, accessKey)
updateAccountID(c.configObj, accountID)
updateCloudURLs(c.configObj)
// If a custom cluster name is provided then set that name, else use the cluster's original name
@@ -228,7 +230,8 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, accessKey, clu
} else { // override the cluster name if it has unwanted characters
c.configObj.ClusterName = AdoptClusterName(c.configObj.ClusterName)
}
initializeCloudAPI(c)
updatedKsCloud := initializeCloudAPI(c)
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", updatedKsCloud.GetCloudAPIURL()), helpers.String("report", updatedKsCloud.GetCloudReportURL()))
return c
}
@@ -237,7 +240,6 @@ func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
func (c *ClusterConfig) GetCloudReportURL() string { return c.configObj.CloudReportURL }
func (c *ClusterConfig) GetCloudAPIURL() string { return c.configObj.CloudAPIURL }
func (c *ClusterConfig) GetAccessKey() string { return c.configObj.AccessKey }
func (c *ClusterConfig) UpdateCachedConfig() error {
logger.L().Debug("updating cached config", helpers.Interface("configObj", c.configObj))
@@ -263,84 +265,42 @@ func (c *ClusterConfig) ToMapString() map[string]interface{} {
}
func (c *ClusterConfig) updateConfigEmptyFieldsFromKubescapeConfigMap() error {
configMaps, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).List(context.Background(), metav1.ListOptions{
LabelSelector: cloudConfigMapLabelSelector,
})
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.ksConfigMapName, metav1.GetOptions{})
if err != nil {
return err
}
var ksConfigMap *corev1.ConfigMap
var urlsConfigMap *corev1.ConfigMap
if len(configMaps.Items) == 0 {
// try to find configmaps by name (for backward compatibility)
ksConfigMap, _ = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), kubescapeConfigMapName, metav1.GetOptions{})
urlsConfigMap, _ = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), kubescapeCloudConfigMapName, metav1.GetOptions{})
} else {
// use the first configmap with the label
ksConfigMap = &configMaps.Items[0]
urlsConfigMap = &configMaps.Items[0]
}
if ksConfigMap != nil {
if jsonConf, ok := ksConfigMap.Data["clusterData"]; ok {
tempCO := ConfigObj{}
if err = json.Unmarshal([]byte(jsonConf), &tempCO); err != nil {
return err
}
c.configObj.updateEmptyFields(&tempCO)
tempCO := ConfigObj{}
if jsonConf, ok := configMap.Data["config.json"]; ok {
if err = json.Unmarshal([]byte(jsonConf), &tempCO); err != nil {
return err
}
return c.configObj.updateEmptyFields(&tempCO)
}
if urlsConfigMap != nil {
if jsonConf, ok := urlsConfigMap.Data["services"]; ok {
services, err := servicediscovery.GetServices(
servicediscoveryv2.NewServiceDiscoveryStreamV2([]byte(jsonConf)),
)
if err != nil {
// try to parse as v1
services, err = servicediscovery.GetServices(
servicediscoveryv1.NewServiceDiscoveryStreamV1([]byte(jsonConf)),
)
if err != nil {
return err
}
}
if services.GetApiServerUrl() != "" {
c.configObj.CloudAPIURL = services.GetApiServerUrl()
}
if services.GetReportReceiverHttpUrl() != "" {
c.configObj.CloudReportURL = services.GetReportReceiverHttpUrl()
}
}
}
return err
}
func (c *ClusterConfig) updateConfigEmptyFieldsFromCredentialsSecret() error {
secrets, err := c.k8s.KubernetesClient.CoreV1().Secrets(c.configMapNamespace).List(context.Background(),
metav1.ListOptions{LabelSelector: credsLabelSelectors})
func (c *ClusterConfig) updateConfigEmptyFieldsFromKubescapeCloudConfigMap() error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.ksCloudConfigMapName, metav1.GetOptions{})
if err != nil {
return err
}
if len(secrets.Items) == 0 {
return nil
}
if jsonConf, ok := configMap.Data["services"]; ok {
services, err := servicediscovery.GetServices(
servicediscoveryv1.NewServiceDiscoveryStreamV1([]byte(jsonConf)),
)
if err != nil {
return err
}
if jsonConf, ok := secrets.Items[0].Data["account"]; ok {
if account := string(jsonConf); account != "" {
c.configObj.AccountID = account
if services.GetApiServerUrl() != "" {
c.configObj.CloudAPIURL = services.GetApiServerUrl()
}
if services.GetReportReceiverHttpUrl() != "" {
c.configObj.CloudReportURL = services.GetReportReceiverHttpUrl()
}
}
if jsonConf, ok := secrets.Items[0].Data["accessKey"]; ok {
if accessKey := string(jsonConf); accessKey != "" {
c.configObj.AccessKey = accessKey
}
}
return nil
}
@@ -356,6 +316,11 @@ func loadConfigFromData(co *ConfigObj, data map[string]string) error {
return e
}
func (c *ClusterConfig) existsConfigMap(name string) bool {
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), name, metav1.GetOptions{})
return err == nil
}
func existsConfigFile() bool {
_, err := os.ReadFile(ConfigFileFullPath())
return err == nil
@@ -377,9 +342,8 @@ func (c *ClusterConfig) GenerateAccountID() (string, error) {
return c.configObj.AccountID, err
}
func (c *ClusterConfig) DeleteCredentials() error {
func (c *ClusterConfig) DeleteAccountID() error {
c.configObj.AccountID = ""
c.configObj.AccessKey = ""
return c.UpdateCachedConfig()
}
@@ -425,6 +389,21 @@ func AdoptClusterName(clusterName string) string {
return re.ReplaceAllString(clusterName, "-")
}
func getKubescapeConfigMapName() string {
if n := os.Getenv(defaultConfigMapNameEnvVar); n != "" {
return n
}
return kubescapeConfigMapName
}
func getKubescapeCloudConfigMapName() string {
if n := os.Getenv(defaultCloudConfigMapNameEnvVar); n != "" {
return n
}
return kubescapeCloudConfigMapName
}
// GetConfigMapNamespace returns the namespace of the cluster config, which is the same for all in-cluster components
func GetConfigMapNamespace() string {
if n := os.Getenv(defaultConfigMapNamespaceEnvVar); n != "" {
@@ -433,15 +412,7 @@ func GetConfigMapNamespace() string {
return kubescapeNamespace
}
func updateCredentials(configObj *ConfigObj, accountID, accessKey string) {
if accessKey != "" {
configObj.AccessKey = accessKey
}
if envAccessKey := os.Getenv(accessKeyEnvVar); envAccessKey != "" {
configObj.AccessKey = envAccessKey
}
func updateAccountID(configObj *ConfigObj, accountID string) {
if accountID != "" {
configObj.AccountID = accountID
}
@@ -478,52 +449,18 @@ func updateCloudURLs(configObj *ConfigObj) {
}
func initializeCloudAPI(c ITenantConfig) *v1.KSCloudAPI {
if ksCloud := getter.GetKSCloudAPIConnector(); ksCloud != nil {
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))
ksCloud.SetCloudAPIURL(val)
}
if val := c.GetCloudReportURL(); val != "" && val != ksCloud.GetCloudReportURL() {
logger.L().Debug("updating KS Cloud Report from config", helpers.String("old", ksCloud.GetCloudReportURL()), helpers.String("new", val))
ksCloud.SetCloudReportURL(val)
}
if val := c.GetAccountID(); val != "" && val != ksCloud.GetAccountID() {
logger.L().Debug("updating Account ID from config", helpers.String("old", ksCloud.GetAccountID()), helpers.String("new", val))
ksCloud.SetAccountID(val)
}
if val := c.GetAccessKey(); val != "" && val != ksCloud.GetAccessKey() {
logger.L().Debug("updating Access Key from config", helpers.Int("old (len)", len(ksCloud.GetAccessKey())), helpers.Int("new (len)", len(val)))
ksCloud.SetAccessKey(val)
}
getter.SetKSCloudAPIConnector(ksCloud)
} else {
logger.L().Debug("initializing KS Cloud API from config", helpers.String("accountID", c.GetAccountID()), helpers.String("cloudAPIURL", c.GetCloudAPIURL()), helpers.String("cloudReportURL", c.GetCloudReportURL()))
cloud, err := v1.NewKSCloudAPI(
c.GetCloudAPIURL(),
c.GetCloudReportURL(),
c.GetAccountID(),
c.GetAccessKey())
if err != nil {
logger.L().Fatal("failed to create KS Cloud client", helpers.Error(err))
}
getter.SetKSCloudAPIConnector(cloud)
logger.L().Debug("initializing KS Cloud API from config", helpers.String("accountID", c.GetAccountID()), helpers.String("cloudAPIURL", c.GetCloudAPIURL()), helpers.String("cloudReportURL", c.GetCloudReportURL()))
cloud, err := v1.NewKSCloudAPI(c.GetCloudAPIURL(), c.GetCloudReportURL(), c.GetAccountID())
if err != nil {
logger.L().Fatal("failed to create KS Cloud client", helpers.Error(err))
}
getter.SetKSCloudAPIConnector(cloud)
return getter.GetKSCloudAPIConnector()
}
func GetTenantConfig(accountID, accessKey, clusterName, customClusterName string, k8s *k8sinterface.KubernetesApi) ITenantConfig {
func GetTenantConfig(accountID, clusterName, customClusterName string, k8s *k8sinterface.KubernetesApi) ITenantConfig {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return NewLocalConfig(accountID, accessKey, clusterName, customClusterName)
return NewLocalConfig(accountID, clusterName, customClusterName)
}
return NewClusterConfig(k8s, accountID, accessKey, clusterName, customClusterName)
}
// firstNonEmpty returns the first non-empty string
func firstNonEmpty(s1, s2 string) string {
if s1 != "" {
return s1
}
return s2
return NewClusterConfig(k8s, accountID, clusterName, customClusterName)
}

View File

@@ -5,7 +5,7 @@ import (
"os"
"testing"
"github.com/kubescape/kubescape/v3/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
)

View File

@@ -27,7 +27,7 @@ type ImageScanData struct {
type ScanTypes string
const (
TopWorkloadsNumber = 3
TopWorkloadsNumber = 5
ScanTypeCluster ScanTypes = "cluster"
ScanTypeRepo ScanTypes = "repo"
ScanTypeImage ScanTypes = "image"
@@ -58,8 +58,6 @@ type OPASessionObj struct {
OmitRawResources bool // omit raw resources from output
SingleResourceScan workloadinterface.IWorkload // single resource scan
TopWorkloadsByScore []reporthandling.IResource
TemplateMapping map[string]MappingNodes // Map chart obj to template (only for rendering from path)
TriggeredByCLI bool
}
func NewOPASessionObj(ctx context.Context, frameworks []reporthandling.Framework, k8sResources K8SResources, scanInfo *ScanInfo) *OPASessionObj {
@@ -76,8 +74,6 @@ func NewOPASessionObj(ctx context.Context, frameworks []reporthandling.Framework
SessionID: scanInfo.ScanID,
Metadata: scanInfoToScanMetadata(ctx, scanInfo),
OmitRawResources: scanInfo.OmitRawResources,
TriggeredByCLI: scanInfo.TriggeredByCLI,
TemplateMapping: make(map[string]MappingNodes),
}
}

View File

@@ -1,11 +1,12 @@
package cautils
import (
"github.com/kubescape/backend/pkg/versioncheck"
"golang.org/x/mod/semver"
"github.com/armosec/utils-go/boolutils"
cloudsupport "github.com/kubescape/k8s-interface/cloudsupport/v1"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/apis"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
"golang.org/x/mod/semver"
)
func NewPolicies() *Policies {
@@ -15,7 +16,7 @@ func NewPolicies() *Policies {
}
}
func (policies *Policies) Set(frameworks []reporthandling.Framework, excludedRules map[string]bool, scanningScope reporthandling.ScanningScopeType) {
func (policies *Policies) Set(frameworks []reporthandling.Framework, version string, excludedRules map[string]bool, scanningScope reporthandling.ScanningScopeType) {
for i := range frameworks {
if !isFrameworkFitToScanScope(frameworks[i], scanningScope) {
continue
@@ -33,12 +34,9 @@ func (policies *Policies) Set(frameworks []reporthandling.Framework, excludedRul
}
}
if ShouldSkipRule(frameworks[i].Controls[j], frameworks[i].Controls[j].Rules[r], scanningScope) {
continue
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 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
@@ -58,16 +56,12 @@ func (policies *Policies) Set(frameworks []reporthandling.Framework, excludedRul
}
}
// 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, versioncheck.BuildNumber) {
return true
func ruleWithKSOpaDependency(attributes map[string]interface{}) bool {
if attributes == nil {
return false
}
if !isControlFitToScanScope(control, scanningScope) {
return true
if s, ok := attributes["armoOpa"]; ok { // TODO - make global
return boolutils.StringToBool(s.(string))
}
return false
}
@@ -77,31 +71,49 @@ func ShouldSkipRule(control reporthandling.Control, rule reporthandling.PolicyRu
// 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 {
switch sfrom := from.(type) {
case string:
if version != "" && semver.Compare(version, sfrom) == -1 {
if version != "" {
if semver.Compare(version, from.(string)) == -1 {
return false
}
default:
// Handle case where useFromKubescapeVersion is not a string
return false
}
}
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
if version == "" {
return false
}
if semver.Compare(version, until.(string)) >= 0 {
return false
}
}
return true
}
func getCloudProvider(scanInfo *ScanInfo) reporthandling.ScanningScopeType {
if cloudsupport.IsAKS() {
return reporthandling.ScopeCloudAKS
}
if cloudsupport.IsEKS() {
return reporthandling.ScopeCloudEKS
}
if cloudsupport.IsGKE() {
return reporthandling.ScopeCloudGKE
}
return ""
}
func GetScanningScope(scanInfo *ScanInfo) reporthandling.ScanningScopeType {
switch scanInfo.GetScanningContext() {
case ContextCluster:
if cloudProvider := getCloudProvider(scanInfo); cloudProvider != "" {
return cloudProvider
}
return reporthandling.ScopeCluster
default:
return reporthandling.ScopeFile
}
}
func isScanningScopeMatchToControlScope(scanScope reporthandling.ScanningScopeType, controlScope reporthandling.ScanningScopeType) bool {
switch controlScope {
@@ -153,13 +165,3 @@ 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
}

View File

@@ -9,6 +9,99 @@ import (
"github.com/stretchr/testify/assert"
)
func TestIsControlFitToScanScope(t *testing.T) {
tests := []struct {
scanInfo *ScanInfo
Control reporthandling.Control
expected_res bool
}{
{
scanInfo: &ScanInfo{
InputPatterns: []string{
"./testdata/any_file_for_test.json",
},
},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeFile,
},
},
},
expected_res: true,
},
{
scanInfo: &ScanInfo{
InputPatterns: []string{
"./testdata/any_file_for_test.json",
},
},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCluster,
reporthandling.ScopeFile,
},
},
},
expected_res: true,
},
{
scanInfo: &ScanInfo{},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCluster,
},
},
},
expected_res: true,
},
{
scanInfo: &ScanInfo{
InputPatterns: []string{
"./testdata/any_file_for_test.json",
},
},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCloudGKE,
},
},
},
expected_res: false,
},
{
scanInfo: &ScanInfo{},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCloudEKS,
},
},
},
expected_res: false,
},
{
scanInfo: &ScanInfo{},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCloud,
},
},
},
expected_res: false,
}}
for i := range tests {
assert.Equal(t, tests[i].expected_res, isControlFitToScanScope(tests[i].Control, GetScanningScope(tests[i].scanInfo)), fmt.Sprintf("tests_true index %d", i))
}
}
func TestIsScanningScopeMatchToControlScope(t *testing.T) {
tests := []struct {
scanScope reporthandling.ScanningScopeType
@@ -239,59 +332,3 @@ func TestIsFrameworkFitToScanScope(t *testing.T) {
})
}
}
var rule_v1_0_131 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useUntilKubescapeVersion": "v1.0.132"}}}
var rule_v1_0_132 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.132", "useUntilKubescapeVersion": "v1.0.133"}}}
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 = ""
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))
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
// should only use rules that version is in range of use
buildNumberMock = "v1.0.130"
assert.True(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))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
// should only use rules that version is in range of use
buildNumberMock = "v1.0.132"
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_131.Attributes, buildNumberMock))
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_132.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_133.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
// should only use rules that version is in range of use
buildNumberMock = "v1.0.133"
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_131.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_132.Attributes, buildNumberMock))
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_133.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
// should only use rules that version is in range of use
buildNumberMock = "v1.0.135"
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))
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
}

View File

@@ -4,12 +4,11 @@ import (
"fmt"
"io"
"os"
"strings"
"time"
spinnerpkg "github.com/briandowns/spinner"
"github.com/jwalton/gchalk"
"github.com/kubescape/go-logger"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/mattn/go-isatty"
"github.com/schollz/progressbar/v3"
@@ -28,7 +27,7 @@ func FailureTextDisplay(w io.Writer, format string, a ...interface{}) {
}
func InfoDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.WithBrightWhite().Bold(format), a...)
fmt.Fprintf(w, gchalk.WithCyan().Bold(format), a...)
}
func InfoTextDisplay(w io.Writer, format string, a ...interface{}) {
@@ -51,20 +50,6 @@ 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() {

View File

@@ -1,12 +1,9 @@
package cautils
import (
"io"
"os"
"testing"
"github.com/kubescape/go-logger"
"github.com/stretchr/testify/assert"
)
func TestStartSpinner(t *testing.T) {
@@ -33,422 +30,3 @@ 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)
})
}
}

View File

@@ -7,14 +7,16 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/workloadinterface"
"golang.org/x/exp/slices"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/opa-utils/objectsenvelopes"
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
"gopkg.in/yaml.v3"
)
@@ -55,6 +57,7 @@ func LoadResourcesFromHelmCharts(ctx context.Context, basePath string) (map[stri
logger.L().Ctx(ctx).Warning(fmt.Sprintf("Rendering of Helm chart template '%s', failed: %v", chart.GetName(), errs))
continue
}
chartName := chart.GetName()
for k, v := range wls {
sourceToWorkloads[k] = v
@@ -71,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, ""
@@ -91,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))
@@ -134,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))
}
@@ -193,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 = "*"
}
@@ -321,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
}
@@ -339,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
@@ -349,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
@@ -359,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) {

View File

@@ -27,7 +27,7 @@ func TestListFiles(t *testing.T) {
files, errs := listFiles(filesPath)
assert.Equal(t, 0, len(errs))
assert.Equal(t, 13, len(files))
assert.Equal(t, 12, len(files))
}
func TestLoadResourcesFromFiles(t *testing.T) {
@@ -49,7 +49,6 @@ func TestLoadResourcesFromHelmCharts(t *testing.T) {
assert.Equal(t, 6, len(sourceToWorkloads))
for file, workloads := range sourceToWorkloads {
assert.Equalf(t, 1, len(workloads), "expected 1 workload in file %s", file)
w := workloads[0]
@@ -106,110 +105,3 @@ 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))
})
}
}

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